1use crate::event::{self, Handler};
6use crate::netdevice_helper;
7use crate::wlancfg_helper::{NetworkConfigBuilder, start_ap_and_wait_for_confirmation};
8use anyhow::Context;
9use fidl::endpoints::{Proxy, ServiceMarker, create_endpoints, create_proxy};
10use fidl_fuchsia_component_test as fcomponent_test;
11use fidl_fuchsia_driver_test as fidl_driver_test;
12use fidl_fuchsia_wlan_policy as fidl_policy;
13use fidl_fuchsia_wlan_tap as wlantap;
14use fidl_test_wlan_realm as fidl_realm;
15use fuchsia_async::{DurationExt, MonotonicInstant, TimeoutExt, Timer};
16use fuchsia_component::client::{
17 Service, connect_channel_to_protocol_at, connect_to_protocol, connect_to_protocol_at,
18};
19use fuchsia_fs::directory::{WatchEvent, Watcher};
20use futures::channel::oneshot;
21use futures::{FutureExt, StreamExt};
22use ieee80211::{MacAddr, MacAddrBytes};
23use log::{debug, info, warn};
24use realm_client::{InstalledNamespace, extend_namespace};
25use std::fmt::Display;
26use std::future::Future;
27use std::pin::Pin;
28use std::sync::Arc;
29use std::task::Poll;
30use test_realm_helpers::tracing::Tracing;
31use wlan_common::test_utils::ExpectWithin;
32use wlan_fidl_ext::try_unpack::{TryUnpack, WithName};
33
34const TIMEOUT_WARN_THRESHOLD: f64 = 0.8;
36
37pub struct TestRealmContext {
66 test_ns: Arc<InstalledNamespace>,
70
71 devfs: fidl_fuchsia_io::DirectoryProxy,
73
74 wlan_policy_moniker: Option<String>,
75 wlandevicemonitor_moniker: String,
76
77 _tracing: Tracing,
80}
81
82impl TestRealmContext {
83 pub async fn new(config: fidl_realm::WlanConfig) -> Arc<Self> {
88 let with_policy = config
89 .with_policy
90 .with_name("with_policy")
91 .try_unpack()
92 .expect("with_policy must be specified");
93
94 let realm_factory = connect_to_protocol::<fidl_realm::RealmFactoryMarker>()
95 .expect("Could not connect to realm factory protocol");
96
97 let (dict_1, dict_2) = zx::EventPair::create();
98
99 let trace_manager_hermeticity = config.trace_manager_hermeticity.clone();
104 let options = fidl_realm::RealmOptions { wlan_config: Some(config), ..Default::default() };
105 let response = realm_factory
106 .create_realm(options, dict_1)
107 .await
108 .expect("FIDL error on create_realm")
109 .expect("Could not create realm");
110 let test_ns =
111 Arc::new(extend_namespace(realm_factory, dict_2).await.expect("failed to extend ns"));
112
113 let devfs = fuchsia_fs::directory::open_in_namespace(
114 &format!("{}/dev-topological", test_ns.prefix()),
115 fidl_fuchsia_io::PERM_READABLE,
116 )
117 .expect("Failed to open devfs from extended namespace");
118
119 let driver_test_realm_proxy =
121 connect_to_protocol_at::<fidl_driver_test::RealmMarker>(&*test_ns)
122 .expect("Failed to connect to driver test realm");
123
124 let (pkg_client, pkg_server) = create_endpoints();
125 fuchsia_fs::directory::open_channel_in_namespace(
126 "/pkg",
127 fidl_fuchsia_io::PERM_READABLE | fidl_fuchsia_io::PERM_EXECUTABLE,
128 pkg_server,
129 )
130 .expect("Could not open /pkg");
131
132 let test_component = fidl_fuchsia_component_resolution::Component {
133 package: Some(fidl_fuchsia_component_resolution::Package {
134 directory: Some(pkg_client),
135 ..Default::default()
136 }),
137 ..Default::default()
138 };
139
140 let dtr_exposes = vec![
141 fcomponent_test::Capability::Service(fcomponent_test::Service {
142 name: Some("fuchsia.wlan.phy.Service".to_string()),
143 ..Default::default()
144 }),
145 fcomponent_test::Capability::Service(fcomponent_test::Service {
146 name: Some("fuchsia.wlan.tap.Service".to_string()),
147 ..Default::default()
148 }),
149 ];
150
151 driver_test_realm_proxy
152 .start(fidl_driver_test::RealmArgs {
153 test_component: Some(test_component),
154 dtr_exposes: Some(dtr_exposes),
155 ..Default::default()
156 })
157 .await
158 .expect("FIDL error when starting driver test realm")
159 .expect("Driver test realm server returned an error");
160
161 let _tracing = match trace_manager_hermeticity {
162 Some(fidl_realm::TraceManagerHermeticity::Hermetic) | None => {
163 Tracing::start_at(Arc::clone(&test_ns)).await.unwrap()
164 }
165 Some(fidl_realm::TraceManagerHermeticity::NonHermetic) => {
166 Tracing::start_non_hermetic(test_ns.prefix().strip_prefix("/").unwrap())
167 .await
168 .unwrap()
169 }
170 };
171
172 let wlan_policy_moniker = if with_policy {
173 Some(
174 response
175 .wlan_policy_moniker
176 .as_ref()
177 .expect("wlan_policy moniker must be present")
178 .clone(),
179 )
180 } else {
181 assert!(
182 response.wlan_policy_moniker.is_none(),
183 "wlan_policy moniker should not be present"
184 );
185 None
186 };
187 let wlandevicemonitor_moniker = response
188 .wlandevicemonitor_moniker
189 .as_ref()
190 .expect("wlandevicemonitor moniker must be present")
191 .clone();
192
193 Arc::new(Self { test_ns, devfs, wlan_policy_moniker, wlandevicemonitor_moniker, _tracing })
194 }
195
196 pub fn test_ns_prefix(&self) -> &str {
197 self.test_ns.prefix()
198 }
199
200 pub fn devfs(&self) -> &fidl_fuchsia_io::DirectoryProxy {
201 &self.devfs
202 }
203}
204
205type EventStream = wlantap::WlantapPhyEventStream;
206pub struct TestHelper {
207 ctx: Arc<TestRealmContext>,
208 netdevice_task_handles: Vec<fuchsia_async::Task<()>>,
209 _wlantap: Wlantap,
210 proxy: Arc<wlantap::WlantapPhyProxy>,
211 event_stream: Option<EventStream>,
212}
213struct TestHelperFuture<H, F>
214where
215 H: Handler<(), wlantap::WlantapPhyEvent>,
216 F: Future + Unpin,
217{
218 event_stream: Option<EventStream>,
219 handler: H,
220 future: F,
221}
222impl<H, F> Unpin for TestHelperFuture<H, F>
223where
224 H: Handler<(), wlantap::WlantapPhyEvent>,
225 F: Future + Unpin,
226{
227}
228impl<H, F> Future for TestHelperFuture<H, F>
229where
230 H: Handler<(), wlantap::WlantapPhyEvent>,
231 F: Future + Unpin,
232{
233 type Output = (F::Output, EventStream);
234 fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
236 let helper = &mut *self;
237 let stream = helper.event_stream.as_mut().unwrap();
238 loop {
239 if let Poll::Ready(x) = helper.future.poll_unpin(cx) {
247 return Poll::Ready((x, helper.event_stream.take().unwrap()));
248 }
249
250 match stream.poll_next_unpin(cx) {
251 Poll::Ready(optional_result) => {
252 let event = optional_result
253 .expect("Unexpected end of the WlantapPhy event stream")
254 .expect("WlantapPhy event stream returned an error");
255 helper.handler.call(&mut (), &event);
256 }
257 Poll::Pending => {
258 debug!(
259 "Main future poll response is pending and there are no events to process."
260 );
261 return Poll::Pending;
262 }
263 }
264 }
265 }
266}
267impl TestHelper {
268 pub async fn begin_test(
272 phy_config: wlantap::WlantapPhyConfig,
273 realm_config: fidl_realm::WlanConfig,
274 ) -> Self {
275 let ctx = TestRealmContext::new(realm_config).await;
276 Self::begin_test_with_context(ctx, phy_config).await
277 }
278
279 pub async fn begin_test_with_context(
298 ctx: Arc<TestRealmContext>,
299 config: wlantap::WlantapPhyConfig,
300 ) -> Self {
301 let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
302 helper.wait_for_phy().await;
303 if helper.ctx.wlan_policy_moniker.is_some() {
304 helper.wait_for_wlan_softmac_start().await;
305 }
306 helper
307 }
308
309 pub async fn begin_ap_test(
313 phy_config: wlantap::WlantapPhyConfig,
314 network_config: NetworkConfigBuilder,
315 realm_config: fidl_realm::WlanConfig,
316 ) -> Self {
317 let ctx = TestRealmContext::new(realm_config).await;
318 Self::begin_ap_test_with_context(ctx, phy_config, network_config).await
319 }
320
321 pub async fn begin_ap_test_with_context(
343 ctx: Arc<TestRealmContext>,
344 config: wlantap::WlantapPhyConfig,
345 network_config: NetworkConfigBuilder,
346 ) -> Self {
347 let mut helper = TestHelper::create_phy_and_helper(config, ctx).await;
348 helper.wait_for_phy().await;
349 start_ap_and_wait_for_confirmation(helper.ctx.test_ns_prefix(), network_config).await;
350 helper.wait_for_wlan_softmac_start().await;
351 helper
352 }
353
354 async fn create_phy_and_helper(
355 config: wlantap::WlantapPhyConfig,
356 ctx: Arc<TestRealmContext>,
357 ) -> Self {
358 let wlantap = Wlantap::open_from_namespace(ctx.test_ns_prefix())
360 .await
361 .expect("Failed to open wlantapctl");
362 let proxy = wlantap.create_phy(config).await.expect("Failed to create wlantap PHY");
363 let event_stream = Some(proxy.take_event_stream());
364 TestHelper {
365 ctx,
366 netdevice_task_handles: vec![],
367 _wlantap: wlantap,
368 proxy: Arc::new(proxy),
369 event_stream,
370 }
371 }
372
373 async fn wait_for_phy(&self) {
374 let dm = fuchsia_component::client::connect_to_protocol_at::<
375 fidl_fuchsia_wlan_device_service::DeviceMonitorMarker,
376 >(self.ctx.test_ns_prefix())
377 .expect("failed to connect to DeviceMonitor");
378 let (watcher_proxy, watcher_server_end) = fidl::endpoints::create_proxy::<
379 fidl_fuchsia_wlan_device_service::DeviceWatcherMarker,
380 >();
381 dm.watch_devices(watcher_server_end).expect("failed to watch devices");
382 let mut stream = watcher_proxy.take_event_stream();
383 while let Some(event) = stream.next().await {
384 match event.expect("Watcher event error") {
385 fidl_fuchsia_wlan_device_service::DeviceWatcherEvent::OnPhyAdded { .. } => {
386 break;
387 }
388 _ => {}
389 }
390 }
391 }
392
393 async fn wait_for_wlan_softmac_start(&mut self) {
394 let (sender, receiver) = oneshot::channel::<()>();
395 self.run_until_complete_or_timeout(
396 zx::MonotonicDuration::from_seconds(12),
397 "receive a WlanSoftmacStart event",
398 event::on_start_mac(event::once(|_, _| sender.send(()))),
399 receiver,
400 )
401 .await
402 .unwrap_or_else(|oneshot::Canceled| panic!());
403 }
404
405 pub fn proxy(&self) -> Arc<wlantap::WlantapPhyProxy> {
410 Arc::clone(&self.proxy)
411 }
412
413 pub fn test_ns_prefix(&self) -> &str {
414 self.ctx.test_ns_prefix()
415 }
416
417 pub fn devfs(&self) -> &fidl_fuchsia_io::DirectoryProxy {
418 self.ctx.devfs()
419 }
420
421 pub async fn start_netdevice_session(
422 &mut self,
423 mac: MacAddr,
424 ) -> (netdevice_client::Session, netdevice_client::Port) {
425 let mac = fidl_fuchsia_net::MacAddress { octets: mac.to_array() };
426 let (client, port) = netdevice_helper::create_client(self.devfs(), mac)
427 .await
428 .expect("failed to create netdevice client");
429 let (session, task_handle) = netdevice_helper::start_session(client, port).await;
430 self.netdevice_task_handles.push(task_handle);
431 (session, port)
432 }
433
434 pub async fn run_until_complete_or_timeout<H, F>(
440 &mut self,
441 timeout: zx::MonotonicDuration,
442 context: impl Display,
443 handler: H,
444 future: F,
445 ) -> F::Output
446 where
447 H: Handler<(), wlantap::WlantapPhyEvent>,
448 F: Future + Unpin,
449 {
450 info!("Running main future until completion or timeout with event handler: {}", context);
451 let start_time = zx::MonotonicInstant::get();
452 let (item, stream) = TestHelperFuture {
453 event_stream: Some(self.event_stream.take().unwrap()),
454 handler,
455 future,
456 }
457 .expect_within(timeout, format!("Main future timed out: {}", context))
458 .await;
459 let end_time = zx::MonotonicInstant::get();
460 let elapsed = end_time - start_time;
461 let elapsed_seconds = elapsed.into_seconds_f64();
462 let elapsed_ratio = elapsed_seconds / timeout.into_seconds_f64();
463 if elapsed_ratio < TIMEOUT_WARN_THRESHOLD {
464 info!("Main future completed in {:.2} seconds: {}", elapsed_seconds, context);
465 } else {
466 warn!(
467 "Main future completed in {:.2} seconds ({:.1}% of timeout): {}",
468 elapsed_seconds,
469 elapsed_ratio * 100.,
470 context,
471 );
472 }
473 self.event_stream = Some(stream);
474 item
475 }
476}
477impl Drop for TestHelper {
478 fn drop(&mut self) {
479 while let Some(task_handle) = self.netdevice_task_handles.pop() {
483 drop(task_handle);
484 }
485
486 let (placeholder_proxy, _server_end) =
489 fidl::endpoints::create_proxy::<wlantap::WlantapPhyMarker>();
490 let mut proxy = Arc::new(placeholder_proxy);
491 std::mem::swap(&mut self.proxy, &mut proxy);
492
493 let event_stream = self.event_stream.take();
497 drop(event_stream);
498
499 let sync_proxy = wlantap::WlantapPhySynchronousProxy::new(
500 Arc::<wlantap::WlantapPhyProxy>::into_inner(proxy)
503 .expect("Outstanding references to WlantapPhyProxy! Failed to drop TestHelper.")
504 .into_channel()
505 .expect("failed to get fidl::AsyncChannel from proxy")
506 .into_zx_channel(),
507 );
508
509 let (lc_client_end, lc_server_end) = zx::Channel::create();
510 connect_channel_to_protocol_at::<fidl_fuchsia_sys2::LifecycleControllerMarker>(
511 lc_server_end,
512 self.ctx.test_ns_prefix(),
513 )
514 .expect("Failed to connect to LifecycleController");
515 let lc = fidl_fuchsia_sys2::LifecycleControllerSynchronousProxy::new(lc_client_end);
516
517 if let Some(wlan_policy_moniker) = &self.ctx.wlan_policy_moniker {
518 lc.stop_instance(wlan_policy_moniker, zx::MonotonicInstant::INFINITE)
519 .expect("Failed to call stop_instance")
520 .expect("stop_instance returned an error");
521 }
522
523 let (dm_client_end, dm_server_end) = zx::Channel::create();
524 connect_channel_to_protocol_at::<fidl_fuchsia_wlan_device_service::DeviceMonitorMarker>(
525 dm_server_end,
526 self.ctx.test_ns_prefix(),
527 )
528 .expect("Failed to connect to DeviceMonitor");
529 let dm =
530 fidl_fuchsia_wlan_device_service::DeviceMonitorSynchronousProxy::new(dm_client_end);
531
532 let ifaces =
533 dm.list_ifaces(zx::MonotonicInstant::INFINITE).expect("Failed to call list_ifaces");
534 for iface in ifaces {
535 let req = &fidl_fuchsia_wlan_device_service::DestroyIfaceRequest { iface_id: iface };
536 let status = dm
537 .destroy_iface(req, zx::MonotonicInstant::INFINITE)
538 .expect("Failed to call destroy_iface");
539 assert_eq!(status, 0, "destroy_iface returned an error: {}", status);
540 }
541
542 lc.stop_instance(&self.ctx.wlandevicemonitor_moniker, zx::MonotonicInstant::INFINITE)
543 .expect("Failed to call stop_instance")
544 .expect("stop_instance returned an error");
545
546 sync_proxy
561 .shutdown(zx::MonotonicInstant::INFINITE)
562 .expect("Failed to shutdown WlantapPhy gracefully.");
563 }
564}
565
566pub struct RetryWithBackoff {
567 deadline: MonotonicInstant,
568 prev_delay: zx::MonotonicDuration,
569 next_delay: zx::MonotonicDuration,
570 max_delay: zx::MonotonicDuration,
571}
572impl RetryWithBackoff {
573 pub fn new(timeout: zx::MonotonicDuration) -> Self {
574 RetryWithBackoff {
575 deadline: MonotonicInstant::after(timeout),
576 prev_delay: zx::MonotonicDuration::from_millis(0),
577 next_delay: zx::MonotonicDuration::from_millis(1),
578 max_delay: zx::MonotonicDuration::INFINITE,
579 }
580 }
581 pub fn infinite_with_max_interval(max_delay: zx::MonotonicDuration) -> Self {
582 Self {
583 deadline: MonotonicInstant::INFINITE,
584 max_delay,
585 ..Self::new(zx::MonotonicDuration::from_nanos(0))
586 }
587 }
588
589 async fn sleep_unless_after_deadline_(
595 &mut self,
596 verbose: bool,
597 ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
598 {
603 if MonotonicInstant::after(zx::MonotonicDuration::from_millis(0)) > self.deadline {
604 if verbose {
605 info!("Skipping sleep. Deadline exceeded.");
606 }
607 return Err(self.deadline - MonotonicInstant::now());
608 }
609
610 let sleep_deadline =
611 std::cmp::min(MonotonicInstant::after(self.next_delay), self.deadline);
612 if verbose {
613 let micros = sleep_deadline.into_nanos() / 1_000;
614 info!("Sleeping until {}.{} 😴", micros / 1_000_000, micros % 1_000_000);
615 }
616
617 Timer::new(sleep_deadline).await;
618 }
619
620 if self.next_delay < self.max_delay {
623 let next_delay = std::cmp::min(
624 self.max_delay,
625 zx::MonotonicDuration::from_nanos(
626 self.prev_delay.into_nanos().saturating_add(self.next_delay.into_nanos()),
627 ),
628 );
629 self.prev_delay = self.next_delay;
630 self.next_delay = next_delay;
631 }
632
633 Ok(self.deadline - MonotonicInstant::now())
634 }
635
636 pub async fn sleep_unless_after_deadline(
637 &mut self,
638 ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
639 self.sleep_unless_after_deadline_(false).await
640 }
641
642 pub async fn sleep_unless_after_deadline_verbose(
643 &mut self,
644 ) -> Result<zx::MonotonicDuration, zx::MonotonicDuration> {
645 self.sleep_unless_after_deadline_(true).await
646 }
647}
648
649pub fn strip_timestamp_nanos_from_scan_results(
653 mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
654) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
655 for scan_result in &mut scan_result_list {
656 scan_result
657 .entries
658 .as_mut()
659 .unwrap()
660 .sort_by(|a, b| a.bssid.as_ref().unwrap().cmp(&b.bssid.as_ref().unwrap()));
661 for entry in scan_result.entries.as_mut().unwrap() {
662 entry.timestamp_nanos.take();
664 }
665 }
666 scan_result_list
667}
668
669pub fn sort_policy_scan_result_list(
674 mut scan_result_list: Vec<fidl_fuchsia_wlan_policy::ScanResult>,
675) -> Vec<fidl_fuchsia_wlan_policy::ScanResult> {
676 scan_result_list
677 .sort_by(|a, b| a.id.as_ref().expect("empty id").cmp(&b.id.as_ref().expect("empty id")));
678 scan_result_list
679}
680
681pub async fn policy_scan_for_networks<'a>(
688 client_controller: fidl_policy::ClientControllerProxy,
689) -> Vec<fidl_policy::ScanResult> {
690 let (scan_proxy, server_end) = create_proxy();
692 client_controller.scan_for_networks(server_end).expect("requesting scan");
693 let mut scan_result_list = Vec::new();
694 loop {
695 let proxy_result = scan_proxy.get_next().await.expect("getting scan results");
696 let next_scan_result_list = proxy_result.expect("scanning failed");
697 if next_scan_result_list.is_empty() {
698 break;
699 }
700 scan_result_list.extend(next_scan_result_list);
701 }
702 sort_policy_scan_result_list(strip_timestamp_nanos_from_scan_results(scan_result_list))
703}
704
705pub async fn timeout_after<R, F: Future<Output = R> + Unpin>(
708 timeout: zx::MonotonicDuration,
709 main_future: &mut F,
710) -> Result<R, ()> {
711 async { Ok(main_future.await) }.on_timeout(timeout.after_now(), || Err(())).await
712}
713
714pub struct Wlantap {
715 proxy: wlantap::WlantapCtlProxy,
716}
717
718impl Wlantap {
719 pub async fn open_from_namespace(prefix: &str) -> Result<Self, anyhow::Error> {
720 let test_ns_dir =
721 fuchsia_fs::directory::open_in_namespace(prefix, fidl_fuchsia_io::Flags::empty())?;
722
723 let mut watcher = Watcher::new(&test_ns_dir).await?;
724 loop {
725 let message = watcher
726 .next()
727 .await
728 .context("Directory watcher finished without finding wlantap service")??;
729 if message.event == WatchEvent::ADD_FILE || message.event == WatchEvent::EXISTING {
730 if message.filename.to_str() == Some(wlantap::ServiceMarker::SERVICE_NAME) {
731 break;
732 }
733 }
734 }
735
736 let service = Service::open_from_dir(&test_ns_dir, wlantap::ServiceMarker)?;
737 let service_proxy = service.watch_for_any().await?;
738 let proxy = service_proxy.connect_to_wlantap_ctl()?;
739 Ok(Self { proxy })
740 }
741
742 pub async fn create_phy(
743 &self,
744 config: wlantap::WlantapPhyConfig,
745 ) -> Result<wlantap::WlantapPhyProxy, anyhow::Error> {
746 let Self { proxy } = self;
747 let (ours, theirs) = fidl::endpoints::create_proxy();
748
749 let status = proxy.create_phy(&config, theirs).await?;
750 let () = zx::ok(status)?;
751
752 Ok(ours)
753 }
754}