blob: 447e6ac4d88b48b392623d4c0f4db85f4c0f6592 [file] [log] [blame]
// Copyright 2020 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 {
crate::regulatory_manager::REGION_CODE_LEN,
async_trait::async_trait,
eui48::MacAddress,
fidl_fuchsia_wlan_device as fidl_device,
fidl_fuchsia_wlan_device::MacRole,
fidl_fuchsia_wlan_device_service as fidl_service,
fuchsia_inspect::{self as inspect, NumericProperty},
fuchsia_zircon,
log::{error, info, warn},
std::collections::{HashMap, HashSet},
thiserror::Error,
};
/// Errors raised while attempting to query information about or configure PHYs and ifaces.
#[derive(Debug, Error)]
pub(crate) enum PhyManagerError {
#[error("the requested operation is not supported")]
Unsupported,
#[error("unable to query phy information")]
PhyQueryFailure,
#[error("failed to set country for new PHY")]
PhySetCountryFailure,
#[error("unable to query iface information")]
IfaceQueryFailure,
#[error("unable to create iface")]
IfaceCreateFailure,
#[error("unable to destroy iface")]
IfaceDestroyFailure,
}
/// Stores information about a WLAN PHY and any interfaces that belong to it.
pub(crate) struct PhyContainer {
phy_info: fidl_device::PhyInfo,
client_ifaces: HashSet<u16>,
ap_ifaces: HashSet<u16>,
}
#[async_trait]
pub(crate) trait PhyManagerApi {
/// Checks to see if this PHY is already accounted for. If it is not, queries its PHY
/// attributes and places it in the hash map.
async fn add_phy(&mut self, phy_id: u16) -> Result<(), PhyManagerError>;
/// If the PHY is accounted for, removes the associated `PhyContainer` from the hash map.
fn remove_phy(&mut self, phy_id: u16);
/// Queries the interface properties to get the PHY ID. If the `PhyContainer`
/// representing the interface's parent PHY is already present and its
/// interface information is obsolete, updates it. The PhyManager will track ifaces
/// as it creates and deletes them, but it is possible that another caller circumvents the
/// policy layer and creates an interface. If no `PhyContainer` exists
/// for the new interface, creates one and adds the newly discovered interface
/// ID to it.
async fn on_iface_added(&mut self, iface_id: u16) -> Result<(), PhyManagerError>;
/// Ensures that the `iface_id` is not present in any of the `PhyContainer` interface lists.
fn on_iface_removed(&mut self, iface_id: u16);
/// Creates client interfaces for all PHYs that are capable of acting as clients. For newly
/// discovered PHYs, create client interfaces if the PHY can support them.
async fn create_all_client_ifaces(&mut self) -> Result<(), PhyManagerError>;
/// Destroys all client interfaces. Do not allow the creation of client interfaces for newly
/// discovered PHYs.
async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError>;
/// Finds a PHY with a client interface and returns the interface's ID to the caller.
fn get_client(&mut self) -> Option<u16>;
/// Finds a PHY that is capable of functioning as an AP. PHYs that do not yet have AP ifaces
/// associated with them are searched first. If one is found, an AP iface is created and its
/// ID is returned. If all AP-capable PHYs already have AP ifaces associated with them, one of
/// the existing AP iface IDs is returned. If there are no AP-capable PHYs, None is returned.
async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError>;
/// Destroys the interface associated with the given interface ID.
async fn destroy_ap_iface(&mut self, iface_id: u16) -> Result<(), PhyManagerError>;
/// Destroys all AP interfaces.
async fn destroy_all_ap_ifaces(&mut self) -> Result<(), PhyManagerError>;
/// Sets a suggested MAC address to be used by new AP interfaces.
fn suggest_ap_mac(&mut self, mac: MacAddress);
/// Returns the IDs for all currently known PHYs.
fn get_phy_ids(&self) -> Vec<u16>;
/// Logs phy add failure inspect metrics.
fn log_phy_add_failure(&mut self);
/// Stores the current region code to be applied to newly-discovered PHYs.
fn save_region_code(&mut self, region_code: Option<[u8; REGION_CODE_LEN]>);
}
/// Maintains a record of all PHYs that are present and their associated interfaces.
pub(crate) struct PhyManager {
phys: HashMap<u16, PhyContainer>,
device_service: fidl_service::DeviceServiceProxy,
client_connections_enabled: bool,
suggested_ap_mac: Option<MacAddress>,
saved_region_code: Option<[u8; REGION_CODE_LEN]>,
_node: inspect::Node,
phy_add_fail_count: inspect::UintProperty,
}
impl PhyContainer {
/// Stores the PhyInfo associated with a newly discovered PHY and creates empty vectors to hold
/// interface IDs that belong to this PHY.
pub fn new(phy_info: fidl_device::PhyInfo) -> Self {
PhyContainer {
phy_info: phy_info,
client_ifaces: HashSet::new(),
ap_ifaces: HashSet::new(),
}
}
}
// TODO(fxbug.dev/49590): PhyManager makes the assumption that WLAN PHYs that support client and AP modes can
// can operate as clients and APs simultaneously. For PHYs where this is not the case, the
// existing interface should be destroyed before the new interface is created.
impl PhyManager {
/// Internally stores a DeviceServiceProxy to query PHY and interface properties and create and
/// destroy interfaces as requested.
pub fn new(device_service: fidl_service::DeviceServiceProxy, node: inspect::Node) -> Self {
let phy_add_fail_count = node.create_uint("phy_add_fail_count", 0);
PhyManager {
phys: HashMap::new(),
device_service,
client_connections_enabled: false,
suggested_ap_mac: None,
saved_region_code: None,
_node: node,
phy_add_fail_count,
}
}
/// Verifies that a given PHY ID is accounted for and, if not, adds a new entry for it.
async fn ensure_phy(&mut self, phy_id: u16) -> Result<&mut PhyContainer, PhyManagerError> {
if !self.phys.contains_key(&phy_id) {
self.add_phy(phy_id).await?;
}
// The phy_id is guaranteed to exist at this point because it was either previously
// accounted for or was just added above.
Ok(self.phys.get_mut(&phy_id).unwrap())
}
/// Queries the information associated with the given iface ID.
async fn query_iface(
&self,
iface_id: u16,
) -> Result<Option<fidl_service::QueryIfaceResponse>, PhyManagerError> {
match self.device_service.query_iface(iface_id).await {
Ok((status, response)) => match status {
fuchsia_zircon::sys::ZX_OK => match response {
Some(response) => Ok(Some(*response)),
None => Ok(None),
},
fuchsia_zircon::sys::ZX_ERR_NOT_FOUND => Ok(None),
_ => Err(PhyManagerError::IfaceQueryFailure),
},
Err(_) => Err(PhyManagerError::IfaceQueryFailure),
}
}
/// Returns a list of PHY IDs that can have interfaces of the requested MAC role.
fn phys_for_role(&self, role: MacRole) -> Vec<u16> {
self.phys
.iter()
.filter_map(|(k, v)| {
if v.phy_info.supported_mac_roles.contains(&role) {
Some(*k)
} else {
None
}
})
.collect()
}
}
#[async_trait]
impl PhyManagerApi for PhyManager {
async fn add_phy(&mut self, phy_id: u16) -> Result<(), PhyManagerError> {
let query_phy_response = match self
.device_service
.query_phy(&mut fidl_service::QueryPhyRequest { phy_id: phy_id })
.await
{
Ok((status, query_phy_response)) => {
if fuchsia_zircon::ok(status).is_err() {
return Err(PhyManagerError::PhyQueryFailure);
}
query_phy_response
}
Err(_) => {
return Err(PhyManagerError::PhyQueryFailure);
}
};
let response = match query_phy_response {
Some(response) => response,
None => return Ok(()),
};
// Create a new container to store the PHY's information.
info!("adding PHY ID #{}", phy_id);
let phy_id = response.info.id;
let mut phy_container = PhyContainer::new(response.info);
// Attempt to set the regulatory region for the newly-discovered PHY.
let region_set_result = match self.saved_region_code {
Some(region) => {
match self
.device_service
.set_country(&mut fidl_service::SetCountryRequest { phy_id, alpha2: region })
.await
{
Ok(status) => {
if fuchsia_zircon::ok(status).is_err() {
error!("Received bad status when setting country code: {}", status);
Err(PhyManagerError::PhySetCountryFailure)
} else {
Ok(())
}
}
Err(e) => {
error!("Failed to set country code: {:?}", e);
Err(PhyManagerError::PhySetCountryFailure)
}
}
}
None => Ok(()),
};
// If setting the regulatory region fails, clear the PHY's regulatory region so that it is
// in WW and can continue to operate. If this process fails, return early and do not use
// this PHY.
if region_set_result.is_err() {
match self
.device_service
.clear_country(&mut fidl_service::ClearCountryRequest { phy_id })
.await
{
Ok(status) => {
if fuchsia_zircon::ok(status).is_err() {
error!("Received bad status when clearing country: {}", status);
return Err(PhyManagerError::PhySetCountryFailure);
}
}
Err(e) => {
error!("Failed to clear set country code: {:?}", e);
return Err(PhyManagerError::PhySetCountryFailure);
}
}
}
if self.client_connections_enabled
&& phy_container.phy_info.supported_mac_roles.contains(&MacRole::Client)
{
let iface_id =
create_iface(&self.device_service, phy_id, MacRole::Client, None).await?;
phy_container.client_ifaces.insert(iface_id);
}
self.phys.insert(phy_id, phy_container);
Ok(())
}
fn remove_phy(&mut self, phy_id: u16) {
self.phys.remove(&phy_id);
}
async fn on_iface_added(&mut self, iface_id: u16) -> Result<(), PhyManagerError> {
if let Some(query_iface_response) = self.query_iface(iface_id).await? {
let phy = self.ensure_phy(query_iface_response.phy_id).await?;
let iface_id = query_iface_response.id;
match query_iface_response.role {
MacRole::Client => {
if !phy.client_ifaces.contains(&iface_id) {
warn!("Detected an unexpected client iface created outside of PhyManager");
let _ = phy.client_ifaces.insert(iface_id);
}
}
MacRole::Ap => {
if !phy.ap_ifaces.contains(&iface_id) {
warn!("Detected an unexpected AP iface created outside of PhyManager");
let _ = phy.ap_ifaces.insert(iface_id);
}
}
MacRole::Mesh => {
return Err(PhyManagerError::Unsupported);
}
}
}
Ok(())
}
fn on_iface_removed(&mut self, iface_id: u16) {
for (_, phy_info) in self.phys.iter_mut() {
phy_info.client_ifaces.remove(&iface_id);
phy_info.ap_ifaces.remove(&iface_id);
}
}
async fn create_all_client_ifaces(&mut self) -> Result<(), PhyManagerError> {
self.client_connections_enabled = true;
let client_capable_phy_ids = self.phys_for_role(MacRole::Client);
for client_phy in client_capable_phy_ids.iter() {
let phy_container =
self.phys.get_mut(&client_phy).ok_or(PhyManagerError::PhyQueryFailure)?;
if phy_container.client_ifaces.is_empty() {
let iface_id =
create_iface(&self.device_service, *client_phy, MacRole::Client, None).await?;
phy_container.client_ifaces.insert(iface_id);
}
}
Ok(())
}
async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError> {
self.client_connections_enabled = false;
let client_capable_phys = self.phys_for_role(MacRole::Client);
let mut result = Ok(());
for client_phy in client_capable_phys.iter() {
let phy_container =
self.phys.get_mut(&client_phy).ok_or(PhyManagerError::PhyQueryFailure)?;
// Continue tracking interface IDs for which deletion fails.
let mut lingering_ifaces = HashSet::new();
for iface_id in phy_container.client_ifaces.drain() {
match destroy_iface(&self.device_service, iface_id).await {
Ok(()) => {}
Err(e) => {
result = Err(e);
lingering_ifaces.insert(iface_id);
}
}
}
phy_container.client_ifaces = lingering_ifaces;
}
result
}
fn get_client(&mut self) -> Option<u16> {
let client_capable_phys = self.phys_for_role(MacRole::Client);
if client_capable_phys.is_empty() {
return None;
}
// Find the first PHY with any client interfaces and return its first client interface.
let phy = self.phys.get_mut(&client_capable_phys[0])?;
match phy.client_ifaces.iter().next() {
Some(iface_id) => Some(*iface_id),
None => None,
}
}
async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError> {
let ap_capable_phy_ids = self.phys_for_role(MacRole::Ap);
if ap_capable_phy_ids.is_empty() {
return Ok(None);
}
// First check for any PHYs that can have AP interfaces but do not yet
for ap_phy_id in ap_capable_phy_ids.iter() {
let phy_container =
self.phys.get_mut(&ap_phy_id).ok_or(PhyManagerError::PhyQueryFailure)?;
if phy_container.ap_ifaces.is_empty() {
let mac = match self.suggested_ap_mac {
Some(mac) => Some(mac.as_bytes().to_vec()),
None => None,
};
let iface_id =
create_iface(&self.device_service, *ap_phy_id, MacRole::Ap, mac).await?;
phy_container.ap_ifaces.insert(iface_id);
return Ok(Some(iface_id));
}
}
// If all of the AP-capable PHYs have created AP interfaces already, return the
// first observed existing AP interface
// TODO(fxbug.dev/49843): Figure out a better method of interface selection.
let phy = match self.phys.get_mut(&ap_capable_phy_ids[0]) {
Some(phy_container) => phy_container,
None => return Ok(None),
};
match phy.ap_ifaces.iter().next() {
Some(iface_id) => Ok(Some(*iface_id)),
None => Ok(None),
}
}
async fn destroy_ap_iface(&mut self, iface_id: u16) -> Result<(), PhyManagerError> {
// If the interface has already been destroyed, return Ok. Only error out in the case that
// the request to destroy the interface results in a failure.
for (_, phy_container) in self.phys.iter_mut() {
if phy_container.ap_ifaces.remove(&iface_id) {
match destroy_iface(&self.device_service, iface_id).await {
Ok(()) => {}
Err(e) => {
phy_container.ap_ifaces.insert(iface_id);
return Err(e);
}
}
}
}
Ok(())
}
async fn destroy_all_ap_ifaces(&mut self) -> Result<(), PhyManagerError> {
let ap_capable_phys = self.phys_for_role(MacRole::Ap);
let mut result = Ok(());
for ap_phy in ap_capable_phys.iter() {
let phy_container =
self.phys.get_mut(&ap_phy).ok_or(PhyManagerError::PhyQueryFailure)?;
// Continue tracking interface IDs for which deletion fails.
let mut lingering_ifaces = HashSet::new();
for iface_id in phy_container.ap_ifaces.drain() {
match destroy_iface(&self.device_service, iface_id).await {
Ok(()) => {}
Err(e) => {
result = Err(e);
lingering_ifaces.insert(iface_id);
}
}
}
phy_container.ap_ifaces = lingering_ifaces;
}
result
}
fn suggest_ap_mac(&mut self, mac: MacAddress) {
self.suggested_ap_mac = Some(mac);
}
fn get_phy_ids(&self) -> Vec<u16> {
self.phys.keys().cloned().collect()
}
fn log_phy_add_failure(&mut self) {
self.phy_add_fail_count.add(1);
}
fn save_region_code(&mut self, region_code: Option<[u8; REGION_CODE_LEN]>) {
self.saved_region_code = region_code;
}
}
/// Creates an interface of the requested role for the requested PHY ID. Returns either the
/// ID of the created interface or an error.
async fn create_iface(
proxy: &fidl_service::DeviceServiceProxy,
phy_id: u16,
role: MacRole,
mac: Option<Vec<u8>>,
) -> Result<u16, PhyManagerError> {
let mut request = fidl_service::CreateIfaceRequest { phy_id, role, mac_addr: mac };
let create_iface_response = match proxy.create_iface(&mut request).await {
Ok((status, iface_response)) => {
if fuchsia_zircon::ok(status).is_err() || iface_response.is_none() {
return Err(PhyManagerError::IfaceCreateFailure);
}
iface_response.ok_or_else(|| PhyManagerError::IfaceCreateFailure)?
}
Err(e) => {
warn!("failed to create iface for PHY {}: {}", phy_id, e);
return Err(PhyManagerError::IfaceCreateFailure);
}
};
Ok(create_iface_response.iface_id)
}
/// Destroys the specified interface.
async fn destroy_iface(
proxy: &fidl_service::DeviceServiceProxy,
iface_id: u16,
) -> Result<(), PhyManagerError> {
let mut request = fidl_service::DestroyIfaceRequest { iface_id: iface_id };
match proxy.destroy_iface(&mut request).await {
Ok(status) => match status {
fuchsia_zircon::sys::ZX_OK => Ok(()),
ref e => {
warn!("failed to destroy iface {}: {}", iface_id, e);
Err(PhyManagerError::IfaceDestroyFailure)
}
},
Err(e) => {
warn!("failed to send destroy iface {}: {}", iface_id, e);
Err(PhyManagerError::IfaceDestroyFailure)
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints,
fidl_fuchsia_wlan_device as fidl_device, fidl_fuchsia_wlan_device_service as fidl_service,
fuchsia_async::{run_singlethreaded, Executor},
fuchsia_inspect::{self as inspect, assert_inspect_tree},
fuchsia_zircon::sys::{ZX_ERR_NOT_FOUND, ZX_OK},
futures::stream::StreamExt,
futures::task::Poll,
pin_utils::pin_mut,
test_case::test_case,
wlan_common::assert_variant,
};
/// Hold the client and service ends for DeviceService to allow mocking DeviceService responses
/// for unit tests.
struct TestValues {
proxy: fidl_service::DeviceServiceProxy,
stream: fidl_service::DeviceServiceRequestStream,
inspector: inspect::Inspector,
node: inspect::Node,
}
/// Create a TestValues for a unit test.
fn test_setup() -> TestValues {
let (proxy, requests) = endpoints::create_proxy::<fidl_service::DeviceServiceMarker>()
.expect("failed to create SeviceService proxy");
let stream = requests.into_stream().expect("failed to create stream");
let inspector = inspect::Inspector::new();
let node = inspector.root().create_child("phy_manager");
TestValues { proxy: proxy, stream: stream, inspector: inspector, node: node }
}
/// Take in the service side of a DeviceService::QueryPhy request and respond with the given
/// PhyInfo.
fn send_query_phy_response(
exec: &mut Executor,
server: &mut fidl_service::DeviceServiceRequestStream,
phy_info: Option<fidl_device::PhyInfo>,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::QueryPhy {
responder, ..
}
))) => {
match phy_info {
Some(phy_info) => responder.send(
ZX_OK,
Some(&mut fidl_service::QueryPhyResponse {
info: phy_info,
})
)
.expect("sending fake phy info"),
None => responder.send(ZX_ERR_NOT_FOUND, None).expect("sending fake response with none")
}
}
);
}
/// Create a PhyInfo object for unit testing.
fn send_query_iface_response(
exec: &mut Executor,
server: &mut fidl_service::DeviceServiceRequestStream,
iface_info: Option<&mut fidl_service::QueryIfaceResponse>,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::QueryIface {
iface_id: _,
responder,
}
))) => {
responder.send(ZX_OK, iface_info).expect("sending fake iface info");
}
);
}
/// Handles the service side of a DeviceService::CreateIface request by replying with the
/// provided optional iface ID.
fn send_create_iface_response(
exec: &mut Executor,
server: &mut fidl_service::DeviceServiceRequestStream,
iface_id: Option<u16>,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::CreateIface {
req: _,
responder,
}
))) => {
match iface_id {
Some(iface_id) => responder.send(
ZX_OK,
Some(&mut fidl_service::CreateIfaceResponse {
iface_id: iface_id,
})
)
.expect("sending fake iface id"),
None => responder.send(ZX_ERR_NOT_FOUND, None).expect("sending fake response with none")
}
}
);
}
/// Handles the service side of a DeviceService::DestroyIface request by replying with the
/// provided zx_status_t.
fn send_destroy_iface_response(
exec: &mut Executor,
server: &mut fidl_service::DeviceServiceRequestStream,
return_status: fuchsia_zircon::zx_status_t,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::DestroyIface {
req: _,
responder,
}
))) => {
responder
.send(return_status)
.expect(format!("sending fake response: {}", return_status).as_str());
}
);
}
/// Create a PhyInfo object for unit testing.
fn fake_phy_info(id: u16, mac_roles: Vec<MacRole>) -> fidl_device::PhyInfo {
fidl_device::PhyInfo { id: id, dev_path: None, supported_mac_roles: mac_roles }
}
/// Creates a QueryIfaceResponse from the arguments provided by the caller.
fn create_iface_response(
role: MacRole,
id: u16,
phy_id: u16,
phy_assigned_id: u16,
mac: [u8; 6],
) -> fidl_service::QueryIfaceResponse {
fidl_service::QueryIfaceResponse {
role: role,
id: id,
phy_id: phy_id,
phy_assigned_id: phy_assigned_id,
mac_addr: mac,
}
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
/// calling add_phy on PhyManager for a PHY that exists. The expectation is that the
/// PhyManager initially does not have any PHYs available. After the call to add_phy, the
/// PhyManager should have a new PhyContainer.
#[test]
fn add_valid_phy() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
assert_eq!(
phy_manager.phys.get(&fake_phy_id).unwrap().phy_info,
fake_phy_info(fake_phy_id, fake_mac_roles)
);
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
/// calling add_phy on PhyManager for a PHY that does not exist. The PhyManager in this case
/// should not create and store a new PhyContainer.
#[test]
fn add_invalid_phy() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
{
let add_phy_fut = phy_manager.add_phy(1);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, None);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
assert!(phy_manager.phys.is_empty());
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyCreated event and
/// calling add_phy on PhyManager for a PHY that has already been accounted for, but whose
/// properties have changed. The PhyManager in this case should update the associated PhyInfo.
#[test]
fn add_duplicate_phy() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
{
assert!(phy_manager.phys.contains_key(&fake_phy_id));
assert_eq!(
phy_manager.phys.get(&fake_phy_id).unwrap().phy_info,
fake_phy_info(fake_phy_id, fake_mac_roles.clone())
);
}
// Send an update for the same PHY ID and ensure that the PHY info is updated.
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
assert!(phy_manager.phys.contains_key(&fake_phy_id));
assert_eq!(phy_manager.phys.get(&fake_phy_id).unwrap().phy_info, phy_info);
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyRemoved event and
/// calling remove_phy on PhyManager for a PHY that not longer exists. The PhyManager in this
/// case should remove the PhyContainer associated with the removed PHY ID.
#[test]
fn add_phy_after_create_all_client_ifaces() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
{
let start_connections_fut = phy_manager.create_all_client_ifaces();
pin_mut!(start_connections_fut);
assert!(exec.run_until_stalled(&mut start_connections_fut).is_ready());
}
// Add a new phy. Since client connections have been started, it should also create a
// client iface.
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_create_iface_response(&mut exec, &mut test_values.stream, Some(fake_iface_id));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.client_ifaces.contains(&fake_iface_id));
}
/// Tests the case where a new PHY is discovered after the region code has been set.
#[test]
fn test_add_phy_after_saving_region_code() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
phy_manager.save_region_code(Some([0, 1]));
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
assert_variant!(
exec.run_until_stalled(&mut test_values.stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::SetCountry {
req: fidl_service::SetCountryRequest {
phy_id: 1,
alpha2: [0, 1],
},
responder,
}
))) => {
responder.send(ZX_OK).expect("sending fake set country response");
}
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
assert_eq!(
phy_manager.phys.get(&fake_phy_id).unwrap().phy_info,
fake_phy_info(fake_phy_id, fake_mac_roles)
);
}
/// Tests the case where setting the region code for a new PHY fails.
#[test_case(true; "clear country succeeds")]
#[test_case(false; "clear country fails")]
fn test_add_phy_after_saving_region_code_fails(clear_country_succeeds: bool) {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles.clone());
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
phy_manager.save_region_code(Some([0, 1]));
{
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
// Fail to set the country code.
assert_variant!(
exec.run_until_stalled(&mut test_values.stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::SetCountry {
req: fidl_service::SetCountryRequest {
phy_id: 1,
alpha2: [0, 1],
},
responder,
}
))) => {
responder
.send(fuchsia_zircon::sys::ZX_ERR_NOT_SUPPORTED)
.expect("sending fake set country response");
}
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
// Handle the clear country code request.
assert_variant!(
exec.run_until_stalled(&mut test_values.stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::ClearCountry {
req: fidl_service::ClearCountryRequest {
phy_id: 1,
},
responder,
}
))) => {
let response = if clear_country_succeeds {
fuchsia_zircon::sys::ZX_OK
} else {
fuchsia_zircon::sys::ZX_ERR_NOT_SUPPORTED
};
responder
.send(response)
.expect("sending fake clear country response");
}
);
if clear_country_succeeds {
assert_variant!(exec.run_until_stalled(&mut add_phy_fut), Poll::Ready(Ok(())));
} else {
assert_variant!(
exec.run_until_stalled(&mut add_phy_fut),
Poll::Ready(Err(PhyManagerError::PhySetCountryFailure))
);
}
}
if clear_country_succeeds {
assert!(phy_manager.phys.contains_key(&fake_phy_id));
} else {
assert!(!phy_manager.phys.contains_key(&fake_phy_id));
}
}
#[run_singlethreaded(test)]
async fn remove_valid_phy() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
phy_manager.remove_phy(fake_phy_id);
assert!(phy_manager.phys.is_empty());
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnPhyRemoved event and
/// calling remove_phy on PhyManager for a PHY ID that is not accounted for by the PhyManager.
/// The PhyManager should realize that it is unaware of this PHY ID and leave its PhyContainers
/// unchanged.
#[run_singlethreaded(test)]
async fn remove_nonexistent_phy() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
phy_manager.remove_phy(2);
assert!(phy_manager.phys.contains_key(&fake_phy_id));
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
/// an iface that belongs to a PHY that has been accounted for. The PhyManager should add the
/// newly discovered iface to the existing PHY's list of client ifaces.
#[test]
fn on_iface_added() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
// Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
let fake_role = MacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_mac_addr = [0, 1, 2, 3, 4, 5];
let mut iface_response = create_iface_response(
fake_role,
fake_iface_id,
fake_phy_id,
fake_phy_assigned_id,
fake_mac_addr,
);
{
// Inject the fake PHY information
phy_manager.phys.insert(fake_phy_id, phy_container);
// Add the fake iface
let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
pin_mut!(on_iface_added_fut);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_query_iface_response(
&mut exec,
&mut test_values.stream,
Some(&mut iface_response),
);
// Wait for the PhyManager to finish processing the received iface information
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
}
// Expect that the PhyContainer associated with the fake PHY has been updated with the
// fake client
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.client_ifaces.contains(&fake_iface_id));
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
/// an iface that belongs to a PHY that has not been accounted for. The PhyManager should
/// query the PHY's information, create a new PhyContainer, and insert the new iface ID into
/// the PHY's list of client ifaces.
#[test]
fn on_iface_added_missing_phy() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
// Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
let fake_role = MacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_mac_addr = [0, 1, 2, 3, 4, 5];
let mut iface_response = create_iface_response(
fake_role,
fake_iface_id,
fake_phy_id,
fake_phy_assigned_id,
fake_mac_addr,
);
{
// Add the fake iface
let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
pin_mut!(on_iface_added_fut);
// Since the PhyManager has not accounted for any PHYs, it will get the iface
// information first and then query for the iface's PHY's information.
// The iface query goes out first
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_query_iface_response(
&mut exec,
&mut test_values.stream,
Some(&mut iface_response),
);
// And then the PHY information is queried.
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
// Wait for the PhyManager to finish processing the received iface information
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
}
// Expect that the PhyContainer associated with the fake PHY has been updated with the
// fake client
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.client_ifaces.contains(&fake_iface_id));
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
/// an iface that was created by PhyManager and has already been accounted for. The PhyManager
/// should simply ignore the duplicate iface ID and not append it to its list of clients.
#[test]
fn add_duplicate_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
// Inject the fake PHY information
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
let fake_role = MacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_mac_addr = [0, 1, 2, 3, 4, 5];
let mut iface_response = create_iface_response(
fake_role,
fake_iface_id,
fake_phy_id,
fake_phy_assigned_id,
fake_mac_addr,
);
// Add the same iface ID twice
for _ in 0..2 {
// Add the fake iface
let on_iface_added_fut = phy_manager.on_iface_added(fake_iface_id);
pin_mut!(on_iface_added_fut);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_query_iface_response(
&mut exec,
&mut test_values.stream,
Some(&mut iface_response),
);
// Wait for the PhyManager to finish processing the received iface information
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
}
// Expect that the PhyContainer associated with the fake PHY has been updated with only one
// reference to the fake client
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert_eq!(phy_container.client_ifaces.len(), 1);
assert!(phy_container.client_ifaces.contains(&fake_iface_id));
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceAdded event for
/// an iface that has already been removed. The PhyManager should fail to query the iface info
/// and not account for the iface ID.
#[test]
fn add_nonexistent_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
{
// Add the non-existent iface
let on_iface_added_fut = phy_manager.on_iface_added(1);
pin_mut!(on_iface_added_fut);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_query_iface_response(&mut exec, &mut test_values.stream, None);
// Wait for the PhyManager to finish processing the received iface information
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_ready());
}
// Expect that the PhyContainer associated with the fake PHY has been updated with the
// fake client
assert!(phy_manager.phys.is_empty());
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceRemoved event
/// for an iface that has been accounted for by the PhyManager. The PhyManager should remove
/// the iface ID from the PHY's list of client ifaces.
#[run_singlethreaded(test)]
async fn test_on_iface_removed() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
// Inject the fake PHY information
let mut phy_container = PhyContainer::new(phy_info);
let fake_iface_id = 1;
phy_container.client_ifaces.insert(fake_iface_id);
phy_manager.phys.insert(fake_phy_id, phy_container);
phy_manager.on_iface_removed(fake_iface_id);
// Expect that the iface ID has been removed from the PhyContainer
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.client_ifaces.is_empty());
}
/// This test mimics a client of the DeviceWatcher watcher receiving an OnIfaceRemoved event
/// for an iface that has not been accounted for. The PhyManager should simply ignore the
/// request and leave its list of client iface IDs unchanged.
#[run_singlethreaded(test)]
async fn remove_missing_iface() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = Vec::new();
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let present_iface_id = 1;
let removed_iface_id = 2;
// Inject the fake PHY information
let mut phy_container = PhyContainer::new(phy_info);
phy_container.client_ifaces.insert(present_iface_id);
phy_container.client_ifaces.insert(removed_iface_id);
phy_manager.phys.insert(fake_phy_id, phy_container);
phy_manager.on_iface_removed(removed_iface_id);
// Expect that the iface ID has been removed from the PhyContainer
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert_eq!(phy_container.client_ifaces.len(), 1);
assert!(phy_container.client_ifaces.contains(&present_iface_id));
}
/// Tests the response of the PhyManager when a client iface is requested, but no PHYs are
/// present. The expectation is that the PhyManager returns None.
#[run_singlethreaded(test)]
async fn get_client_no_phys() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let client = phy_manager.get_client();
assert!(client.is_none());
}
/// Tests the response of the PhyManager when a client iface is requested, a client-capable PHY
/// has been discovered, but client connections have not been started. The expectation is that
/// the PhyManager returns None.
#[run_singlethreaded(test)]
async fn get_unconfigured_client() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Retrieve the client ID
let client = phy_manager.get_client();
assert!(client.is_none());
}
/// Tests the response of the PhyManager when a client iface is requested and a client iface is
/// present. The expectation is that the PhyManager should reply with the iface ID of the
/// client iface.
#[run_singlethreaded(test)]
async fn get_configured_client() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let fake_iface_id = 1;
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.client_ifaces.insert(fake_iface_id);
// Retrieve the client ID
let client = phy_manager.get_client();
assert_eq!(client.unwrap(), fake_iface_id)
}
/// Tests the response of the PhyManager when a client iface is requested and the only PHY
/// that is present does not support client ifaces and has an AP iface present. The
/// expectation is that the PhyManager returns None.
#[run_singlethreaded(test)]
async fn get_client_no_compatible_phys() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Ap];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let mut phy_container = PhyContainer::new(phy_info);
phy_container.ap_ifaces.insert(fake_iface_id);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Retrieve the client ID
let client = phy_manager.get_client();
assert!(client.is_none());
}
/// Tests the PhyManager's response to stop_client_connection when there is an existing client
/// iface. The expectation is that the client iface is destroyed and there is no remaining
/// record of the iface ID in the PhyManager.
#[test]
fn destroy_all_client_ifaces() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
{
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.client_ifaces.insert(fake_iface_id);
// Stop client connections
let stop_clients_future = phy_manager.destroy_all_client_ifaces();
pin_mut!(stop_clients_future);
assert!(exec.run_until_stalled(&mut stop_clients_future).is_pending());
send_destroy_iface_response(&mut exec, &mut test_values.stream, ZX_OK);
assert!(exec.run_until_stalled(&mut stop_clients_future).is_ready());
}
// Ensure that the client interface that was added has been removed.
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(!phy_container.client_ifaces.contains(&fake_iface_id));
}
/// Tests the PhyManager's response to destroy_all_client_ifaces when no client ifaces are
/// present but an AP iface is present. The expectation is that the AP iface is left intact.
#[test]
fn destroy_all_client_ifaces_no_clients() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![MacRole::Ap];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
// Insert the fake AP iface and then stop clients
{
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake AP iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.ap_ifaces.insert(fake_iface_id);
// Stop client connections
let stop_clients_future = phy_manager.destroy_all_client_ifaces();
pin_mut!(stop_clients_future);
assert!(exec.run_until_stalled(&mut stop_clients_future).is_ready());
}
// Ensure that the fake PHY and AP interface are still present.
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.ap_ifaces.contains(&fake_iface_id));
}
/// Tests the PhyManager's response to a request for an AP when no PHYs are present. The
/// expectation is that the PhyManager will return None in this case.
#[test]
fn get_ap_no_phys() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
let get_ap_future = phy_manager.create_or_get_ap_iface();
pin_mut!(get_ap_future);
assert_variant!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(Ok(None)));
}
/// Tests the PhyManager's response when the PhyManager holds a PHY that can have an AP iface
/// but the AP iface has not been created yet. The expectation is that the PhyManager creates
/// a new AP iface and returns its ID to the caller.
#[test]
fn get_unconfigured_ap() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Retrieve the AP interface ID
let fake_iface_id = 1;
{
let get_ap_future = phy_manager.create_or_get_ap_iface();
pin_mut!(get_ap_future);
assert!(exec.run_until_stalled(&mut get_ap_future).is_pending());
send_create_iface_response(&mut exec, &mut test_values.stream, Some(fake_iface_id));
assert_variant!(
exec.run_until_stalled(&mut get_ap_future),
Poll::Ready(Ok(Some(iface_id))) => assert_eq!(iface_id, fake_iface_id)
);
}
assert!(phy_manager.phys[&fake_phy_id].ap_ifaces.contains(&fake_iface_id));
}
/// Tests the PhyManager's response to a create_or_get_ap_iface call when there is a PHY with an AP iface
/// that has already been created. The expectation is that the PhyManager should return the
/// iface ID of the existing AP iface.
#[test]
fn get_configured_ap() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let fake_iface_id = 1;
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.ap_ifaces.insert(fake_iface_id);
// Retrieve the AP iface ID
let get_ap_future = phy_manager.create_or_get_ap_iface();
pin_mut!(get_ap_future);
assert_variant!(
exec.run_until_stalled(&mut get_ap_future),
Poll::Ready(Ok(Some(iface_id))) => assert_eq!(iface_id, fake_iface_id)
);
}
/// This test attempts to get an AP iface from a PhyManager that has a PHY that can only have
/// a client interface. The PhyManager should return None.
#[test]
fn get_ap_no_compatible_phys() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Retrieve the client ID
let get_ap_future = phy_manager.create_or_get_ap_iface();
pin_mut!(get_ap_future);
assert_variant!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(Ok(None)));
}
/// This test stops a valid AP iface on a PhyManager. The expectation is that the PhyManager
/// should retain the record of the PHY, but the AP iface ID should be removed.
#[test]
fn stop_valid_ap_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
{
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.ap_ifaces.insert(fake_iface_id);
// Remove the AP iface ID
let destroy_ap_iface_future = phy_manager.destroy_ap_iface(fake_iface_id);
pin_mut!(destroy_ap_iface_future);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
send_destroy_iface_response(&mut exec, &mut test_values.stream, ZX_OK);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(!phy_container.ap_ifaces.contains(&fake_iface_id));
}
/// This test attempts to stop an invalid AP iface ID. The expectation is that a valid iface
/// ID is unaffected.
#[test]
fn stop_invalid_ap_iface() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
{
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.ap_ifaces.insert(fake_iface_id);
// Remove a non-existent AP iface ID
let destroy_ap_iface_future = phy_manager.destroy_ap_iface(2);
pin_mut!(destroy_ap_iface_future);
assert_variant!(
exec.run_until_stalled(&mut destroy_ap_iface_future),
Poll::Ready(Ok(()))
);
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.ap_ifaces.contains(&fake_iface_id));
}
/// This test creates two AP ifaces for a PHY that supports AP ifaces. destroy_all_ap_ifaces is then
/// called on the PhyManager. The expectation is that both AP ifaces should be destroyed and
/// the records of the iface IDs should be removed from the PhyContainer.
#[test]
fn stop_all_ap_ifaces() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// ifaces are added.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
{
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.ap_ifaces.insert(0);
phy_container.ap_ifaces.insert(1);
// Expect two interface destruction requests
let destroy_ap_iface_future = phy_manager.destroy_all_ap_ifaces();
pin_mut!(destroy_ap_iface_future);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
send_destroy_iface_response(&mut exec, &mut test_values.stream, ZX_OK);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
send_destroy_iface_response(&mut exec, &mut test_values.stream, ZX_OK);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.ap_ifaces.is_empty());
}
/// This test calls destroy_all_ap_ifaces on a PhyManager that only has a client iface. The expectation
/// is that no interfaces should be destroyed and the client iface ID should remain in the
/// PhyManager
#[test]
fn stop_all_ap_ifaces_with_client() {
let mut exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Client];
{
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.client_ifaces.insert(fake_iface_id);
// Stop all AP ifaces
let destroy_ap_iface_future = phy_manager.destroy_all_ap_ifaces();
pin_mut!(destroy_ap_iface_future);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_ready());
}
assert!(phy_manager.phys.contains_key(&fake_phy_id));
let phy_container = phy_manager.phys.get(&fake_phy_id).unwrap();
assert!(phy_container.client_ifaces.contains(&fake_iface_id));
}
/// Verifies that setting a suggested AP MAC address results in that MAC address being used as
/// a part of the request to create an AP interface. Ensures that this does not affect client
/// interface requests.
#[test]
fn test_suggest_ap_mac() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Ap];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Insert the fake iface
let phy_container = phy_manager.phys.get_mut(&fake_phy_id).unwrap();
phy_container.client_ifaces.insert(fake_iface_id);
// Suggest an AP MAC
let mac = MacAddress::from_bytes(&[1, 2, 3, 4, 5, 6]).unwrap();
phy_manager.suggest_ap_mac(mac.clone());
let get_ap_future = phy_manager.create_or_get_ap_iface();
pin_mut!(get_ap_future);
assert_variant!(exec.run_until_stalled(&mut get_ap_future), Poll::Pending);
// Verify that the suggested MAC is included in the request
assert_variant!(
exec.run_until_stalled(&mut test_values.stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::CreateIface {
req,
responder,
}
))) => {
let requested_mac = match req.mac_addr {
Some(mac) => MacAddress::from_bytes(&mac).unwrap(),
None => panic!("requested mac is None")
};
assert_eq!(requested_mac, mac);
let mut response = fidl_service::CreateIfaceResponse { iface_id: fake_iface_id };
let response = Some(&mut response);
responder.send(ZX_OK, response).expect("sending fake iface id");
}
);
assert_variant!(exec.run_until_stalled(&mut get_ap_future), Poll::Ready(_));
}
#[test]
fn test_suggested_mac_does_not_apply_to_client() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
// Create an initial PhyContainer to be inserted into the test PhyManager before the fake
// iface is added.
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_fuchsia_wlan_device::MacRole::Client];
let phy_info = fake_phy_info(fake_phy_id, fake_mac_roles);
let phy_container = PhyContainer::new(phy_info);
phy_manager.phys.insert(fake_phy_id, phy_container);
// Suggest an AP MAC
let mac = MacAddress::from_bytes(&[1, 2, 3, 4, 5, 6]);
phy_manager.suggest_ap_mac(mac.clone().unwrap());
// Start client connections so that an IfaceRequest is issued for the client.
let start_client_future = phy_manager.create_all_client_ifaces();
pin_mut!(start_client_future);
assert_variant!(exec.run_until_stalled(&mut start_client_future), Poll::Pending);
// Verify that the suggested MAC is NOT included in the request
assert_variant!(
exec.run_until_stalled(&mut test_values.stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::CreateIface {
req,
responder,
}
))) => {
assert!(req.mac_addr.is_none());
let mut response = fidl_service::CreateIfaceResponse { iface_id: fake_iface_id };
let response = Some(&mut response);
responder.send(ZX_OK, response).expect("sending fake iface id");
}
);
assert_variant!(exec.run_until_stalled(&mut start_client_future), Poll::Ready(_));
}
/// Tests get_phy_ids() when no PHYs are present. The expectation is that the PhyManager will
/// return an empty `Vec` in this case.
#[run_singlethreaded(test)]
async fn get_phy_ids_no_phys() {
let test_values = test_setup();
let phy_manager = PhyManager::new(test_values.proxy, test_values.node);
assert_eq!(phy_manager.get_phy_ids(), Vec::<u16>::new());
}
/// Tests get_phy_ids() when a single PHY is present. The expectation is that the PhyManager will
/// return a single element `Vec`, with the appropriate ID.
#[test]
fn get_phy_ids_single_phy() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
{
let phy_info = fake_phy_info(1, vec![]);
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
assert_eq!(phy_manager.get_phy_ids(), vec![1]);
}
/// Tests get_phy_ids() when two PHYs are present. The expectation is that the PhyManager will
/// return a two-element `Vec`, containing the appropriate IDs. Ordering is not guaranteed.
#[test]
fn get_phy_ids_two_phys() {
let mut exec = Executor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
{
let phy_info = fake_phy_info(1, Vec::new());
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
{
let phy_info = fake_phy_info(2, Vec::new());
let add_phy_fut = phy_manager.add_phy(phy_info.id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_query_phy_response(&mut exec, &mut test_values.stream, Some(phy_info));
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
let phy_ids = phy_manager.get_phy_ids();
assert!(phy_ids.contains(&1), "expected phy_ids to contain `1`, but phy_ids={:?}", phy_ids);
assert!(phy_ids.contains(&2), "expected phy_ids to contain `2`, but phy_ids={:?}", phy_ids);
}
/// Tests log_phy_add_failure() to ensure the appropriate inspect count is incremented by 1.
#[run_singlethreaded(test)]
async fn log_phy_add_failure() {
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
assert_inspect_tree!(test_values.inspector, root: {
phy_manager: {
phy_add_fail_count: 0u64,
},
});
phy_manager.log_phy_add_failure();
assert_inspect_tree!(test_values.inspector, root: {
phy_manager: {
phy_add_fail_count: 1u64,
},
});
}
/// Tests the initialization of the region code and the ability of the PhyManager to cache a
/// region update.
#[test]
fn test_save_region_code() {
let _exec = Executor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager = PhyManager::new(test_values.proxy, test_values.node);
assert!(phy_manager.saved_region_code.is_none());
phy_manager.save_region_code(Some([0, 1]));
assert_eq!(phy_manager.saved_region_code, Some([0, 1]));
phy_manager.save_region_code(None);
assert_eq!(phy_manager.saved_region_code, None);
}
}