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