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 fuchsia_async::{DurationExt, MonotonicInstant, TimeoutExt, Timer};
10use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at};
11use futures::channel::oneshot;
12use futures::{FutureExt, StreamExt};
13use ieee80211::{MacAddr, MacAddrBytes};
14use log::{debug, info, warn};
15use realm_client::{InstalledNamespace, extend_namespace};
16use std::fmt::Display;
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::Arc;
20use std::task::{Context, Poll};
21use test_realm_helpers::tracing::Tracing;
22use wlan_common::test_utils::ExpectWithin;
23use wlantap_client::Wlantap;
24use {
25    fidl_fuchsia_driver_test as fidl_driver_test, fidl_fuchsia_wlan_policy as fidl_policy,
26    fidl_fuchsia_wlan_tap as wlantap, fidl_test_wlan_realm as fidl_realm,
27};
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    /// Any events that accumulated in the |event_stream| since last poll will be passed to
180    /// |event_handler| before the |main_future| is polled
181    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
182        let helper = &mut *self;
183        let stream = helper.event_stream.as_mut().unwrap();
184        while let Poll::Ready(optional_result) = stream.poll_next_unpin(cx) {
185            let event = optional_result
186                .expect("Unexpected end of the WlantapPhy event stream")
187                .expect("WlantapPhy event stream returned an error");
188            helper.handler.call(&mut (), &event);
189        }
190        match helper.future.poll_unpin(cx) {
191            Poll::Pending => {
192                debug!("Main future poll response is pending. Waiting for completion.");
193                Poll::Pending
194            }
195            Poll::Ready(x) => Poll::Ready((x, helper.event_stream.take().unwrap())),
196        }
197    }
198}
199impl TestHelper {
200    // Create a client TestHelper with a new TestRealmContext.
201    // NOTE: if a test case creates multiple TestHelpers that should all share the same test realm,
202    // it should use TestHelper::begin_test_with_context.
203    pub async fn begin_test(
204        phy_config: wlantap::WlantapPhyConfig,
205        realm_config: fidl_realm::WlanConfig,
206    ) -> Self {
207        let ctx = TestRealmContext::new(realm_config).await;
208        Self::begin_test_with_context(ctx, phy_config).await
209    }
210
211    // Create client TestHelper with a given TestRealmContext.
212    // If a test case creates multiple TestHelpers that must refer to the same instance of WLAN
213    // components, then all TestHelpers must use a copy of the same TestRealmContext.
214    //
215    // Example:
216    //
217    // // Create a new test realm context
218    // let ctx = TestRealmContext::new(fidl_realm::WlanConfig{ ..Default::default() };
219    //
220    // // Create both helpers with copies of the same context
221    // let helper1 = TestHelper::begin_test_with_context(
222    //    ctx.clone(),
223    //    default_wlantap_client_config(),
224    // ).await;
225    //
226    // let helper2 = TestHelper::begin_test_with_context(
227    //    ctx.clone(),
228    //    default_wlantap_client_config()).await;
229    pub async fn begin_test_with_context(
230        ctx: Arc<TestRealmContext>,
231        config: wlantap::WlantapPhyConfig,
232    ) -> Self {
233        let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
234        helper.wait_for_wlan_softmac_start().await;
235        helper
236    }
237
238    // Create an AP TestHelper with a new TestRealmContext.
239    // NOTE: if a test case creates multiple TestHelpers that should all share the same test realm,
240    // it should use TestHelper::begin_ap_test_with_context.
241    pub async fn begin_ap_test(
242        phy_config: wlantap::WlantapPhyConfig,
243        network_config: NetworkConfigBuilder,
244        realm_config: fidl_realm::WlanConfig,
245    ) -> Self {
246        let ctx = TestRealmContext::new(realm_config).await;
247        Self::begin_ap_test_with_context(ctx, phy_config, network_config).await
248    }
249
250    // Create AP TestHelper with a given TestRealmContext.
251    // If a test case creates multiple TestHelpers that must refer to the same instance of WLAN
252    // components, then all TestHelpers must use a copy of the same TestRealmContext.
253    //
254    // Example:
255    //
256    // // Create a new test realm context
257    // let ctx = TestRealmContext::new(fidl_realm::WlanConfig{ ..Default::default() };
258    //
259    // // Create both helpers with copies of the same context
260    // let helper1 = TestHelper::begin_ap_test_with_context(
261    //    ctx.clone(),
262    //    default_wlantap_client_config(),
263    //    network_config1,
264    // ).await;
265    //
266    // let helper2 = TestHelper::begin_ap_test_with_context(
267    //    ctx.clone(),
268    //    default_wlantap_client_config(),
269    //    network_config2
270    // ).await;
271    pub async fn begin_ap_test_with_context(
272        ctx: Arc<TestRealmContext>,
273        config: wlantap::WlantapPhyConfig,
274        network_config: NetworkConfigBuilder,
275    ) -> Self {
276        let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
277        start_ap_and_wait_for_confirmation(helper.ctx.test_ns_prefix(), network_config).await;
278        helper.wait_for_wlan_softmac_start().await;
279        helper
280    }
281
282    async fn create_phy_and_helper(
283        config: wlantap::WlantapPhyConfig,
284        ctx: Arc<TestRealmContext>,
285    ) -> Self {
286        // Trigger creation of wlantap serviced phy and iface for testing.
287        let wlantap =
288            Wlantap::open_from_devfs(&ctx.devfs).await.expect("Failed to open wlantapctl");
289        let proxy = wlantap.create_phy(config).await.expect("Failed to create wlantap PHY");
290        let event_stream = Some(proxy.take_event_stream());
291        TestHelper {
292            ctx,
293            netdevice_task_handles: vec![],
294            _wlantap: wlantap,
295            proxy: Arc::new(proxy),
296            event_stream,
297        }
298    }
299
300    async fn wait_for_wlan_softmac_start(&mut self) {
301        let (sender, receiver) = oneshot::channel::<()>();
302        self.run_until_complete_or_timeout(
303            zx::MonotonicDuration::from_seconds(12),
304            "receive a WlanSoftmacStart event",
305            event::on_start_mac(event::once(|_, _| sender.send(()))),
306            receiver,
307        )
308        .await
309        .unwrap_or_else(|oneshot::Canceled| panic!());
310    }
311
312    /// Returns a clone of the `Arc<wlantap::WlantapPhyProxy>` as a convenience for passing
313    /// the proxy to futures. Tests must drop every `Arc<wlantap::WlantapPhyProxy>` returned from this
314    /// method before dropping the TestHelper. Otherwise, TestHelper::drop() cannot synchronously
315    /// block on WlantapPhy.Shutdown().
316    pub fn proxy(&self) -> Arc<wlantap::WlantapPhyProxy> {
317        Arc::clone(&self.proxy)
318    }
319
320    pub fn test_ns_prefix(&self) -> &str {
321        self.ctx.test_ns_prefix()
322    }
323
324    pub fn devfs(&self) -> &fidl_fuchsia_io::DirectoryProxy {
325        self.ctx.devfs()
326    }
327
328    pub async fn start_netdevice_session(
329        &mut self,
330        mac: MacAddr,
331    ) -> (netdevice_client::Session, netdevice_client::Port) {
332        let mac = fidl_fuchsia_net::MacAddress { octets: mac.to_array() };
333        let (client, port) = netdevice_helper::create_client(self.devfs(), mac)
334            .await
335            .expect("failed to create netdevice client");
336        let (session, task_handle) = netdevice_helper::start_session(client, port).await;
337        self.netdevice_task_handles.push(task_handle);
338        (session, port)
339    }
340
341    /// Will run the main future until it completes or when it has run past the specified duration.
342    /// Note that any events that are observed on the event stream will be passed to the
343    /// |event_handler| closure first before making progress on the main future.
344    /// So if a test generates many events each of which requires significant computational time in
345    /// the event handler, the main future may not be able to complete in time.
346    pub async fn run_until_complete_or_timeout<H, F>(
347        &mut self,
348        timeout: zx::MonotonicDuration,
349        context: impl Display,
350        handler: H,
351        future: F,
352    ) -> F::Output
353    where
354        H: Handler<(), wlantap::WlantapPhyEvent>,
355        F: Future + Unpin,
356    {
357        info!("Running main future until completion or timeout with event handler: {}", context);
358        let start_time = zx::MonotonicInstant::get();
359        let (item, stream) = TestHelperFuture {
360            event_stream: Some(self.event_stream.take().unwrap()),
361            handler,
362            future,
363        }
364        .expect_within(timeout, format!("Main future timed out: {}", context))
365        .await;
366        let end_time = zx::MonotonicInstant::get();
367        let elapsed = end_time - start_time;
368        let elapsed_seconds = elapsed.into_seconds_f64();
369        let elapsed_ratio = elapsed_seconds / timeout.into_seconds_f64();
370        if elapsed_ratio < TIMEOUT_WARN_THRESHOLD {
371            info!("Main future completed in {:.2} seconds: {}", elapsed_seconds, context);
372        } else {
373            warn!(
374                "Main future completed in {:.2} seconds ({:.1}% of timeout): {}",
375                elapsed_seconds,
376                elapsed_ratio * 100.,
377                context,
378            );
379        }
380        self.event_stream = Some(stream);
381        item
382    }
383}
384impl Drop for TestHelper {
385    fn drop(&mut self) {
386        // Drop each fuchsia_async::Task driving each
387        // netdevice_client::Session in the reverse order the test
388        // created them.
389        while let Some(task_handle) = self.netdevice_task_handles.pop() {
390            drop(task_handle);
391        }
392
393        // Create a placeholder proxy to swap into place of self.proxy. This allows this
394        // function to create a synchronous proxy from the real proxy.
395        let (placeholder_proxy, _server_end) =
396            fidl::endpoints::create_proxy::<wlantap::WlantapPhyMarker>();
397        let mut proxy = Arc::new(placeholder_proxy);
398        std::mem::swap(&mut self.proxy, &mut proxy);
399
400        // Drop the event stream so the WlantapPhyProxy can be converted
401        // back into a channel. Conversion from a proxy into a channel fails
402        // otherwise.
403        let event_stream = self.event_stream.take();
404        drop(event_stream);
405
406        let sync_proxy = wlantap::WlantapPhySynchronousProxy::new(
407            // Arc::into_inner() should succeed in a properly constructed test. Using a WlantapPhyProxy
408            // returned from TestHelper beyond the lifetime of TestHelper is not supported.
409            Arc::<wlantap::WlantapPhyProxy>::into_inner(proxy)
410                .expect("Outstanding references to WlantapPhyProxy! Failed to drop TestHelper.")
411                .into_channel()
412                .expect("failed to get fidl::AsyncChannel from proxy")
413                .into_zx_channel(),
414        );
415
416        // TODO(b/307808624): At this point in the shutdown, we should
417        // stop wlancfg first and destroy all ifaces through
418        // fuchsia.wlan.device.service/DeviceMonitor.DestroyIface().
419        // This test framework does not currently support stopping
420        // individual components. If instead we drop the
421        // TestRealmProxy, and thus stop both wlancfg and
422        // wlandevicemonitor, wlandevicemonitor which will drop the
423        // GenericSme channel before graceful destruction of the
424        // iface. Dropping the GenericSme channel for an existing
425        // iface is considered an error because doing so prevents
426        // future communication with the iface.
427        //
428        // In lieu of stopping wlancfg first, we instead shutdown the
429        // phy device via WlantapPhy.Shutdown() which will block until
430        // both the phy and any remaining ifaces are shutdown. We
431        // first shutdown the phy to prevent any automated CreateIface
432        // calls from wlancfg after removing the iface.
433        sync_proxy
434            .shutdown(zx::MonotonicInstant::INFINITE)
435            .expect("Failed to shutdown WlantapPhy gracefully.");
436    }
437}
438
439pub struct RetryWithBackoff {
440    deadline: MonotonicInstant,
441    prev_delay: zx::MonotonicDuration,
442    next_delay: zx::MonotonicDuration,
443    max_delay: zx::MonotonicDuration,
444}
445impl RetryWithBackoff {
446    pub fn new(timeout: zx::MonotonicDuration) -> Self {
447        RetryWithBackoff {
448            deadline: MonotonicInstant::after(timeout),
449            prev_delay: zx::MonotonicDuration::from_millis(0),
450            next_delay: zx::MonotonicDuration::from_millis(1),
451            max_delay: zx::MonotonicDuration::INFINITE,
452        }
453    }
454    pub fn infinite_with_max_interval(max_delay: zx::MonotonicDuration) -> Self {
455        Self {
456            deadline: MonotonicInstant::INFINITE,
457            max_delay,
458            ..Self::new(zx::MonotonicDuration::from_nanos(0))
459        }
460    }
461
462    /// Return Err if the deadline was exceeded when this function was called.
463    /// Otherwise, sleep for a little longer (following Fibonacci series) or up
464    /// to the deadline, whichever is soonest. If a sleep occurred, this function
465    /// returns Ok. The value contained in both Ok and Err is the zx::MonotonicDuration
466    /// until or after the deadline when the function returns.
467    async fn sleep_unless_after_deadline_(
468        &mut self,
469        verbose: bool,
470    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
471        // Add an inner scope up to just after Timer::new to ensure all
472        // time assignments are dropped after the sleep occurs. This
473        // prevents misusing them after the sleep since they are all
474        // no longer correct after the clock moves.
475        {
476            if MonotonicInstant::after(zx::MonotonicDuration::from_millis(0)) > self.deadline {
477                if verbose {
478                    info!("Skipping sleep. Deadline exceeded.");
479                }
480                return Err(self.deadline - MonotonicInstant::now());
481            }
482
483            let sleep_deadline =
484                std::cmp::min(MonotonicInstant::after(self.next_delay), self.deadline);
485            if verbose {
486                let micros = sleep_deadline.into_nanos() / 1_000;
487                info!("Sleeping until {}.{} 😴", micros / 1_000_000, micros % 1_000_000);
488            }
489
490            Timer::new(sleep_deadline).await;
491        }
492
493        // If the next delay interval exceeds max_delay (even if by overflow),
494        // then saturate at max_delay.
495        if self.next_delay < self.max_delay {
496            let next_delay = std::cmp::min(
497                self.max_delay,
498                zx::MonotonicDuration::from_nanos(
499                    self.prev_delay.into_nanos().saturating_add(self.next_delay.into_nanos()),
500                ),
501            );
502            self.prev_delay = self.next_delay;
503            self.next_delay = next_delay;
504        }
505
506        Ok(self.deadline - MonotonicInstant::now())
507    }
508
509    pub async fn sleep_unless_after_deadline(
510        &mut self,
511    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
512        self.sleep_unless_after_deadline_(false).await
513    }
514
515    pub async fn sleep_unless_after_deadline_verbose(
516        &mut self,
517    ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
518        self.sleep_unless_after_deadline_(true).await
519    }
520}
521
522/// TODO(https://fxbug.dev/42164608): This function strips the `timestamp_nanos` field
523/// from each `fidl_fuchsia_wlan_policy::ScanResult` entry since the `timestamp_nanos`
524/// field is undefined.
525pub fn strip_timestamp_nanos_from_scan_results(
526    mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
527) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
528    for scan_result in &mut scan_result_list {
529        scan_result
530            .entries
531            .as_mut()
532            .unwrap()
533            .sort_by(|a, b| a.bssid.as_ref().unwrap().cmp(&b.bssid.as_ref().unwrap()));
534        for entry in scan_result.entries.as_mut().unwrap() {
535            // TODO(https://fxbug.dev/42164608): Strip timestamp_nanos since it's not implemented.
536            entry.timestamp_nanos.take();
537        }
538    }
539    scan_result_list
540}
541
542/// Sort a list of scan results by the `id` and `bssid` fields.
543///
544/// This function will panic if either of the `id` or `entries` fields
545/// are `None`.
546pub fn sort_policy_scan_result_list(
547    mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
548) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
549    scan_result_list
550        .sort_by(|a, b| a.id.as_ref().expect("empty id").cmp(&b.id.as_ref().expect("empty id")));
551    scan_result_list
552}
553
554/// Returns a map with the scan results returned by the policy layer. The map is
555/// keyed by the `id` field of each `fidl_fuchsia_policy::ScanResult`.
556///
557/// This function will panic if the `id` field is ever `None` or if policy returns
558/// the same `id` twice. Both of these are invariants we expect the policy layer
559/// to uphold.
560pub async fn policy_scan_for_networks<'a>(
561    client_controller: fidl_policy::ClientControllerProxy,
562) -> Vec<fidl_policy::ScanResult> {
563    // Request a scan from the policy layer.
564    let (scan_proxy, server_end) = create_proxy();
565    client_controller.scan_for_networks(server_end).expect("requesting scan");
566    let mut scan_result_list = Vec::new();
567    loop {
568        let proxy_result = scan_proxy.get_next().await.expect("getting scan results");
569        let next_scan_result_list = proxy_result.expect("scanning failed");
570        if next_scan_result_list.is_empty() {
571            break;
572        }
573        scan_result_list.extend(next_scan_result_list);
574    }
575    sort_policy_scan_result_list(strip_timestamp_nanos_from_scan_results(scan_result_list))
576}
577
578/// This function returns `Ok(r)`, where `r` is the return value from `main_future`,
579/// if `main_future` completes before the `timeout` duration. Otherwise, `Err(())` is returned.
580pub async fn timeout_after<R, F: Future<Output = R> + Unpin>(
581    timeout: zx::MonotonicDuration,
582    main_future: &mut F,
583) -> Result<R, ()> {
584    async { Ok(main_future.await) }.on_timeout(timeout.after_now(), || Err(())).await
585}