blob: 25b0637f36011e70d2950b56dfb972e8e1fec66e [file] [log] [blame]
// 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, Error},
fidl_fuchsia_wlan_device as fidl_wlan_dev, fidl_fuchsia_wlan_sme as fidl_wlan_sme,
fuchsia_inspect_contrib::inspect_log,
futures::{
future::FutureExt,
select,
stream::{FuturesUnordered, StreamExt, TryStreamExt},
},
log::{error, info},
pin_utils::pin_mut,
std::sync::Arc,
void::Void,
};
use crate::{device_watch, inspect, watchable_map::WatchableMap};
/// Iface's PHY information.
#[derive(Debug, PartialEq)]
pub struct PhyOwnership {
// Iface's global PHY ID.
pub phy_id: u16,
// Local ID assigned by this iface's PHY.
pub phy_assigned_id: u16,
}
#[derive(Debug)]
pub struct NewIface {
// Global, unique iface ID.
pub id: u16,
// Information about this iface's PHY.
pub phy_ownership: PhyOwnership,
// The handle for connecting channels to this iface's SME.
pub generic_sme: fidl_wlan_sme::GenericSmeProxy,
}
pub struct PhyDevice {
pub proxy: fidl_wlan_dev::PhyProxy,
pub device: wlan_dev::Device,
}
pub struct IfaceDevice {
pub phy_ownership: PhyOwnership,
pub generic_sme: fidl_wlan_sme::GenericSmeProxy,
}
pub type PhyMap = WatchableMap<u16, PhyDevice>;
pub type IfaceMap = WatchableMap<u16, IfaceDevice>;
/// Handles newly-discovered PHYs.
///
/// `serve_phys` watches for new PHY devices in the appropriate `DeviceEnv` (ie: real or
/// `IsolatedDevMgr`).
///
/// When new PHYs are discovered, the `device_watch` module produces a `NewPhyDevice`. This struct
/// contains a PHY proxy and a `wlan_dev::Device`. This `Device` lifetime is then managed by
/// `serve_phy`.
pub async fn serve_phys<Env: wlan_dev::DeviceEnv>(
phys: Arc<PhyMap>,
inspect_tree: Arc<inspect::WlanMonitorTree>,
) -> Result<Void, Error> {
let new_phys = device_watch::watch_phy_devices::<Env>()?;
pin_mut!(new_phys);
let mut active_phys = FuturesUnordered::new();
loop {
select! {
// OK to fuse directly in the `select!` since we bail immediately
// when a `None` is encountered.
new_phy = new_phys.next().fuse() => match new_phy {
None => return Err(format_err!("new phy stream unexpectedly finished")),
Some(Err(e)) => return Err(format_err!("new phy stream returned an error: {}", e)),
Some(Ok(new_phy)) => {
let fut = serve_phy(&phys, new_phy, inspect_tree.clone());
active_phys.push(fut);
}
},
() = active_phys.select_next_some() => {},
}
}
}
/// Handles the lifetime of discovered PHY devices.
///
/// `serve_phy` takes newly discovered PHY devices and inserts them into a `WatchableMap`.
///
/// `serve_phy` then waits for the PHY device to be removed from the system. When the PHY is
/// removed, `serve_phy` removes the device from the `WatchableMap`.
///
/// The `WatchableMap` produces events when elements are added to or removed from it. These events
/// are consumed by another future that manages the `DeviceWatcher` protocol and notifies API
/// clients of PHY addition or removal.
async fn serve_phy(
phys: &PhyMap,
new_phy: device_watch::NewPhyDevice,
inspect_tree: Arc<inspect::WlanMonitorTree>,
) {
let msg = format!("new phy #{}: {}", new_phy.id, new_phy.device.path().to_string_lossy());
info!("{}", msg);
inspect_log!(inspect_tree.device_events.lock(), msg: msg);
let id = new_phy.id;
// Take the event stream from the PHY proxy so that it can be monitored. An event produced by
// this stream indicates that the PHY has been removed from the system.
let event_stream = new_phy.proxy.take_event_stream();
// Insert the newly discovered device into the `WatchableMap`. This will trigger the watchable
// map to produce an event so that the `DeviceWatcher` service can produce an update for API
// consumers.
phys.insert(id, PhyDevice { proxy: new_phy.proxy, device: new_phy.device });
// The event stream's production of an event indicates that the PHY has been removed from the
// system. Remove the PHY from the `WatchableMap`. This will result in the `WatchableMap`
// producing a removal event which will trigger the `DeviceWatcher` service to send a
// notification to API consumers.
let r = event_stream.map_ok(|_| ()).try_collect::<()>().await;
phys.remove(&id);
if let Err(e) = r {
let msg = format!("error reading from FIDL channel of phy #{}: {}", id, e);
error!("{}", msg);
inspect_log!(inspect_tree.device_events.lock(), msg: msg);
}
info!("phy removed: #{}", id);
inspect_log!(inspect_tree.device_events.lock(), msg: format!("phy removed: #{}", id));
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{inspect, watchable_map},
fidl::endpoints::create_proxy,
fuchsia_async as fasync,
fuchsia_inspect::Inspector,
fuchsia_zircon::{self as zx, prelude::*},
futures::task::Poll,
std::{fs::File, path::Path},
tempfile,
wlan_common::{assert_variant, test_utils::ExpectWithin},
};
// This struct is an implementation of the DeviceEnv trait that returns errors in all cases to
// enable testing the case where watching for devices fails.
struct FaultyDeviceEnv;
impl wlan_dev::DeviceEnv for FaultyDeviceEnv {
const PHY_PATH: &'static str = "/some/bogus/path";
fn open_dir<P: AsRef<Path>>(_path: P) -> Result<File, zx::Status> {
Err(zx::Status::NOT_FOUND)
}
fn device_from_path<P: AsRef<Path>>(_path: P) -> Result<wlan_dev::Device, zx::Status> {
Err(zx::Status::NOT_SUPPORTED)
}
}
#[test]
fn test_serve_phys_exits_when_watching_devices_fails() {
let mut exec = fasync::TestExecutor::new().expect("failed to create an executor");
let (phys, _phy_events) = PhyMap::new();
let phys = Arc::new(phys);
let inspector = Inspector::new_with_size(inspect::VMO_SIZE_BYTES);
let inspect_tree = Arc::new(inspect::WlanMonitorTree::new(inspector));
// Serve PHYs from the bogus device environment that returns errors for all operations.
// This will ensure that attempting to watch devices fails immediately.
let fut = serve_phys::<FaultyDeviceEnv>(phys.clone(), inspect_tree);
pin_mut!(fut);
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
}
#[test]
fn test_serve_phy_adds_and_removes_phy() {
let mut exec = fasync::TestExecutor::new().expect("failed to create an executor");
let (phys, mut phy_events) = PhyMap::new();
let phys = Arc::new(phys);
let inspector = Inspector::new_with_size(inspect::VMO_SIZE_BYTES);
let inspect_tree = Arc::new(inspect::WlanMonitorTree::new(inspector));
let temp_dir = tempfile::TempDir::new().expect("failed to create temp dir");
let test_path = temp_dir.path().join("test_device");
let file = File::create(test_path.clone()).expect("failed to open file");
let device = wlan_dev::Device { node: file, path: test_path };
let (phy_proxy, phy_server) =
create_proxy::<fidl_wlan_dev::PhyMarker>().expect("failed to create PHY proxy");
let new_phy = device_watch::NewPhyDevice { id: 0, proxy: phy_proxy, device };
let fut = serve_phy(&phys, new_phy, inspect_tree);
pin_mut!(fut);
// Run the PHY service to pick up the new PHY.
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
match exec.run_until_stalled(
&mut phy_events.next().expect_within(5.seconds(), "phy_watcher did not respond"),
) {
Poll::Ready(Some(event)) => match event {
watchable_map::MapEvent::KeyInserted(key) => {
assert_eq!(key, 0)
}
_ => panic!("unexpected watcher event"),
},
Poll::Ready(None) => panic!("watcher events ended unexpectedly"),
Poll::Pending => panic!("no pending watcher events"),
}
assert!(phys.get(&0).is_some());
// Now drop the other end of the PHY and observe that the PHY is removed from the map.
drop(phy_server);
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
match exec.run_until_stalled(
&mut phy_events.next().expect_within(5.seconds(), "phy_watcher did not respond"),
) {
Poll::Ready(Some(event)) => match event {
watchable_map::MapEvent::KeyRemoved(key) => {
assert_eq!(key, 0)
}
_ => panic!("unexpected watcher event"),
},
Poll::Ready(None) => panic!("watcher events ended unexpectedly"),
Poll::Pending => panic!("no pending watcher events"),
}
assert!(phys.get(&0).is_none());
}
}