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