| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| access_point::{state_machine as ap_fsm, state_machine::AccessPointApi, types as ap_types}, |
| client::{ |
| connection_selection::ConnectionSelectionRequester, |
| roaming::local_roam_manager::LocalRoamManagerApi, state_machine as client_fsm, |
| types as client_types, |
| }, |
| config_management::SavedNetworksManagerApi, |
| mode_management::{ |
| iface_manager_api::{ConnectAttemptRequest, SmeForScan}, |
| iface_manager_types::*, |
| phy_manager::{CreateClientIfacesReason, PhyManagerApi}, |
| recovery, Defect, |
| }, |
| telemetry::{TelemetryEvent, TelemetrySender}, |
| util::{atomic_oneshot_stream, future_with_metadata, listener}, |
| }, |
| anyhow::{format_err, Error}, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_wlan_common as fidl_common, fuchsia_async as fasync, fuchsia_zircon as zx, |
| futures::{ |
| channel::{mpsc, oneshot}, |
| future::{ready, BoxFuture}, |
| lock::Mutex, |
| select, |
| stream::FuturesUnordered, |
| FutureExt, StreamExt, |
| }, |
| std::{convert::Infallible, fmt::Debug, sync::Arc, unimplemented}, |
| tracing::{debug, error, info, warn}, |
| }; |
| |
| // Maximum allowed interval between scans when attempting to reconnect client interfaces. This |
| // value is taken from legacy state machine. |
| const MAX_AUTO_CONNECT_RETRY_SECONDS: i64 = 10; |
| |
| #[cfg_attr(test, derive(Debug))] |
| enum ConnectionSelectionResponse { |
| ConnectRequest { |
| candidate: Option<client_types::ScannedCandidate>, |
| request: ConnectAttemptRequest, |
| }, |
| Autoconnect(Option<client_types::ScannedCandidate>), |
| } |
| |
| /// Wraps around vital information associated with a WLAN client interface. In all cases, a client |
| /// interface will have an ID and a ClientSmeProxy to make requests of the interface. If a client |
| /// is configured to connect to a WLAN network, it will store the network configuration information |
| /// associated with that network as well as a communcation channel to make requests of the state |
| /// machine that maintains client connectivity. |
| struct ClientIfaceContainer { |
| iface_id: u16, |
| sme_proxy: fidl_fuchsia_wlan_sme::ClientSmeProxy, |
| config: Option<ap_types::NetworkIdentifier>, |
| client_state_machine: Option<Box<dyn client_fsm::ClientApi + Send>>, |
| security_support: fidl_common::SecuritySupport, |
| /// The time of the last scan for roaming or new connection on this iface. |
| last_roam_time: fasync::Time, |
| } |
| |
| pub(crate) struct ApIfaceContainer { |
| pub iface_id: u16, |
| pub config: Option<ap_fsm::ApConfig>, |
| pub ap_state_machine: Box<dyn AccessPointApi + Send + Sync>, |
| enabled_time: Option<zx::Time>, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub struct StateMachineMetadata { |
| pub iface_id: u16, |
| pub role: fidl_fuchsia_wlan_common::WlanMacRole, |
| } |
| |
| async fn create_client_state_machine( |
| iface_id: u16, |
| dev_monitor_proxy: &mut fidl_fuchsia_wlan_device_service::DeviceMonitorProxy, |
| client_update_sender: listener::ClientListenerMessageSender, |
| saved_networks: Arc<dyn SavedNetworksManagerApi>, |
| connect_selection: Option<client_types::ConnectSelection>, |
| telemetry_sender: TelemetrySender, |
| defect_sender: mpsc::UnboundedSender<Defect>, |
| roam_manager: Arc<Mutex<dyn LocalRoamManagerApi>>, |
| ) -> Result< |
| ( |
| Box<dyn client_fsm::ClientApi + Send>, |
| future_with_metadata::FutureWithMetadata<(), StateMachineMetadata>, |
| ), |
| Error, |
| > { |
| if connect_selection.is_some() { |
| telemetry_sender.send(TelemetryEvent::StartEstablishConnection { reset_start_time: false }); |
| } |
| |
| // Create a client state machine for the newly discovered interface. |
| let (sender, receiver) = mpsc::channel(1); |
| let new_client = client_fsm::Client::new(sender); |
| |
| // Create a new client SME proxy. This is required because each new client state machine will |
| // take the event stream from the SME proxy. A subsequent attempt to take the event stream |
| // would cause wlancfg to panic. |
| let (sme_proxy, remote) = create_proxy()?; |
| dev_monitor_proxy.get_client_sme(iface_id, remote).await?.map_err(zx::Status::from_raw)?; |
| let event_stream = sme_proxy.take_event_stream(); |
| |
| let fut = client_fsm::serve( |
| iface_id, |
| sme_proxy, |
| event_stream, |
| receiver, |
| client_update_sender, |
| saved_networks, |
| connect_selection, |
| telemetry_sender, |
| defect_sender, |
| roam_manager, |
| ); |
| |
| let metadata = |
| StateMachineMetadata { iface_id, role: fidl_fuchsia_wlan_common::WlanMacRole::Client }; |
| let fut = future_with_metadata::FutureWithMetadata::new(metadata, Box::pin(fut)); |
| |
| Ok((Box::new(new_client), fut)) |
| } |
| |
| /// Accounts for WLAN interfaces that are present and utilizes them to service requests that are |
| /// made of the policy layer. |
| pub(crate) struct IfaceManagerService { |
| phy_manager: Arc<Mutex<dyn PhyManagerApi + Send>>, |
| client_update_sender: listener::ClientListenerMessageSender, |
| ap_update_sender: listener::ApListenerMessageSender, |
| dev_monitor_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy, |
| clients: Vec<ClientIfaceContainer>, |
| aps: Vec<ApIfaceContainer>, |
| saved_networks: Arc<dyn SavedNetworksManagerApi>, |
| connection_selection_requester: ConnectionSelectionRequester, |
| local_roam_manager: Arc<Mutex<dyn LocalRoamManagerApi>>, |
| fsm_futures: |
| FuturesUnordered<future_with_metadata::FutureWithMetadata<(), StateMachineMetadata>>, |
| connection_selection_futures: |
| FuturesUnordered<BoxFuture<'static, Result<ConnectionSelectionResponse, anyhow::Error>>>, |
| telemetry_sender: TelemetrySender, |
| // A sender to be cloned for state machines to report defects to the IfaceManager. |
| defect_sender: mpsc::UnboundedSender<Defect>, |
| } |
| |
| impl IfaceManagerService { |
| pub fn new( |
| phy_manager: Arc<Mutex<dyn PhyManagerApi + Send>>, |
| client_update_sender: listener::ClientListenerMessageSender, |
| ap_update_sender: listener::ApListenerMessageSender, |
| dev_monitor_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy, |
| saved_networks: Arc<dyn SavedNetworksManagerApi>, |
| connection_selection_requester: ConnectionSelectionRequester, |
| local_roam_manager: Arc<Mutex<dyn LocalRoamManagerApi>>, |
| telemetry_sender: TelemetrySender, |
| defect_sender: mpsc::UnboundedSender<Defect>, |
| ) -> Self { |
| IfaceManagerService { |
| phy_manager: phy_manager.clone(), |
| client_update_sender, |
| ap_update_sender, |
| dev_monitor_proxy, |
| clients: Vec::new(), |
| aps: Vec::new(), |
| saved_networks, |
| connection_selection_requester, |
| local_roam_manager, |
| fsm_futures: FuturesUnordered::new(), |
| connection_selection_futures: FuturesUnordered::new(), |
| telemetry_sender, |
| defect_sender, |
| } |
| } |
| |
| /// Checks for any known, unconfigured clients. If one exists, its ClientIfaceContainer is |
| /// returned to the caller. |
| /// |
| /// If all ifaces are configured, asks the PhyManager for an iface ID. If the returned iface |
| /// ID is an already-configured iface, that iface is removed from the configured clients list |
| /// and returned to the caller. |
| /// |
| /// If it is not, a new ClientIfaceContainer is created from the returned iface ID and returned |
| /// to the caller. |
| async fn get_client(&mut self, iface_id: Option<u16>) -> Result<ClientIfaceContainer, Error> { |
| let iface_id = match iface_id { |
| Some(iface_id) => iface_id, |
| None => { |
| // If no iface_id is specified and if there there are any unconfigured client |
| // ifaces, use the first available unconfigured iface. |
| if let Some(removal_index) = self |
| .clients |
| .iter() |
| .position(|client_container| client_container.config.is_none()) |
| { |
| return Ok(self.clients.remove(removal_index)); |
| } |
| |
| // If all of the known client ifaces are configured, ask the PhyManager for a |
| // client iface. |
| match self.phy_manager.lock().await.get_client() { |
| None => return Err(format_err!("no client ifaces available")), |
| Some(id) => id, |
| } |
| } |
| }; |
| self.setup_client_container(iface_id).await |
| } |
| |
| async fn get_wpa3_capable_client( |
| &mut self, |
| iface_id: Option<u16>, |
| ) -> Result<ClientIfaceContainer, Error> { |
| let iface_id = match iface_id { |
| Some(iface_id) => iface_id, |
| None => { |
| // If no iface_id is specified and if there there are any unconfigured client |
| // ifaces, use the first available unconfigured iface. |
| if let Some(removal_index) = self.clients.iter().position(|client_container| { |
| client_container.config.is_none() |
| && wpa3_supported(client_container.security_support) |
| }) { |
| return Ok(self.clients.remove(removal_index)); |
| } |
| |
| // If all of the known client ifaces are configured, ask the PhyManager for a |
| // client iface. |
| match self.phy_manager.lock().await.get_wpa3_capable_client() { |
| None => return Err(format_err!("no client ifaces available")), |
| Some(id) => id, |
| } |
| } |
| }; |
| |
| self.setup_client_container(iface_id).await |
| } |
| |
| /// Remove the client iface from the list of configured ifaces if it is there, and create a |
| /// ClientIfaceContainer for it if it is needed. |
| async fn setup_client_container( |
| &mut self, |
| iface_id: u16, |
| ) -> Result<ClientIfaceContainer, Error> { |
| // See if the selected iface ID is among the configured clients. |
| if let Some(removal_index) = |
| self.clients.iter().position(|client_container| client_container.iface_id == iface_id) |
| { |
| return Ok(self.clients.remove(removal_index)); |
| } |
| |
| // If the iface ID is not among configured clients, create a new ClientIfaceContainer for |
| // the iface ID. |
| let (sme_proxy, sme_server) = create_proxy()?; |
| self.dev_monitor_proxy |
| .get_client_sme(iface_id, sme_server) |
| .await? |
| .map_err(zx::Status::from_raw)?; |
| let (features_proxy, features_server) = create_proxy()?; |
| self.dev_monitor_proxy.get_feature_support(iface_id, features_server).await?.map_err( |
| |e| format_err!("Error occurred getting iface's features support proxy: {}", e), |
| )?; |
| |
| // Get the security support for this iface. |
| let security_support = |
| features_proxy.query_security_support().await?.map_err(zx::Status::from_raw)?; |
| Ok(ClientIfaceContainer { |
| iface_id, |
| sme_proxy, |
| config: None, |
| client_state_machine: None, |
| security_support, |
| last_roam_time: fasync::Time::now(), |
| }) |
| } |
| |
| /// Queries to PhyManager to determine if there are any interfaces that can be used as AP's. |
| /// |
| /// If the PhyManager indicates that there is an existing interface that should be used for the |
| /// AP request, return the existing AP interface. |
| /// |
| /// If the indicated AP interface has not been used before, spawn a new AP state machine for |
| /// the interface and return the new interface. |
| async fn get_ap(&mut self, iface_id: Option<u16>) -> Result<ApIfaceContainer, Error> { |
| let iface_id = match iface_id { |
| Some(iface_id) => iface_id, |
| None => { |
| // If no iface ID is specified, ask the PhyManager for an AP iface ID. |
| let mut phy_manager = self.phy_manager.lock().await; |
| match phy_manager.create_or_get_ap_iface().await { |
| Ok(Some(iface_id)) => iface_id, |
| Ok(None) => return Err(format_err!("no available PHYs can support AP ifaces")), |
| phy_manager_error => { |
| return Err(format_err!("could not get AP {:?}", phy_manager_error)); |
| } |
| } |
| } |
| }; |
| |
| // Check if this iface ID is already accounted for. |
| if let Some(removal_index) = |
| self.aps.iter().position(|ap_container| ap_container.iface_id == iface_id) |
| { |
| return Ok(self.aps.remove(removal_index)); |
| } |
| |
| // If this iface ID is not yet accounted for, create a new ApIfaceContainer. |
| let (sme_proxy, sme_server) = create_proxy()?; |
| self.dev_monitor_proxy |
| .get_ap_sme(iface_id, sme_server) |
| .await? |
| .map_err(zx::Status::from_raw)?; |
| |
| // Spawn the AP state machine. |
| let (sender, receiver) = mpsc::channel(1); |
| let state_machine = ap_fsm::AccessPoint::new(sender); |
| |
| let event_stream = sme_proxy.take_event_stream(); |
| let state_machine_fut = ap_fsm::serve( |
| iface_id, |
| sme_proxy, |
| event_stream, |
| receiver.fuse(), |
| self.ap_update_sender.clone(), |
| self.telemetry_sender.clone(), |
| self.defect_sender.clone(), |
| ) |
| .boxed(); |
| |
| // Begin running and monitoring the AP state machine future. |
| let metadata = |
| StateMachineMetadata { iface_id, role: fidl_fuchsia_wlan_common::WlanMacRole::Ap }; |
| let fut = future_with_metadata::FutureWithMetadata::new(metadata, state_machine_fut); |
| self.fsm_futures.push(fut); |
| |
| Ok(ApIfaceContainer { |
| iface_id, |
| config: None, |
| ap_state_machine: Box::new(state_machine), |
| enabled_time: None, |
| }) |
| } |
| |
| /// Attempts to stop the AP and then exit the AP state machine. |
| async fn stop_and_exit_ap_state_machine( |
| mut ap_state_machine: Box<dyn AccessPointApi + Send + Sync>, |
| ) -> Result<(), Error> { |
| let (sender, receiver) = oneshot::channel(); |
| ap_state_machine.stop(sender)?; |
| receiver.await?; |
| |
| let (sender, receiver) = oneshot::channel(); |
| ap_state_machine.exit(sender)?; |
| receiver.await?; |
| |
| Ok(()) |
| } |
| |
| fn disconnect( |
| &mut self, |
| network_id: ap_types::NetworkIdentifier, |
| reason: client_types::DisconnectReason, |
| ) -> BoxFuture<'static, Result<(), Error>> { |
| // Cancel any ongoing network selection, since a disconnect makes it invalid. |
| self.connection_selection_futures.clear(); |
| |
| // Find the client interface associated with the given network config and disconnect from |
| // the network. |
| let mut fsm_ack_receiver = None; |
| let mut iface_id = None; |
| |
| // If a client is configured for the specified network, tell the state machine to |
| // disconnect. This will cause the state machine's future to exit so that the monitoring |
| // loop discovers the completed future and attempts to reconnect the interface. |
| for client in self.clients.iter_mut() { |
| if client.config.as_ref() == Some(&network_id) { |
| client.config = None; |
| |
| let (responder, receiver) = oneshot::channel(); |
| match client.client_state_machine.as_mut() { |
| Some(state_machine) => match state_machine.disconnect(reason, responder) { |
| Ok(()) => {} |
| Err(e) => { |
| client.client_state_machine = None; |
| |
| return ready(Err(format_err!("failed to send disconnect: {:?}", e))) |
| .boxed(); |
| } |
| }, |
| None => { |
| return ready(Ok(())).boxed(); |
| } |
| } |
| |
| client.config = None; |
| client.client_state_machine = None; |
| fsm_ack_receiver = Some(receiver); |
| iface_id = Some(client.iface_id); |
| break; |
| } |
| } |
| |
| let receiver = match fsm_ack_receiver { |
| Some(receiver) => receiver, |
| None => return ready(Ok(())).boxed(), |
| }; |
| let iface_id = match iface_id { |
| Some(iface_id) => iface_id, |
| None => return ready(Ok(())).boxed(), |
| }; |
| |
| let fut = async move { |
| match receiver.await { |
| Ok(()) => return Ok(()), |
| error => { |
| Err(format_err!("failed to disconnect client iface {}: {:?}", iface_id, error)) |
| } |
| } |
| }; |
| return fut.boxed(); |
| } |
| |
| async fn handle_connect_request( |
| &mut self, |
| connect_request: ConnectAttemptRequest, |
| ) -> Result<(), Error> { |
| // Check to see if client connections are enabled. |
| { |
| let phy_manager = self.phy_manager.lock().await; |
| if !phy_manager.client_connections_enabled() { |
| return Err(format_err!("client connections are not enabled")); |
| } |
| } |
| |
| // Check if already connected to requested network |
| if self.clients.iter().any(|client| match &client.config { |
| Some(config) => config == &connect_request.network, |
| None => false, |
| }) { |
| info!("Received connect request to already connected network."); |
| return Ok(()); |
| }; |
| |
| self.telemetry_sender |
| .send(TelemetryEvent::StartEstablishConnection { reset_start_time: true }); |
| |
| // Send listener update that connection attempt is starting. |
| let networks = vec![listener::ClientNetworkState { |
| id: connect_request.network.clone(), |
| state: client_types::ConnectionState::Connecting, |
| status: None, |
| }]; |
| |
| let update = listener::ClientStateUpdate { |
| state: client_types::ClientState::ConnectionsEnabled, |
| networks, |
| }; |
| match self |
| .client_update_sender |
| .clone() |
| .unbounded_send(listener::Message::NotifyListeners(update)) |
| { |
| Ok(_) => (), |
| Err(e) => error!("failed to send state update: {:?}", e), |
| }; |
| |
| initiate_connection_selection_for_connect_request(connect_request, self).await |
| } |
| |
| async fn connect(&mut self, selection: client_types::ConnectSelection) -> Result<(), Error> { |
| // Get a ClientIfaceContainer. |
| let mut client_iface = |
| if selection.target.network.security_type == client_types::SecurityType::Wpa3 { |
| self.get_wpa3_capable_client(None).await? |
| } else { |
| self.get_client(None).await? |
| }; |
| // Set the new config on this client |
| client_iface.config = Some(selection.target.network.clone()); |
| |
| // Check if there's an existing state machine we can use |
| match client_iface.client_state_machine.as_mut() { |
| Some(existing_csm) => { |
| existing_csm.connect(selection)?; |
| } |
| None => { |
| // Create the state machine and controller. |
| let (new_client, fut) = create_client_state_machine( |
| client_iface.iface_id, |
| &mut self.dev_monitor_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| Some(selection), |
| self.telemetry_sender.clone(), |
| self.defect_sender.clone(), |
| self.local_roam_manager.clone(), |
| ) |
| .await?; |
| client_iface.client_state_machine = Some(new_client); |
| |
| // Begin running and monitoring the client state machine future. |
| self.fsm_futures.push(fut); |
| } |
| } |
| |
| // Cancel any ongoing attempt to auto connect the previously idle iface. |
| self.connection_selection_futures.clear(); |
| |
| client_iface.last_roam_time = fasync::Time::now(); |
| self.clients.push(client_iface); |
| Ok(()) |
| } |
| |
| fn record_idle_client(&mut self, iface_id: u16) { |
| for client in self.clients.iter_mut() { |
| if client.iface_id == iface_id { |
| // Check if the state machine has exited. If it has not, then another call to |
| // connect has replaced the state machine already and this interface should be left |
| // alone. |
| match client.client_state_machine.as_ref() { |
| Some(state_machine) => { |
| if state_machine.is_alive() { |
| return; |
| } |
| } |
| None => {} |
| } |
| client.config = None; |
| client.client_state_machine = None; |
| return; |
| } |
| } |
| } |
| |
| fn idle_clients(&self) -> Vec<u16> { |
| let mut idle_clients = Vec::new(); |
| |
| for client in self.clients.iter() { |
| if client.config.is_none() { |
| idle_clients.push(client.iface_id); |
| continue; |
| } |
| |
| match client.client_state_machine.as_ref() { |
| Some(state_machine) => { |
| if !state_machine.is_alive() { |
| idle_clients.push(client.iface_id); |
| } |
| } |
| None => idle_clients.push(client.iface_id), |
| } |
| } |
| |
| idle_clients |
| } |
| |
| /// Checks the specified interface to see if there is an active state machine for it. If there |
| /// is, this indicates that a connect request has already reconnected this interface and no |
| /// further action is required. If no state machine exists for the interface, attempts to |
| /// connect the interface to the specified network. |
| async fn attempt_client_reconnect( |
| &mut self, |
| iface_id: u16, |
| connect_selection: client_types::ConnectSelection, |
| ) -> Result<(), Error> { |
| for client in self.clients.iter_mut() { |
| if client.iface_id == iface_id { |
| match client.client_state_machine.as_ref() { |
| None => {} |
| Some(state_machine) => { |
| if state_machine.is_alive() { |
| return Ok(()); |
| } |
| } |
| } |
| |
| // Create the state machine and controller. |
| let (new_client, fut) = create_client_state_machine( |
| client.iface_id, |
| &mut self.dev_monitor_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| Some(connect_selection.clone()), |
| self.telemetry_sender.clone(), |
| self.defect_sender.clone(), |
| self.local_roam_manager.clone(), |
| ) |
| .await?; |
| |
| self.fsm_futures.push(fut); |
| client.config = Some(connect_selection.target.network); |
| client.client_state_machine = Some(new_client); |
| client.last_roam_time = fasync::Time::now(); |
| break; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn handle_added_iface(&mut self, iface_id: u16) -> Result<(), Error> { |
| let iface_info = |
| self.dev_monitor_proxy.query_iface(iface_id).await?.map_err(zx::Status::from_raw)?; |
| |
| match iface_info.role { |
| fidl_fuchsia_wlan_common::WlanMacRole::Client => { |
| let mut client_iface = self.get_client(Some(iface_id)).await?; |
| |
| // If this client has already been recorded and it has a client state machine |
| // running, return success early. |
| if client_iface.client_state_machine.is_some() { |
| self.clients.push(client_iface); |
| return Ok(()); |
| } |
| |
| // Create the state machine and controller. The state machine is setup with no |
| // initial network config. This will cause it to quickly exit, notifying the |
| // monitor loop that the interface needs attention. |
| let (new_client, fut) = create_client_state_machine( |
| client_iface.iface_id, |
| &mut self.dev_monitor_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| None, |
| self.telemetry_sender.clone(), |
| self.defect_sender.clone(), |
| self.local_roam_manager.clone(), |
| ) |
| .await?; |
| |
| // Begin running and monitoring the client state machine future. |
| self.fsm_futures.push(fut); |
| |
| client_iface.client_state_machine = Some(new_client); |
| self.clients.push(client_iface); |
| } |
| fidl_fuchsia_wlan_common::WlanMacRole::Ap => { |
| let ap_iface = self.get_ap(Some(iface_id)).await?; |
| self.aps.push(ap_iface); |
| } |
| fidl_fuchsia_wlan_common::WlanMacRole::Mesh => { |
| // Mesh roles are not currently supported. |
| } |
| fidl_fuchsia_wlan_common::WlanMacRoleUnknown!() => { |
| error!("Unknown WlanMacRole type {:?} on iface {}", iface_info.role, iface_id); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn handle_removed_iface(&mut self, iface_id: u16) { |
| // Delete the reference from the PhyManager. |
| self.phy_manager.lock().await.on_iface_removed(iface_id); |
| |
| // If the interface was deleted, but IfaceManager still has a reference to it, then the |
| // driver has likely performed some low-level recovery or the interface driver has crashed. |
| // In this case, remove the old reference to the interface ID and then create a new |
| // interface of the appropriate type. |
| if let Some(iface_index) = |
| self.clients.iter().position(|client_container| client_container.iface_id == iface_id) |
| { |
| let _ = self.clients.remove(iface_index); |
| let client_ifaces = match self |
| .phy_manager |
| .lock() |
| .await |
| .create_all_client_ifaces(CreateClientIfacesReason::RecoverClientIfaces) |
| .await |
| { |
| Ok(iface_ids) => iface_ids, |
| Err((iface_ids, e)) => { |
| warn!("failed to recover some client interfaces: {:?}", e); |
| iface_ids |
| } |
| }; |
| |
| for iface_id in client_ifaces { |
| match self.get_client(Some(iface_id)).await { |
| Ok(iface) => self.clients.push(iface), |
| Err(e) => { |
| error!("failed to recreate client {}: {:?}", iface_id, e); |
| } |
| }; |
| } |
| } |
| |
| // Check to see if there are any remaining client interfaces. If there are not any, send |
| // listeners a notification indicating that client connections are disabled. |
| if self.clients.is_empty() { |
| let update = listener::ClientStateUpdate { |
| state: fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsDisabled, |
| networks: vec![], |
| }; |
| if let Err(e) = |
| self.client_update_sender.unbounded_send(listener::Message::NotifyListeners(update)) |
| { |
| error!("Failed to notify listeners of lack of client interfaces: {:?}", e) |
| }; |
| } |
| |
| // While client behavior is automated based on saved network configs and available SSIDs |
| // observed in scan results, the AP behavior is largely controlled by API clients. The AP |
| // state machine will send back a failure notification in the event that the interface was |
| // destroyed unexpectedly. For the AP, simply remove the reference to the interface. If |
| // the API client so desires, they may ask the policy layer to start another AP interface. |
| self.aps.retain(|ap_container| ap_container.iface_id != iface_id); |
| } |
| |
| async fn get_sme_proxy_for_scan(&mut self) -> Result<SmeForScan, Error> { |
| let client_iface = self.get_client(None).await?; |
| let proxy = client_iface.sme_proxy.clone(); |
| let iface_id = client_iface.iface_id; |
| self.clients.push(client_iface); |
| Ok(SmeForScan::new(proxy, iface_id, self.defect_sender.clone())) |
| } |
| |
| fn stop_client_connections( |
| &mut self, |
| reason: client_types::DisconnectReason, |
| ) -> BoxFuture<'static, Result<(), Error>> { |
| self.telemetry_sender.send(TelemetryEvent::ClearEstablishConnectionStartTime); |
| |
| let client_ifaces: Vec<ClientIfaceContainer> = self.clients.drain(..).collect(); |
| let phy_manager = self.phy_manager.clone(); |
| let update_sender = self.client_update_sender.clone(); |
| |
| let fut = async move { |
| // Disconnect and discard all of the configured client ifaces. |
| for mut client_iface in client_ifaces { |
| let client = match client_iface.client_state_machine.as_mut() { |
| Some(state_machine) => state_machine, |
| None => continue, |
| }; |
| let (responder, receiver) = oneshot::channel(); |
| match client.disconnect(reason, responder) { |
| Ok(()) => {} |
| Err(e) => error!("failed to issue disconnect: {:?}", e), |
| } |
| match receiver.await { |
| Ok(()) => {} |
| Err(e) => error!("failed to disconnect: {:?}", e), |
| } |
| } |
| |
| // Signal to the update listener that client connections have been disabled. |
| let update = listener::ClientStateUpdate { |
| state: fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsDisabled, |
| networks: vec![], |
| }; |
| if let Err(e) = update_sender.unbounded_send(listener::Message::NotifyListeners(update)) |
| { |
| error!("Failed to send state update: {:?}", e) |
| }; |
| |
| // Tell the PhyManager to stop all client connections. |
| let mut phy_manager = phy_manager.lock().await; |
| phy_manager.destroy_all_client_ifaces().await?; |
| |
| Ok(()) |
| }; |
| |
| fut.boxed() |
| } |
| |
| async fn start_client_connections(&mut self) -> Result<(), Error> { |
| let mut phy_manager = self.phy_manager.lock().await; |
| let client_iface_ids = match phy_manager |
| .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections) |
| .await |
| { |
| Ok(client_iface_ids) => client_iface_ids, |
| Err((_, phy_manager_error)) => { |
| return Err(format_err!( |
| "could not start client connection {:?}", |
| phy_manager_error |
| )); |
| } |
| }; |
| |
| // Resume client interfaces. |
| drop(phy_manager); |
| for iface_id in client_iface_ids.clone() { |
| if let Err(e) = self.handle_added_iface(iface_id).await { |
| error!("failed to resume client {}: {:?}", iface_id, e); |
| }; |
| } |
| |
| Ok(()) |
| } |
| |
| async fn start_ap(&mut self, config: ap_fsm::ApConfig) -> Result<oneshot::Receiver<()>, Error> { |
| let mut ap_iface_container = self.get_ap(None).await?; |
| |
| let (sender, receiver) = oneshot::channel(); |
| ap_iface_container.config = Some(config.clone()); |
| match ap_iface_container.ap_state_machine.start(config, sender) { |
| Ok(()) => { |
| if ap_iface_container.enabled_time.is_none() { |
| ap_iface_container.enabled_time = Some(zx::Time::get_monotonic()); |
| } |
| |
| self.aps.push(ap_iface_container) |
| } |
| Err(e) => { |
| let mut phy_manager = self.phy_manager.lock().await; |
| phy_manager.destroy_ap_iface(ap_iface_container.iface_id).await?; |
| return Err(format_err!("could not start ap: {}", e)); |
| } |
| } |
| |
| Ok(receiver) |
| } |
| |
| fn stop_ap( |
| &mut self, |
| ssid: ap_types::Ssid, |
| credential: Vec<u8>, |
| ) -> BoxFuture<'static, Result<(), Error>> { |
| if let Some(removal_index) = |
| self.aps.iter().position(|ap_container| match ap_container.config.as_ref() { |
| Some(config) => config.id.ssid == ssid && config.credential == credential, |
| None => false, |
| }) |
| { |
| let phy_manager = self.phy_manager.clone(); |
| let mut ap_container = self.aps.remove(removal_index); |
| |
| if let Some(start_time) = ap_container.enabled_time.take() { |
| let enabled_duration = zx::Time::get_monotonic() - start_time; |
| self.telemetry_sender.send(TelemetryEvent::StopAp { enabled_duration }); |
| } |
| |
| let fut = async move { |
| let _ = &ap_container; |
| let stop_result = |
| Self::stop_and_exit_ap_state_machine(ap_container.ap_state_machine).await; |
| |
| let mut phy_manager = phy_manager.lock().await; |
| phy_manager.destroy_ap_iface(ap_container.iface_id).await?; |
| |
| stop_result?; |
| Ok(()) |
| }; |
| |
| return fut.boxed(); |
| } |
| |
| return ready(Ok(())).boxed(); |
| } |
| |
| // Stop all APs, exit all of the state machines, and destroy all AP ifaces. |
| fn stop_all_aps(&mut self) -> BoxFuture<'static, Result<(), Error>> { |
| let mut aps: Vec<ApIfaceContainer> = self.aps.drain(..).collect(); |
| let phy_manager = self.phy_manager.clone(); |
| |
| for ap_container in aps.iter_mut() { |
| if let Some(start_time) = ap_container.enabled_time.take() { |
| let enabled_duration = zx::Time::get_monotonic() - start_time; |
| self.telemetry_sender.send(TelemetryEvent::StopAp { enabled_duration }); |
| } |
| } |
| |
| let fut = async move { |
| let mut failed_iface_deletions: u8 = 0; |
| for iface in aps.drain(..) { |
| match IfaceManagerService::stop_and_exit_ap_state_machine(iface.ap_state_machine) |
| .await |
| { |
| Ok(()) => {} |
| Err(e) => { |
| failed_iface_deletions += 1; |
| error!("failed to stop AP: {}", e); |
| } |
| } |
| |
| let mut phy_manager = phy_manager.lock().await; |
| match phy_manager.destroy_ap_iface(iface.iface_id).await { |
| Ok(()) => {} |
| Err(e) => { |
| failed_iface_deletions += 1; |
| error!("failed to delete AP {}: {}", iface.iface_id, e); |
| } |
| } |
| } |
| |
| if failed_iface_deletions == 0 { |
| return Ok(()); |
| } else { |
| return Err(format_err!("failed to delete {} ifaces", failed_iface_deletions)); |
| } |
| }; |
| |
| fut.boxed() |
| } |
| |
| /// Returns whether or not there is a client interface that is WPA3 capable, in other words |
| /// wheter or not this device can connect to a network using WPA3. Check with the PhyManager |
| /// in case it is aware of an iface that IfaceManager is not tracking. |
| pub async fn has_wpa3_capable_client(&self) -> bool { |
| let guard = self.phy_manager.lock().await; |
| return guard.has_wpa3_client_iface(); |
| } |
| } |
| |
| /// Returns whether the security support indicates WPA3 support. |
| pub fn wpa3_supported(security_support: fidl_common::SecuritySupport) -> bool { |
| security_support.mfp.supported |
| && (security_support.sae.driver_handler_supported |
| || security_support.sae.sme_handler_supported) |
| } |
| |
| /// Queues a connection selection future for idle interface autoconnect. |
| async fn initiate_automatic_connection_selection(iface_manager: &mut IfaceManagerService) { |
| if !iface_manager.idle_clients().is_empty() |
| && iface_manager.saved_networks.known_network_count().await > 0 |
| && iface_manager.connection_selection_futures.is_empty() |
| { |
| iface_manager |
| .telemetry_sender |
| .send(TelemetryEvent::StartEstablishConnection { reset_start_time: false }); |
| info!("Initiating network selection for idle client interface."); |
| |
| let mut requester = iface_manager.connection_selection_requester.clone(); |
| let fut = async move { |
| requester |
| .do_connection_selection( |
| None, |
| client_types::ConnectReason::IdleInterfaceAutoconnect, |
| ) |
| .await |
| .map_err(|e| format_err!("Error sending connection selection request: {}.", e)) |
| .map(|candidate| ConnectionSelectionResponse::Autoconnect(candidate)) |
| }; |
| iface_manager.connection_selection_futures.push(fut.boxed()); |
| } |
| } |
| |
| /// Queues a connection selection future for a connect request. |
| async fn initiate_connection_selection_for_connect_request( |
| request: ConnectAttemptRequest, |
| iface_manager: &mut IfaceManagerService, |
| ) -> Result<(), Error> { |
| let mut requester = iface_manager.connection_selection_requester.clone(); |
| let fut = async move { |
| requester |
| .do_connection_selection(Some(request.network.clone()), request.reason) |
| .await |
| .map(|candidate| ConnectionSelectionResponse::ConnectRequest { candidate, request }) |
| }; |
| |
| iface_manager.connection_selection_futures.push(fut.boxed()); |
| Ok(()) |
| } |
| |
| /// Handles results of idle interface autoconnect connection selection, including attempting to |
| /// connect on the idle interface. |
| async fn handle_automatic_connection_selection_results( |
| connection_selection_result: Option<client_types::ScannedCandidate>, |
| iface_manager: &mut IfaceManagerService, |
| reconnect_monitor_interval: &mut i64, |
| connectivity_monitor_timer: &mut fasync::Interval, |
| ) { |
| if let Some(scanned_candidate) = connection_selection_result { |
| *reconnect_monitor_interval = 1; |
| |
| let connect_selection = client_types::ConnectSelection { |
| target: scanned_candidate, |
| reason: client_types::ConnectReason::IdleInterfaceAutoconnect, |
| }; |
| |
| let mut idle_clients = iface_manager.idle_clients(); |
| if !idle_clients.is_empty() { |
| // Any client interfaces that have recently presented as idle will be |
| // reconnected. |
| for iface_id in idle_clients.drain(..) { |
| if let Err(e) = iface_manager |
| .attempt_client_reconnect(iface_id, connect_selection.clone()) |
| .await |
| { |
| warn!("Could not reconnect iface {}: {:?}", iface_id, e); |
| } |
| } |
| } |
| } else { |
| *reconnect_monitor_interval = |
| (2 * (*reconnect_monitor_interval)).min(MAX_AUTO_CONNECT_RETRY_SECONDS); |
| } |
| |
| *connectivity_monitor_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(*reconnect_monitor_interval)); |
| } |
| |
| /// Handles results of a connect request connection selection, including attempting to connect and |
| /// managing retry attempts. |
| async fn handle_connection_selection_for_connect_request_results( |
| mut request: ConnectAttemptRequest, |
| result: Option<client_types::ScannedCandidate>, |
| iface_manager: &mut IfaceManagerService, |
| ) { |
| request.attempts += 1; |
| match result { |
| Some(scanned_candidate) => { |
| let selection = client_types::ConnectSelection { |
| target: scanned_candidate, |
| reason: request.reason, |
| }; |
| info!("Starting connection to {}", selection.target.bss.bssid.to_string(),); |
| let _ = iface_manager.connect(selection).await; |
| } |
| None => { |
| if request.attempts < 3 { |
| debug!("No candidates found for connect request, queueing retrying."); |
| match initiate_connection_selection_for_connect_request(request, iface_manager) |
| .await |
| { |
| Ok(_) => {} |
| Err(e) => error!("Failed to initiate bss selection for scan retry: {:?}", e), |
| } |
| } else { |
| info!("Failed to find a candidate for the connect request"); |
| // Send connection failed update. |
| let networks = vec![listener::ClientNetworkState { |
| id: request.network.clone(), |
| state: client_types::ConnectionState::Failed, |
| status: Some(client_types::DisconnectStatus::ConnectionFailed), |
| }]; |
| |
| let update = listener::ClientStateUpdate { |
| state: client_types::ClientState::ConnectionsEnabled, |
| networks, |
| }; |
| match iface_manager |
| .client_update_sender |
| .clone() |
| .unbounded_send(listener::Message::NotifyListeners(update)) |
| { |
| Ok(_) => (), |
| Err(e) => error!("failed to send connection_failed state update: {:?}", e), |
| }; |
| } |
| } |
| } |
| } |
| |
| async fn handle_terminated_state_machine( |
| terminated_fsm: StateMachineMetadata, |
| iface_manager: &mut IfaceManagerService, |
| ) { |
| match terminated_fsm.role { |
| fidl_fuchsia_wlan_common::WlanMacRole::Ap => { |
| // If the state machine exited normally, the IfaceManagerService will have already |
| // destroyed the interface. If not, then the state machine exited because it could not |
| // communicate with the SME and the interface is likely unusable. |
| let mut phy_manager = iface_manager.phy_manager.lock().await; |
| if phy_manager.destroy_ap_iface(terminated_fsm.iface_id).await.is_err() { |
| return; |
| } |
| } |
| fidl_fuchsia_wlan_common::WlanMacRole::Client => { |
| iface_manager.record_idle_client(terminated_fsm.iface_id); |
| initiate_automatic_connection_selection(iface_manager).await; |
| } |
| fidl_fuchsia_wlan_common::WlanMacRole::Mesh => { |
| // Not yet supported. |
| unimplemented!(); |
| } |
| fidl_fuchsia_wlan_common::WlanMacRoleUnknown!() => { |
| unimplemented!(); |
| } |
| } |
| } |
| |
| fn initiate_set_country( |
| iface_manager: &mut IfaceManagerService, |
| req: SetCountryRequest, |
| ) -> BoxFuture<'static, IfaceManagerOperation> { |
| // Store the initial AP configs so that they can be started later. |
| let initial_ap_configs = |
| iface_manager.aps.iter().filter_map(|container| container.config.clone()).collect(); |
| |
| // Create futures to stop all of the APs and stop client connections |
| let stop_client_connections_fut = iface_manager |
| .stop_client_connections(client_types::DisconnectReason::RegulatoryRegionChange); |
| let stop_aps_fut = iface_manager.stop_all_aps(); |
| |
| // Once the clients and APs have been stopped, set the country code. |
| let phy_manager = iface_manager.phy_manager.clone(); |
| let regulatory_fut = async move { |
| let client_connections_initially_enabled = |
| phy_manager.lock().await.client_connections_enabled(); |
| |
| let set_country_result = if let Err(e) = stop_client_connections_fut.await { |
| Err(format_err!( |
| "failed to stop client connection in preparation for setting country code: {:?}", |
| e |
| )) |
| } else if let Err(e) = stop_aps_fut.await { |
| Err(format_err!("failed to stop APs in preparation for setting country code: {:?}", e)) |
| } else { |
| let mut phy_manager = phy_manager.lock().await; |
| phy_manager |
| .set_country_code(req.country_code) |
| .await |
| .map_err(|e| format_err!("failed to set regulatory region: {:?}", e)) |
| }; |
| |
| let result = SetCountryOperationState { |
| client_connections_initially_enabled, |
| initial_ap_configs, |
| set_country_result, |
| responder: req.responder, |
| }; |
| |
| // Return all information required to resume to the old state. |
| IfaceManagerOperation::SetCountry(result) |
| }; |
| regulatory_fut.boxed() |
| } |
| |
| async fn restore_state_after_setting_country_code( |
| iface_manager: &mut IfaceManagerService, |
| previous_state: SetCountryOperationState, |
| ) { |
| // Prior to setting the country code, it is essential that client connections and APs are all |
| // stopped. If stopping clients or APs fails or if setting the country code fails, the whole |
| // process of setting the country code must be considered a failure. |
| // |
| // Bringing clients and APs back online following the regulatory region setting may fail and is |
| // possibly recoverable. Log failures, but do not report errors in scenarios where recreating |
| // the client and AP interfaces fails. This allows API clients to retry and attempt to create |
| // the interfaces themselves by making policy API requests. |
| if previous_state.client_connections_initially_enabled { |
| if let Err(e) = iface_manager.start_client_connections().await { |
| error!("failed to resume client connections after setting country code: {:?}", e); |
| } |
| } |
| |
| for config in previous_state.initial_ap_configs { |
| if let Err(e) = iface_manager.start_ap(config).await { |
| error!("failed to resume AP after setting country code: {:?}", e); |
| } |
| } |
| |
| if previous_state.responder.send(previous_state.set_country_result).is_err() { |
| error!("could not respond to SetCountryRequest"); |
| } |
| } |
| |
| // This function allows the defect recording to run in parallel with the regulatory region setting |
| // routine. For full context, see https://fxbug.dev/42063961. |
| fn initiate_record_defect( |
| phy_manager: Arc<Mutex<dyn PhyManagerApi + Send>>, |
| defect: Defect, |
| ) -> BoxFuture<'static, IfaceManagerOperation> { |
| let fut = async move { |
| let mut phy_manager = phy_manager.lock().await; |
| phy_manager.record_defect(defect); |
| IfaceManagerOperation::ReportDefect |
| }; |
| fut.boxed() |
| } |
| |
| fn initiate_recovery( |
| phy_manager: Arc<Mutex<dyn PhyManagerApi + Send>>, |
| summary: recovery::RecoverySummary, |
| ) -> BoxFuture<'static, IfaceManagerOperation> { |
| let fut = async move { |
| let mut phy_manager = phy_manager.lock().await; |
| phy_manager.perform_recovery(summary).await; |
| IfaceManagerOperation::PerformRecovery |
| }; |
| fut.boxed() |
| } |
| |
| async fn handle_iface_manager_request( |
| iface_manager: &mut IfaceManagerService, |
| operation_futures: &mut FuturesUnordered<BoxFuture<'static, IfaceManagerOperation>>, |
| token: atomic_oneshot_stream::Token, |
| request: IfaceManagerRequest, |
| ) { |
| match request { |
| IfaceManagerRequest::Connect(ConnectRequest { request, responder }) => { |
| if responder.send(iface_manager.handle_connect_request(request).await).is_err() { |
| error!("could not respond to ScanForConnectionSelection"); |
| } |
| } |
| IfaceManagerRequest::RecordIdleIface(RecordIdleIfaceRequest { iface_id, responder }) => { |
| iface_manager.record_idle_client(iface_id); |
| if responder.send(()).is_err() { |
| error!("could not respond to RecordIdleIfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::HasIdleIface(HasIdleIfaceRequest { responder }) => { |
| if responder.send(!iface_manager.idle_clients().is_empty()).is_err() { |
| error!("could not respond to HasIdleIfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::AddIface(AddIfaceRequest { iface_id, responder }) => { |
| if let Err(e) = iface_manager.handle_added_iface(iface_id).await { |
| warn!("failed to add new interface {}: {:?}", iface_id, e); |
| } |
| if responder.send(()).is_err() { |
| error!("could not respond to AddIfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::RemoveIface(RemoveIfaceRequest { iface_id, responder }) => { |
| iface_manager.handle_removed_iface(iface_id).await; |
| if responder.send(()).is_err() { |
| error!("could not respond to RemoveIfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::GetScanProxy(ScanProxyRequest { responder }) => { |
| if responder.send(iface_manager.get_sme_proxy_for_scan().await).is_err() { |
| error!("could not respond to ScanRequest"); |
| } |
| } |
| IfaceManagerRequest::StartClientConnections(StartClientConnectionsRequest { |
| responder, |
| }) => { |
| if responder.send(iface_manager.start_client_connections().await).is_err() { |
| error!("could not respond to StartClientConnectionRequest"); |
| } |
| } |
| IfaceManagerRequest::StartAp(StartApRequest { config, responder }) => { |
| if responder.send(iface_manager.start_ap(config).await).is_err() { |
| error!("could not respond to StartApRequest"); |
| } |
| } |
| IfaceManagerRequest::HasWpa3Iface(HasWpa3IfaceRequest { responder }) => { |
| if responder.send(iface_manager.has_wpa3_capable_client().await).is_err() { |
| error!("could not respond to HasWpa3IfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::AtomicOperation(operation) => { |
| let fut = match operation { |
| AtomicOperation::Disconnect(DisconnectRequest { |
| network_id, |
| responder, |
| reason, |
| }) => { |
| let fut = iface_manager.disconnect(network_id, reason); |
| let disconnect_fut = async move { |
| if responder.send(fut.await).is_err() { |
| error!("could not respond to DisconnectRequest"); |
| } |
| IfaceManagerOperation::ConfigureStateMachine |
| }; |
| disconnect_fut.boxed() |
| } |
| AtomicOperation::StopClientConnections(StopClientConnectionsRequest { |
| reason, |
| responder, |
| }) => { |
| let fut = iface_manager.stop_client_connections(reason); |
| let stop_client_connections_fut = async move { |
| if responder.send(fut.await).is_err() { |
| error!("could not respond to StopClientConnectionsRequest"); |
| } |
| IfaceManagerOperation::ConfigureStateMachine |
| }; |
| stop_client_connections_fut.boxed() |
| } |
| AtomicOperation::StopAp(StopApRequest { ssid, password, responder }) => { |
| let stop_ap_fut = iface_manager.stop_ap(ssid, password); |
| let stop_ap_fut = async move { |
| if responder.send(stop_ap_fut.await).is_err() { |
| error!("could not respond to StopApRequest"); |
| } |
| IfaceManagerOperation::ConfigureStateMachine |
| }; |
| stop_ap_fut.boxed() |
| } |
| AtomicOperation::StopAllAps(StopAllApsRequest { responder }) => { |
| let stop_all_aps_fut = iface_manager.stop_all_aps(); |
| let stop_all_aps_fut = async move { |
| if responder.send(stop_all_aps_fut.await).is_err() { |
| error!("could not respond to StopAllApsRequest"); |
| } |
| IfaceManagerOperation::ConfigureStateMachine |
| }; |
| stop_all_aps_fut.boxed() |
| } |
| AtomicOperation::SetCountry(req) => { |
| let regulatory_fut = initiate_set_country(iface_manager, req); |
| regulatory_fut.boxed() |
| } |
| }; |
| |
| let fut = attempt_atomic_operation(fut, token); |
| operation_futures.push(fut); |
| } |
| }; |
| } |
| |
| // Bundle the operations of running the caller's future with dropping of the `AtomicOneshotStream` |
| // `Token` |
| fn attempt_atomic_operation<T: 'static>( |
| fut: BoxFuture<'static, T>, |
| token: atomic_oneshot_stream::Token, |
| ) -> BoxFuture<'static, T> { |
| Box::pin(async move { |
| let result = fut.await; |
| drop(token); |
| result |
| }) |
| } |
| |
| pub(crate) async fn serve_iface_manager_requests( |
| mut iface_manager: IfaceManagerService, |
| requests: mpsc::Receiver<IfaceManagerRequest>, |
| mut defect_receiver: mpsc::UnboundedReceiver<Defect>, |
| mut recovery_action_receiver: recovery::RecoveryActionReceiver, |
| ) -> Result<Infallible, Error> { |
| // Client and AP state machines need to be allowed to run in order for several operations to |
| // complete. In such cases, futures can be added to this list to progress them once the state |
| // machines have the opportunity to run. |
| let mut operation_futures = FuturesUnordered::new(); |
| |
| // This allows routines servicing `IfaceManagerRequest`s to prevent incoming requests from |
| // being serviced to prevent potential deadlocks on the `PhyManager`. |
| let mut requests = atomic_oneshot_stream::AtomicOneshotStream::new(requests); |
| |
| // Create a timer to periodically check to ensure that all client interfaces are connected. |
| let mut reconnect_monitor_interval: i64 = 1; |
| let mut connectivity_monitor_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(reconnect_monitor_interval)); |
| |
| loop { |
| let mut atomic_iface_manager_requests = requests.get_atomic_oneshot_stream(); |
| |
| select! { |
| terminated_fsm = iface_manager.fsm_futures.select_next_some() => { |
| info!("state machine exited: {:?}", terminated_fsm.1); |
| handle_terminated_state_machine( |
| terminated_fsm.1, |
| &mut iface_manager, |
| ).await; |
| }, |
| () = connectivity_monitor_timer.select_next_some() => { |
| initiate_automatic_connection_selection( |
| &mut iface_manager, |
| ).await; |
| }, |
| op = operation_futures.select_next_some() => match op { |
| IfaceManagerOperation::SetCountry(previous_state) => { |
| restore_state_after_setting_country_code( |
| &mut iface_manager, |
| previous_state |
| ).await; |
| }, |
| IfaceManagerOperation::ConfigureStateMachine |
| | IfaceManagerOperation::ReportDefect |
| | IfaceManagerOperation::PerformRecovery => {}, |
| }, |
| connection_selection_result = iface_manager.connection_selection_futures.select_next_some() => { |
| match connection_selection_result { |
| // Automatic network selection |
| Ok(ConnectionSelectionResponse::Autoconnect(candidate)) => { |
| handle_automatic_connection_selection_results( |
| candidate, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer |
| ).await |
| }, |
| // Specific network connect request |
| Ok(ConnectionSelectionResponse::ConnectRequest {candidate, request}) => { |
| handle_connection_selection_for_connect_request_results( |
| request, |
| candidate, |
| &mut iface_manager, |
| ).await; |
| } |
| Err(..) => { |
| error!("Connection selector unexpectedly dropped response sender.") |
| } |
| } |
| }, |
| defect = defect_receiver.select_next_some() => { |
| operation_futures.push(initiate_record_defect(iface_manager.phy_manager.clone(), defect)) |
| }, |
| action = recovery_action_receiver.select_next_some() => { |
| operation_futures.push(initiate_recovery(iface_manager.phy_manager.clone(), action)) |
| }, |
| (token, req) = atomic_iface_manager_requests.select_next_some() => { |
| handle_iface_manager_request( |
| &mut iface_manager, |
| &mut operation_futures, |
| token, |
| req |
| ).await; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| access_point::types, |
| client::{connection_selection::ConnectionSelectionRequest, types as client_types}, |
| config_management::{ |
| Credential, NetworkIdentifier, SavedNetworksManager, SecurityType, |
| }, |
| mode_management::{ |
| phy_manager::{self, PhyManagerError}, |
| recovery::RecoverySummary, |
| IfaceFailure, PhyFailure, |
| }, |
| regulatory_manager::REGION_CODE_LEN, |
| util::testing::{ |
| fakes::{FakeLocalRoamManager, FakeScanRequester}, |
| generate_connect_selection, generate_random_bss, generate_random_scanned_candidate, |
| poll_sme_req, |
| }, |
| }, |
| async_trait::async_trait, |
| fidl_fuchsia_stash as fidl_stash, fidl_fuchsia_wlan_common as fidl_common, |
| fuchsia_async::{DurationExt, TestExecutor}, |
| fuchsia_inspect as inspect, |
| futures::{stream::StreamFuture, task::Poll, TryStreamExt}, |
| ieee80211::MacAddr, |
| lazy_static::lazy_static, |
| std::pin::pin, |
| test_case::test_case, |
| wlan_common::{assert_variant, channel::Cbw, random_fidl_bss_description, RadioConfig}, |
| }; |
| |
| // Responses that FakePhyManager will provide |
| pub const TEST_CLIENT_IFACE_ID: u16 = 0; |
| pub const TEST_AP_IFACE_ID: u16 = 1; |
| |
| // Fake WLAN network that tests will scan for and connect to. |
| lazy_static! { |
| pub static ref TEST_SSID: ap_types::Ssid = ap_types::Ssid::try_from("test_ssid").unwrap(); |
| } |
| pub static TEST_PASSWORD: &str = "test_password"; |
| |
| /// Holds all of the boilerplate required for testing IfaceManager. |
| /// * DeviceMonitorProxy and DeviceMonitorRequestStream |
| /// * Allow for the construction of Clients and ClientIfaceContainers and the ability to send |
| /// responses to their requests. |
| /// * ClientListenerMessageSender and MessageStream |
| /// * Allow for the construction of ClientIfaceContainers and the absorption of |
| /// ClientStateUpdates. |
| /// * ApListenerMessageSender and MessageStream |
| /// * Allow for the construction of ApIfaceContainers and the absorption of |
| /// ApStateUpdates. |
| /// * KnownEssStore, SavedNetworksManager, TempDir |
| /// * Allow for the querying of network credentials and storage of connection history. |
| pub struct TestValues { |
| pub monitor_service_proxy: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy, |
| pub monitor_service_stream: fidl_fuchsia_wlan_device_service::DeviceMonitorRequestStream, |
| pub client_update_sender: listener::ClientListenerMessageSender, |
| pub client_update_receiver: mpsc::UnboundedReceiver<listener::ClientListenerMessage>, |
| pub ap_update_sender: listener::ApListenerMessageSender, |
| // TODO(https://fxbug.dev/332405442): Remove or explain #[allow(dead_code)]. |
| #[allow(dead_code)] |
| pub ap_update_receiver: mpsc::UnboundedReceiver<listener::ApMessage>, |
| pub saved_networks: Arc<dyn SavedNetworksManagerApi>, |
| pub local_roam_manager: Arc<Mutex<dyn LocalRoamManagerApi>>, |
| pub scan_requester: Arc<FakeScanRequester>, |
| pub node: inspect::Node, |
| pub telemetry_sender: TelemetrySender, |
| pub telemetry_receiver: mpsc::Receiver<TelemetryEvent>, |
| pub defect_sender: mpsc::UnboundedSender<Defect>, |
| pub defect_receiver: mpsc::UnboundedReceiver<Defect>, |
| pub recovery_sender: recovery::RecoveryActionSender, |
| pub recovery_receiver: recovery::RecoveryActionReceiver, |
| pub connection_selection_requester: ConnectionSelectionRequester, |
| pub connection_selection_request_receiver: mpsc::Receiver<ConnectionSelectionRequest>, |
| } |
| |
| /// Create a TestValues for a unit test. |
| pub fn test_setup(exec: &mut TestExecutor) -> TestValues { |
| let (monitor_service_proxy, monitor_service_requests) = |
| create_proxy::<fidl_fuchsia_wlan_device_service::DeviceMonitorMarker>() |
| .expect("failed to create SeviceService proxy"); |
| let monitor_service_stream = |
| monitor_service_requests.into_stream().expect("failed to create stream"); |
| |
| let (client_sender, client_receiver) = mpsc::unbounded(); |
| let (ap_sender, ap_receiver) = mpsc::unbounded(); |
| |
| let saved_networks = exec.run_singlethreaded(SavedNetworksManager::new_for_test()); |
| let saved_networks = Arc::new(saved_networks); |
| let inspector = inspect::Inspector::default(); |
| let node = inspector.root().create_child("phy_manager"); |
| let (telemetry_sender, telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100); |
| let telemetry_sender = TelemetrySender::new(telemetry_sender); |
| let scan_requester = Arc::new(FakeScanRequester::new()); |
| let (defect_sender, defect_receiver) = mpsc::unbounded(); |
| let local_roam_manager = Arc::new(Mutex::new(FakeLocalRoamManager::new())); |
| let (recovery_sender, recovery_receiver) = |
| mpsc::channel::<recovery::RecoverySummary>(recovery::RECOVERY_SUMMARY_CHANNEL_CAPACITY); |
| let (connection_selection_request_sender, connection_selection_request_receiver) = |
| mpsc::channel(5); |
| let connection_selection_requester = |
| ConnectionSelectionRequester::new(connection_selection_request_sender); |
| |
| TestValues { |
| monitor_service_proxy, |
| monitor_service_stream, |
| client_update_sender: client_sender, |
| client_update_receiver: client_receiver, |
| ap_update_sender: ap_sender, |
| ap_update_receiver: ap_receiver, |
| saved_networks, |
| local_roam_manager, |
| scan_requester, |
| node, |
| telemetry_sender, |
| telemetry_receiver, |
| defect_sender, |
| defect_receiver, |
| recovery_sender, |
| recovery_receiver, |
| connection_selection_requester, |
| connection_selection_request_receiver, |
| } |
| } |
| |
| /// Creates a new PhyManagerPtr for tests. |
| fn create_empty_phy_manager( |
| monitor_service: fidl_fuchsia_wlan_device_service::DeviceMonitorProxy, |
| node: inspect::Node, |
| telemetry_sender: TelemetrySender, |
| recovery_sender: recovery::RecoveryActionSender, |
| ) -> Arc<Mutex<dyn PhyManagerApi + Send>> { |
| Arc::new(Mutex::new(phy_manager::PhyManager::new( |
| monitor_service, |
| recovery::lookup_recovery_profile(""), |
| false, |
| node, |
| telemetry_sender, |
| recovery_sender, |
| ))) |
| } |
| |
| #[derive(Debug)] |
| struct FakePhyManager { |
| create_iface_ok: bool, |
| destroy_iface_ok: bool, |
| set_country_ok: bool, |
| wpa3_iface: Option<u16>, |
| country_code: Option<[u8; REGION_CODE_LEN]>, |
| client_connections_enabled: bool, |
| client_ifaces: Vec<u16>, |
| defects: Vec<Defect>, |
| recoveries: Vec<RecoverySummary>, |
| } |
| |
| #[async_trait] |
| impl PhyManagerApi for FakePhyManager { |
| async fn add_phy(&mut self, _phy_id: u16) -> Result<(), PhyManagerError> { |
| unimplemented!() |
| } |
| |
| fn remove_phy(&mut self, _phy_id: u16) { |
| unimplemented!() |
| } |
| |
| async fn on_iface_added(&mut self, _iface_id: u16) -> Result<(), PhyManagerError> { |
| unimplemented!() |
| } |
| |
| fn on_iface_removed(&mut self, _iface_id: u16) {} |
| |
| async fn create_all_client_ifaces( |
| &mut self, |
| _reason: CreateClientIfacesReason, |
| ) -> Result<Vec<u16>, (Vec<u16>, PhyManagerError)> { |
| if self.create_iface_ok { |
| Ok(self.client_ifaces.clone()) |
| } else { |
| Err((vec![], PhyManagerError::IfaceCreateFailure)) |
| } |
| } |
| |
| fn client_connections_enabled(&self) -> bool { |
| self.client_connections_enabled |
| } |
| |
| async fn destroy_all_client_ifaces(&mut self) -> Result<(), PhyManagerError> { |
| if self.destroy_iface_ok { |
| Ok(()) |
| } else { |
| Err(PhyManagerError::IfaceDestroyFailure) |
| } |
| } |
| |
| fn get_client(&mut self) -> Option<u16> { |
| Some(TEST_CLIENT_IFACE_ID) |
| } |
| |
| fn get_wpa3_capable_client(&mut self) -> Option<u16> { |
| self.wpa3_iface |
| } |
| |
| async fn create_or_get_ap_iface(&mut self) -> Result<Option<u16>, PhyManagerError> { |
| if self.create_iface_ok { |
| Ok(Some(TEST_AP_IFACE_ID)) |
| } else { |
| Err(PhyManagerError::IfaceCreateFailure) |
| } |
| } |
| |
| async fn destroy_ap_iface(&mut self, _iface_id: u16) -> Result<(), PhyManagerError> { |
| if self.destroy_iface_ok { |
| Ok(()) |
| } else { |
| Err(PhyManagerError::IfaceDestroyFailure) |
| } |
| } |
| |
| async fn destroy_all_ap_ifaces(&mut self) -> Result<(), PhyManagerError> { |
| if self.destroy_iface_ok { |
| Ok(()) |
| } else { |
| Err(PhyManagerError::IfaceDestroyFailure) |
| } |
| } |
| |
| fn suggest_ap_mac(&mut self, _mac: MacAddr) { |
| unimplemented!() |
| } |
| |
| fn get_phy_ids(&self) -> Vec<u16> { |
| unimplemented!() |
| } |
| |
| fn log_phy_add_failure(&mut self) { |
| unimplemented!(); |
| } |
| |
| async fn set_country_code( |
| &mut self, |
| country_code: Option<[u8; REGION_CODE_LEN]>, |
| ) -> Result<(), PhyManagerError> { |
| if self.set_country_ok { |
| self.country_code = country_code; |
| Ok(()) |
| } else { |
| Err(PhyManagerError::PhySetCountryFailure) |
| } |
| } |
| |
| fn has_wpa3_client_iface(&self) -> bool { |
| self.wpa3_iface.is_some() |
| } |
| |
| async fn set_power_state( |
| &mut self, |
| _low_power_enabled: fidl_fuchsia_wlan_common::PowerSaveType, |
| ) -> Result<fuchsia_zircon::Status, anyhow::Error> { |
| unimplemented!(); |
| } |
| |
| fn record_defect(&mut self, defect: Defect) { |
| self.defects.push(defect); |
| } |
| |
| async fn perform_recovery(&mut self, summary: RecoverySummary) { |
| self.recoveries.push(summary); |
| } |
| } |
| |
| struct FakeClient { |
| disconnect_ok: bool, |
| is_alive: bool, |
| expected_connect_selection: Option<client_types::ConnectSelection>, |
| } |
| |
| impl FakeClient { |
| fn new() -> Self { |
| FakeClient { disconnect_ok: true, is_alive: true, expected_connect_selection: None } |
| } |
| } |
| |
| #[async_trait] |
| impl client_fsm::ClientApi for FakeClient { |
| fn connect(&mut self, selection: client_types::ConnectSelection) -> Result<(), Error> { |
| assert_eq!(Some(selection), self.expected_connect_selection); |
| Ok(()) |
| } |
| fn disconnect( |
| &mut self, |
| _reason: client_types::DisconnectReason, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), Error> { |
| if self.disconnect_ok { |
| let _ = responder.send(()); |
| Ok(()) |
| } else { |
| Err(format_err!("fake failed to disconnect")) |
| } |
| } |
| fn is_alive(&self) -> bool { |
| self.is_alive |
| } |
| } |
| |
| struct FakeAp { |
| start_succeeds: bool, |
| stop_succeeds: bool, |
| exit_succeeds: bool, |
| } |
| |
| #[async_trait] |
| impl AccessPointApi for FakeAp { |
| fn start( |
| &mut self, |
| _request: ap_fsm::ApConfig, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), anyhow::Error> { |
| if self.start_succeeds { |
| let _ = responder.send(()); |
| Ok(()) |
| } else { |
| Err(format_err!("start failed")) |
| } |
| } |
| |
| fn stop(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error> { |
| if self.stop_succeeds { |
| let _ = responder.send(()); |
| Ok(()) |
| } else { |
| Err(format_err!("stop failed")) |
| } |
| } |
| |
| fn exit(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error> { |
| if self.exit_succeeds { |
| let _ = responder.send(()); |
| Ok(()) |
| } else { |
| Err(format_err!("exit failed")) |
| } |
| } |
| } |
| |
| fn create_iface_manager_with_client( |
| test_values: &TestValues, |
| configured: bool, |
| ) -> (IfaceManagerService, StreamFuture<fidl_fuchsia_wlan_sme::ClientSmeRequestStream>) { |
| let (sme_proxy, server) = create_proxy::<fidl_fuchsia_wlan_sme::ClientSmeMarker>() |
| .expect("failed to create an sme channel"); |
| let mut client_container = ClientIfaceContainer { |
| iface_id: TEST_CLIENT_IFACE_ID, |
| sme_proxy, |
| config: None, |
| client_state_machine: None, |
| security_support: fake_security_support(), |
| last_roam_time: fasync::Time::now(), |
| }; |
| let phy_manager = FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }; |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.monitor_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester.clone(), |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender.clone(), |
| test_values.defect_sender.clone(), |
| ); |
| |
| if configured { |
| client_container.config = Some(ap_types::NetworkIdentifier { |
| ssid: TEST_SSID.clone(), |
| security_type: ap_types::SecurityType::Wpa, |
| }); |
| client_container.client_state_machine = Some(Box::new(FakeClient::new())); |
| } |
| iface_manager.clients.push(client_container); |
| |
| (iface_manager, server.into_stream().unwrap().into_future()) |
| } |
| |
| fn create_ap_config(ssid: &ap_types::Ssid, password: &str) -> ap_fsm::ApConfig { |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| ap_fsm::ApConfig { |
| id: ap_types::NetworkIdentifier { |
| ssid: ssid.clone(), |
| security_type: ap_types::SecurityType::None, |
| }, |
| credential: password.as_bytes().to_vec(), |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| } |
| } |
| |
| fn create_iface_manager_with_ap( |
| test_values: &TestValues, |
| fake_ap: FakeAp, |
| ) -> IfaceManagerService { |
| let ap_container = ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: None, |
| ap_state_machine: Box::new(fake_ap), |
| enabled_time: None, |
| }; |
| let phy_manager = FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| }; |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.monitor_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester.clone(), |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender.clone(), |
| test_values.defect_sender.clone(), |
| ); |
| |
| iface_manager.aps.push(ap_container); |
| iface_manager |
| } |
| |
| /// Move stash requests forward so that a save request can progress. |
| fn process_stash_write( |
| exec: &mut fuchsia_async::TestExecutor, |
| stash_server: &mut fidl_stash::StoreAccessorRequestStream, |
| ) { |
| assert_variant!( |
| exec.run_until_stalled(&mut stash_server.try_next()), |
| Poll::Ready(Ok(Some(fidl_stash::StoreAccessorRequest::SetValue { .. }))) |
| ); |
| process_stash_flush(exec, stash_server); |
| } |
| |
| fn process_stash_delete( |
| exec: &mut fuchsia_async::TestExecutor, |
| stash_server: &mut fidl_stash::StoreAccessorRequestStream, |
| ) { |
| assert_variant!( |
| exec.run_until_stalled(&mut stash_server.try_next()), |
| Poll::Ready(Ok(Some(fidl_stash::StoreAccessorRequest::DeletePrefix { .. }))) |
| ); |
| process_stash_flush(exec, stash_server); |
| } |
| |
| fn process_stash_flush( |
| exec: &mut fuchsia_async::TestExecutor, |
| stash_server: &mut fidl_stash::StoreAccessorRequestStream, |
| ) { |
| assert_variant!( |
| exec.run_until_stalled(&mut stash_server.try_next()), |
| Poll::Ready(Ok(Some(fidl_stash::StoreAccessorRequest::Flush{responder}))) => { |
| responder.send(Ok(())).expect("failed to send stash response"); |
| } |
| ); |
| } |
| |
| #[track_caller] |
| fn run_state_machine_futures( |
| exec: &mut fuchsia_async::TestExecutor, |
| iface_manager: &mut IfaceManagerService, |
| ) { |
| for mut state_machine in iface_manager.fsm_futures.iter_mut() { |
| assert_variant!(exec.run_until_stalled(&mut state_machine), Poll::Pending); |
| } |
| } |
| |
| /// Tests the case where connect is called and the only available client interface is already |
| /// configured. |
| #[fuchsia::test] |
| fn test_connect_with_configured_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Configure the mock CSM with the expected connect request |
| let connect_selection = generate_connect_selection(); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_selection: Some(connect_selection.clone()), |
| })); |
| |
| // Ask the IfaceManager to connect. |
| { |
| let connect_fut = iface_manager.connect(connect_selection.clone()); |
| let mut connect_fut = pin!(connect_fut); |
| |
| // Run the connect request to completion. |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(_) => {} |
| Err(e) => panic!("failed to connect with {}", e), |
| }, |
| Poll::Pending => panic!("expected the connect request to finish"), |
| }; |
| } |
| // Start running the client state machine. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Verify that the ClientIfaceContainer has the correct config. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].config, Some(connect_selection.target.network.clone())); |
| } |
| |
| /// Tests the case where connect is called while the only available interface is currently |
| /// unconfigured. |
| #[fuchsia::test] |
| fn test_connect_with_unconfigured_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Add credentials for the test network to the saved networks. |
| let connect_selection = generate_connect_selection(); |
| let save_network_fut = test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| ); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| { |
| let connect_fut = iface_manager.connect(connect_selection.clone()); |
| let mut connect_fut = pin!(connect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Pending); |
| |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| |
| sme_server |
| } |
| ); |
| _sme_stream = sme_server.into_stream().unwrap().into_future(); |
| |
| let mut connect_fut = pin!(connect_fut); |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(_) => {} |
| Err(e) => panic!("failed to connect with {}", e), |
| }, |
| Poll::Pending => panic!("expected the connect request to finish"), |
| }; |
| } |
| // Start running the client state machine. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Acknowledge the disconnection attempt. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut _sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_fuchsia_wlan_sme::UserDisconnectReason::Startup }) => { |
| responder.send().expect("could not send response") |
| } |
| ); |
| |
| // Make sure that the connect request has been sent out. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| let connect_txn_handle = assert_variant!( |
| poll_sme_req(&mut exec, &mut _sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, connect_selection.target.network.ssid.clone()); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl |
| } |
| ); |
| connect_txn_handle |
| .send_on_connect_result(&fake_successful_connect_result()) |
| .expect("failed to send connection completion"); |
| |
| // Run the state machine future again so that it acks the oneshot. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Verify that the ClientIfaceContainer has been moved from unconfigured to configured. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].config, Some(connect_selection.target.network.clone())); |
| } |
| |
| #[fuchsia::test] |
| fn test_connect_cancels_auto_reconnect_future() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Add credentials for the test network to the saved networks. |
| let connect_selection = generate_connect_selection(); |
| let save_network_fut = test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| ); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| // Add a network selection future which won't complete that should be canceled by a |
| // connect request. |
| async fn blocking_fn() -> Result<ConnectionSelectionResponse, anyhow::Error> { |
| loop { |
| fasync::Timer::new(zx::Duration::from_millis(1).after_now()).await |
| } |
| } |
| iface_manager.connection_selection_futures.push(blocking_fn().boxed()); |
| |
| // Request a connect through IfaceManager and respond to requests needed to complete it. |
| { |
| let connect_fut = iface_manager.connect(connect_selection); |
| let mut connect_fut = pin!(connect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Pending); |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| let mut connect_fut = pin!(connect_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(connect_result) => { |
| assert!(connect_result.is_ok()); |
| }); |
| } |
| |
| // Verify that the network selection future was dropped from the list. |
| assert!(iface_manager.connection_selection_futures.is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_disconnect_cancels_autoconnect_future() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| |
| // Add a network selection future which won't complete that should be canceled by a |
| // disconnect call. |
| async fn blocking_fn() -> Result<ConnectionSelectionResponse, anyhow::Error> { |
| loop { |
| fasync::Timer::new(zx::Duration::from_millis(1).after_now()).await |
| } |
| } |
| iface_manager.connection_selection_futures.push(blocking_fn().boxed()); |
| assert!(!iface_manager.connection_selection_futures.is_empty()); |
| |
| // Request a disconnect through IfaceManager. |
| let disconnect_fut = iface_manager.disconnect( |
| NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa), |
| client_types::DisconnectReason::NetworkUnsaved, |
| ); |
| let mut disconnect_fut = pin!(disconnect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(()))); |
| |
| // Verify that the network selection future was dropped from the list. |
| assert!(iface_manager.connection_selection_futures.is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_wpa3_supported() { |
| // Valid WPA3 feature sets |
| let mut driver_handler_mfp = fake_security_support(); |
| driver_handler_mfp.sae.driver_handler_supported = true; |
| driver_handler_mfp.mfp.supported = true; |
| assert!(wpa3_supported(driver_handler_mfp)); |
| |
| let mut sme_handler_mfp = fake_security_support(); |
| sme_handler_mfp.sae.sme_handler_supported = true; |
| sme_handler_mfp.mfp.supported = true; |
| assert!(wpa3_supported(sme_handler_mfp)); |
| |
| let mut driver_and_sme_handler_mfp = fake_security_support(); |
| driver_and_sme_handler_mfp.sae.driver_handler_supported = true; |
| driver_and_sme_handler_mfp.sae.sme_handler_supported = true; |
| driver_and_sme_handler_mfp.mfp.supported = true; |
| assert!(wpa3_supported(driver_and_sme_handler_mfp)); |
| |
| // Invalid WPA3 feature sets |
| let mut driver_handler_only = fake_security_support(); |
| driver_handler_only.sae.driver_handler_supported = true; |
| assert!(!wpa3_supported(driver_handler_only)); |
| |
| let mut sme_handler_only = fake_security_support(); |
| sme_handler_only.sae.sme_handler_supported = true; |
| assert!(!wpa3_supported(sme_handler_only)); |
| |
| let mut driver_and_sme_handler_only = fake_security_support(); |
| driver_and_sme_handler_only.sae.driver_handler_supported = true; |
| driver_and_sme_handler_only.sae.sme_handler_supported = true; |
| assert!(!wpa3_supported(driver_and_sme_handler_only)); |
| |
| let mut mfp_only = fake_security_support(); |
| mfp_only.mfp.supported = true; |
| assert!(!wpa3_supported(mfp_only)); |
| } |
| |
| /// Tests the case where connect is called for a WPA3 connection an there is an unconfigured |
| /// WPA3 iface available. |
| #[test_case(true, true, false)] |
| #[test_case(true, false, true)] |
| #[test_case(true, true, true)] |
| #[fuchsia::test(add_test_attr = false)] |
| fn test_connect_with_unconfigured_wpa3_iface( |
| mfp_supported: bool, |
| sae_driver_handler_supported: bool, |
| sae_sme_handler_supported: bool, |
| ) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| let mut test_values = test_setup(&mut exec); |
| // The default FakePhyManager will not have a WPA3 iface which is intentional because if |
| // an unconfigured iface is available with WPA3, it should be used without asking |
| // PhyManager for an iface. |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| 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; |
| iface_manager.clients[0].security_support = security_support; |
| |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa3); |
| let connect_selection = client_types::ConnectSelection { |
| target: client_types::ScannedCandidate { |
| network: network_id, |
| security_type_detailed: client_types::SecurityTypeDetailed::Wpa3Personal, |
| bss: client_types::Bss { |
| bss_description: random_fidl_bss_description!(Wpa3, ssid: TEST_SSID.clone()) |
| .into(), |
| ..generate_random_bss() |
| }, |
| ..generate_random_scanned_candidate() |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| }; |
| |
| let save_network_fut = test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| ); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| { |
| let connect_fut = iface_manager.connect(connect_selection.clone()); |
| let mut connect_fut = pin!(connect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Pending); |
| |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| |
| sme_server |
| } |
| ); |
| _sme_stream = sme_server.into_stream().unwrap().into_future(); |
| |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(_) => {} |
| Err(e) => panic!("failed to connect with {}", e), |
| }, |
| Poll::Pending => panic!("expected the connect request to finish"), |
| }; |
| } |
| // Start running the client state machine. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Acknowledge the disconnection attempt. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut _sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_fuchsia_wlan_sme::UserDisconnectReason::Startup }) => { |
| responder.send().expect("could not send response") |
| } |
| ); |
| |
| // Make sure that the connect request has been sent out. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| let connect_txn_handle = assert_variant!( |
| poll_sme_req(&mut exec, &mut _sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, connect_selection.target.network.ssid.clone()); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl |
| } |
| ); |
| connect_txn_handle |
| .send_on_connect_result(&fake_successful_connect_result()) |
| .expect("failed to send connection completion"); |
| |
| // Run the state machine future again so that it acks the oneshot. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Verify that the ClientIfaceContainer has been moved from unconfigured to configured. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].config, Some(connect_selection.target.network.clone())); |
| } |
| |
| /// Tests the case where connect is called for a WPA3 connection and a client iface exists but |
| /// does not support WPA3. The connect should fail without trying to connect. |
| #[fuchsia::test] |
| fn test_connect_wpa3_no_wpa3_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| let test_values = test_setup(&mut exec); |
| // By default IfaceManager has an iface but not one with WPA3. |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| |
| // Call connect on the IfaceManager |
| let connect_selection = client_types::ConnectSelection { |
| target: client_types::ScannedCandidate { |
| network: NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa3), |
| security_type_detailed: client_types::SecurityTypeDetailed::Wpa3Personal, |
| bss: client_types::Bss { |
| bss_description: random_fidl_bss_description!(Wpa3, ssid: TEST_SSID.clone()) |
| .into(), |
| ..generate_random_bss() |
| }, |
| ..generate_random_scanned_candidate() |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| }; |
| let connect_fut = iface_manager.connect(connect_selection); |
| |
| // Verify that the request to connect results in an error. |
| let mut connect_fut = pin!(connect_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where a connect is called and the WPA3 capable iface is configured. |
| /// PhyManager should be asked for an iface that supports WPA3. |
| #[fuchsia::test] |
| fn test_connect_wpa3_with_configured_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| // Build the connect request for connecting to the WPA3 network. |
| let connect_selection = client_types::ConnectSelection { |
| target: client_types::ScannedCandidate { |
| network: NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa3), |
| security_type_detailed: client_types::SecurityTypeDetailed::Wpa3Personal, |
| bss: client_types::Bss { |
| bss_description: random_fidl_bss_description!(Wpa3, ssid: TEST_SSID.clone()) |
| .into(), |
| ..generate_random_bss() |
| }, |
| ..generate_random_scanned_candidate() |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| }; |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| // Use a PhyManager that has a WPA3 client iface. |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: false, |
| wpa3_iface: Some(0), |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| // Configure the mock CSM with the expected connect request |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_selection: Some(connect_selection.clone()), |
| })); |
| { |
| let connect_fut = iface_manager.connect(connect_selection.clone()); |
| let mut connect_fut = pin!(connect_fut); |
| |
| // Run the connect request to completion. |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(_) => {} |
| Err(e) => panic!("failed to connect with {}", e), |
| }, |
| Poll::Pending => panic!("expected the connect request to finish"), |
| }; |
| } |
| |
| // Start running the client state machine. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Verify that the ClientIfaceContainer has the correct config. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].config, Some(connect_selection.target.network)); |
| } |
| |
| /// Tests the case where connect is called, but no client ifaces exist. |
| #[fuchsia::test] |
| fn test_connect_with_no_ifaces() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a PhyManager with no knowledge of any client ifaces. |
| let test_values = test_setup(&mut exec); |
| let phy_manager = create_empty_phy_manager( |
| test_values.monitor_service_proxy.clone(), |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Call connect on the IfaceManager |
| let connect_fut = iface_manager.connect(generate_connect_selection()); |
| |
| // Verify that the request to connect results in an error. |
| let mut connect_fut = pin!(connect_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where connect is called, but client connections are disabled. |
| #[fuchsia::test] |
| fn test_connect_while_connections_disabled() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a PhyManager with no knowledge of any client ifaces. |
| let test_values = test_setup(&mut exec); |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| set_country_ok: true, |
| wpa3_iface: None, |
| country_code: None, |
| client_connections_enabled: false, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| // Create the IfaceManager |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Construct the connect request. |
| let connect_selection = generate_connect_selection(); |
| let request = ConnectAttemptRequest::new( |
| connect_selection.target.network, |
| connect_selection.target.credential, |
| client_types::ConnectReason::FidlConnectRequest, |
| ); |
| |
| // Attempt to handle a connect request. |
| let connect_fut = iface_manager.handle_connect_request(request); |
| |
| // Verify that the request to connect results in an error. |
| let mut connect_fut = pin!(connect_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where the PhyManager knows of a client iface, but the IfaceManager is not |
| /// able to create an SME proxy for it. |
| #[fuchsia::test] |
| fn test_connect_sme_creation_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager and drop its client |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| let _ = iface_manager.clients.pop(); |
| |
| // Drop the serving end of our device service proxy so that the request to create an SME |
| // proxy fails. |
| drop(test_values.monitor_service_stream); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let connect_selection = generate_connect_selection(); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone() |
| )) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| // Ask the IfaceManager to connect and make sure that it fails. |
| let connect_fut = iface_manager.connect(connect_selection); |
| |
| let mut connect_fut = pin!(connect_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where disconnect is called on a configured client. |
| #[fuchsia::test] |
| fn test_disconnect_configured_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Issue a call to disconnect from the network. |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: TEST_SSID.clone(), |
| security_type: ap_types::SecurityType::Wpa, |
| }; |
| let disconnect_fut = iface_manager |
| .disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Ensure that disconnect returns a successful response. |
| let mut disconnect_fut = pin!(disconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Verify that the ClientIfaceContainer has been moved from configured to unconfigured. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert!(iface_manager.clients[0].config.is_none()); |
| } |
| |
| /// Tests the case where disconnect is called for a network for which the IfaceManager is not |
| /// configured. Verifies that the configured client is unaffected. |
| #[fuchsia::test] |
| fn test_disconnect_nonexistent_config() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a ClientIfaceContainer with a valid client. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create a PhyManager with knowledge of a single client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Issue a disconnect request for a bogus network configuration. |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: ap_types::Ssid::try_from("nonexistent_ssid").unwrap(), |
| security_type: ap_types::SecurityType::Wpa, |
| }; |
| let disconnect_fut = iface_manager |
| .disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Ensure that the request returns immediately. |
| let mut disconnect_fut = pin!(disconnect_fut); |
| assert!(exec.run_until_stalled(&mut disconnect_fut).is_ready()); |
| } |
| |
| // Verify that the configured client has not been affected. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert!(iface_manager.clients[0].config.is_some()); |
| } |
| |
| /// Tests the case where disconnect is called and no client ifaces are present. |
| #[fuchsia::test] |
| fn test_disconnect_no_clients() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Call disconnect on the IfaceManager |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: ap_types::Ssid::try_from("nonexistent_ssid").unwrap(), |
| security_type: ap_types::SecurityType::Wpa, |
| }; |
| let disconnect_fut = |
| iface_manager.disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Verify that disconnect returns immediately. |
| let mut disconnect_fut = pin!(disconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(_))); |
| } |
| |
| /// Tests the case where the call to disconnect the client fails. |
| #[fuchsia::test] |
| fn test_disconnect_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Make the client state machine's disconnect call fail. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_selection: None, |
| })); |
| |
| // Call disconnect on the IfaceManager |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: TEST_SSID.clone(), |
| security_type: ap_types::SecurityType::Wpa, |
| }; |
| let disconnect_fut = |
| iface_manager.disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| let mut disconnect_fut = pin!(disconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut disconnect_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests stop_client_connections when there is a client that is connected. |
| #[fuchsia::test] |
| fn test_stop_connected_client() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Stop all client connections. |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure that no client interfaces are accounted for. |
| assert!(iface_manager.clients.is_empty()); |
| |
| // Ensure an update was sent |
| let client_state_update = listener::ClientStateUpdate { |
| state: fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsDisabled, |
| networks: vec![], |
| }; |
| assert_variant!( |
| test_values.client_update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| } |
| |
| /// Call stop_client_connections when the only available client is unconfigured. |
| #[fuchsia::test] |
| fn test_stop_unconfigured_client() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create a PhyManager with one known client. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Call stop_client_connections. |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure there are no remaining client ifaces. |
| assert!(iface_manager.clients.is_empty()); |
| } |
| |
| /// Tests the case where stop_client_connections is called, but there are no client ifaces. |
| #[fuchsia::test] |
| fn test_stop_no_clients() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create and empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Call stop_client_connections. |
| { |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| |
| // Ensure stop_client_connections returns immediately and is successful. |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| } |
| |
| /// Tests the case where client connections are stopped, but stopping one of the client state |
| /// machines fails. |
| #[fuchsia::test] |
| fn test_stop_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_selection: None, |
| })); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Stop all client connections. |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure that no client interfaces are accounted for. |
| assert!(iface_manager.clients.is_empty()); |
| } |
| |
| /// Tests the case where the IfaceManager fails to tear down all of the client ifaces. |
| #[fuchsia::test] |
| fn test_stop_iface_destruction_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: false, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store(network_id, credential)) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| { |
| // Stop all client connections. |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Err(_))); |
| } |
| |
| // Ensure that no client interfaces are accounted for. |
| assert!(iface_manager.clients.is_empty()); |
| } |
| |
| /// Tests the case where StopClientConnections is called when the client interfaces are already |
| /// stopped. |
| #[fuchsia::test] |
| fn test_stop_client_when_already_stopped() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Call stop_client_connections. |
| { |
| let stop_fut = iface_manager.stop_client_connections( |
| client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| ); |
| |
| // Ensure stop_client_connections returns immediately and is successful. |
| let mut stop_fut = pin!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Verify that telemetry event has been sent |
| let event = assert_variant!(test_values.telemetry_receiver.try_next(), Ok(Some(ev)) => ev); |
| assert_variant!(event, TelemetryEvent::ClearEstablishConnectionStartTime); |
| } |
| |
| /// Tests the case where an existing iface is marked as idle. |
| #[fuchsia::test] |
| fn test_mark_iface_idle() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Setup the client state machine so that it looks like it is no longer alive. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| assert!(iface_manager.clients[0].config.is_some()); |
| iface_manager.record_idle_client(TEST_CLIENT_IFACE_ID); |
| assert!(iface_manager.clients[0].config.is_none()); |
| } |
| |
| /// Tests the case where a running and configured iface is marked as idle. |
| #[fuchsia::test] |
| fn test_mark_active_iface_idle() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| assert!(iface_manager.clients[0].config.is_some()); |
| |
| // The request to mark the interface as idle should be ignored since the interface's state |
| // machine is still running. |
| iface_manager.record_idle_client(TEST_CLIENT_IFACE_ID); |
| assert!(iface_manager.clients[0].config.is_some()); |
| } |
| |
| /// Tests the case where a non-existent iface is marked as idle. |
| #[fuchsia::test] |
| fn test_mark_nonexistent_iface_idle() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| assert!(iface_manager.clients[0].config.is_some()); |
| iface_manager.record_idle_client(123); |
| assert!(iface_manager.clients[0].config.is_some()); |
| } |
| |
| /// Tests the case where an iface is not configured and has_idle_client is called. |
| #[fuchsia::test] |
| fn test_unconfigured_iface_idle_check() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _) = create_iface_manager_with_client(&test_values, false); |
| assert!(!iface_manager.idle_clients().is_empty()); |
| } |
| |
| /// Tests the case where an iface is configured and alive and has_idle_client is called. |
| #[fuchsia::test] |
| fn test_configured_alive_iface_idle_check() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| assert!(iface_manager.idle_clients().is_empty()); |
| } |
| |
| /// Tests the case where an iface is configured and dead and has_idle_client is called. |
| #[fuchsia::test] |
| fn test_configured_dead_iface_idle_check() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Make the client state machine's liveness check fail. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| assert!(!iface_manager.idle_clients().is_empty()); |
| } |
| |
| /// Tests the case where not ifaces are present and has_idle_client is called. |
| #[fuchsia::test] |
| fn test_no_ifaces_idle_check() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| let _ = iface_manager.clients.pop(); |
| assert!(iface_manager.idle_clients().is_empty()); |
| } |
| |
| /// Tests the case where starting client connections succeeds. |
| #[fuchsia::test] |
| fn test_start_clients_succeeds() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| { |
| let start_fut = iface_manager.start_client_connections(); |
| |
| // Ensure start_client_connections returns immediately and is successful. |
| let mut start_fut = pin!(start_fut); |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure no update is sent |
| assert_variant!(test_values.client_update_receiver.try_next(), Err(_)); |
| } |
| |
| /// Tests the case where starting client connections fails. |
| #[fuchsia::test] |
| fn test_start_clients_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: false, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| { |
| let start_fut = iface_manager.start_client_connections(); |
| let mut start_fut = pin!(start_fut); |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Ready(Err(_))); |
| } |
| } |
| |
| /// Tests the case where there is a lingering client interface to ensure that it is resumed to |
| /// a working state. |
| #[fuchsia::test] |
| fn test_start_clients_with_lingering_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager with no clients and client connections initially disabled. Seed |
| // it with a PhyManager that knows of a lingering client interface. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, false); |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: false, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| // Delete all client records initially. |
| iface_manager.clients.clear(); |
| assert!(iface_manager.fsm_futures.is_empty()); |
| |
| { |
| let start_fut = iface_manager.start_client_connections(); |
| let mut start_fut = pin!(start_fut); |
| |
| // The IfaceManager will first query to determine the type of interface. |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }))) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Failed to send iface response"); |
| } |
| ); |
| |
| // The request should stall out while attempting to get a client interface. |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }))) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| // There will be a security support query. |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Pending); |
| let features_server = assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetFeatureSupport { |
| iface_id: TEST_CLIENT_IFACE_ID, feature_support_server, responder |
| }))) => { |
| assert!(responder.send(Ok(())).is_ok()); |
| feature_support_server |
| } |
| ); |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Pending); |
| let mut features_req_fut = features_server |
| .into_stream() |
| .expect("Failed to create features req stream") |
| .into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut features_req_fut), |
| Poll::Ready(fidl_fuchsia_wlan_sme::FeatureSupportRequest::QuerySecuritySupport { |
| responder |
| }) => { |
| responder.send(Ok(&fake_security_support())).expect("Failed to send security support response"); |
| } |
| ); |
| |
| // Expect that we have requested a client SME proxy from creating the client state |
| // machine. |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }))) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| // The request should complete successfully. |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Ready(Ok(()))); |
| } |
| |
| assert!(!iface_manager.clients.is_empty()); |
| assert!(!iface_manager.fsm_futures.is_empty()); |
| } |
| |
| /// Tests the case where the IfaceManager is able to request that the AP state machine start |
| /// the access point. |
| #[fuchsia::test] |
| fn test_start_ap_succeeds() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| |
| { |
| let fut = iface_manager.start_ap(config); |
| |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(_))); |
| } |
| |
| assert!(iface_manager.aps[0].enabled_time.is_some()); |
| } |
| |
| /// Tests the case where the IfaceManager is not able to request that the AP state machine start |
| /// the access point. |
| #[fuchsia::test] |
| fn test_start_ap_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: false, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| |
| { |
| let fut = iface_manager.start_ap(config); |
| |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| } |
| |
| /// Tests the case where start is called on the IfaceManager, but there are no AP ifaces. |
| #[fuchsia::test] |
| fn test_start_ap_no_ifaces() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Call start_ap. |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| let fut = iface_manager.start_ap(config); |
| |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where stop_ap is called for a config that is accounted for by the |
| /// IfaceManager. |
| #[fuchsia::test] |
| fn test_stop_ap_succeeds() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| iface_manager.aps[0].config = Some(config); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| { |
| let fut = iface_manager.stop_ap(TEST_SSID.clone(), TEST_PASSWORD.as_bytes().to_vec()); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure a metric was logged. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where IfaceManager is requested to stop a config that is not accounted for. |
| #[fuchsia::test] |
| fn test_stop_ap_invalid_config() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| { |
| let fut = iface_manager.stop_ap(TEST_SSID.clone(), TEST_PASSWORD.as_bytes().to_vec()); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| assert!(!iface_manager.aps.is_empty()); |
| |
| // Ensure no metric was logged. |
| assert_variant!(test_values.telemetry_receiver.try_next(), Err(_)); |
| |
| // Ensure the AP start time has not been cleared. |
| assert!(iface_manager.aps[0].enabled_time.is_some()); |
| } |
| |
| /// Tests the case where IfaceManager attempts to stop the AP state machine, but the request |
| /// fails. |
| #[fuchsia::test] |
| fn test_stop_ap_stop_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: false, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| iface_manager.aps[0].config = Some(config); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| { |
| let fut = iface_manager.stop_ap(TEST_SSID.clone(), TEST_PASSWORD.as_bytes().to_vec()); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure metric was logged. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where IfaceManager stops the AP state machine, but the request to exit |
| /// fails. |
| #[fuchsia::test] |
| fn test_stop_ap_exit_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: false }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| iface_manager.aps[0].config = Some(config); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| { |
| let fut = iface_manager.stop_ap(TEST_SSID.clone(), TEST_PASSWORD.as_bytes().to_vec()); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure metric was logged. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where stop is called on the IfaceManager, but there are no AP ifaces. |
| #[fuchsia::test] |
| fn test_stop_ap_no_ifaces() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| let fut = iface_manager.stop_ap(TEST_SSID.clone(), TEST_PASSWORD.as_bytes().to_vec()); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| /// Tests the case where stop_all_aps is called and it succeeds. |
| #[fuchsia::test] |
| fn test_stop_all_aps_succeeds() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // Insert a second iface and add it to the list of APs. |
| let second_iface = ApIfaceContainer { |
| iface_id: 2, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: Some(zx::Time::get_monotonic()), |
| }; |
| iface_manager.aps.push(second_iface); |
| |
| { |
| let fut = iface_manager.stop_all_aps(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure metrics are logged for both AP interfaces. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where stop_all_aps is called and the request to stop fails for an iface. |
| #[fuchsia::test] |
| fn test_stop_all_aps_stop_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: false, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // Insert a second iface and add it to the list of APs. |
| let second_iface = ApIfaceContainer { |
| iface_id: 2, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: Some(zx::Time::get_monotonic()), |
| }; |
| iface_manager.aps.push(second_iface); |
| |
| { |
| let fut = iface_manager.stop_all_aps(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure metrics are logged for both AP interfaces. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where stop_all_aps is called and the request to stop fails for an iface. |
| #[fuchsia::test] |
| fn test_stop_all_aps_exit_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: false }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| iface_manager.aps[0].enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // Insert a second iface and add it to the list of APs. |
| let second_iface = ApIfaceContainer { |
| iface_id: 2, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: Some(zx::Time::get_monotonic()), |
| }; |
| iface_manager.aps.push(second_iface); |
| |
| { |
| let fut = iface_manager.stop_all_aps(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Ensure metrics are logged for both AP interfaces. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| /// Tests the case where stop_all_aps is called on the IfaceManager, but there are no AP |
| /// ifaces. |
| #[fuchsia::test] |
| fn test_stop_all_aps_no_ifaces() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| let fut = iface_manager.stop_all_aps(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| |
| // Ensure no metrics are logged. |
| assert_variant!(test_values.telemetry_receiver.try_next(), Err(_)); |
| } |
| |
| /// Tests the case where there is a single AP interface and it is asked to start twice and then |
| /// asked to stop. |
| #[fuchsia::test] |
| fn test_start_ap_twice_then_stop() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| |
| // Issue an initial start command. |
| { |
| let fut = iface_manager.start_ap(config); |
| |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Record the initial start time. |
| let initial_start_time = iface_manager.aps[0].enabled_time; |
| |
| // Now issue a second start command. |
| let alternate_ssid = ap_types::Ssid::try_from("some_other_ssid").unwrap(); |
| let alternate_password = "some_other_password"; |
| let config = create_ap_config(&alternate_ssid, alternate_password); |
| { |
| let fut = iface_manager.start_ap(config); |
| |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Verify that the start time has not been updated. |
| assert_eq!(initial_start_time, iface_manager.aps[0].enabled_time); |
| |
| // Verify that no metric has been recorded. |
| assert_variant!(test_values.telemetry_receiver.try_next(), Err(_)); |
| |
| // Now issue a stop command. |
| { |
| let fut = iface_manager.stop_all_aps(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| assert!(iface_manager.aps.is_empty()); |
| |
| // Make sure the metric has been sent. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StopAp { .. })) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_recover_removed_client_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager with a client and an AP. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| let removed_iface_id = 123; |
| iface_manager.clients[0].iface_id = removed_iface_id; |
| |
| let ap_iface = ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: None, |
| }; |
| iface_manager.aps.push(ap_iface); |
| |
| // Notify the IfaceManager that the client interface has been removed. |
| { |
| let fut = iface_manager.handle_removed_iface(removed_iface_id); |
| let mut fut = pin!(fut); |
| |
| // Expect a DeviceMonitor request an SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }))) => { |
| responder |
| .send(Ok(())) |
| .expect("failed to send AP SME response."); |
| } |
| ); |
| |
| // Expected a DeviceMonitor request to get iface info. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let features_server = assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetFeatureSupport { |
| iface_id: TEST_CLIENT_IFACE_ID, feature_support_server, responder |
| }))) => { |
| assert!(responder.send(Ok(())).is_ok()); |
| feature_support_server |
| } |
| ); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let mut features_req_fut = features_server |
| .into_stream() |
| .expect("Failed to create features req stream") |
| .into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut features_req_fut), |
| Poll::Ready(fidl_fuchsia_wlan_sme::FeatureSupportRequest::QuerySecuritySupport { |
| responder |
| }) => { |
| responder.send(Ok(&fake_security_support())).expect("Failed to send security support response"); |
| } |
| ); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| assert!(!iface_manager.aps.is_empty()); |
| |
| // Verify that a new client interface was created. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].iface_id, TEST_CLIENT_IFACE_ID); |
| } |
| |
| #[fuchsia::test] |
| fn test_client_iface_recovery_fails() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager with a client and an AP. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| let removed_iface_id = 123; |
| iface_manager.clients[0].iface_id = removed_iface_id; |
| |
| let ap_iface = ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: None, |
| }; |
| iface_manager.aps.push(ap_iface); |
| |
| // Notify the IfaceManager that the client interface has been removed. |
| { |
| let fut = iface_manager.handle_removed_iface(removed_iface_id); |
| let mut fut = pin!(fut); |
| |
| // Expect a DeviceMonitor request an SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.monitor_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }))) => { |
| responder |
| .send(Err(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND)) |
| .expect("failed to send client SME response."); |
| } |
| ); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| assert!(!iface_manager.aps.is_empty()); |
| |
| // Verify that not new client interface was created. |
| assert!(iface_manager.clients.is_empty()); |
| |
| // Verify that a ConnectionsDisabled notification was sent. |
| // Ensure an update was sent |
| let expected_update = listener::ClientStateUpdate { |
| state: fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsDisabled, |
| networks: vec![], |
| }; |
| assert_variant!( |
| test_values.client_update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, expected_update); |
| } |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_do_not_recover_removed_ap_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager with a client and an AP. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| |
| let removed_iface_id = 123; |
| let ap_iface = ApIfaceContainer { |
| iface_id: removed_iface_id, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: None, |
| }; |
| iface_manager.aps.push(ap_iface); |
| |
| // Notify the IfaceManager that the AP interface has been removed. |
| { |
| let fut = iface_manager.handle_removed_iface(removed_iface_id); |
| let mut fut = pin!(fut); |
| |
| // The future should now run to completion. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| assert!(!iface_manager.clients.is_empty()); |
| |
| // Verify that a new AP interface was not created. |
| assert!(iface_manager.aps.is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_remove_nonexistent_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager with a client and an AP. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| |
| let ap_iface = ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: None, |
| ap_state_machine: Box::new(FakeAp { |
| start_succeeds: true, |
| stop_succeeds: true, |
| exit_succeeds: true, |
| }), |
| enabled_time: None, |
| }; |
| iface_manager.aps.push(ap_iface); |
| |
| // Notify the IfaceManager that an interface that has not been accounted for has been |
| // removed. |
| { |
| let fut = iface_manager.handle_removed_iface(1234); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| assert!(!iface_manager.clients.is_empty()); |
| assert!(!iface_manager.aps.is_empty()); |
| } |
| |
| fn poll_service_req< |
| T, |
| E: std::fmt::Debug, |
| R: fidl::endpoints::RequestStream |
| + futures::Stream<Item = Result<T, E>> |
| + futures::TryStream<Ok = T>, |
| >( |
| exec: &mut fuchsia_async::TestExecutor, |
| next_req: &mut StreamFuture<R>, |
| ) -> Poll<fidl::endpoints::Request<R::Protocol>> { |
| exec.run_until_stalled(next_req).map(|(req, stream)| { |
| *next_req = stream.into_future(); |
| req.expect("did not expect the SME request stream to end") |
| .expect("error polling SME request stream") |
| }) |
| } |
| |
| #[fuchsia::test] |
| fn test_add_client_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_CLIENT_IFACE_ID); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect and interface query and notify that this is a client interface. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // Expect that we have requested a client SME proxy from get_client. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let features_server = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetFeatureSupport { |
| iface_id: TEST_CLIENT_IFACE_ID, feature_support_server, responder |
| }) => { |
| assert!(responder.send(Ok(())).is_ok()); |
| feature_support_server |
| } |
| ); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let mut features_req_fut = features_server |
| .into_stream() |
| .expect("Failed to create features req stream") |
| .into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut features_req_fut), |
| Poll::Ready(fidl_fuchsia_wlan_sme::FeatureSupportRequest::QuerySecuritySupport { |
| responder |
| }) => { |
| responder.send(Ok(&fake_security_support())).expect("Failed to send security support response"); |
| } |
| ); |
| |
| // Expect that we have requested a client SME proxy from creating the client state |
| // machine. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| // Run the future to completion. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Ensure that the client interface has been added. |
| assert!(iface_manager.aps.is_empty()); |
| assert_eq!(iface_manager.clients[0].iface_id, TEST_CLIENT_IFACE_ID); |
| } |
| |
| #[fuchsia::test] |
| fn test_add_ap_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_AP_IFACE_ID); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect that the interface properties are queried and notify that it is an AP iface. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Ap, |
| id: TEST_AP_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // Run the future so that an AP SME proxy is requested. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let responder = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme { |
| iface_id: TEST_AP_IFACE_ID, sme_server: _, responder |
| }) => responder |
| ); |
| |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| |
| // Run the future to completion. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Ensure that the AP interface has been added. |
| assert!(iface_manager.clients.is_empty()); |
| assert_eq!(iface_manager.aps[0].iface_id, TEST_AP_IFACE_ID); |
| } |
| |
| #[fuchsia::test] |
| fn test_add_nonexistent_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks, |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_AP_IFACE_ID); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an iface query and send back an error |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| responder |
| .send(Err(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // Run the future to completion. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Err(_))); |
| } |
| |
| // Ensure that no interfaces have been added. |
| assert!(iface_manager.clients.is_empty()); |
| assert!(iface_manager.aps.is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_add_existing_client_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| |
| // Notify the IfaceManager of a new interface. |
| { |
| let fut = iface_manager.handle_added_iface(TEST_CLIENT_IFACE_ID); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an interface query and notify that it is a client. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // The future should then run to completion as it finds the existing interface. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Verify that nothing new has been appended to the clients vector or the aps vector. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.aps.len(), 0); |
| } |
| |
| #[fuchsia::test] |
| fn test_add_existing_ap_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| |
| // Notify the IfaceManager of a new interface. |
| { |
| let fut = iface_manager.handle_added_iface(TEST_AP_IFACE_ID); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an interface query and notify that it is a client. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Ap, |
| id: TEST_AP_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // The future should then run to completion as it finds the existing interface. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Verify that nothing new has been appended to the clients vector or the aps vector. |
| assert_eq!(iface_manager.clients.len(), 0); |
| assert_eq!(iface_manager.aps.len(), 1); |
| } |
| |
| enum TestType { |
| Pass, |
| Fail, |
| ClientError, |
| } |
| |
| fn run_service_test<T: std::fmt::Debug>( |
| exec: &mut fuchsia_async::TestExecutor, |
| iface_manager: IfaceManagerService, |
| req: IfaceManagerRequest, |
| mut req_receiver: oneshot::Receiver<Result<T, Error>>, |
| monitor_service_stream: fidl_fuchsia_wlan_device_service::DeviceMonitorRequestStream, |
| test_type: TestType, |
| ) { |
| // Start the service loop |
| let (mut sender, receiver) = mpsc::channel(1); |
| let (_defect_sender, defect_receiver) = mpsc::unbounded(); |
| let (_recovery_sender, recovery_receiver) = |
| mpsc::channel::<recovery::RecoverySummary>(recovery::RECOVERY_SUMMARY_CHANNEL_CAPACITY); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| defect_receiver, |
| recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Send the client's request |
| sender.try_send(req).expect("failed to send request"); |
| |
| // Service any device service requests in the event that a new client SME proxy is required |
| // for the operation under test. |
| let mut monitor_service_fut = monitor_service_stream.into_future(); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| match poll_service_req(exec, &mut monitor_service_fut) { |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, |
| sme_server: _, |
| responder, |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetApSme { |
| iface_id: TEST_AP_IFACE_ID, |
| sme_server: _, |
| responder, |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| _ => {} |
| } |
| |
| match test_type { |
| TestType::Pass => { |
| // Process the request. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Assert that the receiving end gets a successful result. |
| assert_variant!(exec.run_until_stalled(&mut req_receiver), Poll::Ready(Ok(Ok(_)))); |
| } |
| TestType::Fail => { |
| // Process the request. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Assert that the receiving end gets a successful result. |
| assert_variant!(exec.run_until_stalled(&mut req_receiver), Poll::Ready(Ok(Err(_)))); |
| } |
| TestType::ClientError => { |
| // Simulate a client failure. |
| drop(req_receiver); |
| } |
| } |
| |
| // Make sure the service keeps running. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| } |
| |
| fn run_service_test_with_unit_return( |
| exec: &mut fuchsia_async::TestExecutor, |
| iface_manager: IfaceManagerService, |
| req: IfaceManagerRequest, |
| mut req_receiver: oneshot::Receiver<()>, |
| test_type: TestType, |
| ) { |
| // Start the service loop |
| let (mut sender, receiver) = mpsc::channel(1); |
| let (_defect_sender, defect_receiver) = mpsc::unbounded(); |
| let (_recovery_sender, recovery_receiver) = |
| mpsc::channel::<recovery::RecoverySummary>(recovery::RECOVERY_SUMMARY_CHANNEL_CAPACITY); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| defect_receiver, |
| recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Send the client's request |
| sender.try_send(req).expect("failed to send request"); |
| |
| match test_type { |
| TestType::Pass => { |
| // Process the request. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Assert that the receiving end gets a successful result. |
| assert_variant!(exec.run_until_stalled(&mut req_receiver), Poll::Ready(Ok(()))); |
| } |
| TestType::Fail => { |
| // Process the request. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Assert that the receiving end gets a successful result. |
| assert_variant!(exec.run_until_stalled(&mut req_receiver), Poll::Ready(Ok(()))); |
| } |
| TestType::ClientError => { |
| // Simulate a client failure. |
| drop(req_receiver); |
| } |
| } |
| |
| // Make sure the service keeps running. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| } |
| |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_selection: None}, |
| TestType::Pass; "successfully disconnects configured client")] |
| #[test_case(FakeClient {disconnect_ok: false, is_alive:true, expected_connect_selection: None}, |
| TestType::Fail; "fails to disconnect configured client")] |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_selection: None}, |
| TestType::ClientError; "client drops receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_disconnect_test(fake_client: FakeClient, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(fake_client)); |
| |
| // Send a disconnect request. |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = DisconnectRequest { |
| network_id: client_types::NetworkIdentifier { |
| ssid: TEST_SSID.clone(), |
| security_type: client_types::SecurityType::Wpa, |
| }, |
| reason: client_types::DisconnectReason::NetworkUnsaved, |
| responder: ack_sender, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::Disconnect(req)); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| ack_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_selection: Some(generate_connect_selection())}, |
| TestType::Pass; "successfully connected a client")] |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_selection: Some(generate_connect_selection())}, |
| TestType::ClientError; "client drops receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_connect_test(fake_client: FakeClient, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _stream) = create_iface_manager_with_client(&test_values, false); |
| let connect_selection = fake_client.expected_connect_selection.clone().unwrap(); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(fake_client)); |
| |
| // Send a connect request. |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = ConnectRequest { |
| request: ConnectAttemptRequest::new( |
| connect_selection.target.network, |
| connect_selection.target.credential, |
| client_types::ConnectReason::FidlConnectRequest, |
| ), |
| responder: ack_sender, |
| }; |
| let req = IfaceManagerRequest::Connect(req); |
| |
| // Currently the FakeScanRequester will panic if a scan is requested and no results are |
| // queued, so add something. This test needs a few: |
| // initial idle interface selection |
| // for the connect request |
| // idle interface selection after connect request fails |
| exec.run_singlethreaded(test_values.scan_requester.add_scan_result(Ok(vec![]))); |
| exec.run_singlethreaded(test_values.scan_requester.add_scan_result(Ok(vec![]))); |
| exec.run_singlethreaded(test_values.scan_requester.add_scan_result(Ok(vec![]))); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| ack_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| // This test is a bit of a twofer as it covers both the mechanism for recording idle interfaces |
| // as well as the mechanism for querying idle interfaces. |
| #[fuchsia::test] |
| fn test_service_record_idle_client() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Make the client state machine's liveness check fail. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Send an idle interface request |
| let (ack_sender, mut ack_receiver) = oneshot::channel(); |
| let req = RecordIdleIfaceRequest { iface_id: TEST_CLIENT_IFACE_ID, responder: ack_sender }; |
| let req = IfaceManagerRequest::RecordIdleIface(req); |
| |
| sender.try_send(req).expect("failed to send request"); |
| |
| // Run the service loop and expect the request to be serviced and for the loop to not exit. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Wait for the response. |
| assert_variant!(exec.run_until_stalled(&mut ack_receiver), Poll::Ready(Ok(()))); |
| |
| // Check if an idle interface is present. |
| let (idle_iface_sender, mut idle_iface_receiver) = oneshot::channel(); |
| let req = HasIdleIfaceRequest { responder: idle_iface_sender }; |
| let req = IfaceManagerRequest::HasIdleIface(req); |
| sender.try_send(req).expect("failed to send request"); |
| |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Make sure that the interface has been marked idle. |
| assert_variant!(exec.run_until_stalled(&mut idle_iface_receiver), Poll::Ready(Ok(true))); |
| } |
| |
| #[fuchsia::test] |
| fn test_service_record_idle_client_response_failure() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Send an idle interface request |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = RecordIdleIfaceRequest { iface_id: TEST_CLIENT_IFACE_ID, responder: ack_sender }; |
| let req = IfaceManagerRequest::RecordIdleIface(req); |
| |
| sender.try_send(req).expect("failed to send request"); |
| |
| // Drop the receiving end and make sure the service continues running. |
| drop(ack_receiver); |
| |
| // Run the service loop and expect the request to be serviced and for the loop to not exit. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| } |
| |
| #[fuchsia::test] |
| fn test_service_query_idle_client_response_failure() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Check if an idle interface is present. |
| let (idle_iface_sender, idle_iface_receiver) = oneshot::channel(); |
| let req = HasIdleIfaceRequest { responder: idle_iface_sender }; |
| let req = IfaceManagerRequest::HasIdleIface(req); |
| sender.try_send(req).expect("failed to send request"); |
| |
| // Drop the receiving end and make sure the service continues running. |
| drop(idle_iface_receiver); |
| |
| // Run the service loop and expect the request to be serviced and for the loop to not exit. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| } |
| |
| #[fuchsia::test] |
| fn test_service_add_iface_succeeds() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Report a new interface. |
| let (new_iface_sender, mut new_iface_receiver) = oneshot::channel(); |
| let req = AddIfaceRequest { iface_id: TEST_CLIENT_IFACE_ID, responder: new_iface_sender }; |
| let req = IfaceManagerRequest::AddIface(req); |
| sender.try_send(req).expect("failed to send request"); |
| |
| // Run the service loop to begin processing the request. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Expect and interface query and notify that this is a client interface. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| sta_addr: [0; 6], |
| }; |
| responder |
| .send(Ok(&response)) |
| .expect("Sending iface response"); |
| } |
| ); |
| |
| // Expect that we have requested a client SME proxy from get_client. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| // Expect that we have queried an iface from get_client creating a new IfaceContainer. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| let features_server = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetFeatureSupport { |
| iface_id: TEST_CLIENT_IFACE_ID, feature_support_server, responder |
| }) => { |
| assert!(responder.send(Ok(())).is_ok()); |
| feature_support_server |
| } |
| ); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| let mut features_req_fut = features_server |
| .into_stream() |
| .expect("Failed to create features req stream") |
| .into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut features_req_fut), |
| Poll::Ready(fidl_fuchsia_wlan_sme::FeatureSupportRequest::QuerySecuritySupport { |
| responder |
| }) => { |
| responder.send(Ok(&fake_security_support())).expect("Failed to send security support response"); |
| } |
| ); |
| |
| // Expect that we have requested a client SME proxy from creating the client state machine. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| } |
| ); |
| |
| // Run the service again to ensure the response is sent. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Check that the response was received. |
| assert_variant!(exec.run_until_stalled(&mut new_iface_receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test_case(TestType::Fail; "failed to add interface")] |
| #[test_case(TestType::ClientError; "client dropped receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_add_iface_negative_tests(test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.monitor_service_proxy.clone(), |
| recovery::lookup_recovery_profile(""), |
| false, |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Report a new interface. |
| let (new_iface_sender, new_iface_receiver) = oneshot::channel(); |
| let req = AddIfaceRequest { iface_id: TEST_CLIENT_IFACE_ID, responder: new_iface_sender }; |
| let req = IfaceManagerRequest::AddIface(req); |
| |
| // Drop the device monitor stream so that querying the interface properties will fail. |
| drop(test_values.monitor_service_stream); |
| |
| run_service_test_with_unit_return( |
| &mut exec, |
| iface_manager, |
| req, |
| new_iface_receiver, |
| test_type, |
| ); |
| } |
| |
| #[test_case(TestType::Pass; "successfully removed iface")] |
| #[test_case(TestType::ClientError; "client dropped receiving end")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_remove_iface_test(test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Notify of interface removal. |
| let (remove_iface_sender, remove_iface_receiver) = oneshot::channel(); |
| let req = RemoveIfaceRequest { iface_id: 123, responder: remove_iface_sender }; |
| let req = IfaceManagerRequest::RemoveIface(req); |
| |
| run_service_test_with_unit_return( |
| &mut exec, |
| iface_manager, |
| req, |
| remove_iface_receiver, |
| test_type, |
| ); |
| } |
| |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::Pass; |
| "successfully started client connections" |
| )] |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: false, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::Fail; |
| "failed to start client connections" |
| )] |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::ClientError; |
| "client dropped receiver" |
| )] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_start_client_connections_test(phy_manager: FakePhyManager, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Make start client connections request |
| let (start_sender, start_receiver) = oneshot::channel(); |
| let req = StartClientConnectionsRequest { responder: start_sender }; |
| let req = IfaceManagerRequest::StartClientConnections(req); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| start_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::Pass; |
| "successfully stopped client connections" |
| )] |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: false, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::Fail; |
| "failed to stop client connections" |
| )] |
| #[test_case( |
| FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| }, |
| TestType::ClientError; |
| "client dropped receiver" |
| )] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_stop_client_connections_test(phy_manager: FakePhyManager, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Make stop client connections request |
| let (stop_sender, stop_receiver) = oneshot::channel(); |
| let req = StopClientConnectionsRequest { |
| responder: stop_sender, |
| reason: client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopClientConnections(req)); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::Pass; "successfully starts AP")] |
| #[test_case(FakeAp { start_succeeds: false, stop_succeeds: true, exit_succeeds: true }, TestType::Fail; "fails to start AP")] |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::ClientError; "client drops receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_start_ap_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an IfaceManager with a fake AP interface. |
| let iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| |
| // Request that an AP be started. |
| let (start_sender, start_receiver) = oneshot::channel(); |
| let req = StartApRequest { |
| config: create_ap_config(&TEST_SSID, TEST_PASSWORD), |
| responder: start_sender, |
| }; |
| let req = IfaceManagerRequest::StartAp(req); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| start_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::Pass; "successfully stops AP")] |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: false, exit_succeeds: true }, TestType::Fail; "fails to stop AP")] |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::ClientError; "client drops receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_stop_ap_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an IfaceManager with a configured fake AP interface. |
| let mut iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| let config = create_ap_config(&TEST_SSID, TEST_PASSWORD); |
| iface_manager.aps[0].config = Some(config); |
| |
| // Request that an AP be stopped. |
| let (stop_sender, stop_receiver) = oneshot::channel(); |
| let req = StopApRequest { |
| ssid: TEST_SSID.clone(), |
| password: TEST_PASSWORD.as_bytes().to_vec(), |
| responder: stop_sender, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopAp(req)); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::Pass; "successfully stops AP")] |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: false, exit_succeeds: true }, TestType::Fail; "fails to stop AP")] |
| #[test_case(FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }, TestType::ClientError; "client drops receiver")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn service_stop_all_aps_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an IfaceManager with a fake AP interface. |
| let iface_manager = create_iface_manager_with_ap(&test_values, fake_ap); |
| |
| // Request that an AP be started. |
| let (stop_sender, stop_receiver) = oneshot::channel(); |
| let req = StopAllApsRequest { responder: stop_sender }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopAllAps(req)); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| } |
| |
| /// Tests the case where the IfaceManager attempts to reconnect a client interface that has |
| /// disconnected. |
| #[fuchsia::test] |
| fn test_reconnect_disconnected_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Make the client state machine report that it is dead. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let connect_selection = generate_connect_selection(); |
| let save_network_fut = test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| ); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| // Ask the IfaceManager to reconnect. |
| let mut sme_stream = { |
| let reconnect_fut = iface_manager |
| .attempt_client_reconnect(TEST_CLIENT_IFACE_ID, connect_selection.clone()); |
| let mut reconnect_fut = pin!(reconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut reconnect_fut), Poll::Pending); |
| |
| // There should be a request for a client SME proxy. |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server, responder |
| }) => { |
| assert!(responder.send(Ok(())).is_ok()); |
| sme_server |
| } |
| ); |
| |
| // The reconnect future should finish up. |
| assert_variant!(exec.run_until_stalled(&mut reconnect_fut), Poll::Ready(Ok(()))); |
| |
| sme_server.into_stream().unwrap().into_future() |
| }; |
| |
| // Start running the new state machine. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| |
| // Verify telemetry event has been sent. |
| let event = assert_variant!(test_values.telemetry_receiver.try_next(), Ok(Some(ev)) => ev); |
| assert_variant!( |
| event, |
| TelemetryEvent::StartEstablishConnection { reset_start_time: false } |
| ); |
| |
| // Acknowledge the disconnection attempt. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_fuchsia_wlan_sme::UserDisconnectReason::Startup }) => { |
| responder.send().expect("could not send response") |
| } |
| ); |
| |
| // Make sure that the connect request has been sent out. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| let connect_txn_handle = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_stream), |
| Poll::Ready(fidl_fuchsia_wlan_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, connect_selection.target.network.ssid.clone()); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl |
| } |
| ); |
| connect_txn_handle |
| .send_on_connect_result(&fake_successful_connect_result()) |
| .expect("failed to send connection completion"); |
| |
| // Verify that the state machine future is still alive. |
| run_state_machine_futures(&mut exec, &mut iface_manager); |
| assert!(!iface_manager.fsm_futures.is_empty()); |
| } |
| |
| /// Tests the case where the IfaceManager attempts to reconnect a client interface that does |
| /// not exist. This simulates the case where the client state machine exits because client |
| /// connections have been stopped. |
| #[fuchsia::test] |
| fn test_reconnect_nonexistent_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an empty IfaceManager |
| let test_values = test_setup(&mut exec); |
| let phy_manager = create_empty_phy_manager( |
| test_values.monitor_service_proxy.clone(), |
| test_values.node, |
| test_values.telemetry_sender.clone(), |
| test_values.recovery_sender, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.monitor_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester, |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender, |
| test_values.defect_sender, |
| ); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let connect_selection = generate_connect_selection(); |
| assert!(exec |
| .run_singlethreaded(test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone() |
| )) |
| .expect("failed to store a network password") |
| .is_none()); |
| |
| // Ask the IfaceManager to reconnect. |
| { |
| let reconnect_fut = |
| iface_manager.attempt_client_reconnect(TEST_CLIENT_IFACE_ID, connect_selection); |
| let mut reconnect_fut = pin!(reconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut reconnect_fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Ensure that there are no new state machines. |
| assert!(iface_manager.fsm_futures.is_empty()); |
| } |
| |
| /// Tests the case where the IfaceManager attempts to reconnect a client interface that is |
| /// already connected to another networks. This simulates the case where a client state |
| /// machine exits because a new connect request has come in. |
| #[fuchsia::test] |
| fn test_reconnect_connected_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _sme_stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Make the client state machine report that it is alive. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_selection: None, |
| })); |
| |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let connect_selection = generate_connect_selection(); |
| let save_network_fut = test_values.saved_networks.store( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| ); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| // Ask the IfaceManager to reconnect. |
| { |
| let reconnect_fut = |
| iface_manager.attempt_client_reconnect(TEST_CLIENT_IFACE_ID, connect_selection); |
| let mut reconnect_fut = pin!(reconnect_fut); |
| assert_variant!(exec.run_until_stalled(&mut reconnect_fut), Poll::Ready(Ok(()))); |
| } |
| |
| // Ensure that there are no new state machines. |
| assert!(iface_manager.fsm_futures.is_empty()); |
| } |
| |
| enum NetworkSelectionMissingAttribute { |
| AllAttributesPresent, |
| IdleClient, |
| SavedNetwork, |
| NetworkSelectionInProgress, |
| } |
| |
| #[test_case(NetworkSelectionMissingAttribute::AllAttributesPresent; "scan is requested")] |
| #[test_case(NetworkSelectionMissingAttribute::IdleClient; "no idle clients")] |
| #[test_case(NetworkSelectionMissingAttribute::SavedNetwork; "no saved networks")] |
| #[test_case(NetworkSelectionMissingAttribute::NetworkSelectionInProgress; "selection already in progress")] |
| #[fuchsia::test(add_test_attr = false)] |
| fn test_initiate_connection_selection(test_type: NetworkSelectionMissingAttribute) { |
| // Start out by setting the test up such that we would expect a scan to be requested. |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| |
| // Insert a saved network. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let mut stash_server = { |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let save_network_fut = |
| test_values.saved_networks.store(network_id.clone(), credential.clone()); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| |
| stash_server |
| }; |
| |
| // Make the client state machine report that it is not alive. |
| let (mut iface_manager, _sme_stream) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| // Setup the test to prevent a network selection from happening for whatever reason was specified. |
| match test_type { |
| NetworkSelectionMissingAttribute::AllAttributesPresent => { |
| // Currently the FakeScanRequester will panic if a scan is requested and no results |
| // are queued, so add something. There may be two scans from network selection. |
| exec.run_singlethreaded(test_values.scan_requester.add_scan_result(Ok(vec![]))); |
| exec.run_singlethreaded(test_values.scan_requester.add_scan_result(Ok(vec![]))); |
| } |
| NetworkSelectionMissingAttribute::IdleClient => { |
| // Make the client state machine report that it is alive. |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: true, |
| expected_connect_selection: None, |
| })); |
| } |
| NetworkSelectionMissingAttribute::SavedNetwork => { |
| // Remove the saved network so that there are no known networks to connect to. |
| let mut remove_network_fut = |
| pin!(test_values.saved_networks.remove(network_id.clone(), credential.clone())); |
| assert_variant!(exec.run_until_stalled(&mut remove_network_fut), Poll::Pending); |
| process_stash_delete(&mut exec, &mut stash_server); |
| } |
| NetworkSelectionMissingAttribute::NetworkSelectionInProgress => { |
| // Insert a future so that it looks like a scan is in progress. |
| iface_manager |
| .connection_selection_futures |
| .push(ready(Ok(ConnectionSelectionResponse::Autoconnect(None))).boxed()); |
| } |
| } |
| |
| { |
| // Run the future to completion. |
| let mut fut = pin!(initiate_automatic_connection_selection(&mut iface_manager)); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // Verify telemetry event if the condition is right |
| match test_type { |
| NetworkSelectionMissingAttribute::AllAttributesPresent => { |
| let event = |
| assert_variant!(test_values.telemetry_receiver.try_next(), Ok(Some(ev)) => ev); |
| assert_variant!( |
| event, |
| TelemetryEvent::StartEstablishConnection { reset_start_time: false } |
| ); |
| } |
| _ => { |
| assert_variant!(test_values.telemetry_receiver.try_next(), Err(_)); |
| } |
| } |
| |
| match test_type { |
| NetworkSelectionMissingAttribute::AllAttributesPresent => { |
| // Run forward connection selection futures to kick them off. |
| iface_manager.connection_selection_futures.iter_mut().for_each(|fut| { |
| assert_variant!(exec.run_until_stalled(fut), Poll::Pending); |
| }); |
| // Connection selector should receive request if all attributes are present. |
| assert_variant!(test_values.connection_selection_request_receiver.try_next(), Ok(Some(request)) => { |
| assert_variant!(request, ConnectionSelectionRequest::NewConnectionSelection {network_id, reason, responder} => { |
| assert_eq!(network_id, None); |
| assert_eq!(reason, client_types::ConnectReason::IdleInterfaceAutoconnect); |
| responder.send(Some(generate_random_scanned_candidate())).expect("failed to send selection"); |
| }); |
| }); |
| } |
| _ => { |
| // No connection selection request should be sent. |
| assert_variant!( |
| test_values.connection_selection_request_receiver.try_next(), |
| Err(_) |
| ); |
| } |
| } |
| |
| // Run all connection_selection futures to completion. |
| for mut connection_selection_future in iface_manager.connection_selection_futures.iter_mut() |
| { |
| assert_variant!( |
| exec.run_until_stalled(&mut connection_selection_future), |
| Poll::Ready(_) |
| ); |
| } |
| } |
| |
| #[fuchsia::test] |
| fn test_scan_result_backoff() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManagerService |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create a timer to periodically check to ensure that all client interfaces are connected. |
| let mut reconnect_monitor_interval: i64 = 1; |
| let mut connectivity_monitor_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(reconnect_monitor_interval)); |
| |
| // Simulate multiple failed scan attempts and ensure that the timer interval backs off as |
| // expected. |
| let expected_wait_times = |
| vec![2, 4, 8, MAX_AUTO_CONNECT_RETRY_SECONDS, MAX_AUTO_CONNECT_RETRY_SECONDS]; |
| |
| for i in 0..5 { |
| { |
| let fut = handle_automatic_connection_selection_results( |
| None, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| assert_eq!(reconnect_monitor_interval, expected_wait_times[i]); |
| } |
| } |
| |
| #[fuchsia::test] |
| fn test_reconnect_on_connection_selection_results() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an interface manager with an unconfigured client interface. |
| let (mut iface_manager, _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| // Setup for a reconnection attempt. |
| let mut reconnect_monitor_interval = 1; |
| let mut connectivity_monitor_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(reconnect_monitor_interval)); |
| |
| // Create a candidate network. |
| let ssid = TEST_SSID.clone(); |
| let network_id = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa); |
| let network = Some(client_types::ScannedCandidate { |
| network: network_id, |
| ..generate_random_scanned_candidate() |
| }); |
| |
| { |
| // Run reconnection attempt |
| let fut = handle_automatic_connection_selection_results( |
| network, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| |
| let mut fut = pin!(fut); |
| |
| // Expect a client SME proxy request |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let mut monitor_service_fut = test_values.monitor_service_stream.into_future(); |
| assert_variant!( |
| poll_service_req(&mut exec, &mut monitor_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceMonitorRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme_server, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(Ok(())).is_ok()); |
| |
| sme_server |
| } |
| ); |
| |
| // The future should then complete. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // The reconnect attempt should have seen an idle client interface and created a new client |
| // state machine future for it. |
| assert!(!iface_manager.fsm_futures.is_empty()); |
| |
| // There should not be any idle clients. |
| assert!(iface_manager.idle_clients().is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_idle_client_remains_after_failed_reconnection() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an interface manager with an unconfigured client interface. |
| let (mut iface_manager, _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| // Setup for a reconnection attempt. |
| let mut reconnect_monitor_interval = 1; |
| let mut connectivity_monitor_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(reconnect_monitor_interval)); |
| |
| // Create a candidate network. |
| let ssid = TEST_SSID.clone(); |
| let network_id = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa); |
| let network = Some(client_types::ScannedCandidate { |
| network: network_id, |
| ..generate_random_scanned_candidate() |
| }); |
| |
| { |
| // Run reconnection attempt |
| let fut = handle_automatic_connection_selection_results( |
| network, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| |
| let mut fut = pin!(fut); |
| |
| // Drop the device service stream so that the SME request fails. |
| drop(test_values.monitor_service_stream); |
| |
| // The future should then complete. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // There should still be an idle client |
| assert!(!iface_manager.idle_clients().is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_handle_bss_selection_for_connect_request_results() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an interface manager with an unconfigured client interface. |
| let (mut iface_manager, _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| |
| // Create a request |
| let mut connect_selection = generate_connect_selection(); |
| connect_selection.reason = client_types::ConnectReason::FidlConnectRequest; |
| let request = ConnectAttemptRequest::new( |
| connect_selection.target.network.clone(), |
| connect_selection.target.credential.clone(), |
| client_types::ConnectReason::FidlConnectRequest, |
| ); |
| |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: true, |
| expected_connect_selection: Some(connect_selection.clone()), |
| })); |
| |
| assert!(!iface_manager.idle_clients().is_empty()); |
| |
| { |
| let fut = handle_connection_selection_for_connect_request_results( |
| request, |
| Some(connect_selection.target.clone()), |
| &mut iface_manager, |
| ); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // There should not be any idle clients. |
| assert!(iface_manager.idle_clients().is_empty()); |
| assert_eq!(iface_manager.clients[0].config, Some(connect_selection.target.network.clone())); |
| } |
| |
| #[fuchsia::test] |
| fn test_terminated_client() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create a fake network entry so that a reconnect will be attempted. |
| let network_id = NetworkIdentifier::new(TEST_SSID.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server()); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| { |
| let save_network_fut = |
| test_values.saved_networks.store(network_id.clone(), credential.clone()); |
| let mut save_network_fut = pin!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| } |
| |
| // Create an interface manager with an unconfigured client interface. |
| let (mut iface_manager, _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: true, |
| is_alive: false, |
| expected_connect_selection: None, |
| })); |
| |
| // Create remaining boilerplate to call handle_terminated_state_machine. |
| let metadata = StateMachineMetadata { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Client, |
| iface_id: TEST_CLIENT_IFACE_ID, |
| }; |
| |
| { |
| let mut fut = pin!(handle_terminated_state_machine(metadata, &mut iface_manager)); |
| assert_eq!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // Verify that there is a disconnected client. |
| assert!(iface_manager.idle_clients().contains(&TEST_CLIENT_IFACE_ID)); |
| |
| // Verify that a scan has been kicked off. |
| assert!(!iface_manager.connection_selection_futures.is_empty()); |
| } |
| |
| #[fuchsia::test] |
| fn test_terminated_ap() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an interface manager with an unconfigured client interface. |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, true); |
| |
| // Create remaining boilerplate to call handle_terminated_state_machine. |
| let metadata = StateMachineMetadata { |
| role: fidl_fuchsia_wlan_common::WlanMacRole::Ap, |
| iface_id: TEST_AP_IFACE_ID, |
| }; |
| |
| { |
| let mut fut = pin!(handle_terminated_state_machine(metadata, &mut iface_manager)); |
| assert_eq!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // Verify that the IfaceManagerService does not have an AP interface. |
| assert!(iface_manager.aps.is_empty()); |
| } |
| |
| /// Test that if PhyManager knows of a client iface that supports WPA3, has_wpa3_capable_client |
| /// will return true. |
| #[fuchsia::test] |
| fn test_has_wpa3_capable_client() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: Some(0), |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| let fut = iface_manager.has_wpa3_capable_client(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(true)); |
| } |
| |
| /// Test that if PhyManager does not have a WPA3 client iface, has_wpa3_capable_client will |
| /// return false. |
| #[fuchsia::test] |
| fn test_has_no_wpa3_capable_iface() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| iface_manager.phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![TEST_CLIENT_IFACE_ID], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| let fut = iface_manager.has_wpa3_capable_client(); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(false)); |
| } |
| |
| /// Tests the operation of setting the country code. The test cases of interest are: |
| /// 1. Client connections and APs are initially enabled and all operations succeed. |
| /// 2. Client connections are initially enabled and all operations succeed except restarting |
| /// client connections. |
| /// * In this scenario, the country code has been properly applied and the device should be |
| /// allowed to continue running. Higher layers can optionally re-enable client |
| /// connections to recover. |
| /// 3. APs are initially enabled and all operations succeed except restarting the AP. |
| /// * As in the above scenario, the device can be allowed to continue running and the API |
| /// client can attempt to restart the AP manually. |
| /// 4. Stop client connections fails. |
| /// 5. Stop all APs fails. |
| /// 6. Set country fails. |
| #[test_case( |
| true, true, true, true, true, TestType::Pass; |
| "client and AP enabled and all operations succeed" |
| )] |
| #[test_case( |
| true, false, true, true, false, TestType::Pass; |
| "client enabled and restarting client fails" |
| )] |
| #[test_case( |
| false, true, true, true, false, TestType::Pass; |
| "AP enabled and start AP fails after setting country" |
| )] |
| #[test_case( |
| true, false, false, true, true, TestType::Fail; |
| "stop client connections fails" |
| )] |
| #[test_case( |
| false, true, false, true, true, TestType::Fail; |
| "stop APs fails" |
| )] |
| #[test_case( |
| false, true, true, false, true, TestType::Fail; |
| "set country fails" |
| )] |
| #[fuchsia::test(add_test_attr = false)] |
| fn set_country_service_test( |
| client_connections_enabled: bool, |
| ap_enabled: bool, |
| destroy_iface_ok: bool, |
| set_country_ok: bool, |
| create_iface_ok: bool, |
| test_type: TestType, |
| ) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| |
| // Seed a FakePhyManager with the test configuration information and provide the PhyManager |
| // to a new IfaceManagerService to test the behavior given the configuration. |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok, |
| destroy_iface_ok, |
| set_country_ok, |
| wpa3_iface: None, |
| country_code: None, |
| client_connections_enabled, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager.clone(), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.monitor_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester.clone(), |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender.clone(), |
| test_values.defect_sender, |
| ); |
| |
| // If the test calls for it, create an AP interface to test that the IfaceManager preserves |
| // the configuration and restores it after setting the country code. |
| if ap_enabled { |
| let fake_ap = FakeAp { start_succeeds: true, stop_succeeds: true, exit_succeeds: true }; |
| let ap_container = ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: Some(create_ap_config(&TEST_SSID, TEST_PASSWORD)), |
| ap_state_machine: Box::new(fake_ap), |
| enabled_time: None, |
| }; |
| iface_manager.aps.push(ap_container); |
| } |
| |
| // Call set_country and drive the operation to completion. |
| let (set_country_sender, set_country_receiver) = oneshot::channel(); |
| let req = SetCountryRequest { country_code: Some([0, 0]), responder: set_country_sender }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::SetCountry(req)); |
| |
| run_service_test( |
| &mut exec, |
| iface_manager, |
| req, |
| set_country_receiver, |
| test_values.monitor_service_stream, |
| test_type, |
| ); |
| |
| if destroy_iface_ok && set_country_ok { |
| let phy_manager_fut = phy_manager.lock(); |
| let mut phy_manager_fut = pin!(phy_manager_fut); |
| assert_variant!( |
| exec.run_until_stalled(&mut phy_manager_fut), |
| Poll::Ready(phy_manager) => { |
| assert_eq!(phy_manager.country_code, Some([0, 0])) |
| } |
| ); |
| } |
| } |
| |
| fn fake_successful_connect_result() -> fidl_fuchsia_wlan_sme::ConnectResult { |
| fidl_fuchsia_wlan_sme::ConnectResult { |
| code: fidl_fuchsia_wlan_ieee80211::StatusCode::Success, |
| is_credential_rejected: false, |
| is_reconnect: false, |
| } |
| } |
| |
| /// 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, |
| }, |
| } |
| } |
| |
| /// Ensure that defect reports are passed through to the PhyManager. |
| #[fuchsia::test] |
| fn test_record_defect() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| |
| { |
| let mut defect_fut = initiate_record_defect( |
| phy_manager.clone(), |
| Defect::Phy(PhyFailure::IfaceCreationFailure { phy_id: 2 }), |
| ); |
| |
| // The future should complete immediately. |
| assert_variant!( |
| exec.run_until_stalled(&mut defect_fut), |
| Poll::Ready(IfaceManagerOperation::ReportDefect) |
| ); |
| } |
| |
| // Verify that the defect has been recorded. |
| let phy_manager_fut = phy_manager.lock(); |
| let mut phy_manager_fut = pin!(phy_manager_fut); |
| assert_variant!( |
| exec.run_until_stalled(&mut phy_manager_fut), |
| Poll::Ready(phy_manager) => { |
| assert_eq!(phy_manager.defects, vec![Defect::Phy(PhyFailure::IfaceCreationFailure {phy_id: 2})]) |
| } |
| ); |
| } |
| |
| /// Ensure that state machine defects are passed through to the PhyManager. |
| #[fuchsia::test] |
| fn test_record_state_machine_defect() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let test_values = test_setup(&mut exec); |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| let iface_manager = IfaceManagerService::new( |
| phy_manager.clone(), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.monitor_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester.clone(), |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender.clone(), |
| test_values.defect_sender.clone(), |
| ); |
| |
| // Send a defect to the IfaceManager service loop. |
| test_values |
| .defect_sender |
| .unbounded_send(Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 0 })) |
| .expect("failed to send defect notification"); |
| |
| // Run the IfaceManager service so that it can process the defect. |
| let (_, receiver) = mpsc::channel(0); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Verify that the defect has been recorded. |
| let phy_manager_fut = phy_manager.lock(); |
| let mut phy_manager_fut = pin!(phy_manager_fut); |
| assert_variant!( |
| exec.run_until_stalled(&mut phy_manager_fut), |
| Poll::Ready(phy_manager) => { |
| assert_eq!(phy_manager.defects, vec![Defect::Iface(IfaceFailure::ApStartFailure {iface_id: 0})]) |
| } |
| ); |
| } |
| |
| /// Verify that recovery actions are acknowledged by the IfaceManager service loop. |
| #[fuchsia::test] |
| fn test_receive_recovery_summaries() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| let mut test_values = test_setup(&mut exec); |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: true, |
| destroy_iface_ok: true, |
| wpa3_iface: None, |
| set_country_ok: true, |
| country_code: None, |
| client_connections_enabled: true, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| let iface_manager = IfaceManagerService::new( |
| phy_manager.clone(), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.monitor_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.connection_selection_requester.clone(), |
| test_values.local_roam_manager.clone(), |
| test_values.telemetry_sender.clone(), |
| test_values.defect_sender.clone(), |
| ); |
| |
| // Send a recovery summary to the IfaceManager service loop. |
| let defect = Defect::Iface(IfaceFailure::ApStartFailure { iface_id: 0 }); |
| let action = |
| recovery::RecoveryAction::PhyRecovery(recovery::PhyRecoveryOperation::ResetPhy { |
| phy_id: 0, |
| }); |
| test_values |
| .recovery_sender |
| .try_send(recovery::RecoverySummary { defect, action }) |
| .expect("failed to send recovery summary"); |
| |
| // Run the IfaceManager service so that it can process the recovery summary. |
| let (_, receiver) = mpsc::channel(0); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| receiver, |
| test_values.defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| |
| // Verify that the recovery summary has been recorded. |
| let phy_manager_fut = phy_manager.lock(); |
| let mut phy_manager_fut = pin!(phy_manager_fut); |
| assert_variant!( |
| exec.run_until_stalled(&mut phy_manager_fut), |
| Poll::Ready(phy_manager) => { |
| assert_eq!( |
| phy_manager.recoveries, |
| vec![recovery::RecoverySummary { defect, action}] |
| ) |
| } |
| ); |
| } |
| |
| // Demonstrates that the token passed to attempt_atomic_operation is held until the wrapped |
| // future completes. |
| #[fuchsia::test] |
| fn test_attempt_atomic_operation() { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // The mpsc channel pair represents a requestor/worker pair to be managed by an |
| // AtomicOneshotReceiver. |
| let (mpsc_sender, mpsc_receiver) = mpsc::unbounded(); |
| let mut mpsc_receiver = atomic_oneshot_stream::AtomicOneshotStream::new(mpsc_receiver); |
| |
| // A oneshot pair is created to allow the test to synchronize around some work that the |
| // worker will do while holding the token from the AtomicOneshotReceiver. |
| let (oneshot_sender, oneshot_receiver) = oneshot::channel::<()>(); |
| |
| // Create a dummy future that waits on the receiver and just returns nil. This will be the |
| // "work" that the worker does when it receives a request. |
| let fut = Box::pin(oneshot_receiver); |
| |
| // The mpsc_sender will send two requests to show that the second one is not processed |
| // until the atomic operation completes. |
| mpsc_sender.unbounded_send(()).expect("failed to send first message"); |
| mpsc_sender.unbounded_send(()).expect("failed to send second message"); |
| |
| // Grab the request and the token from the receiving end. |
| let token = { |
| let mut oneshot_stream = mpsc_receiver.get_atomic_oneshot_stream(); |
| assert_variant!( |
| exec.run_until_stalled(&mut oneshot_stream.next()), |
| Poll::Ready(Some((token, ()))) => token |
| ) |
| }; |
| |
| // Throw the future and token into the atomic operation wrapper. Verify that the future is |
| // waiting for the oneshot sender to send something. |
| let mut fut = attempt_atomic_operation(fut, token); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Demonstrate that the AtomicOneshotStream is not able to produce the second request yet. |
| { |
| let mut oneshot_stream = mpsc_receiver.get_atomic_oneshot_stream(); |
| assert_variant!(exec.run_until_stalled(&mut oneshot_stream.next()), Poll::Ready(None)); |
| } |
| |
| // Send on the oneshot sender so that the wrapped future can run to completion and the |
| // token will be dropped, allowing new requests to be received. |
| oneshot_sender.send(()).expect("failed to send oneshot message"); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(_)); |
| |
| let mut oneshot_stream = mpsc_receiver.get_atomic_oneshot_stream(); |
| assert_variant!(exec.run_until_stalled(&mut oneshot_stream.next()), Poll::Ready(Some(_))); |
| } |
| |
| fn test_atomic_operation<T: Debug>( |
| req: IfaceManagerRequest, |
| mut receiver: oneshot::Receiver<T>, |
| ) { |
| let mut exec = fuchsia_async::TestExecutor::new(); |
| |
| // Create an IfaceManager that has both a fake client and a fake AP state machine. Hold on |
| // to the receiving ends of the state machine command channels. The receiving ends will |
| // never be serviced, resulting in the atomic operations stalling while holding the |
| // AtomicOneshotStream Token. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| |
| // Setup the fake client. |
| let (client_sender, client_receiver) = mpsc::channel(1); |
| iface_manager.clients[0].client_state_machine = |
| Some(Box::new(client_fsm::Client::new(client_sender))); |
| |
| // Setup the fake AP. |
| let (ap_sender, ap_receiver) = mpsc::channel(1); |
| iface_manager.aps.push(ApIfaceContainer { |
| iface_id: TEST_AP_IFACE_ID, |
| config: Some(create_ap_config(&TEST_SSID, TEST_PASSWORD)), |
| ap_state_machine: Box::new(ap_fsm::AccessPoint::new(ap_sender)), |
| enabled_time: None, |
| }); |
| |
| // For all of the operations except SetCountry, the atomic operation can be stalled by |
| // mocking out the state machine interactions. For SetCountry, instead make it look as if |
| // client connections are disabled and there are no APs. This ensures that the second half |
| // of the operations which restores interfaces does not trigger. |
| match &req { |
| &IfaceManagerRequest::AtomicOperation(AtomicOperation::SetCountry(_)) => { |
| let phy_manager = Arc::new(Mutex::new(FakePhyManager { |
| create_iface_ok: false, |
| destroy_iface_ok: false, |
| wpa3_iface: None, |
| set_country_ok: false, |
| country_code: None, |
| client_connections_enabled: false, |
| client_ifaces: vec![], |
| defects: vec![], |
| recoveries: vec![], |
| })); |
| iface_manager.phy_manager = phy_manager; |
| let _ = iface_manager.aps.drain(..); |
| } |
| _ => {} |
| } |
| |
| // Start the service loop |
| let (mut req_sender, req_receiver) = mpsc::channel(1); |
| let (_defect_sender, defect_receiver) = mpsc::unbounded(); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| req_receiver, |
| defect_receiver, |
| test_values.recovery_receiver, |
| ); |
| let mut serve_fut = pin!(serve_fut); |
| |
| // Make the atomic call and run the service future until it stalls. |
| req_sender.try_send(req).expect("failed to make atomic request"); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Pending,); |
| |
| // Make a second IfaceManager call and observe that there is no response. |
| let (idle_iface_sender, mut idle_iface_receiver) = oneshot::channel(); |
| let req = HasIdleIfaceRequest { responder: idle_iface_sender }; |
| req_sender |
| .try_send(IfaceManagerRequest::HasIdleIface(req)) |
| .expect("failed to send idle client check"); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut idle_iface_receiver), Poll::Pending,); |
| |
| // It doesn't matter whether the state machines respond successfully, just that the |
| // operation finishes so that the atomic operation can progress. Simply drop the receivers |
| // to demonstrate that behavior. |
| drop(client_receiver); |
| drop(ap_receiver); |
| |
| // The atomic portion of the operation should now complete. |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(_),); |
| |
| // The second operation should also get a response. |
| assert_variant!(exec.run_until_stalled(&mut idle_iface_receiver), Poll::Ready(_),); |
| } |
| |
| #[fuchsia::test] |
| fn test_atomic_disconnect() { |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = DisconnectRequest { |
| network_id: client_types::NetworkIdentifier { |
| ssid: TEST_SSID.clone(), |
| security_type: client_types::SecurityType::Wpa, |
| }, |
| reason: client_types::DisconnectReason::NetworkUnsaved, |
| responder: ack_sender, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::Disconnect(req)); |
| test_atomic_operation(req, ack_receiver); |
| } |
| |
| #[fuchsia::test] |
| fn test_atomic_stop_client_connections() { |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = StopClientConnectionsRequest { |
| responder: ack_sender, |
| reason: client_types::DisconnectReason::FidlStopClientConnectionsRequest, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopClientConnections(req)); |
| test_atomic_operation(req, ack_receiver); |
| } |
| |
| #[fuchsia::test] |
| fn test_atomic_stop_all_aps() { |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = StopAllApsRequest { responder: ack_sender }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopAllAps(req)); |
| test_atomic_operation(req, ack_receiver); |
| } |
| |
| #[fuchsia::test] |
| fn test_atomic_stop_ap() { |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = StopApRequest { |
| ssid: TEST_SSID.clone(), |
| password: TEST_PASSWORD.as_bytes().to_vec(), |
| responder: ack_sender, |
| }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::StopAp(req)); |
| test_atomic_operation(req, ack_receiver); |
| } |
| |
| #[fuchsia::test] |
| fn test_atomic_set_country() { |
| let (ack_sender, ack_receiver) = oneshot::channel(); |
| let req = SetCountryRequest { country_code: Some([0, 0]), responder: ack_sender }; |
| let req = IfaceManagerRequest::AtomicOperation(AtomicOperation::SetCountry(req)); |
| test_atomic_operation(req, ack_receiver); |
| } |
| } |