blob: 741dd95ab3f80891d5c9a1d57f0404592d3dc8a1 [file] [log] [blame]
// 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 failure::format_err;
use fidl_fuchsia_wlan_device as fidl_wlan_dev;
use fidl_fuchsia_wlan_mlme as fidl_mlme;
use fuchsia_vfs_watcher::{WatchEvent, Watcher};
use fuchsia_wlan_dev as wlan_dev;
use fuchsia_zircon::Status as zx_Status;
use futures::prelude::*;
use log::{error, info};
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
const PHY_PATH: &str = "/dev/class/wlanphy";
const IFACE_PATH: &str = "/dev/class/wlanif";
pub struct NewPhyDevice {
pub id: u16,
pub proxy: fidl_wlan_dev::PhyProxy,
pub device: wlan_dev::Device,
}
pub struct NewIfaceDevice {
pub id: u16,
pub proxy: fidl_mlme::MlmeProxy,
pub device: wlan_dev::Device,
}
pub fn watch_phy_devices() -> io::Result<impl Stream<Item = io::Result<NewPhyDevice>>> {
Ok(watch_new_devices(PHY_PATH)?
.try_filter_map(|path| future::ready(Ok(handle_open_error(&path, new_phy(&path))))))
}
pub fn watch_iface_devices() -> io::Result<impl Stream<Item = io::Result<NewIfaceDevice>>> {
Ok(watch_new_devices(IFACE_PATH)?
.try_filter_map(|path| future::ready(Ok(handle_open_error(&path, new_iface(&path))))))
}
fn handle_open_error<T>(path: &PathBuf, r: Result<T, failure::Error>) -> Option<T> {
if let Err(ref e) = &r {
if let Some(&zx_Status::ALREADY_BOUND) = e.as_fail().downcast_ref::<zx_Status>() {
info!("iface {:?} already open, deferring", path.display())
} else {
error!("Error opening device '{}': {}", path.display(), e);
}
}
r.ok()
}
fn watch_new_devices<P: AsRef<Path>>(
path: P,
) -> io::Result<impl Stream<Item = io::Result<PathBuf>>> {
let dir = File::open(&path)?;
let watcher = Watcher::new(&dir)?;
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,
}))
}))
}
fn new_phy(path: &PathBuf) -> Result<NewPhyDevice, failure::Error> {
let id = id_from_path(path)?;
let device = wlan_dev::Device::new(path)?;
let proxy = wlan_dev::connect_wlan_phy(&device)?;
Ok(NewPhyDevice { id, proxy, device })
}
fn new_iface(path: &PathBuf) -> Result<NewIfaceDevice, failure::Error> {
let id = id_from_path(path)?;
let device = wlan_dev::Device::new(path)?;
let proxy = fidl_mlme::MlmeProxy::new(wlan_dev::connect_wlan_iface(&device)?);
Ok(NewIfaceDevice { id, proxy, device })
}
fn id_from_path(path: &PathBuf) -> Result<u16, failure::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::*;
use fidl_fuchsia_wlan_common as fidl_common;
use fidl_fuchsia_wlan_device::{self as fidl_wlan_dev, SupportedPhy};
use fidl_fuchsia_wlan_tap as fidl_wlantap;
use fuchsia_async::{self as fasync, TimeoutExt};
use fuchsia_zircon::prelude::*;
use wlantap_client;
#[test]
fn watch_phys() {
let mut exec = fasync::Executor::new().expect("Failed to create an executor");
let mut new_phy_stream = watch_phy_devices().expect("watch_phy_devices() failed");
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()
.on_timeout(2.seconds().after_now(), || panic!("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 {
ht_cap_info: fidl_mlme::HtCapabilityInfo {
ldpc_coding_cap: false,
chan_width_set: fidl_mlme::ChanWidthSet::TwentyForty as u8,
sm_power_save: fidl_mlme::SmPowerSave::Disabled as u8,
greenfield: true,
short_gi_20: true,
short_gi_40: true,
tx_stbc: true,
rx_stbc: 1,
delayed_block_ack: false,
max_amsdu_len: fidl_mlme::MaxAmsduLen::Octets3839 as u8,
dsss_in_40: false,
intolerant_40: false,
lsig_txop_protect: false,
},
ampdu_params: fidl_mlme::AmpduParams {
exponent: 0,
min_start_spacing: fidl_mlme::MinMpduStartSpacing::NoRestrict as u8,
},
mcs_set: fidl_mlme::SupportedMcsSet {
rx_mcs_set: 0x01000000ff,
rx_highest_rate: 0,
tx_mcs_set_defined: true,
tx_rx_diff: false,
tx_max_ss: 1,
tx_ueqm: false,
},
ht_ext_cap: fidl_mlme::HtExtCapabilities {
pco: false,
pco_transition: fidl_mlme::PcoTransitionTime::PcoReserved as u8,
mcs_feedback: fidl_mlme::McsFeedback::McsNofeedback as u8,
htc_ht_support: false,
rd_responder: false,
},
txbf_cap: fidl_mlme::TxBfCapability {
implicit_rx: false,
rx_stag_sounding: false,
tx_stag_sounding: false,
rx_ndp: false,
tx_ndp: false,
implicit: false,
calibration: fidl_mlme::Calibration::CalibrationNone as u8,
csi: false,
noncomp_steering: false,
comp_steering: false,
csi_feedback: fidl_mlme::Feedback::FeedbackNone as u8,
noncomp_feedback: fidl_mlme::Feedback::FeedbackNone as u8,
comp_feedback: fidl_mlme::Feedback::FeedbackNone as u8,
min_grouping: fidl_mlme::MinGroup::MinGroupOne as u8,
csi_antennas: 1,
noncomp_steering_ants: 1,
comp_steering_ants: 1,
csi_rows: 1,
chan_estimation: 1,
},
asel_cap: fidl_mlme::AselCapability {
asel: false,
csi_feedback_tx_asel: false,
ant_idx_feedback_tx_asel: false,
explicit_csi_feedback: false,
antenna_idx_feedback: false,
rx_asel: false,
tx_sounding_ppdu: false,
},
})),
vht_caps: None,
basic_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],
},
}
}
}