blob: 651fb2b6400d38b80431401e8a2c0386fcd1ae3d [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::{mode_management::iface_manager::wpa3_supported, regulatory_manager::REGION_CODE_LEN},
async_trait::async_trait,
eui48::MacAddress,
fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_device_service as fidl_service,
fuchsia_inspect::{self as inspect, NumericProperty},
fuchsia_zircon,
ieee80211::{MacAddr, NULL_MAC_ADDR},
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 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 apply power setting")]
PhySetLowPowerFailure,
#[error("unable to query iface information")]
IfaceQueryFailure,
#[error("unable to create iface")]
IfaceCreateFailure,
#[error("unable to destroy iface")]
IfaceDestroyFailure,
}
/// There are a variety of reasons why the calling code may want to create client interfaces. The
/// main logic to do so is identical, but there are different intents for making the call. This
/// enum allows callers to express their intent when making the call to ensure that internal
/// PhyManager state remains consistent with the current desired mode of operation.
#[derive(PartialEq)]
pub enum CreateClientIfacesReason {
StartClientConnections,
RecoverClientIfaces,
}
/// Stores information about a WLAN PHY and any interfaces that belong to it.
pub(crate) struct PhyContainer {
supported_mac_roles: HashSet<fidl_common::WlanMacRole>,
// Security support is tracked for each interface so that callers can request an interface
// based on security features (e.g. for WPA3).
client_ifaces: HashMap<u16, fidl_common::SecuritySupport>,
ap_ifaces: HashSet<u16>,
}
#[async_trait]
pub 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. This method returns
/// a vector containing all newly-created client interface IDs along with a representation of
/// any errors encountered along the way.
async fn create_all_client_ifaces(
&mut self,
reason: CreateClientIfacesReason,
) -> Result<Vec<u16>, (Vec<u16>, PhyManagerError)>;
/// The PhyManager is the authoritative source of whether or not the policy layer is allowed to
/// create client interfaces. This method allows other parts of the policy layer to determine
/// whether the API client has allowed client interfaces to be created.
fn client_connections_enabled(&self) -> bool;
/// 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 WPA3 capable PHY with a client interface and returns the interface's ID.
fn get_wpa3_capable_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);
/// Sets the country code on all known PHYs and stores the country code to be applied to
/// newly-discovered PHYs.
async fn set_country_code(
&mut self,
country_code: Option<[u8; REGION_CODE_LEN]>,
) -> Result<(), PhyManagerError>;
/// Returns whether any PHY has a client interface that supports WPA3.
fn has_wpa3_client_iface(&self) -> bool;
/// Sets the low power state for all PHYs. PHYs discovered in the future will have the low
/// power setting applied to them as well.
async fn set_power_state(
&mut self,
power_state: fidl_common::PowerSaveType,
) -> Result<fuchsia_zircon::Status, anyhow::Error>;
}
/// Maintains a record of all PHYs that are present and their associated interfaces.
pub struct PhyManager {
phys: HashMap<u16, PhyContainer>,
device_service: fidl_service::DeviceServiceProxy,
device_monitor: fidl_service::DeviceMonitorProxy,
client_connections_enabled: bool,
power_state: fidl_common::PowerSaveType,
suggested_ap_mac: Option<MacAddress>,
saved_country_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(supported_mac_roles: Vec<fidl_common::WlanMacRole>) -> Self {
PhyContainer {
supported_mac_roles: supported_mac_roles.into_iter().collect(),
client_ifaces: HashMap::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 DeviceMonitorProxy to query PHY and interface properties and create and
/// destroy interfaces as requested.
pub fn new(
device_service: fidl_service::DeviceServiceProxy,
device_monitor: fidl_service::DeviceMonitorProxy,
node: inspect::Node,
) -> Self {
let phy_add_fail_count = node.create_uint("phy_add_fail_count", 0);
PhyManager {
phys: HashMap::new(),
device_service,
device_monitor,
client_connections_enabled: false,
power_state: fidl_common::PowerSaveType::PsModePerformance,
suggested_ap_mac: None,
saved_country_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: fidl_common::WlanMacRole) -> Vec<u16> {
self.phys
.iter()
.filter_map(|(k, v)| {
if v.supported_mac_roles.contains(&role) {
return Some(*k);
}
None
})
.collect()
}
async fn get_iface_security_support(
&self,
iface_id: u16,
) -> Result<fidl_common::SecuritySupport, PhyManagerError> {
let (status, security_support) = self
.device_service
.query_security_support(iface_id)
.await
.map_err(|_| PhyManagerError::IfaceQueryFailure)?;
fuchsia_zircon::ok(status).map_err(|_| PhyManagerError::IfaceQueryFailure)?;
let security_support =
security_support.ok_or_else(|| PhyManagerError::IfaceQueryFailure)?;
Ok(*security_support)
}
}
#[async_trait]
impl PhyManagerApi for PhyManager {
async fn add_phy(&mut self, phy_id: u16) -> Result<(), PhyManagerError> {
let supported_mac_roles = self
.device_monitor
.get_supported_mac_roles(phy_id)
.await
.map_err(|e| {
warn!("Failed to communicate with monitor service: {:?}", e);
PhyManagerError::PhyQueryFailure
})?
.map_err(|e| {
warn!("Unable to get supported MAC roles: {:?}", e);
PhyManagerError::PhyQueryFailure
})?;
// Create a new container to store the PHY's information.
info!("adding PHY ID #{}", phy_id);
let mut phy_container = PhyContainer::new(supported_mac_roles);
// Attempt to set the country for the newly-discovered PHY.
let set_country_result = match self.saved_country_code {
Some(country_code) => {
set_phy_country_code(&self.device_monitor, phy_id, country_code).await
}
None => Ok(()),
};
// If setting the country code fails, clear the PHY's country code so that it is in WW
// and can continue to operate. If this process fails, return early and do not use this
// PHY.
if set_country_result.is_err() {
clear_phy_country_code(&self.device_monitor, phy_id).await?;
}
if self.client_connections_enabled
&& phy_container.supported_mac_roles.contains(&fidl_common::WlanMacRole::Client)
{
let iface_id = create_iface(
&self.device_monitor,
phy_id,
fidl_common::WlanMacRole::Client,
NULL_MAC_ADDR,
)
.await?;
let security_support = self.get_iface_security_support(iface_id).await?;
if let Some(_) = phy_container.client_ifaces.insert(iface_id, security_support) {
warn!("Unexpectedly replaced existing iface security support for id {}", iface_id);
};
}
if self.power_state != fidl_common::PowerSaveType::PsModePerformance {
let ps_result = set_ps_mode(&self.device_monitor, phy_id, self.power_state)
.await
.map_err(|_| PhyManagerError::PhySetLowPowerFailure)?;
fuchsia_zircon::ok(ps_result).map_err(|_| PhyManagerError::PhySetLowPowerFailure)?
}
if let Some(_) = self.phys.insert(phy_id, phy_container) {
warn!("Unexpectedly replaced existing phy information for id {}", phy_id);
};
Ok(())
}
fn remove_phy(&mut self, phy_id: u16) {
if self.phys.remove(&phy_id).is_none() {
warn!("Attempted to remove non-existed phy {}", 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 iface_id = query_iface_response.id;
let security_support = query_security_support(&self.device_service, iface_id).await?;
let phy = self.ensure_phy(query_iface_response.phy_id).await?;
match query_iface_response.role {
fidl_common::WlanMacRole::Client => {
if let Some(old_security_support) =
phy.client_ifaces.insert(iface_id, security_support)
{
if old_security_support != security_support {
warn!("Security support changed unexpectedly for id {}", iface_id);
}
} else {
// The iface wasn't in the hashmap, so it was created by someone else
warn!("Detected an unexpected client iface id {} created outside of PhyManager", iface_id);
}
}
fidl_common::WlanMacRole::Ap => {
if phy.ap_ifaces.insert(iface_id) {
// `.insert()` returns true if the value was not already present
warn!("Detected an unexpected AP iface created outside of PhyManager");
}
}
fidl_common::WlanMacRole::Mesh => {
return Err(PhyManagerError::Unsupported);
}
}
}
Ok(())
}
fn on_iface_removed(&mut self, iface_id: u16) {
for (_, phy_info) in self.phys.iter_mut() {
if phy_info.client_ifaces.remove(&iface_id).is_none()
&& !phy_info.ap_ifaces.remove(&iface_id)
{
warn!(
"Attempted to remove iface id {} but it was not in client or ap iface list",
iface_id
);
};
}
}
async fn create_all_client_ifaces(
&mut self,
reason: CreateClientIfacesReason,
) -> Result<Vec<u16>, (Vec<u16>, PhyManagerError)> {
if reason == CreateClientIfacesReason::StartClientConnections {
self.client_connections_enabled = true;
}
let mut available_iface_ids = Vec::new();
let mut error_encountered = Ok(());
if self.client_connections_enabled {
let client_capable_phy_ids = self.phys_for_role(fidl_common::WlanMacRole::Client);
for client_phy in client_capable_phy_ids.iter() {
let phy_container = match self.phys.get_mut(&client_phy) {
Some(phy_container) => phy_container,
None => {
error_encountered = Err(PhyManagerError::PhyQueryFailure);
continue;
}
};
// If a PHY should be able to have a client interface and it does not, create a new
// client interface for the PHY.
if phy_container.client_ifaces.is_empty() {
let iface_id = match create_iface(
&self.device_monitor,
*client_phy,
fidl_common::WlanMacRole::Client,
NULL_MAC_ADDR,
)
.await
{
Ok(iface_id) => iface_id,
Err(e) => {
warn!("Failed to recover iface for PHY {}: {:?}", client_phy, e);
error_encountered = Err(e);
continue;
}
};
let security_support = query_security_support(&self.device_service, iface_id)
.await
.unwrap_or_else(|e| {
error!("Error occurred getting iface security support: {}", e);
fidl_common::SecuritySupport {
sae: fidl_common::SaeFeature {
driver_handler_supported: false,
sme_handler_supported: false,
},
mfp: fidl_common::MfpFeature { supported: false },
}
});
let _ = phy_container.client_ifaces.insert(iface_id, security_support);
}
// Regardless of whether or not new interfaces were created or existing interfaces
// were discovered, notify the caller of all interface IDs available for this PHY.
let mut available_interfaces =
phy_container.client_ifaces.keys().into_iter().cloned().collect();
available_iface_ids.append(&mut available_interfaces);
}
}
match error_encountered {
Ok(()) => Ok(available_iface_ids),
Err(e) => Err((available_iface_ids, e)),
}
}
fn client_connections_enabled(&self) -> bool {
self.client_connections_enabled
}
async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError> {
self.client_connections_enabled = false;
let client_capable_phys = self.phys_for_role(fidl_common::WlanMacRole::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 = HashMap::new();
for (iface_id, security_support) in phy_container.client_ifaces.drain() {
match destroy_iface(&self.device_monitor, iface_id).await {
Ok(()) => {}
Err(e) => {
result = Err(e);
if let Some(_) = lingering_ifaces.insert(iface_id, security_support) {
warn!("Unexpected duplicate lingering iface for id {}", iface_id);
};
}
}
}
phy_container.client_ifaces = lingering_ifaces;
}
result
}
fn get_client(&mut self) -> Option<u16> {
if !self.client_connections_enabled {
return None;
}
let client_capable_phys = self.phys_for_role(fidl_common::WlanMacRole::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.keys().next() {
Some(iface_id) => Some(*iface_id),
None => None,
}
}
fn get_wpa3_capable_client(&mut self) -> Option<u16> {
// Search phys for a client iface with security support indicating WPA3 support.
for (_, phy) in self.phys.iter() {
for (iface_id, security_support) in phy.client_ifaces.iter() {
if wpa3_supported(*security_support) {
return Some(*iface_id);
}
}
}
None
}
async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError> {
let ap_capable_phy_ids = self.phys_for_role(fidl_common::WlanMacRole::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) => mac.to_array(),
None => NULL_MAC_ADDR,
};
let iface_id = create_iface(
&self.device_monitor,
*ap_phy_id,
fidl_common::WlanMacRole::Ap,
mac,
)
.await?;
let _ = 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_monitor, iface_id).await {
Ok(()) => {}
Err(e) => {
let _ = 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(fidl_common::WlanMacRole::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_monitor, iface_id).await {
Ok(()) => {}
Err(e) => {
result = Err(e);
let _ = 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);
}
async fn set_country_code(
&mut self,
country_code: Option<[u8; REGION_CODE_LEN]>,
) -> Result<(), PhyManagerError> {
self.saved_country_code = country_code;
match country_code {
Some(country_code) => {
for phy_id in self.phys.keys() {
set_phy_country_code(&self.device_monitor, *phy_id, country_code).await?;
}
}
None => {
for phy_id in self.phys.keys() {
clear_phy_country_code(&self.device_monitor, *phy_id).await?;
}
}
}
Ok(())
}
// Returns whether at least one of phy's iface supports WPA3.
fn has_wpa3_client_iface(&self) -> bool {
// Search phys for a client iface with security support indicating WPA3 support.
for (_, phy) in self.phys.iter() {
for (_, security_support) in phy.client_ifaces.iter() {
if wpa3_supported(*security_support) {
return true;
}
}
}
false
}
async fn set_power_state(
&mut self,
power_state: fidl_common::PowerSaveType,
) -> Result<fuchsia_zircon::Status, anyhow::Error> {
self.power_state = power_state;
let mut final_status = fuchsia_zircon::Status::OK;
for phy_id in self.phys.keys() {
let result = set_ps_mode(&self.device_monitor, *phy_id, power_state).await?;
if let Err(status) = fuchsia_zircon::ok(result) {
final_status = status.into();
}
}
Ok(final_status)
}
}
/// 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::DeviceMonitorProxy,
phy_id: u16,
role: fidl_common::WlanMacRole,
sta_addr: MacAddr,
) -> Result<u16, PhyManagerError> {
let mut request = fidl_service::CreateIfaceRequest { phy_id, role, sta_addr };
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::DeviceMonitorProxy,
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)
}
}
}
async fn query_security_support(
proxy: &fidl_service::DeviceServiceProxy,
iface_id: u16,
) -> Result<fidl_common::SecuritySupport, PhyManagerError> {
let (status, security_support) = proxy
.query_security_support(iface_id)
.await
.map_err(|_| PhyManagerError::IfaceQueryFailure)?;
fuchsia_zircon::ok(status).map_err(|_| PhyManagerError::IfaceQueryFailure)?;
match security_support {
Some(security_support) => Ok(*security_support),
None => {
warn!("Security support unavailable for iface {}", iface_id);
Err(PhyManagerError::IfaceQueryFailure)
}
}
}
async fn set_phy_country_code(
proxy: &fidl_service::DeviceMonitorProxy,
phy_id: u16,
country_code: [u8; REGION_CODE_LEN],
) -> Result<(), PhyManagerError> {
let status = proxy
.set_country(&mut fidl_service::SetCountryRequest { phy_id, alpha2: country_code })
.await
.map_err(|e| {
error!("Failed to set country code for PHY {}: {:?}", phy_id, e);
PhyManagerError::PhySetCountryFailure
})?;
fuchsia_zircon::ok(status).map_err(|e| {
error!("Received bad status when setting country code for PHY {}: {}", phy_id, e);
PhyManagerError::PhySetCountryFailure
})
}
async fn clear_phy_country_code(
proxy: &fidl_service::DeviceMonitorProxy,
phy_id: u16,
) -> Result<(), PhyManagerError> {
let status = proxy
.clear_country(&mut fidl_service::ClearCountryRequest { phy_id })
.await
.map_err(|e| {
error!("Failed to clear country code for PHY {}: {:?}", phy_id, e);
PhyManagerError::PhySetCountryFailure
})?;
fuchsia_zircon::ok(status).map_err(|e| {
error!("Received bad status when clearing country code for PHY {}: {}", phy_id, e);
PhyManagerError::PhySetCountryFailure
})
}
async fn set_ps_mode(
proxy: &fidl_service::DeviceMonitorProxy,
phy_id: u16,
state: fidl_common::PowerSaveType,
) -> Result<i32, anyhow::Error> {
let mut req = fidl_service::SetPsModeRequest { phy_id, ps_mode: state };
proxy.set_ps_mode(&mut req).await.map_err(|e| e.into())
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints,
fidl_fuchsia_wlan_device_service as fidl_service,
fuchsia_async::{run_singlethreaded, TestExecutor},
fuchsia_inspect::{self as inspect, assert_data_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 DeviceMonitor to allow mocking DeviceMonitor responses
/// for unit tests.
struct TestValues {
dev_svc_proxy: fidl_service::DeviceServiceProxy,
dev_svc_stream: fidl_service::DeviceServiceRequestStream,
monitor_proxy: fidl_service::DeviceMonitorProxy,
monitor_stream: fidl_service::DeviceMonitorRequestStream,
inspector: inspect::Inspector,
node: inspect::Node,
}
/// Create a TestValues for a unit test.
fn test_setup() -> TestValues {
let (dev_svc_proxy, dev_svc_requests) =
endpoints::create_proxy::<fidl_service::DeviceServiceMarker>()
.expect("failed to create DeviceService proxy");
let dev_svc_stream = dev_svc_requests.into_stream().expect("failed to create stream");
let (monitor_proxy, monitor_requests) =
endpoints::create_proxy::<fidl_service::DeviceMonitorMarker>()
.expect("failed to create DeviceMonitor proxy");
let monitor_stream = monitor_requests.into_stream().expect("failed to create stream");
let inspector = inspect::Inspector::new();
let node = inspector.root().create_child("phy_manager");
TestValues { dev_svc_proxy, dev_svc_stream, monitor_proxy, monitor_stream, inspector, node }
}
/// Take in the service side of a DeviceMonitor::GetSupportedMacRoles request and respond with
/// the given WlanMacRoles responst.
fn send_get_supported_mac_roles_response(
exec: &mut TestExecutor,
server: &mut fidl_service::DeviceMonitorRequestStream,
mut supported_mac_roles: Result<
Vec<fidl_common::WlanMacRole>,
fuchsia_zircon::sys::zx_status_t,
>,
) {
let _ = assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::GetSupportedMacRoles {
responder, ..
}
))) => {
responder.send(&mut supported_mac_roles)
}
);
}
/// Create a PhyInfo object for unit testing.
#[track_caller]
fn send_query_iface_response(
exec: &mut TestExecutor,
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");
}
);
}
/// Serve the given security support for unit testing.
#[track_caller]
fn send_security_support(
exec: &mut TestExecutor,
server: &mut fidl_service::DeviceServiceRequestStream,
security_support: Option<&mut fidl_common::SecuritySupport>,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceServiceRequest::QuerySecuritySupport {
iface_id: _,
responder,
}
))) => {
responder.send(ZX_OK, security_support).expect("sending fake security support");
}
);
}
/// Handles the service side of a DeviceMonitor::CreateIface request by replying with the
/// provided optional iface ID.
#[track_caller]
fn send_create_iface_response(
exec: &mut TestExecutor,
server: &mut fidl_service::DeviceMonitorRequestStream,
iface_id: Option<u16>,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::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 DeviceMonitor::DestroyIface request by replying with the
/// provided zx_status_t.
fn send_destroy_iface_response(
exec: &mut TestExecutor,
server: &mut fidl_service::DeviceMonitorRequestStream,
return_status: fuchsia_zircon::zx_status_t,
) {
assert_variant!(
exec.run_until_stalled(&mut server.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::DestroyIface {
req: _,
responder,
}
))) => {
responder
.send(return_status)
.expect(format!("sending fake response: {}", return_status).as_str());
}
);
}
/// Creates a QueryIfaceResponse from the arguments provided by the caller.
fn create_iface_response(
role: fidl_common::WlanMacRole,
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,
sta_addr: mac,
}
}
/// Create an empty security support structure.
fn fake_security_support() -> fidl_common::SecuritySupport {
fidl_common::SecuritySupport {
mfp: fidl_common::MfpFeature { supported: false },
sae: fidl_common::SaeFeature {
driver_handler_supported: false,
sme_handler_supported: false,
},
}
}
/// 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.
#[fuchsia::test]
fn add_valid_phy() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let fake_phy_id = 0;
let fake_mac_roles = vec![];
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
{
let add_phy_fut = phy_manager.add_phy(0);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles.clone()),
);
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().supported_mac_roles,
fake_mac_roles.into_iter().collect()
);
}
/// 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.
#[fuchsia::test]
fn add_invalid_phy() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Err(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND),
);
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.
#[fuchsia::test]
fn add_duplicate_phy() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_phy_id = 0;
let fake_mac_roles = vec![];
{
let add_phy_fut = phy_manager.add_phy(fake_phy_id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles.clone()),
);
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().supported_mac_roles,
fake_mac_roles.clone().into_iter().collect()
);
}
// Send an update for the same PHY ID and ensure that the PHY info is updated.
{
let add_phy_fut = phy_manager.add_phy(fake_phy_id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles.clone()),
);
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().supported_mac_roles,
fake_mac_roles.into_iter().collect()
);
}
/// 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.
#[fuchsia::test]
fn add_phy_after_create_all_client_ifaces() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_iface_id = 1;
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
{
let start_connections_fut = phy_manager
.create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
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(fake_phy_id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles),
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_create_iface_response(
&mut exec,
&mut test_values.monitor_stream,
Some(fake_iface_id),
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
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_key(&fake_iface_id));
assert_eq!(phy_container.client_ifaces.get(&fake_iface_id), Some(&fake_security_support()));
}
/// Tests the case where a new PHY is discovered after the country code has been set.
#[fuchsia::test]
fn test_add_phy_after_setting_country_code() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let fake_phy_id = 1;
let fake_mac_roles = vec![];
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
{
let set_country_fut = phy_manager.set_country_code(Some([0, 1]));
pin_mut!(set_country_fut);
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
}
{
let add_phy_fut = phy_manager.add_phy(fake_phy_id);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles.clone()),
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::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().supported_mac_roles,
fake_mac_roles.into_iter().collect()
);
}
#[run_singlethreaded(test)]
async fn remove_valid_phy() {
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_phy_id = 1;
let fake_mac_roles = vec![];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_phy_id = 1;
let fake_mac_roles = vec![];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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.
#[fuchsia::test]
fn on_iface_added() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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![];
let phy_container = PhyContainer::new(fake_mac_roles);
// Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
let fake_role = fidl_common::WlanMacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_sta_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_sta_addr,
);
{
// Inject the fake PHY information
let _ = 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.dev_svc_stream,
Some(&mut iface_response),
);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
// 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_key(&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.
#[fuchsia::test]
fn on_iface_added_missing_phy() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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![];
// Create an IfaceResponse to be sent to the PhyManager when the iface ID is queried
let fake_role = fidl_common::WlanMacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_sta_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_sta_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.dev_svc_stream,
Some(&mut iface_response),
);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
// And then the PHY information is queried.
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(fake_mac_roles),
);
// 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_key(&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.
#[fuchsia::test]
fn add_duplicate_iface() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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![];
// Inject the fake PHY information
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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 = fidl_common::WlanMacRole::Client;
let fake_iface_id = 1;
let fake_phy_assigned_id = 1;
let fake_sta_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_sta_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.dev_svc_stream,
Some(&mut iface_response),
);
assert!(exec.run_until_stalled(&mut on_iface_added_fut).is_pending());
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
// 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_key(&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.
#[fuchsia::test]
fn add_nonexistent_iface() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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.dev_svc_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.dev_svc_proxy, test_values.monitor_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![];
// Inject the fake PHY information
let mut phy_container = PhyContainer::new(fake_mac_roles);
let fake_iface_id = 1;
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
let _ = 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.dev_svc_proxy, test_values.monitor_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![];
let present_iface_id = 1;
let removed_iface_id = 2;
// Inject the fake PHY information
let mut phy_container = PhyContainer::new(fake_mac_roles);
let _ = phy_container.client_ifaces.insert(present_iface_id, fake_security_support());
let _ = phy_container.client_ifaces.insert(removed_iface_id, fake_security_support());
let _ = 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_key(&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.dev_svc_proxy, test_values.monitor_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.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Client];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
phy_manager.client_connections_enabled = true;
// 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_common::WlanMacRole::Client];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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();
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
// 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.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let mut phy_container = PhyContainer::new(fake_mac_roles);
let _ = phy_container.ap_ifaces.insert(fake_iface_id);
let _ = 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 WPA3 capable client iface is requested and the
/// only PHY that is present has a client iface that doesn't support WPA3 and has an AP iface
/// present. The expectation is that the PhyManager returns None.
#[run_singlethreaded(test)]
async fn get_wpa3_client_no_compatible_client_phys() {
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let mut phy_container = PhyContainer::new(fake_mac_roles);
let _ = phy_container.ap_ifaces.insert(1);
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Create a PhyContainer that has a client iface but no WPA3 support.
let fake_phy_id_client = 2;
let fake_mac_roles_client = vec![fidl_common::WlanMacRole::Client];
let mut phy_container_client = PhyContainer::new(fake_mac_roles_client);
let _ = phy_container_client.client_ifaces.insert(2, fake_security_support());
let _ = phy_manager.phys.insert(fake_phy_id_client, phy_container_client);
// Retrieve the client ID
let client = phy_manager.get_wpa3_capable_client();
assert_eq!(client, None);
}
/// Tests the response of the PhyManager when a wpa3 capable client iface is requested and
/// a matching client iface is present. The expectation is that the PhyManager should reply
/// with the iface ID of the client iface.
#[test_case(true, true, false)]
#[test_case(true, false, true)]
#[test_case(true, true, true)]
#[fuchsia::test(add_test_attr = false)]
fn get_configured_wpa3_client(
mfp_supported: bool,
sae_driver_handler_supported: bool,
sae_sme_handler_supported: bool,
) {
let mut security_support = fake_security_support();
security_support.mfp.supported = mfp_supported;
security_support.sae.driver_handler_supported = sae_driver_handler_supported;
security_support.sae.sme_handler_supported = sae_sme_handler_supported;
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Create an initial PhyContainer with WPA3 support to be inserted into the test
// PhyManager
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
let mut phy_container = PhyContainer::new(fake_mac_roles);
// Insert the fake iface
let fake_iface_id = 1;
let _ = phy_container.client_ifaces.insert(fake_iface_id, security_support);
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Retrieve the client ID
let client = phy_manager.get_wpa3_capable_client();
assert_eq!(client.unwrap(), fake_iface_id)
}
/// Tests that PhyManager will not return a client interface when client connections are not
/// enabled.
#[fuchsia::test]
fn get_client_while_stopped() {
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
// Create a new PhyManager. On construction, client connections are disabled.
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
assert!(!phy_manager.client_connections_enabled);
// Add a PHY with a lingering client interface.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
let mut phy_container = PhyContainer::new(fake_mac_roles);
let _ = phy_container.client_ifaces.insert(1, fake_security_support());
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Try to get a client interface. No interface should be returned since client connections
// are disabled.
assert_eq!(phy_manager.get_client(), 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.
#[fuchsia::test]
fn destroy_all_client_ifaces() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Client];
let phy_container = PhyContainer::new(fake_mac_roles);
{
let _ = 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();
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
// 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.monitor_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_key(&fake_iface_id));
// Verify that the client_connections_enabled has been set to false.
assert!(!phy_manager.client_connections_enabled);
}
/// 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.
#[fuchsia::test]
fn destroy_all_client_ifaces_no_clients() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let phy_container = PhyContainer::new(fake_mac_roles);
// Insert the fake AP iface and then stop clients
{
let _ = 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();
let _ = 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.
#[fuchsia::test]
fn get_ap_no_phys() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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.
#[fuchsia::test]
fn get_unconfigured_ap() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let phy_container = PhyContainer::new(fake_mac_roles.clone());
let _ = 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.monitor_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.
#[fuchsia::test]
fn get_configured_ap() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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();
let _ = 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.
#[fuchsia::test]
fn get_ap_no_compatible_phys() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Client];
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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.
#[fuchsia::test]
fn stop_valid_ap_iface() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
{
let phy_container = PhyContainer::new(fake_mac_roles.clone());
let _ = 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();
let _ = 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.monitor_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.
#[fuchsia::test]
fn stop_invalid_ap_iface() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
{
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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();
let _ = 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.
#[fuchsia::test]
fn stop_all_ap_ifaces() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
{
let phy_container = PhyContainer::new(fake_mac_roles.clone());
let _ = 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();
let _ = phy_container.ap_ifaces.insert(0);
let _ = 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.monitor_stream, ZX_OK);
assert!(exec.run_until_stalled(&mut destroy_ap_iface_future).is_pending());
send_destroy_iface_response(&mut exec, &mut test_values.monitor_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
#[fuchsia::test]
fn stop_all_ap_ifaces_with_client() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Client];
{
let phy_container = PhyContainer::new(fake_mac_roles);
let _ = 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();
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
// 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_key(&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.
#[fuchsia::test]
fn test_suggest_ap_mac() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Ap];
let phy_container = PhyContainer::new(fake_mac_roles.clone());
let _ = 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();
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
// 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.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::CreateIface {
req,
responder,
}
))) => {
let requested_mac = MacAddress::from_bytes(&req.sta_addr).unwrap();
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(_));
}
#[fuchsia::test]
fn test_suggested_mac_does_not_apply_to_client() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_common::WlanMacRole::Client];
let phy_container = PhyContainer::new(fake_mac_roles.clone());
let _ = 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(CreateClientIfacesReason::StartClientConnections);
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.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::CreateIface {
req,
responder,
}
))) => {
assert_eq!(req.sta_addr, ieee80211::NULL_MAC_ADDR);
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");
}
);
// Expect an iface security support query, and send back a response
assert_variant!(exec.run_until_stalled(&mut start_client_future), Poll::Pending);
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
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.dev_svc_proxy, test_values.monitor_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.
#[fuchsia::test]
fn get_phy_ids_single_phy() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(vec![]),
);
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.
#[fuchsia::test]
fn get_phy_ids_two_phys() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_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_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(vec![]),
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
{
let add_phy_fut = phy_manager.add_phy(2);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(vec![]),
);
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.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
assert_data_tree!(test_values.inspector, root: {
phy_manager: {
phy_add_fail_count: 0u64,
},
});
phy_manager.log_phy_add_failure();
assert_data_tree!(test_values.inspector, root: {
phy_manager: {
phy_add_fail_count: 1u64,
},
});
}
/// Tests the initialization of the country code and the ability of the PhyManager to cache a
/// country code update.
#[fuchsia::test]
fn test_set_country_code() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Insert a couple fake PHYs.
let _ = phy_manager.phys.insert(
0,
PhyContainer {
supported_mac_roles: HashSet::new(),
client_ifaces: HashMap::new(),
ap_ifaces: HashSet::new(),
},
);
let _ = phy_manager.phys.insert(
1,
PhyContainer {
supported_mac_roles: HashSet::new(),
client_ifaces: HashMap::new(),
ap_ifaces: HashSet::new(),
},
);
// Initially the country code should be unset.
assert!(phy_manager.saved_country_code.is_none());
// Apply a country code and ensure that it is propagated to the device service.
{
let set_country_fut = phy_manager.set_country_code(Some([0, 1]));
pin_mut!(set_country_fut);
// Ensure that both PHYs have their country codes set.
for _ in 0..2 {
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::SetCountry {
req: fidl_service::SetCountryRequest {
phy_id: _,
alpha2: [0, 1],
},
responder,
}
))) => {
responder.send(ZX_OK).expect("sending fake set country response");
}
);
}
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
}
assert_eq!(phy_manager.saved_country_code, Some([0, 1]));
// Unset the country code and ensure that the clear country code message is sent to the
// device service.
{
let set_country_fut = phy_manager.set_country_code(None);
pin_mut!(set_country_fut);
// Ensure that both PHYs have their country codes cleared.
for _ in 0..2 {
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::ClearCountry {
req: fidl_service::ClearCountryRequest {
phy_id: _,
},
responder,
}
))) => {
responder.send(ZX_OK).expect("sending fake clear country response");
}
);
}
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Ready(Ok(())));
}
assert_eq!(phy_manager.saved_country_code, None);
}
// Tests the case where setting the country code is unsuccessful.
#[fuchsia::test]
fn test_setting_country_code_fails() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Insert a fake PHY.
let _ = phy_manager.phys.insert(
0,
PhyContainer {
supported_mac_roles: HashSet::new(),
client_ifaces: HashMap::new(),
ap_ifaces: HashSet::new(),
},
);
// Initially the country code should be unset.
assert!(phy_manager.saved_country_code.is_none());
// Apply a country code and ensure that it is propagated to the device service.
{
let set_country_fut = phy_manager.set_country_code(Some([0, 1]));
pin_mut!(set_country_fut);
assert_variant!(exec.run_until_stalled(&mut set_country_fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::SetCountry {
req: fidl_service::SetCountryRequest {
phy_id: 0,
alpha2: [0, 1],
},
responder,
}
))) => {
// Send back a failure.
responder
.send(fuchsia_zircon::sys::ZX_ERR_NOT_SUPPORTED)
.expect("sending fake set country response");
}
);
assert_variant!(
exec.run_until_stalled(&mut set_country_fut),
Poll::Ready(Err(PhyManagerError::PhySetCountryFailure))
);
}
assert_eq!(phy_manager.saved_country_code, Some([0, 1]));
}
/// Tests the case where multiple client interfaces need to be recovered.
#[fuchsia::test]
fn test_recover_client_interfaces_succeeds() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
// Make it look like client connections have been enabled.
phy_manager.client_connections_enabled = true;
// Create four fake PHY entries. For the sake of this test, each PHY will eventually
// receive and interface ID equal to its PHY ID.
for phy_id in 0..4 {
let fake_mac_roles = fake_mac_roles.clone();
let _ = phy_manager.phys.insert(phy_id, PhyContainer::new(fake_mac_roles.clone()));
// Give the 0th and 2nd PHYs have client interfaces.
if phy_id % 2 == 0 {
let phy_container = phy_manager.phys.get_mut(&phy_id).expect("missing PHY");
let _ = phy_container.client_ifaces.insert(phy_id, fake_security_support());
}
}
// There are now two PHYs with client interfaces and two without. This looks like two
// interfaces have undergone recovery. Run recover_client_ifaces and ensure that the two
// PHYs that are missing client interfaces have interfaces created for them.
{
let recovery_fut =
phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
pin_mut!(recovery_fut);
assert_variant!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
loop {
// The recovery future will only stall out when either
// 1. It needs to create a client interface for a PHY that does not have one.
// 2. The futures completes and has recovered all possible interfaces.
match exec.run_until_stalled(&mut recovery_fut) {
Poll::Pending => {}
Poll::Ready(result) => {
let iface_ids = result.expect("recovery failed unexpectedly");
assert!(iface_ids.contains(&1));
assert!(iface_ids.contains(&3));
break;
}
}
// Make sure that the stalled future has made a FIDL request to create a client
// interface. Send back a response assigning an interface ID equal to the PHY ID.
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::CreateIface {
req,
responder,
}
))) => {
let mut response =
fidl_service::CreateIfaceResponse { iface_id: req.phy_id };
let response = Some(&mut response);
responder.send(ZX_OK, response).expect("sending fake iface id");
assert_variant!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
});
}
}
// Make sure all of the PHYs have interface IDs and that the IDs match the PHY IDs,
// indicating that they were assigned correctly.
for phy_id in phy_manager.phys.keys() {
assert_eq!(phy_manager.phys[phy_id].client_ifaces.len(), 1);
assert!(phy_manager.phys[phy_id].client_ifaces.contains_key(phy_id));
}
}
/// Tests the case where a client interface needs to be recovered and recovery fails.
#[fuchsia::test]
fn test_recover_client_interfaces_fails() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
// Make it look like client connections have been enabled.
phy_manager.client_connections_enabled = true;
// For this test, use three PHYs (0, 1, and 2). Let recovery fail for PHYs 0 and 2 and
// succeed for PHY 1. Verify that a create interface request is sent for each PHY and at
// the end, verify that only one recovered interface is listed and that PHY 1 has been
// assigned that interface.
for phy_id in 0..3 {
let _ = phy_manager.phys.insert(phy_id, PhyContainer::new(fake_mac_roles.clone()));
}
// Run recovery.
{
let recovery_fut =
phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
pin_mut!(recovery_fut);
assert_variant!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
loop {
match exec.run_until_stalled(&mut recovery_fut) {
Poll::Pending => {}
Poll::Ready(result) => {
let iface_ids = assert_variant!(result, Err((iface_ids, _)) => iface_ids);
assert_eq!(iface_ids, vec![1]);
break;
}
}
// Keep track of the iface ID if created to expect an iface query if required.
let mut created_iface_id = None;
// Make sure that the stalled future has made a FIDL request to create a client
// interface. Send back a response assigning an interface ID equal to the PHY ID.
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::CreateIface {
req,
responder,
}
))) => {
let iface_id = req.phy_id;
let mut response =
fidl_service::CreateIfaceResponse { iface_id };
let response = Some(&mut response);
// As noted above, let the requests for 0 and 2 "fail" and let the request
// for PHY 1 succeed.
let result_code = match req.phy_id {
1 => {
created_iface_id = Some(iface_id);
ZX_OK
},
_ => ZX_ERR_NOT_FOUND,
};
responder.send(result_code, response).expect("sending fake iface id");
}
);
// If creating the iface succeeded, respond to the security support query.
if let Some(_iface_id) = created_iface_id {
assert_variant!(exec.run_until_stalled(&mut recovery_fut), Poll::Pending);
send_security_support(
&mut exec,
&mut test_values.dev_svc_stream,
Some(&mut fake_security_support()),
);
}
}
}
// Make sure PHYs 0 and 2 do not have interfaces and that PHY 1 does.
for phy_id in phy_manager.phys.keys() {
match phy_id {
1 => {
assert_eq!(phy_manager.phys[phy_id].client_ifaces.len(), 1);
assert!(phy_manager.phys[phy_id].client_ifaces.contains_key(phy_id));
}
_ => assert!(phy_manager.phys[phy_id].client_ifaces.is_empty()),
}
}
}
/// Tests the case where a PHY is client-capable, but client connections are disabled and a
/// caller requests attempts to recover client interfaces.
#[fuchsia::test]
fn test_recover_client_interfaces_while_disabled() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Create a fake PHY entry without client interfaces. Note that client connections have
// not been set to enabled.
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
let _ = phy_manager.phys.insert(0, PhyContainer::new(fake_mac_roles));
// Run recovery and ensure that it completes immediately and does not recover any
// interfaces.
{
let recovery_fut =
phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
pin_mut!(recovery_fut);
assert_variant!(
exec.run_until_stalled(&mut recovery_fut),
Poll::Ready(Ok(recovered_ifaces)) => {
assert!(recovered_ifaces.is_empty());
}
);
}
// Verify that there are no client interfaces.
for (_, phy_container) in phy_manager.phys {
assert!(phy_container.client_ifaces.is_empty());
}
}
/// Tests the case where client connections are re-started following an unsuccessful stop
/// client connections request.
#[fuchsia::test]
fn test_start_after_unsuccessful_stop() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Verify that client connections are initially stopped.
assert!(!phy_manager.client_connections_enabled);
// Create a PHY with a lingering client interface.
let fake_phy_id = 1;
let fake_mac_roles = vec![fidl_common::WlanMacRole::Client];
let mut phy_container = PhyContainer::new(fake_mac_roles);
// Insert the fake iface
let fake_iface_id = 1;
let _ = phy_container.client_ifaces.insert(fake_iface_id, fake_security_support());
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Try creating all client interfaces due to recovery and ensure that no interfaces are
// returned.
{
let start_client_future =
phy_manager.create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces);
pin_mut!(start_client_future);
assert_variant!(
exec.run_until_stalled(&mut start_client_future),
Poll::Ready(Ok(vec)) => {
assert!(vec.is_empty())
});
}
// Create all client interfaces with the reason set to StartClientConnections and verify
// that the existing interface is returned.
{
let start_client_future = phy_manager
.create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections);
pin_mut!(start_client_future);
assert_variant!(
exec.run_until_stalled(&mut start_client_future),
Poll::Ready(Ok(vec)) => {
assert_eq!(vec, vec![1])
});
}
}
#[test_case(true, false)]
#[test_case(false, true)]
#[test_case(true, true)]
#[fuchsia::test(add_test_attr = false)]
fn has_wpa3_client_iface(driver_handler_supported: bool, sme_handler_supported: bool) {
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
// Create a phy with the security features that indicate WPA3 support.
let mut security_support = fake_security_support();
security_support.mfp.supported = true;
security_support.sae.driver_handler_supported = driver_handler_supported;
security_support.sae.sme_handler_supported = sme_handler_supported;
let fake_phy_id = 0;
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let mut phy_container = PhyContainer::new(vec![]);
let _ = phy_container.client_ifaces.insert(0, security_support);
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Check that has_wpa3_client_iface recognizes that WPA3 is supported
assert_eq!(phy_manager.has_wpa3_client_iface(), true);
// Add another phy that does not support WPA3.
let fake_phy_id = 1;
let phy_container = PhyContainer::new(vec![]);
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
// Phy manager has at least one WPA3 capable iface, so has_wpa3_iface should still be true.
assert_eq!(phy_manager.has_wpa3_client_iface(), true);
}
#[fuchsia::test]
fn has_no_wpa3_capable_client_iface() {
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
// Create a phy without security features that indicate WPA3 support.
let fake_phy_id = 0;
let mut phy_container = PhyContainer::new(vec![]);
let _ = phy_container.client_ifaces.insert(0, fake_security_support());
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
let _ = phy_manager.phys.insert(fake_phy_id, phy_container);
assert_eq!(phy_manager.has_wpa3_client_iface(), false);
}
/// Tests reporting of client connections status when client connections are enabled.
#[fuchsia::test]
fn test_client_connections_enabled_when_enabled() {
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
phy_manager.client_connections_enabled = true;
assert!(phy_manager.client_connections_enabled());
}
/// Tests reporting of client connections status when client connections are disabled.
#[fuchsia::test]
fn test_client_connections_enabled_when_disabled() {
let _exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
phy_manager.client_connections_enabled = false;
assert!(!phy_manager.client_connections_enabled());
}
/// Tests the case where setting low power state succeeds.
#[fuchsia::test]
fn test_succeed_in_setting_power_state() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModePerformance);
// Add a couple of PHYs.
let mut phy_ids = HashSet::<u16>::new();
let _ = phy_ids.insert(0);
let _ = phy_ids.insert(1);
for id in phy_ids.iter() {
let phy_container = PhyContainer::new(vec![]);
let _ = phy_manager.phys.insert(*id, phy_container);
}
{
// Set low power state on the PHYs.
let fut = phy_manager.set_power_state(fidl_common::PowerSaveType::PsModeLowPower);
// The future should run until it stalls out requesting that one of the PHYs set its low
// power mode.
pin_mut!(fut);
for _ in 0..phy_ids.len() {
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::SetPsMode {
req: fidl_service::SetPsModeRequest { phy_id, ps_mode },
responder,
}
))) => {
assert!(phy_ids.remove(&phy_id));
assert_eq!(ps_mode, fidl_common::PowerSaveType::PsModeLowPower);
responder.send(ZX_OK).expect("sending fake set PS mode response");
}
)
}
assert_variant!(
exec.run_until_stalled(&mut fut),
Poll::Ready(Ok(fuchsia_zircon::Status::OK))
);
}
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModeLowPower);
}
/// Tests the case where setting low power state fails.
#[fuchsia::test]
fn test_fail_to_set_power_state() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModePerformance);
// Add a couple of PHYs.
let mut phy_ids = HashSet::<u16>::new();
let _ = phy_ids.insert(0);
let _ = phy_ids.insert(1);
for id in phy_ids.iter() {
let phy_container = PhyContainer::new(vec![]);
let _ = phy_manager.phys.insert(*id, phy_container);
}
{
// Set low power state on the PHYs.
let fut = phy_manager.set_power_state(fidl_common::PowerSaveType::PsModeLowPower);
// The future should run until it stalls out requesting that one of the PHYs set its low
// power mode.
pin_mut!(fut);
for _ in 0..phy_ids.len() {
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::SetPsMode {
req: fidl_service::SetPsModeRequest { phy_id, ps_mode },
responder,
}
))) => {
assert!(phy_ids.remove(&phy_id));
assert_eq!(ps_mode, fidl_common::PowerSaveType::PsModeLowPower);
// Send back a failure for one of the PHYs
if phy_id == 0 {
responder.send(ZX_OK).expect("sending fake set PS mode response");
} else {
responder
.send(ZX_ERR_NOT_FOUND)
.expect("sending negativefake set PS mode response");
}
}
)
}
// An error should be reported due to the failure to set the power mode on one of the
// PHYs.
assert_variant!(
exec.run_until_stalled(&mut fut),
Poll::Ready(Ok(fuchsia_zircon::Status::NOT_FOUND))
);
}
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModeLowPower);
}
/// Tests the case where the request cannot be made to configure low power mode for a PHY.
#[fuchsia::test]
fn test_fail_to_request_low_power_mode() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
// Drop the receiving end of the device monitor channel.
drop(test_values.monitor_stream);
// Add a couple of PHYs.
let mut phy_ids = HashSet::<u16>::new();
let _ = phy_ids.insert(0);
let _ = phy_ids.insert(1);
for id in phy_ids.iter() {
let phy_container = PhyContainer::new(vec![]);
let _ = phy_manager.phys.insert(*id, phy_container);
}
// Set low power state on the PHYs.
let fut = phy_manager.set_power_state(fidl_common::PowerSaveType::PsModePerformance);
// The future should run until it stalls out requesting that one of the PHYs set its low
// power mode.
pin_mut!(fut);
assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_)));
}
/// Tests the case where a PHY is added after the low power state has been enabled.
#[fuchsia::test]
fn test_add_phy_after_low_power_enabled() {
let mut exec = TestExecutor::new().expect("failed to create an executor");
let mut test_values = test_setup();
let mut phy_manager =
PhyManager::new(test_values.dev_svc_proxy, test_values.monitor_proxy, test_values.node);
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModePerformance);
// Enable low power mode which should complete immediately.
{
let fut = phy_manager.set_power_state(fidl_common::PowerSaveType::PsModeBalanced);
pin_mut!(fut);
assert_variant!(
exec.run_until_stalled(&mut fut),
Poll::Ready(Ok(fuchsia_zircon::Status::OK))
);
}
assert_eq!(phy_manager.power_state, fidl_common::PowerSaveType::PsModeBalanced);
// Add a new PHY and ensure that the low power mode is set
{
let add_phy_fut = phy_manager.add_phy(0);
pin_mut!(add_phy_fut);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_pending());
send_get_supported_mac_roles_response(
&mut exec,
&mut test_values.monitor_stream,
Ok(Vec::new()),
);
// There should be a stall as the low power mode is requested.
assert_variant!(exec.run_until_stalled(&mut add_phy_fut), Poll::Pending);
assert_variant!(
exec.run_until_stalled(&mut test_values.monitor_stream.next()),
Poll::Ready(Some(Ok(
fidl_service::DeviceMonitorRequest::SetPsMode {
req: fidl_service::SetPsModeRequest { phy_id, ps_mode },
responder,
}
))) => {
assert_eq!(ps_mode, fidl_common::PowerSaveType::PsModeBalanced);
// Send back a failure for one of the PHYs
if phy_id == 0 {
responder.send(ZX_OK).expect("sending fake set PS mode response");
} else {
responder
.send(ZX_ERR_NOT_FOUND)
.expect("sending negativefake set PS mode response");
}
}
);
assert!(exec.run_until_stalled(&mut add_phy_fut).is_ready());
}
}
}