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