Skip to main content

wlan_hw_sim/
test_utils.rs

1// Copyright 2018 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::event::{self, Handler};
6use crate::netdevice_helper;
7use crate::wlancfg_helper::{NetworkConfigBuilder, start_ap_and_wait_for_confirmation};
8use anyhow::Context;
9use fidl::endpoints::{Proxy, ServiceMarker, create_endpoints, create_proxy};
10use fidl_fuchsia_component_test as fcomponent_test;
11use fidl_fuchsia_driver_test as fidl_driver_test;
12use fidl_fuchsia_wlan_policy as fidl_policy;
13use fidl_fuchsia_wlan_tap as wlantap;
14use fidl_test_wlan_realm as fidl_realm;
15use fuchsia_async::{DurationExt, MonotonicInstant, TimeoutExt, Timer};
16use fuchsia_component::client::{
17    Service, connect_channel_to_protocol_at, connect_to_protocol, connect_to_protocol_at,
18};
19use fuchsia_fs::directory::{WatchEvent, Watcher};
20use futures::channel::oneshot;
21use futures::{FutureExt, StreamExt};
22use ieee80211::{MacAddr, MacAddrBytes};
23use log::{debug, info, warn};
24use realm_client::{InstalledNamespace, extend_namespace};
25use std::fmt::Display;
26use std::future::Future;
27use std::pin::Pin;
28use std::sync::Arc;
29use std::task::Poll;
30use test_realm_helpers::tracing::Tracing;
31use wlan_common::test_utils::ExpectWithin;
32use wlan_fidl_ext::try_unpack::{TryUnpack, WithName};
33
34/// Percent of a timeout duration past which we log a warning.
35const TIMEOUT_WARN_THRESHOLD: f64 = 0.8;
36
37// Struct that allows a test suite to interact with the test realm.
38//
39// If the test suite needs to connect to a protocol exposed by the test realm, it MUST use the
40// context's realm_proxy and cannot use fuchsia_component::client::connect_to_protocol.
41//
42// Similarly, if the test suite needs to connect to /dev hosted by the test realm, it must use the
43// context's devfs. There is currently no way to access any other directories in the test realm. If
44// the test suite needs to access any other directories, the test realm factory implementation and
45// FIDL API will need to be changed.
46//
47// Example:
48//
49// // Create a new test realm context
50// let ctx = ctx::new(fidl_realm::WlanConfig{ ..Default::default() };
51//
52// // Connect to a protocol
53// let protocol_proxy = ctx.test_realm_proxy()
54//   .connect_to_protocol::<fidl_fuchsia_protocol::Protocol>()
55//   .await?;
56//
57// // Connect to dev/class/network in the test realm
58// let (directory, directory_server) =
59//      create_proxy::<fidl_fuchsia_io::DirectoryMarker>();
60//  fdio::service_connect_at(
61//     ctx.devfs().as_channel().as_ref(),
62//     "class/network",
63//     directory_server.into_channel(),
64//  )?;
65pub struct TestRealmContext {
66    // The test namespace, which allows the test suite to connect to protocols exposed by
67    // the test realm. The test namespace must outlive any dependencies on components that
68    // run within it.
69    test_ns: Arc<InstalledNamespace>,
70
71    // A directory proxy connected to "/dev" in the test realm.
72    devfs: fidl_fuchsia_io::DirectoryProxy,
73
74    wlan_policy_moniker: Option<String>,
75    wlandevicemonitor_moniker: String,
76
77    // This field must be the last field in the struct so that it is dropped last.
78    // This ensures that traces are collected during realm destruction.
79    _tracing: Tracing,
80}
81
82impl TestRealmContext {
83    // Connect to the test realm factory to create and start a new test realm and return the test
84    // realm context. This will also start the driver test realm.
85    //
86    // Panics if any errors occur when the realm factory is being created.
87    pub async fn new(config: fidl_realm::WlanConfig) -> Arc<Self> {
88        let with_policy = config
89            .with_policy
90            .with_name("with_policy")
91            .try_unpack()
92            .expect("with_policy must be specified");
93
94        let realm_factory = connect_to_protocol::<fidl_realm::RealmFactoryMarker>()
95            .expect("Could not connect to realm factory protocol");
96
97        let (dict_1, dict_2) = zx::EventPair::create();
98
99        // Create the test realm for this test. This returns a
100        // `fuchsia.component.sandbox/Dictionary`, which is then consumed by `extend_namespace`
101        // to turn it into a directory installed in this component's namespace at
102        // `test_ns.prefix()`.
103        let trace_manager_hermeticity = config.trace_manager_hermeticity.clone();
104        let options = fidl_realm::RealmOptions { wlan_config: Some(config), ..Default::default() };
105        let response = realm_factory
106            .create_realm(options, dict_1)
107            .await
108            .expect("FIDL error on create_realm")
109            .expect("Could not create realm");
110        let test_ns =
111            Arc::new(extend_namespace(realm_factory, dict_2).await.expect("failed to extend ns"));
112
113        let devfs = fuchsia_fs::directory::open_in_namespace(
114            &format!("{}/dev-topological", test_ns.prefix()),
115            fidl_fuchsia_io::PERM_READABLE,
116        )
117        .expect("Failed to open devfs from extended namespace");
118
119        // Start the driver test realm
120        let driver_test_realm_proxy =
121            connect_to_protocol_at::<fidl_driver_test::RealmMarker>(&*test_ns)
122                .expect("Failed to connect to driver test realm");
123
124        let (pkg_client, pkg_server) = create_endpoints();
125        fuchsia_fs::directory::open_channel_in_namespace(
126            "/pkg",
127            fidl_fuchsia_io::PERM_READABLE | fidl_fuchsia_io::PERM_EXECUTABLE,
128            pkg_server,
129        )
130        .expect("Could not open /pkg");
131
132        let test_component = fidl_fuchsia_component_resolution::Component {
133            package: Some(fidl_fuchsia_component_resolution::Package {
134                directory: Some(pkg_client),
135                ..Default::default()
136            }),
137            ..Default::default()
138        };
139
140        let dtr_exposes = vec![
141            fcomponent_test::Capability::Service(fcomponent_test::Service {
142                name: Some("fuchsia.wlan.phy.Service".to_string()),
143                ..Default::default()
144            }),
145            fcomponent_test::Capability::Service(fcomponent_test::Service {
146                name: Some("fuchsia.wlan.tap.Service".to_string()),
147                ..Default::default()
148            }),
149        ];
150
151        driver_test_realm_proxy
152            .start(fidl_driver_test::RealmArgs {
153                test_component: Some(test_component),
154                dtr_exposes: Some(dtr_exposes),
155                ..Default::default()
156            })
157            .await
158            .expect("FIDL error when starting driver test realm")
159            .expect("Driver test realm server returned an error");
160
161        let _tracing = match trace_manager_hermeticity {
162            Some(fidl_realm::TraceManagerHermeticity::Hermetic) | None => {
163                Tracing::start_at(Arc::clone(&test_ns)).await.unwrap()
164            }
165            Some(fidl_realm::TraceManagerHermeticity::NonHermetic) => {
166                Tracing::start_non_hermetic(test_ns.prefix().strip_prefix("/").unwrap())
167                    .await
168                    .unwrap()
169            }
170        };
171
172        let wlan_policy_moniker = if with_policy {
173            Some(
174                response
175                    .wlan_policy_moniker
176                    .as_ref()
177                    .expect("wlan_policy moniker must be present")
178                    .clone(),
179            )
180        } else {
181            assert!(
182                response.wlan_policy_moniker.is_none(),
183                "wlan_policy moniker should not be present"
184            );
185            None
186        };
187        let wlandevicemonitor_moniker = response
188            .wlandevicemonitor_moniker
189            .as_ref()
190            .expect("wlandevicemonitor moniker must be present")
191            .clone();
192
193        Arc::new(Self { test_ns, devfs, wlan_policy_moniker, wlandevicemonitor_moniker, _tracing })
194    }
195
196    pub fn test_ns_prefix(&self) -> &str {
197        self.test_ns.prefix()
198    }
199
200    pub fn devfs(&self) -> &fidl_fuchsia_io::DirectoryProxy {
201        &self.devfs
202    }
203}
204
205type EventStream = wlantap::WlantapPhyEventStream;
206pub struct TestHelper {
207    ctx: Arc<TestRealmContext>,
208    netdevice_task_handles: Vec<fuchsia_async::Task<()>>,
209    _wlantap: Wlantap,
210    proxy: Arc<wlantap::WlantapPhyProxy>,
211    event_stream: Option<EventStream>,
212}
213struct TestHelperFuture<H, F>
214where
215    H: Handler<(), wlantap::WlantapPhyEvent>,
216    F: Future + Unpin,
217{
218    event_stream: Option<EventStream>,
219    handler: H,
220    future: F,
221}
222impl<H, F> Unpin for TestHelperFuture<H, F>
223where
224    H: Handler<(), wlantap::WlantapPhyEvent>,
225    F: Future + Unpin,
226{
227}
228impl<H, F> Future for TestHelperFuture<H, F>
229where
230    H: Handler<(), wlantap::WlantapPhyEvent>,
231    F: Future + Unpin,
232{
233    type Output = (F::Output, EventStream);
234    /// Polls the |event_stream| and invokes the |handler| for each event until |future| is ready.
235    fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
236        let helper = &mut *self;
237        let stream = helper.event_stream.as_mut().unwrap();
238        loop {
239            // Always poll for completion of the main future before processing
240            // the next event in the stream. This gives priority to the main
241            // future to avoid silently dropping events that should be processed
242            // by the next phase of a test. For example, every test begins with
243            // a phase that waits for the arrival of the `WlanSoftmacStart`
244            // event and should process exactly that one event from the stream
245            // and return `Poll::Ready`.
246            if let Poll::Ready(x) = helper.future.poll_unpin(cx) {
247                return Poll::Ready((x, helper.event_stream.take().unwrap()));
248            }
249
250            match stream.poll_next_unpin(cx) {
251                Poll::Ready(optional_result) => {
252                    let event = optional_result
253                        .expect("Unexpected end of the WlantapPhy event stream")
254                        .expect("WlantapPhy event stream returned an error");
255                    helper.handler.call(&mut (), &event);
256                }
257                Poll::Pending => {
258                    debug!(
259                        "Main future poll response is pending and there are no events to process."
260                    );
261                    return Poll::Pending;
262                }
263            }
264        }
265    }
266}
267impl TestHelper {
268    // Create a client TestHelper with a new TestRealmContext.
269    // NOTE: if a test case creates multiple TestHelpers that should all share the same test realm,
270    // it should use TestHelper::begin_test_with_context.
271    pub async fn begin_test(
272        phy_config: wlantap::WlantapPhyConfig,
273        realm_config: fidl_realm::WlanConfig,
274    ) -> Self {
275        let ctx = TestRealmContext::new(realm_config).await;
276        Self::begin_test_with_context(ctx, phy_config).await
277    }
278
279    // Create client TestHelper with a given TestRealmContext.
280    // If a test case creates multiple TestHelpers that must refer to the same instance of WLAN
281    // components, then all TestHelpers must use a copy of the same TestRealmContext.
282    //
283    // Example:
284    //
285    // // Create a new test realm context
286    // let ctx = TestRealmContext::new(fidl_realm::WlanConfig{ ..Default::default() };
287    //
288    // // Create both helpers with copies of the same context
289    // let helper1 = TestHelper::begin_test_with_context(
290    //    ctx.clone(),
291    //    default_wlantap_client_config(),
292    // ).await;
293    //
294    // let helper2 = TestHelper::begin_test_with_context(
295    //    ctx.clone(),
296    //    default_wlantap_client_config()).await;
297    pub async fn begin_test_with_context(
298        ctx: Arc<TestRealmContext>,
299        config: wlantap::WlantapPhyConfig,
300    ) -> Self {
301        let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
302        helper.wait_for_phy().await;
303        if helper.ctx.wlan_policy_moniker.is_some() {
304            helper.wait_for_wlan_softmac_start().await;
305        }
306        helper
307    }
308
309    // Create an AP TestHelper with a new TestRealmContext.
310    // NOTE: if a test case creates multiple TestHelpers that should all share the same test realm,
311    // it should use TestHelper::begin_ap_test_with_context.
312    pub async fn begin_ap_test(
313        phy_config: wlantap::WlantapPhyConfig,
314        network_config: NetworkConfigBuilder,
315        realm_config: fidl_realm::WlanConfig,
316    ) -> Self {
317        let ctx = TestRealmContext::new(realm_config).await;
318        Self::begin_ap_test_with_context(ctx, phy_config, network_config).await
319    }
320
321    // Create AP TestHelper with a given TestRealmContext.
322    // If a test case creates multiple TestHelpers that must refer to the same instance of WLAN
323    // components, then all TestHelpers must use a copy of the same TestRealmContext.
324    //
325    // Example:
326    //
327    // // Create a new test realm context
328    // let ctx = TestRealmContext::new(fidl_realm::WlanConfig{ ..Default::default() };
329    //
330    // // Create both helpers with copies of the same context
331    // let helper1 = TestHelper::begin_ap_test_with_context(
332    //    ctx.clone(),
333    //    default_wlantap_client_config(),
334    //    network_config1,
335    // ).await;
336    //
337    // let helper2 = TestHelper::begin_ap_test_with_context(
338    //    ctx.clone(),
339    //    default_wlantap_client_config(),
340    //    network_config2
341    // ).await;
342    pub async fn begin_ap_test_with_context(
343        ctx: Arc<TestRealmContext>,
344        config: wlantap::WlantapPhyConfig,
345        network_config: NetworkConfigBuilder,
346    ) -> Self {
347        let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
348        helper.wait_for_phy().await;
349        start_ap_and_wait_for_confirmation(helper.ctx.test_ns_prefix(), network_config).await;
350        helper.wait_for_wlan_softmac_start().await;
351        helper
352    }
353
354    async fn create_phy_and_helper(
355        config: wlantap::WlantapPhyConfig,
356        ctx: Arc<TestRealmContext>,
357    ) -> Self {
358        // Trigger creation of wlantap serviced phy and iface for testing.
359        let wlantap = Wlantap::open_from_namespace(ctx.test_ns_prefix())
360            .await
361            .expect("Failed to open wlantapctl");
362        let proxy = wlantap.create_phy(config).await.expect("Failed to create wlantap PHY");
363        let event_stream = Some(proxy.take_event_stream());
364        TestHelper {
365            ctx,
366            netdevice_task_handles: vec![],
367            _wlantap: wlantap,
368            proxy: Arc::new(proxy),
369            event_stream,
370        }
371    }
372
373    async fn wait_for_phy(&self) {
374        let dm = fuchsia_component::client::connect_to_protocol_at::<
375            fidl_fuchsia_wlan_device_service::DeviceMonitorMarker,
376        >(self.ctx.test_ns_prefix())
377        .expect("failed to connect to DeviceMonitor");
378        let (watcher_proxy, watcher_server_end) = fidl::endpoints::create_proxy::<
379            fidl_fuchsia_wlan_device_service::DeviceWatcherMarker,
380        >();
381        dm.watch_devices(watcher_server_end).expect("failed to watch devices");
382        let mut stream = watcher_proxy.take_event_stream();
383        while let Some(event) = stream.next().await {
384            match event.expect("Watcher event error") {
385                fidl_fuchsia_wlan_device_service::DeviceWatcherEvent::OnPhyAdded { .. } => {
386                    break;
387                }
388                _ => {}
389            }
390        }
391    }
392
393    async fn wait_for_wlan_softmac_start(&mut self) {
394        let (sender, receiver) = oneshot::channel::<()>();
395        self.run_until_complete_or_timeout(
396            zx::MonotonicDuration::from_seconds(12),
397            "receive a WlanSoftmacStart event",
398            event::on_start_mac(event::once(|_, _| sender.send(()))),
399            receiver,
400        )
401        .await
402        .unwrap_or_else(|oneshot::Canceled| panic!());
403    }
404
405    /// Returns a clone of the `Arc<wlantap::WlantapPhyProxy>` as a convenience for passing
406    /// the proxy to futures. Tests must drop every `Arc<wlantap::WlantapPhyProxy>` returned from this
407    /// method before dropping the TestHelper. Otherwise, TestHelper::drop() cannot synchronously
408    /// block on WlantapPhy.Shutdown().
409    pub fn proxy(&self) -> Arc<wlantap::WlantapPhyProxy> {
410        Arc::clone(&self.proxy)
411    }
412
413    pub fn test_ns_prefix(&self) -> &str {
414        self.ctx.test_ns_prefix()
415    }
416
417    pub fn devfs(&self) -> &fidl_fuchsia_io::DirectoryProxy {
418        self.ctx.devfs()
419    }
420
421    pub async fn start_netdevice_session(
422        &mut self,
423        mac: MacAddr,
424    ) -> (netdevice_client::Session, netdevice_client::Port) {
425        let mac = fidl_fuchsia_net::MacAddress { octets: mac.to_array() };
426        let (client, port) = netdevice_helper::create_client(self.devfs(), mac)
427            .await
428            .expect("failed to create netdevice client");
429        let (session, task_handle) = netdevice_helper::start_session(client, port).await;
430        self.netdevice_task_handles.push(task_handle);
431        (session, port)
432    }
433
434    /// Will run the main future until it completes or when it has run past the specified duration.
435    /// Note that any events that are observed on the event stream will be passed to the
436    /// |event_handler| closure first before making progress on the main future.
437    /// So if a test generates many events each of which requires significant computational time in
438    /// the event handler, the main future may not be able to complete in time.
439    pub async fn run_until_complete_or_timeout<H, F>(
440        &mut self,
441        timeout: zx::MonotonicDuration,
442        context: impl Display,
443        handler: H,
444        future: F,
445    ) -> F::Output
446    where
447        H: Handler<(), wlantap::WlantapPhyEvent>,
448        F: Future + Unpin,
449    {
450        info!("Running main future until completion or timeout with event handler: {}", context);
451        let start_time = zx::MonotonicInstant::get();
452        let (item, stream) = TestHelperFuture {
453            event_stream: Some(self.event_stream.take().unwrap()),
454            handler,
455            future,
456        }
457        .expect_within(timeout, format!("Main future timed out: {}", context))
458        .await;
459        let end_time = zx::MonotonicInstant::get();
460        let elapsed = end_time - start_time;
461        let elapsed_seconds = elapsed.into_seconds_f64();
462        let elapsed_ratio = elapsed_seconds / timeout.into_seconds_f64();
463        if elapsed_ratio < TIMEOUT_WARN_THRESHOLD {
464            info!("Main future completed in {:.2} seconds: {}", elapsed_seconds, context);
465        } else {
466            warn!(
467                "Main future completed in {:.2} seconds ({:.1}% of timeout): {}",
468                elapsed_seconds,
469                elapsed_ratio * 100.,
470                context,
471            );
472        }
473        self.event_stream = Some(stream);
474        item
475    }
476}
477impl Drop for TestHelper {
478    fn drop(&mut self) {
479        // Drop each fuchsia_async::Task driving each
480        // netdevice_client::Session in the reverse order the test
481        // created them.
482        while let Some(task_handle) = self.netdevice_task_handles.pop() {
483            drop(task_handle);
484        }
485
486        // Create a placeholder proxy to swap into place of self.proxy. This allows this
487        // function to create a synchronous proxy from the real proxy.
488        let (placeholder_proxy, _server_end) =
489            fidl::endpoints::create_proxy::<wlantap::WlantapPhyMarker>();
490        let mut proxy = Arc::new(placeholder_proxy);
491        std::mem::swap(&mut self.proxy, &mut proxy);
492
493        // Drop the event stream so the WlantapPhyProxy can be converted
494        // back into a channel. Conversion from a proxy into a channel fails
495        // otherwise.
496        let event_stream = self.event_stream.take();
497        drop(event_stream);
498
499        let sync_proxy = wlantap::WlantapPhySynchronousProxy::new(
500            // Arc::into_inner() should succeed in a properly constructed test. Using a WlantapPhyProxy
501            // returned from TestHelper beyond the lifetime of TestHelper is not supported.
502            Arc::<wlantap::WlantapPhyProxy>::into_inner(proxy)
503                .expect("Outstanding references to WlantapPhyProxy! Failed to drop TestHelper.")
504                .into_channel()
505                .expect("failed to get fidl::AsyncChannel from proxy")
506                .into_zx_channel(),
507        );
508
509        let (lc_client_end, lc_server_end) = zx::Channel::create();
510        connect_channel_to_protocol_at::<fidl_fuchsia_sys2::LifecycleControllerMarker>(
511            lc_server_end,
512            self.ctx.test_ns_prefix(),
513        )
514        .expect("Failed to connect to LifecycleController");
515        let lc = fidl_fuchsia_sys2::LifecycleControllerSynchronousProxy::new(lc_client_end);
516
517        if let Some(wlan_policy_moniker) = &self.ctx.wlan_policy_moniker {
518            lc.stop_instance(wlan_policy_moniker, zx::MonotonicInstant::INFINITE)
519                .expect("Failed to call stop_instance")
520                .expect("stop_instance returned an error");
521        }
522
523        let (dm_client_end, dm_server_end) = zx::Channel::create();
524        connect_channel_to_protocol_at::<fidl_fuchsia_wlan_device_service::DeviceMonitorMarker>(
525            dm_server_end,
526            self.ctx.test_ns_prefix(),
527        )
528        .expect("Failed to connect to DeviceMonitor");
529        let dm =
530            fidl_fuchsia_wlan_device_service::DeviceMonitorSynchronousProxy::new(dm_client_end);
531
532        let ifaces =
533            dm.list_ifaces(zx::MonotonicInstant::INFINITE).expect("Failed to call list_ifaces");
534        for iface in ifaces {
535            let req = &fidl_fuchsia_wlan_device_service::DestroyIfaceRequest { iface_id: iface };
536            let status = dm
537                .destroy_iface(req, zx::MonotonicInstant::INFINITE)
538                .expect("Failed to call destroy_iface");
539            assert_eq!(status, 0, "destroy_iface returned an error: {}", status);
540        }
541
542        lc.stop_instance(&self.ctx.wlandevicemonitor_moniker, zx::MonotonicInstant::INFINITE)
543            .expect("Failed to call stop_instance")
544            .expect("stop_instance returned an error");
545
546        // This test framework does not currently support stopping
547        // individual components. If instead we drop the
548        // TestRealmProxy, and thus stop both wlancfg and
549        // wlandevicemonitor, wlandevicemonitor which will drop the
550        // GenericSme channel before graceful destruction of the
551        // iface. Dropping the GenericSme channel for an existing
552        // iface is considered an error because doing so prevents
553        // future communication with the iface.
554        //
555        // In lieu of stopping wlancfg first, we instead shutdown the
556        // phy device via WlantapPhy.Shutdown() which will block until
557        // both the phy and any remaining ifaces are shutdown. We
558        // first shutdown the phy to prevent any automated CreateIface
559        // calls from wlancfg after removing the iface.
560        sync_proxy
561            .shutdown(zx::MonotonicInstant::INFINITE)
562            .expect("Failed to shutdown WlantapPhy gracefully.");
563    }
564}
565
566pub struct RetryWithBackoff {
567    deadline: MonotonicInstant,
568    prev_delay: zx::MonotonicDuration,
569    next_delay: zx::MonotonicDuration,
570    max_delay: zx::MonotonicDuration,
571}
572impl RetryWithBackoff {
573    pub fn new(timeout: zx::MonotonicDuration) -> Self {
574        RetryWithBackoff {
575            deadline: MonotonicInstant::after(timeout),
576            prev_delay: zx::MonotonicDuration::from_millis(0),
577            next_delay: zx::MonotonicDuration::from_millis(1),
578            max_delay: zx::MonotonicDuration::INFINITE,
579        }
580    }
581    pub fn infinite_with_max_interval(max_delay: zx::MonotonicDuration) -> Self {
582        Self {
583            deadline: MonotonicInstant::INFINITE,
584            max_delay,
585            ..Self::new(zx::MonotonicDuration::from_nanos(0))
586        }
587    }
588
589    /// Return Err if the deadline was exceeded when this function was called.
590    /// Otherwise, sleep for a little longer (following Fibonacci series) or up
591    /// to the deadline, whichever is soonest. If a sleep occurred, this function
592    /// returns Ok. The value contained in both Ok and Err is the zx::MonotonicDuration
593    /// until or after the deadline when the function returns.
594    async fn sleep_unless_after_deadline_(
595        &mut self,
596        verbose: bool,
597    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
598        // Add an inner scope up to just after Timer::new to ensure all
599        // time assignments are dropped after the sleep occurs. This
600        // prevents misusing them after the sleep since they are all
601        // no longer correct after the clock moves.
602        {
603            if MonotonicInstant::after(zx::MonotonicDuration::from_millis(0)) > self.deadline {
604                if verbose {
605                    info!("Skipping sleep. Deadline exceeded.");
606                }
607                return Err(self.deadline - MonotonicInstant::now());
608            }
609
610            let sleep_deadline =
611                std::cmp::min(MonotonicInstant::after(self.next_delay), self.deadline);
612            if verbose {
613                let micros = sleep_deadline.into_nanos() / 1_000;
614                info!("Sleeping until {}.{} 😴", micros / 1_000_000, micros % 1_000_000);
615            }
616
617            Timer::new(sleep_deadline).await;
618        }
619
620        // If the next delay interval exceeds max_delay (even if by overflow),
621        // then saturate at max_delay.
622        if self.next_delay < self.max_delay {
623            let next_delay = std::cmp::min(
624                self.max_delay,
625                zx::MonotonicDuration::from_nanos(
626                    self.prev_delay.into_nanos().saturating_add(self.next_delay.into_nanos()),
627                ),
628            );
629            self.prev_delay = self.next_delay;
630            self.next_delay = next_delay;
631        }
632
633        Ok(self.deadline - MonotonicInstant::now())
634    }
635
636    pub async fn sleep_unless_after_deadline(
637        &mut self,
638    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
639        self.sleep_unless_after_deadline_(false).await
640    }
641
642    pub async fn sleep_unless_after_deadline_verbose(
643        &mut self,
644    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
645        self.sleep_unless_after_deadline_(true).await
646    }
647}
648
649/// TODO(https://fxbug.dev/42164608): This function strips the `timestamp_nanos` field
650/// from each `fidl_fuchsia_wlan_policy::ScanResult` entry since the `timestamp_nanos`
651/// field is undefined.
652pub fn strip_timestamp_nanos_from_scan_results(
653    mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
654) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
655    for scan_result in &mut scan_result_list {
656        scan_result
657            .entries
658            .as_mut()
659            .unwrap()
660            .sort_by(|a, b| a.bssid.as_ref().unwrap().cmp(&b.bssid.as_ref().unwrap()));
661        for entry in scan_result.entries.as_mut().unwrap() {
662            // TODO(https://fxbug.dev/42164608): Strip timestamp_nanos since it's not implemented.
663            entry.timestamp_nanos.take();
664        }
665    }
666    scan_result_list
667}
668
669/// Sort a list of scan results by the `id` and `bssid` fields.
670///
671/// This function will panic if either of the `id` or `entries` fields
672/// are `None`.
673pub fn sort_policy_scan_result_list(
674    mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
675) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
676    scan_result_list
677        .sort_by(|a, b| a.id.as_ref().expect("empty id").cmp(&b.id.as_ref().expect("empty id")));
678    scan_result_list
679}
680
681/// Returns a map with the scan results returned by the policy layer. The map is
682/// keyed by the `id` field of each `fidl_fuchsia_policy::ScanResult`.
683///
684/// This function will panic if the `id` field is ever `None` or if policy returns
685/// the same `id` twice. Both of these are invariants we expect the policy layer
686/// to uphold.
687pub async fn policy_scan_for_networks<'a>(
688    client_controller: fidl_policy::ClientControllerProxy,
689) -> Vec<fidl_policy::ScanResult> {
690    // Request a scan from the policy layer.
691    let (scan_proxy, server_end) = create_proxy();
692    client_controller.scan_for_networks(server_end).expect("requesting scan");
693    let mut scan_result_list = Vec::new();
694    loop {
695        let proxy_result = scan_proxy.get_next().await.expect("getting scan results");
696        let next_scan_result_list = proxy_result.expect("scanning failed");
697        if next_scan_result_list.is_empty() {
698            break;
699        }
700        scan_result_list.extend(next_scan_result_list);
701    }
702    sort_policy_scan_result_list(strip_timestamp_nanos_from_scan_results(scan_result_list))
703}
704
705/// This function returns `Ok(r)`, where `r` is the return value from `main_future`,
706/// if `main_future` completes before the `timeout` duration. Otherwise, `Err(())` is returned.
707pub async fn timeout_after<R, F: Future<Output = R> + Unpin>(
708    timeout: zx::MonotonicDuration,
709    main_future: &mut F,
710) -> Result<R, ()> {
711    async { Ok(main_future.await) }.on_timeout(timeout.after_now(), || Err(())).await
712}
713
714pub struct Wlantap {
715    proxy: wlantap::WlantapCtlProxy,
716}
717
718impl Wlantap {
719    pub async fn open_from_namespace(prefix: &str) -> Result<Self, anyhow::Error> {
720        let test_ns_dir =
721            fuchsia_fs::directory::open_in_namespace(prefix, fidl_fuchsia_io::Flags::empty())?;
722
723        let mut watcher = Watcher::new(&test_ns_dir).await?;
724        loop {
725            let message = watcher
726                .next()
727                .await
728                .context("Directory watcher finished without finding wlantap service")??;
729            if message.event == WatchEvent::ADD_FILE || message.event == WatchEvent::EXISTING {
730                if message.filename.to_str() == Some(wlantap::ServiceMarker::SERVICE_NAME) {
731                    break;
732                }
733            }
734        }
735
736        let service = Service::open_from_dir(&test_ns_dir, wlantap::ServiceMarker)?;
737        let service_proxy = service.watch_for_any().await?;
738        let proxy = service_proxy.connect_to_wlantap_ctl()?;
739        Ok(Self { proxy })
740    }
741
742    pub async fn create_phy(
743        &self,
744        config: wlantap::WlantapPhyConfig,
745    ) -> Result<wlantap::WlantapPhyProxy, anyhow::Error> {
746        let Self { proxy } = self;
747        let (ours, theirs) = fidl::endpoints::create_proxy();
748
749        let status = proxy.create_phy(&config, theirs).await?;
750        let () = zx::ok(status)?;
751
752        Ok(ours)
753    }
754}