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