| // Copyright 2018 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_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, info}, |
| 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>, |
| context: &'static str, |
| ) -> Option<T> { |
| if let Err(ref e) = &r { |
| if let Some(&zx_Status::ALREADY_BOUND) = e.downcast_ref::<zx_Status>() { |
| info!("{} '{}' already open, deferring", context, path.display()) |
| } else { |
| error!("Error opening {} '{}': {}", context, path.display(), e) |
| } |
| } |
| r.ok() |
| } |
| |
| fn watch_new_devices<P: AsRef<Path>, E: wlan_dev::DeviceEnv>( |
| path: P, |
| ) -> io::Result<impl Stream<Item = Result<PathBuf, anyhow::Error>>> { |
| let dir = E::open_dir(&path)?; |
| let channel = fdio::clone_channel(&dir)?; |
| let async_channel = fasync::Channel::from_channel(channel)?; |
| let directory = fidl_fuchsia_io::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_common, |
| fidl_fuchsia_wlan_device::{self as fidl_wlan_dev, SupportedPhy}, |
| fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_tap as fidl_wlantap, |
| fuchsia_async as fasync, |
| fuchsia_zircon::prelude::*, |
| pin_utils::pin_mut, |
| std::convert::TryInto, |
| wlan_common::{ie::*, test_utils::ExpectWithin}, |
| wlantap_client, |
| zerocopy::AsBytes, |
| }; |
| |
| #[test] |
| fn watch_phys() { |
| let mut exec = fasync::Executor::new().expect("Failed to create an executor"); |
| let new_phy_stream = |
| watch_phy_devices::<wlan_dev::RealDeviceEnv>().expect("watch_phy_devices() failed"); |
| pin_mut!(new_phy_stream); |
| let wlantap = wlantap_client::Wlantap::open().expect("Failed to connect to wlantapctl"); |
| let _tap_phy = wlantap.create_phy(create_wlantap_config(*b"wtchph")); |
| for _ in 0..10 { |
| // 5 is more than enough even for Toulouse but let's be generous |
| let new_phy = exec |
| .run_singlethreaded( |
| new_phy_stream.next().expect_within(2.seconds(), "No more phys"), |
| ) |
| .expect("new_phy_stream ended without yielding a phy") |
| .expect("new_phy_stream returned an error"); |
| let query_resp = |
| exec.run_singlethreaded(new_phy.proxy.query()).expect("phy query failed"); |
| if b"wtchph" == &query_resp.info.hw_mac_address { |
| return; |
| } |
| } |
| panic!("Did not get the phy we are looking for"); |
| } |
| |
| fn create_wlantap_config(mac_addr: [u8; 6]) -> fidl_wlantap::WlantapPhyConfig { |
| fidl_wlantap::WlantapPhyConfig { |
| phy_info: fidl_wlan_dev::PhyInfo { |
| id: 0, |
| dev_path: None, |
| hw_mac_address: mac_addr, |
| supported_phys: vec![ |
| SupportedPhy::Dsss, |
| SupportedPhy::Cck, |
| SupportedPhy::Ofdm, |
| SupportedPhy::Ht, |
| ], |
| driver_features: vec![], |
| mac_roles: vec![fidl_wlan_dev::MacRole::Client], |
| caps: vec![], |
| bands: vec![create_2_4_ghz_band_info()], |
| }, |
| name: String::from("devwatchtap"), |
| quiet: false, |
| } |
| } |
| |
| fn create_2_4_ghz_band_info() -> fidl_wlan_dev::BandInfo { |
| fidl_wlan_dev::BandInfo { |
| band_id: fidl_common::Band::WlanBand2Ghz, |
| ht_caps: Some(Box::new(fidl_mlme::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], |
| supported_channels: fidl_wlan_dev::ChannelList { |
| base_freq: 2407, |
| channels: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], |
| }, |
| } |
| } |
| } |