| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::format_err, |
| fidl::endpoints::Proxy, |
| fidl_fuchsia_io as fio, fidl_fuchsia_wlan_device as fidl_wlan_dev, fuchsia_async as fasync, |
| fuchsia_vfs_watcher::{WatchEvent, Watcher}, |
| fuchsia_zircon::Status as zx_Status, |
| futures::prelude::*, |
| log::{error, warn}, |
| std::io, |
| std::path::{Path, PathBuf}, |
| std::str::FromStr, |
| }; |
| |
| pub struct NewPhyDevice { |
| pub id: u16, |
| pub proxy: fidl_wlan_dev::PhyProxy, |
| pub device: wlan_dev::Device, |
| } |
| |
| pub fn watch_phy_devices<E: wlan_dev::DeviceEnv>( |
| ) -> io::Result<impl Stream<Item = Result<NewPhyDevice, anyhow::Error>>> { |
| Ok(watch_new_devices::<_, E>(E::PHY_PATH)?.try_filter_map(|path| { |
| future::ready(Ok(handle_open_error(&path, new_phy::<E>(&path), "phy"))) |
| })) |
| } |
| |
| fn handle_open_error<T>( |
| path: &PathBuf, |
| r: Result<T, anyhow::Error>, |
| device_type: &'static str, |
| ) -> Option<T> { |
| if let Err(ref e) = &r { |
| if let Some(&zx_Status::ALREADY_BOUND) = e.downcast_ref::<zx_Status>() { |
| warn!("Cannot open already-bound device: {} '{}'", device_type, path.display()) |
| } else { |
| error!("Error opening {} '{}': {}", device_type, path.display(), e) |
| } |
| } |
| r.ok() |
| } |
| |
| /// Watches a specified device directory for new WLAN PHYs. |
| /// |
| /// When new entries are discovered in the specified directory the paths to the new devices are |
| /// sent along the stream that is returned by this function. |
| /// |
| /// Note that a `DeviceEnv` trait is required in order for this function to work. This enables |
| /// wlandevicemonitor to function in real and in simulated environments where devices are presented |
| /// differently. |
| /// |
| /// # Arguments |
| /// |
| /// * `path` - Path struct that represents the path to the device directory. |
| fn watch_new_devices<P: AsRef<Path>, E: wlan_dev::DeviceEnv>( |
| path: P, |
| ) -> io::Result<impl Stream<Item = Result<PathBuf, anyhow::Error>>> { |
| let raw_dir = E::open_dir(&path)?; |
| let zircon_channel = fdio::clone_channel(&raw_dir)?; |
| let async_channel = fasync::Channel::from_channel(zircon_channel)?; |
| let directory = fio::DirectoryProxy::from_channel(async_channel); |
| Ok(async move { |
| let watcher = Watcher::new(directory).await?; |
| Ok(watcher |
| .try_filter_map(move |msg| { |
| future::ready(Ok(match msg.event { |
| WatchEvent::EXISTING | WatchEvent::ADD_FILE => { |
| Some(path.as_ref().join(msg.filename)) |
| } |
| _ => None, |
| })) |
| }) |
| .err_into()) |
| } |
| .try_flatten_stream()) |
| } |
| |
| fn new_phy<E: wlan_dev::DeviceEnv>(path: &PathBuf) -> Result<NewPhyDevice, anyhow::Error> { |
| let id = id_from_path(path)?; |
| let device = E::device_from_path(path)?; |
| let proxy = wlan_dev::connect_wlan_phy(&device)?; |
| Ok(NewPhyDevice { id, proxy, device }) |
| } |
| |
| fn id_from_path(path: &PathBuf) -> Result<u16, anyhow::Error> { |
| let file_name = path.file_name().ok_or_else(|| format_err!("Invalid device path"))?; |
| let file_name_str = |
| file_name.to_str().ok_or_else(|| format_err!("Filename is not valid UTF-8"))?; |
| let id = u16::from_str(&file_name_str) |
| .map_err(|e| format_err!("Failed to parse device filename as a numeric ID: {}", e))?; |
| Ok(id) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| fidl_fuchsia_wlan_common as fidl_wlan_common, |
| fidl_fuchsia_wlan_device::{self as fidl_wlan_dev}, |
| fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_tap as fidl_wlantap, |
| fuchsia_zircon::prelude::*, |
| futures::{poll, task::Poll}, |
| log::info, |
| pin_utils::pin_mut, |
| std::convert::TryInto, |
| wlan_common::{ie::*, test_utils::ExpectWithin}, |
| wlantap_client, |
| zerocopy::AsBytes, |
| }; |
| |
| #[cfg(feature = "v2")] |
| use wlan_dev::DeviceEnv; |
| |
| #[cfg(not(feature = "v2"))] |
| use isolated_devmgr::IsolatedDeviceEnv; |
| |
| // TODO(78050): When all WLAN components migrate to Component Framework v2 and the v1 manifests |
| // are deprecated, enable this test. |
| #[test] |
| #[cfg(feature = "v2")] |
| fn watch_phys() { |
| let mut exec = fasync::TestExecutor::new().expect("Failed to create an executor"); |
| let phy_watcher = |
| watch_phy_devices::<wlan_dev::RealDeviceEnv>().expect("Failed to create phy_watcher"); |
| pin_mut!(phy_watcher); |
| |
| // Wait for the wlantap to appear. |
| let raw_dir = wlan_dev::RealDeviceEnv::open_dir("/dev").expect("failed to open /dev"); |
| let zircon_channel = |
| fdio::clone_channel(&raw_dir).expect("failed to clone directory channel"); |
| let async_channel = fasync::Channel::from_channel(zircon_channel) |
| .expect("failed to create async channel from zircon channel"); |
| let dir = fio::DirectoryProxy::from_channel(async_channel); |
| let monitor_fut = device_watcher::recursive_wait_and_open_node(&dir, "sys/test/wlantapctl"); |
| pin_mut!(monitor_fut); |
| |
| info!("Beginning wlantapctl monitor."); |
| exec.run_singlethreaded(async { |
| monitor_fut.await.expect("error while watching for wlantapctl") |
| }); |
| info!("wlantapctl discovered."); |
| |
| // Now that the wlantapctl device is present, connect to it. |
| let wlantap = wlantap_client::Wlantap::open().expect("Failed to connect to wlantapctl"); |
| |
| // Create an intentionally unused variable instead of a plain |
| // underscore. Otherwise, this end of the channel will be |
| // dropped and cause the phy device to begin unbinding. |
| let _wlantap_phy = |
| wlantap.create_phy(create_wlantap_config()).expect("failed to create PHY"); |
| exec.run_singlethreaded(async { |
| phy_watcher |
| .next() |
| .expect_within(5.seconds(), "phy_watcher did not respond") |
| .await |
| .expect("phy_watcher ended without yielding a phy") |
| .expect("phy_watcher returned an error"); |
| if let Poll::Ready(..) = poll!(phy_watcher.next()) { |
| panic!("phy_watcher found more than one phy"); |
| } |
| }) |
| } |
| |
| #[test] |
| #[cfg(not(feature = "v2"))] |
| fn watch_phys() { |
| let mut exec = fasync::TestExecutor::new().expect("Failed to create an executor"); |
| let phy_watcher = |
| watch_phy_devices::<IsolatedDeviceEnv>().expect("Failed to create phy_watcher"); |
| pin_mut!(phy_watcher); |
| let wlantap = wlantap_client::Wlantap::open_from_isolated_devmgr() |
| .expect("Failed to connect to wlantapctl"); |
| // Create an intentionally unused variable instead of a plain |
| // underscore. Otherwise, this end of the channel will be |
| // dropped and cause the phy device to begin unbinding. |
| let _wlantap_phy = wlantap.create_phy(create_wlantap_config()); |
| exec.run_singlethreaded(async { |
| phy_watcher |
| .next() |
| .expect_within(5.seconds(), "phy_watcher did not respond") |
| .await |
| .expect("phy_watcher ended without yielding a phy") |
| .expect("phy_watcher returned an error"); |
| if let Poll::Ready(..) = poll!(phy_watcher.next()) { |
| panic!("phy_watcher found more than one phy"); |
| } |
| }) |
| } |
| |
| #[test] |
| fn handle_open_succeeds() { |
| assert!(handle_open_error(&PathBuf::new(), Ok(()), "phy").is_some()) |
| } |
| |
| #[test] |
| fn handle_open_fails() { |
| assert!(handle_open_error::<()>(&PathBuf::new(), Err(format_err!("test failure")), "phy") |
| .is_none()) |
| } |
| |
| fn create_wlantap_config() -> fidl_wlantap::WlantapPhyConfig { |
| fidl_wlantap::WlantapPhyConfig { |
| sta_addr: [1; 6], |
| supported_phys: vec![ |
| fidl_wlan_common::WlanPhyType::Dsss, |
| fidl_wlan_common::WlanPhyType::Hr, |
| fidl_wlan_common::WlanPhyType::Ofdm, |
| fidl_wlan_common::WlanPhyType::Erp, |
| fidl_wlan_common::WlanPhyType::Ht, |
| ], |
| mac_role: fidl_wlan_common::WlanMacRole::Client, |
| hardware_capability: 0, |
| bands: vec![create_2_4_ghz_band_info()], |
| name: String::from("devwatchtap"), |
| quiet: false, |
| discovery_support: fidl_wlan_common::DiscoverySupport { |
| scan_offload: fidl_wlan_common::ScanOffloadExtension { supported: false }, |
| probe_response_offload: fidl_wlan_common::ProbeResponseOffloadExtension { |
| supported: false, |
| }, |
| }, |
| mac_sublayer_support: fidl_wlan_common::MacSublayerSupport { |
| rate_selection_offload: fidl_wlan_common::RateSelectionOffloadExtension { |
| supported: false, |
| }, |
| data_plane: fidl_wlan_common::DataPlaneExtension { |
| data_plane_type: fidl_wlan_common::DataPlaneType::EthernetDevice, |
| }, |
| device: fidl_wlan_common::DeviceExtension { |
| is_synthetic: false, |
| mac_implementation_type: fidl_wlan_common::MacImplementationType::Softmac, |
| tx_status_report_supported: false, |
| }, |
| }, |
| security_support: fidl_wlan_common::SecuritySupport { |
| sae: fidl_wlan_common::SaeFeature { |
| driver_handler_supported: false, |
| sme_handler_supported: false, |
| }, |
| mfp: fidl_wlan_common::MfpFeature { supported: false }, |
| }, |
| spectrum_management_support: fidl_wlan_common::SpectrumManagementSupport { |
| dfs: fidl_wlan_common::DfsFeature { supported: false }, |
| }, |
| } |
| } |
| |
| fn create_2_4_ghz_band_info() -> fidl_wlan_dev::BandInfo { |
| fidl_wlan_dev::BandInfo { |
| band: fidl_wlan_common::WlanBand::TwoGhz, |
| ht_caps: Some(Box::new(fidl_ieee80211::HtCapabilities { |
| bytes: fake_ht_capabilities().as_bytes().try_into().unwrap(), |
| })), |
| vht_caps: None, |
| rates: vec![2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108], |
| operating_channels: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], |
| } |
| } |
| } |