blob: 4bd308edd1a7c72c25de7651a20a54a865b41933 [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 {
anyhow::{format_err, Error},
fidl_fuchsia_wlan_common::DriverFeature,
fidl_fuchsia_wlan_device as fidl_wlan_dev,
fidl_fuchsia_wlan_mlme::{self as fidl_mlme, DeviceInfo},
fuchsia_cobalt::CobaltSender,
fuchsia_inspect_contrib::inspect_log,
futures::{
channel::mpsc,
future::{Future, FutureExt, FutureObj},
select,
stream::{FuturesUnordered, Stream, StreamExt, TryStreamExt},
},
log::{error, info},
pin_utils::pin_mut,
std::{marker::Unpin, sync::Arc},
void::Void,
wlan_common::hasher::WlanHasher,
wlan_inspect,
wlan_sme::{self, clone_utils},
};
use crate::{
device_watch, inspect,
mlme_query_proxy::MlmeQueryProxy,
station,
stats_scheduler::{self, StatsScheduler},
watchable_map::WatchableMap,
ServiceCfg,
};
/// 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,
// A channel to communicate with the iface's underlying MLME.
pub mlme_channel: fidl_mlme::MlmeProxy,
}
pub struct PhyDevice {
pub proxy: fidl_wlan_dev::PhyProxy,
pub device: wlan_dev::Device,
}
pub type ClientSmeServer = mpsc::UnboundedSender<super::station::client::Endpoint>;
pub type ApSmeServer = mpsc::UnboundedSender<super::station::ap::Endpoint>;
pub type MeshSmeServer = mpsc::UnboundedSender<super::station::mesh::Endpoint>;
pub type ShutdownSender = mpsc::Sender<()>;
pub enum SmeServer {
Client(ClientSmeServer),
Ap(ApSmeServer),
Mesh(MeshSmeServer),
}
pub struct IfaceDevice {
pub phy_ownership: PhyOwnership,
pub sme_server: SmeServer,
pub stats_sched: StatsScheduler,
pub mlme_query: MlmeQueryProxy,
pub device_info: DeviceInfo,
pub shutdown_sender: ShutdownSender,
}
pub type PhyMap = WatchableMap<u16, PhyDevice>;
pub type IfaceMap = WatchableMap<u16, IfaceDevice>;
pub async fn serve_phys<Env: wlan_dev::DeviceEnv>(
phys: Arc<PhyMap>,
inspect_tree: Arc<inspect::WlanstackTree>,
) -> 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() => {},
}
}
}
async fn serve_phy(
phys: &PhyMap,
new_phy: device_watch::NewPhyDevice,
inspect_tree: Arc<inspect::WlanstackTree>,
) {
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;
let event_stream = new_phy.proxy.take_event_stream();
phys.insert(id, PhyDevice { proxy: new_phy.proxy, device: new_phy.device });
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));
}
pub fn create_and_serve_sme(
cfg: ServiceCfg,
id: u16,
phy_ownership: PhyOwnership,
mlme_proxy: fidl_mlme::MlmeProxy,
ifaces: Arc<IfaceMap>,
inspect_tree: Arc<inspect::WlanstackTree>,
iface_tree_holder: Arc<wlan_inspect::iface_mgr::IfaceTreeHolder>,
cobalt_sender: CobaltSender,
cobalt_1dot1_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
device_info: DeviceInfo,
) -> Result<impl Future<Output = Result<(), Error>>, Error> {
let event_stream = mlme_proxy.take_event_stream();
let (stats_sched, stats_reqs) = stats_scheduler::create_scheduler();
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
let (sme, sme_fut) = create_sme(
cfg,
mlme_proxy.clone(),
event_stream,
&device_info,
stats_reqs,
cobalt_sender,
cobalt_1dot1_proxy,
inspect_tree.clone(),
iface_tree_holder.clone(),
inspect_tree.hasher.clone(),
shutdown_receiver,
);
info!("new iface #{} with role '{:?}'", id, device_info.role);
inspect_log!(inspect_tree.device_events.lock(), {
msg: format!("new iface #{} with role '{:?}'", id, device_info.role)
});
if let fidl_mlme::MacRole::Client = device_info.role {
inspect_tree.mark_active_client_iface(id, ifaces.clone(), iface_tree_holder.clone());
}
let mlme_query = MlmeQueryProxy::new(mlme_proxy);
let is_softmac = device_info.driver_features.contains(&DriverFeature::TempSoftmac);
// For testing only: All synthetic devices are softmac devices.
if device_info.driver_features.contains(&DriverFeature::Synth) && !is_softmac {
return Err(format_err!("Synthetic devices must be SoftMAC"));
}
ifaces.insert(
id,
IfaceDevice {
phy_ownership,
sme_server: sme,
stats_sched,
mlme_query,
device_info,
shutdown_sender,
},
);
Ok(async move {
let result = sme_fut.await.map_err(|e| format_err!("error while serving SME: {}", e));
info!("iface removed: #{}", id);
inspect_log!(inspect_tree.device_events.lock(), msg: format!("iface removed: #{}", id));
inspect_tree.unmark_active_client_iface(id);
ifaces.remove(&id);
result
})
}
fn create_sme<S>(
cfg: ServiceCfg,
proxy: fidl_mlme::MlmeProxy,
event_stream: fidl_mlme::MlmeEventStream,
device_info: &DeviceInfo,
stats_requests: S,
cobalt_sender: CobaltSender,
cobalt_1dot1_proxy: fidl_fuchsia_metrics::MetricEventLoggerProxy,
inspect_tree: Arc<inspect::WlanstackTree>,
iface_tree_holder: Arc<wlan_inspect::iface_mgr::IfaceTreeHolder>,
hasher: WlanHasher,
mut shutdown_receiver: mpsc::Receiver<()>,
) -> (SmeServer, impl Future<Output = Result<(), Error>>)
where
S: Stream<Item = stats_scheduler::StatsRequest> + Send + Unpin + 'static,
{
let device_info = clone_utils::clone_device_info(device_info);
let (server, sme_fut) = match device_info.role {
fidl_mlme::MacRole::Client => {
let (sender, receiver) = mpsc::unbounded();
let fut = station::client::serve(
cfg.into(),
proxy,
device_info,
event_stream,
receiver,
stats_requests,
cobalt_sender,
cobalt_1dot1_proxy,
inspect_tree,
iface_tree_holder,
hasher,
);
(SmeServer::Client(sender), FutureObj::new(Box::new(fut)))
}
fidl_mlme::MacRole::Ap => {
let (sender, receiver) = mpsc::unbounded();
let fut =
station::ap::serve(proxy, device_info, event_stream, receiver, stats_requests);
(SmeServer::Ap(sender), FutureObj::new(Box::new(fut)))
}
fidl_mlme::MacRole::Mesh => {
let (sender, receiver) = mpsc::unbounded();
let fut =
station::mesh::serve(proxy, device_info, event_stream, receiver, stats_requests);
(SmeServer::Mesh(sender), FutureObj::new(Box::new(fut)))
}
};
let sme_fut_with_shutdown = async move {
select! {
sme_fut = sme_fut.fuse() => sme_fut,
_ = shutdown_receiver.select_next_some() => Ok(()),
}
};
(server, sme_fut_with_shutdown)
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints::create_proxy,
fidl_fuchsia_wlan_mlme::MlmeMarker,
fuchsia_async as fasync,
fuchsia_cobalt::{self, CobaltSender},
fuchsia_inspect::{assert_inspect_tree, Inspector},
futures::channel::mpsc,
futures::future::join,
futures::sink::SinkExt,
futures::task::Poll,
pin_utils::pin_mut,
wlan_common::assert_variant,
};
fn fake_device_info() -> DeviceInfo {
fidl_mlme::DeviceInfo {
role: fidl_mlme::MacRole::Client,
bands: vec![],
mac_addr: [0xAC; 6],
driver_features: vec![],
qos_capable: false,
}
}
#[test]
fn query_serve_with_sme_channel() {
let mut exec = fasync::Executor::new().expect("failed to create an executor");
let (mlme_proxy, _mlme_server) =
create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
let (iface_map, _iface_map_events) = IfaceMap::new();
let iface_map = Arc::new(iface_map);
let inspect_tree = Arc::new(inspect::WlanstackTree::new(Inspector::new()));
let iface_tree_holder = inspect_tree.create_iface_child(1);
let (sender, _receiver) = mpsc::channel(1);
let cobalt_sender = CobaltSender::new(sender);
let (cobalt_1dot1_proxy, _) =
create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>()
.expect("failed to create Cobalt 1.1 proxy");
// Assert that the IfaceMap is initially empty.
assert!(iface_map.get(&5).is_none());
let serve_fut = create_and_serve_sme(
ServiceCfg::default(),
5,
PhyOwnership { phy_id: 1, phy_assigned_id: 2 },
mlme_proxy,
iface_map.clone(),
inspect_tree.clone(),
iface_tree_holder,
cobalt_sender,
cobalt_1dot1_proxy,
fake_device_info(),
)
.expect("failed to create SME");
// Assert that the IfaceMap now has an entry for the new iface.
assert!(iface_map.get(&5).is_some());
pin_mut!(serve_fut);
// Progress to cause SME creation and serving.
assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
// Retrieve SME instance and close SME (iface must be acquired).
let mut iface = iface_map.get(&5).expect("expected iface");
iface_map.remove(&5);
let mut_iface = Arc::get_mut(&mut iface).expect("error yielding iface");
let sme = assert_variant!(
mut_iface.sme_server,
SmeServer::Client(ref mut sme) => sme,
"expected Client SME to be spawned"
);
let close_fut = sme.close();
pin_mut!(close_fut);
let fut_result = exec.run_until_stalled(&mut close_fut);
assert_variant!(fut_result, Poll::Ready(_), "expected closing SME to succeed");
// Insert iface back into map.
let (mlme_proxy, _) = create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
let (sender, _) = mpsc::unbounded();
let (stats_sched, _) = stats_scheduler::create_scheduler();
let (shutdown_sender, _) = mpsc::channel(1);
iface_map.insert(
5,
IfaceDevice {
phy_ownership: PhyOwnership { phy_id: 0, phy_assigned_id: 0 },
sme_server: SmeServer::Client(sender),
stats_sched,
mlme_query: MlmeQueryProxy::new(mlme_proxy),
device_info: fake_device_info(),
shutdown_sender,
},
);
iface_map.get(&5).expect("expected iface");
// Progress SME serving to completion and verify iface was deleted
let fut_result = exec.run_until_stalled(&mut serve_fut);
assert_variant!(fut_result, Poll::Ready(_), "expected SME serving to be terminated");
assert!(iface_map.get(&5).is_none());
assert_inspect_tree!(inspect_tree.inspector, root: contains {
device_events: {
"0": contains { msg: "new iface #5 with role 'Client'" },
"1": contains { msg: "iface removed: #5" },
},
});
}
#[test]
fn sme_shutdown_signal() {
let mut exec = fasync::Executor::new().expect("failed to create an executor");
let (mlme_proxy, _mlme_server) =
create_proxy::<MlmeMarker>().expect("failed to create MlmeProxy");
let (iface_map, _iface_map_events) = IfaceMap::new();
let iface_map = Arc::new(iface_map);
let inspect_tree = Arc::new(inspect::WlanstackTree::new(Inspector::new()));
let iface_tree_holder = inspect_tree.create_iface_child(1);
let (sender, _receiver) = mpsc::channel(1);
let cobalt_sender = CobaltSender::new(sender);
let (cobalt_1dot1_proxy, _) =
create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>()
.expect("failed to create Cobalt 1.1 proxy");
// Assert that the IfaceMap is initially empty.
assert!(iface_map.get(&5).is_none());
let serve_fut = create_and_serve_sme(
ServiceCfg::default(),
5,
PhyOwnership { phy_id: 1, phy_assigned_id: 2 },
mlme_proxy,
iface_map.clone(),
inspect_tree.clone(),
iface_tree_holder,
cobalt_sender,
cobalt_1dot1_proxy,
fake_device_info(),
)
.expect("failed to create SME");
// Assert that the IfaceMap now has an entry for the new iface.
assert!(iface_map.get(&5).is_some());
pin_mut!(serve_fut);
// Progress to cause SME creation and serving.
assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending);
// SME closes on shutdown.
let mut shutdown_sender = iface_map.get(&5).unwrap().shutdown_sender.clone();
let shutdown_signal = shutdown_sender.send(());
let mut shutdown_fut = join(serve_fut, shutdown_signal);
assert_variant!(exec.run_until_stalled(&mut shutdown_fut), Poll::Ready((Ok(()), Ok(()))));
}
}