1#![warn(missing_docs)]
6
7pub mod constants;
10pub mod devices;
11pub mod dhcpv4;
12pub mod interfaces;
13pub mod ndp;
14pub mod nud;
15pub mod packets;
16pub mod ping;
17#[macro_use]
18pub mod realms;
19
20use anyhow::Context as _;
21use component_events::events::EventStream;
22use diagnostics_hierarchy::{DiagnosticsHierarchy, HierarchyMatcher, filter_hierarchy};
23use fidl::endpoints::DiscoverableProtocolMarker;
24use fidl_fuchsia_diagnostics::Selector;
25use fidl_fuchsia_inspect_deprecated::InspectMarker;
26use fidl_fuchsia_io as fio;
27use fidl_fuchsia_netemul as fnetemul;
28use fuchsia_async::{self as fasync, DurationExt as _};
29use fuchsia_component::client;
30use futures::future::FutureExt as _;
31use futures::stream::{Stream, StreamExt as _, TryStreamExt as _};
32use futures::{Future, select};
33use std::pin::pin;
34
35use crate::realms::TestSandboxExt as _;
36
37pub type Result<T = ()> = std::result::Result<T, anyhow::Error>;
39
40pub const ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT: zx::MonotonicDuration =
44 zx::MonotonicDuration::from_seconds(120);
45
46pub const ASYNC_EVENT_NEGATIVE_CHECK_TIMEOUT: zx::MonotonicDuration =
52 zx::MonotonicDuration::from_seconds(5);
53
54pub const ASYNC_EVENT_CHECK_INTERVAL: zx::MonotonicDuration =
56 zx::MonotonicDuration::from_seconds(1);
57
58pub async fn try_any<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
62 let stream = pin!(stream);
63 stream.try_filter(|v| futures::future::ready(*v)).next().await.unwrap_or(Ok(false))
64}
65
66pub async fn try_all<S: Stream<Item = Result<bool>>>(stream: S) -> Result<bool> {
70 let stream = pin!(stream);
71 stream.try_filter(|v| futures::future::ready(!*v)).next().await.unwrap_or(Ok(true))
72}
73
74pub async fn sleep(secs: i64) {
76 fasync::Timer::new(zx::MonotonicDuration::from_seconds(secs).after_now()).await;
77}
78
79pub async fn get_component_stopped_event_stream() -> Result<component_events::events::EventStream> {
81 EventStream::open_at_path("/events/stopped")
82 .await
83 .context("failed to subscribe to `Stopped` events")
84}
85
86pub async fn wait_for_component_stopped_with_stream(
91 event_stream: &mut component_events::events::EventStream,
92 realm: &netemul::TestRealm<'_>,
93 component_moniker: &str,
94 status_matcher: Option<component_events::matcher::ExitStatusMatcher>,
95) -> Result<component_events::events::Stopped> {
96 let matcher = get_child_component_event_matcher(realm, component_moniker)
97 .await
98 .context("get child component matcher")?;
99 matcher.stop(status_matcher).wait::<component_events::events::Stopped>(event_stream).await
100}
101
102pub async fn wait_for_component_stopped(
108 realm: &netemul::TestRealm<'_>,
109 component_moniker: &str,
110 status_matcher: Option<component_events::matcher::ExitStatusMatcher>,
111) -> Result<component_events::events::Stopped> {
112 let mut stream = get_component_stopped_event_stream().await?;
113 wait_for_component_stopped_with_stream(&mut stream, realm, component_moniker, status_matcher)
114 .await
115}
116
117pub async fn get_child_component_event_matcher(
119 realm: &netemul::TestRealm<'_>,
120 component_moniker: &str,
121) -> Result<component_events::matcher::EventMatcher> {
122 let realm_moniker = &realm.get_moniker().await.context("calling get moniker")?;
123 let moniker_for_match =
124 format!("./{}/{}/{}", NETEMUL_SANDBOX_MONIKER, realm_moniker, component_moniker);
125 Ok(component_events::matcher::EventMatcher::ok().moniker(moniker_for_match))
126}
127
128const NETEMUL_SANDBOX_MONIKER: &str = "sandbox";
131
132pub async fn get_component_moniker<'a>(
135 realm: &netemul::TestRealm<'a>,
136 component: &str,
137) -> Result<String> {
138 let realm_moniker = realm.get_moniker().await.context("calling get moniker")?;
139 Ok([NETEMUL_SANDBOX_MONIKER, &realm_moniker, component].join("/"))
140}
141
142pub async fn get_inspect_data(
146 realm: &netemul::TestRealm<'_>,
147 component_moniker: impl Into<String>,
148 tree_selector: impl Into<String>,
149) -> Result<diagnostics_hierarchy::DiagnosticsHierarchy> {
150 let moniker = realm.get_moniker().await.context("calling get moniker")?;
151 let realm_moniker = selectors::sanitize_string_for_selectors(&moniker);
152 let mut data = diagnostics_reader::ArchiveReader::inspect()
153 .retry(diagnostics_reader::RetryConfig::MinSchemaCount(1))
154 .add_selector(
155 diagnostics_reader::ComponentSelector::new(vec![
156 NETEMUL_SANDBOX_MONIKER.into(),
157 realm_moniker.into_owned(),
158 component_moniker.into(),
159 ])
160 .with_tree_selector(tree_selector.into()),
161 )
162 .snapshot()
163 .await
164 .context("snapshot did not return any inspect data")?
165 .into_iter()
166 .map(|inspect_data| {
167 inspect_data.payload.ok_or_else(|| {
168 anyhow::anyhow!(
169 "empty inspect payload, metadata errors: {:?}",
170 inspect_data.metadata.errors
171 )
172 })
173 });
174
175 let Some(datum) = data.next() else {
176 unreachable!("archive reader RetryConfig specifies non-empty")
177 };
178
179 let data: Vec<_> = data.collect();
180 assert!(
181 data.is_empty(),
182 "expected a single inspect entry; got {:?} and also {:?}",
183 datum,
184 data
185 );
186
187 datum
188}
189
190pub async fn get_inspect_property(
193 realm: &netemul::TestRealm<'_>,
194 component_moniker: impl Into<String>,
195 property_selector: impl Into<String>,
196) -> Result<diagnostics_hierarchy::Property> {
197 let property_selector = property_selector.into();
198 let hierarchy = get_inspect_data(&realm, component_moniker, property_selector.clone())
199 .await
200 .context("getting hierarchy")?;
201 let property_selector = property_selector.split(&['/', ':']).skip(1).collect::<Vec<_>>();
202 let property = hierarchy
203 .get_property_by_path(&property_selector)
204 .ok_or_else(|| anyhow::anyhow!("property not found in hierarchy: {hierarchy:?}"))?;
205 Ok(property.clone())
206}
207
208pub async fn get_deprecated_netstack2_inspect_data(
213 diagnostics_dir: &fio::DirectoryProxy,
214 subdir: &str,
215 selectors: impl IntoIterator<Item = Selector>,
216) -> DiagnosticsHierarchy {
217 let matcher = HierarchyMatcher::new(selectors.into_iter()).expect("invalid selectors");
218 loop {
219 let proxy = client::connect_to_named_protocol_at_dir_root::<InspectMarker>(
223 diagnostics_dir,
224 &format!("{subdir}/{}", InspectMarker::PROTOCOL_NAME),
225 )
226 .unwrap();
227 match inspect_fidl_load::load_hierarchy(proxy).await {
228 Ok(hierarchy) => return filter_hierarchy(hierarchy, &matcher).unwrap(),
229 Err(err) => {
230 println!("Failed to load hierarchy, retrying. Error: {err:?}")
231 }
232 }
233 fasync::Timer::new(fasync::MonotonicDuration::from_millis(100)).await;
234 }
235}
236
237pub async fn setup_network<'a, N: realms::Netstack>(
239 sandbox: &'a netemul::TestSandbox,
240 name: &'a str,
241 metric: Option<u32>,
242) -> Result<(
243 netemul::TestNetwork<'a>,
244 netemul::TestRealm<'a>,
245 netemul::TestInterface<'a>,
246 netemul::TestFakeEndpoint<'a>,
247)> {
248 setup_network_with::<N, _>(
249 sandbox,
250 name,
251 netemul::InterfaceConfig { metric, ..Default::default() },
252 std::iter::empty::<fnetemul::ChildDef>(),
253 )
254 .await
255}
256
257pub async fn setup_network_with<'a, N: realms::Netstack, I>(
264 sandbox: &'a netemul::TestSandbox,
265 name: &'a str,
266 interface_config: netemul::InterfaceConfig<'a>,
267 children: I,
268) -> Result<(
269 netemul::TestNetwork<'a>,
270 netemul::TestRealm<'a>,
271 netemul::TestInterface<'a>,
272 netemul::TestFakeEndpoint<'a>,
273)>
274where
275 I: IntoIterator,
276 I::Item: Into<fnetemul::ChildDef>,
277{
278 let network = sandbox.create_network(name).await.context("failed to create network")?;
279 let realm = sandbox
280 .create_netstack_realm_with::<N, _, _>(name, children)
281 .context("failed to create netstack realm")?;
282 let fake_ep = network.create_fake_endpoint()?;
285
286 let iface = realm
287 .join_network_with_if_config(&network, name, interface_config)
288 .await
289 .context("failed to configure networking")?;
290
291 Ok((network, realm, iface, fake_ep))
292}
293
294pub async fn pause_fake_clock(realm: &netemul::TestRealm<'_>) -> Result<()> {
296 let fake_clock_control = realm
297 .connect_to_protocol::<fidl_fuchsia_testing::FakeClockControlMarker>()
298 .context("failed to connect to FakeClockControl")?;
299 fake_clock_control.pause().await.context("failed to pause time")?;
300 Ok(())
301}
302
303#[track_caller]
306pub fn annotate<'a, 'b: 'a, T>(
307 fut: impl Future<Output = T> + 'a,
308 interval: std::time::Duration,
309 event_name: &'b str,
310) -> impl Future<Output = T> + 'a {
311 let caller = std::panic::Location::caller();
312
313 async move {
314 let mut fut = pin!(fut.fuse());
315 let event_name = event_name.to_string();
316 let mut print_fut = pin!(
317 futures::stream::repeat(())
318 .for_each(|()| async {
319 fasync::Timer::new(interval).await;
320 eprintln!("waiting for {} at {}", event_name, caller);
321 })
322 .fuse()
323 );
324 let result = select! {
325 result = fut => result,
326 () = print_fut => unreachable!("should repeat printing forever"),
327 };
328 eprintln!("completed {} at {}", event_name, caller);
329 result
330 }
331}