| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| access_point::{state_machine as ap_fsm, state_machine::AccessPointApi, types as ap_types}, |
| client::{ |
| network_selection::NetworkSelector, state_machine as client_fsm, types as client_types, |
| }, |
| config_management::SavedNetworksManager, |
| mode_management::{ |
| iface_manager_api::IfaceManagerApi, |
| iface_manager_types::*, |
| phy_manager::{CreateClientIfacesReason, PhyManagerApi}, |
| }, |
| util::{future_with_metadata, listener}, |
| }, |
| anyhow::{format_err, Error}, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_wlan_common, fidl_fuchsia_wlan_device, fidl_fuchsia_wlan_policy, |
| fidl_fuchsia_wlan_sme, fuchsia_async as fasync, |
| fuchsia_cobalt::CobaltSender, |
| fuchsia_zircon as zx, |
| futures::{ |
| channel::{mpsc, oneshot}, |
| future::{ready, BoxFuture}, |
| lock::Mutex, |
| select, |
| stream::FuturesUnordered, |
| FutureExt, StreamExt, |
| }, |
| log::{error, info, warn}, |
| std::{sync::Arc, unimplemented}, |
| void::Void, |
| wlan_metrics_registry, |
| }; |
| |
| // 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; |
| |
| /// 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>>, |
| driver_features: Vec<fidl_fuchsia_wlan_common::DriverFeature>, |
| } |
| |
| 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_device::MacRole, |
| } |
| |
| async fn create_client_state_machine( |
| iface_id: u16, |
| dev_svc_proxy: &mut fidl_fuchsia_wlan_device_service::DeviceServiceProxy, |
| client_update_sender: listener::ClientListenerMessageSender, |
| saved_networks: Arc<SavedNetworksManager>, |
| network_selector: Arc<NetworkSelector>, |
| connect_req: Option<(client_types::ConnectRequest, oneshot::Sender<()>)>, |
| cobalt_api: CobaltSender, |
| wpa3_supported: bool, |
| ) -> Result< |
| ( |
| Box<dyn client_fsm::ClientApi + Send>, |
| future_with_metadata::FutureWithMetadata<(), StateMachineMetadata>, |
| ), |
| Error, |
| > { |
| // 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()?; |
| let status = dev_svc_proxy.get_client_sme(iface_id, remote).await?; |
| fuchsia_zircon::ok(status)?; |
| 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_req, |
| network_selector, |
| cobalt_api, |
| wpa3_supported, |
| ); |
| |
| let metadata = |
| StateMachineMetadata { iface_id, role: fidl_fuchsia_wlan_device::MacRole::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_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceServiceProxy, |
| clients: Vec<ClientIfaceContainer>, |
| aps: Vec<ApIfaceContainer>, |
| saved_networks: Arc<SavedNetworksManager>, |
| network_selector: Arc<NetworkSelector>, |
| fsm_futures: |
| FuturesUnordered<future_with_metadata::FutureWithMetadata<(), StateMachineMetadata>>, |
| cobalt_api: CobaltSender, |
| clients_enabled_time: Option<zx::Time>, |
| } |
| |
| impl IfaceManagerService { |
| pub fn new( |
| phy_manager: Arc<Mutex<dyn PhyManagerApi + Send>>, |
| client_update_sender: listener::ClientListenerMessageSender, |
| ap_update_sender: listener::ApListenerMessageSender, |
| dev_svc_proxy: fidl_fuchsia_wlan_device_service::DeviceServiceProxy, |
| saved_networks: Arc<SavedNetworksManager>, |
| network_selector: Arc<NetworkSelector>, |
| cobalt_api: CobaltSender, |
| ) -> Self { |
| IfaceManagerService { |
| phy_manager: phy_manager.clone(), |
| client_update_sender, |
| ap_update_sender, |
| dev_svc_proxy, |
| clients: Vec::new(), |
| aps: Vec::new(), |
| saved_networks: saved_networks, |
| network_selector, |
| fsm_futures: FuturesUnordered::new(), |
| cobalt_api, |
| clients_enabled_time: None, |
| } |
| } |
| |
| /// 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_in_features(&client_container.driver_features) |
| }) { |
| 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, remote) = create_proxy()?; |
| let status = self.dev_svc_proxy.get_client_sme(iface_id, remote).await?; |
| fuchsia_zircon::ok(status)?; |
| |
| // Get the driver features for this iface. |
| let (status, iface_info) = self.dev_svc_proxy.query_iface(iface_id).await?; |
| fuchsia_zircon::ok(status)?; |
| let iface_info = iface_info |
| .ok_or_else(|| format_err!("Error occurred getting iface's driver features"))?; |
| |
| Ok(ClientIfaceContainer { |
| iface_id: iface_id, |
| sme_proxy, |
| config: None, |
| client_state_machine: None, |
| driver_features: iface_info.driver_features, |
| }) |
| } |
| |
| /// 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, remote) = create_proxy()?; |
| let status = self.dev_svc_proxy.get_ap_sme(iface_id, remote).await?; |
| fuchsia_zircon::ok(status)?; |
| |
| // 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, |
| self.ap_update_sender.clone(), |
| ) |
| .boxed(); |
| |
| // Begin running and monitoring the AP state machine future. |
| let metadata = StateMachineMetadata { |
| iface_id: iface_id, |
| role: fidl_fuchsia_wlan_device::MacRole::Ap, |
| }; |
| let fut = future_with_metadata::FutureWithMetadata::new(metadata, state_machine_fut); |
| self.fsm_futures.push(fut); |
| |
| Ok(ApIfaceContainer { |
| iface_id: 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>> { |
| // 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 connect( |
| &mut self, |
| connect_req: client_types::ConnectRequest, |
| ) -> Result<oneshot::Receiver<()>, Error> { |
| // Get a ClientIfaceContainer. |
| let mut client_iface = |
| if connect_req.target.network.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(connect_req.target.network.clone()); |
| |
| // Create the connection request |
| let (sender, receiver) = oneshot::channel(); |
| |
| // 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(connect_req, sender)?; |
| } |
| None => { |
| // Check if the iface supports WPA3 |
| let wpa3_supported = wpa3_in_features(&client_iface.driver_features); |
| // Create the state machine and controller. |
| let (new_client, fut) = create_client_state_machine( |
| client_iface.iface_id, |
| &mut self.dev_svc_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| self.network_selector.clone(), |
| Some((connect_req, sender)), |
| self.cobalt_api.clone(), |
| wpa3_supported, |
| ) |
| .await?; |
| client_iface.client_state_machine = Some(new_client); |
| |
| // Begin running and monitoring the client state machine future. |
| self.fsm_futures.push(fut); |
| } |
| } |
| |
| self.clients.push(client_iface); |
| Ok(receiver) |
| } |
| |
| 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_req: client_types::ConnectRequest, |
| ) -> Result<(), Error> { |
| let wpa3_supported = self.has_wpa3_capable_client().await; |
| 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 (sender, _) = oneshot::channel(); |
| let (new_client, fut) = create_client_state_machine( |
| client.iface_id, |
| &mut self.dev_svc_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| self.network_selector.clone(), |
| Some((connect_req.clone(), sender)), |
| self.cobalt_api.clone(), |
| wpa3_supported, |
| ) |
| .await?; |
| |
| self.fsm_futures.push(fut); |
| client.config = Some(connect_req.target.network); |
| client.client_state_machine = Some(new_client); |
| break; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn handle_added_iface(&mut self, iface_id: u16) -> Result<(), Error> { |
| let (status, iface_info) = self.dev_svc_proxy.query_iface(iface_id).await?; |
| fuchsia_zircon::ok(status)?; |
| let iface_info = match iface_info { |
| Some(iface_info) => iface_info, |
| None => return Err(format_err!("no iface information available for {:?}", iface_id)), |
| }; |
| |
| match iface_info.role { |
| fidl_fuchsia_wlan_device::MacRole::Client => { |
| // If this client has already been recorded, take no action. |
| for client in self.clients.iter() { |
| if client.iface_id == iface_id { |
| return Ok(()); |
| } |
| } |
| |
| let mut client_iface = self.get_client(Some(iface_id)).await?; |
| let wpa3_supported = self.has_wpa3_capable_client().await; |
| |
| // 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_svc_proxy, |
| self.client_update_sender.clone(), |
| self.saved_networks.clone(), |
| self.network_selector.clone(), |
| None, |
| self.cobalt_api.clone(), |
| wpa3_supported, |
| ) |
| .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_device::MacRole::Ap => { |
| let ap_iface = self.get_ap(Some(iface_id)).await?; |
| self.aps.push(ap_iface); |
| } |
| fidl_fuchsia_wlan_device::MacRole::Mesh => { |
| // Mesh roles are not currently supported. |
| } |
| } |
| |
| 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); |
| } |
| }; |
| } |
| } |
| |
| // 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 scan( |
| &mut self, |
| mut scan_request: fidl_fuchsia_wlan_sme::ScanRequest, |
| ) -> Result<fidl_fuchsia_wlan_sme::ScanTransactionProxy, Error> { |
| let client_iface = self.get_client(None).await?; |
| |
| let (local, remote) = fidl::endpoints::create_proxy()?; |
| let scan_result = client_iface.sme_proxy.scan(&mut scan_request, remote); |
| |
| self.clients.push(client_iface); |
| |
| match scan_result { |
| Ok(()) => Ok(local), |
| Err(e) => Err(format_err!("failed to scan: {:?}", e)), |
| } |
| } |
| |
| async fn get_sme_proxy_for_scan( |
| &mut self, |
| ) -> Result<fidl_fuchsia_wlan_sme::ClientSmeProxy, Error> { |
| let client_iface = self.get_client(None).await?; |
| let sme_proxy = client_iface.sme_proxy.clone(); |
| self.clients.push(client_iface); |
| Ok(sme_proxy) |
| } |
| |
| fn stop_client_connections( |
| &mut self, |
| reason: client_types::DisconnectReason, |
| ) -> BoxFuture<'static, Result<(), Error>> { |
| if let Some(start_time) = self.clients_enabled_time.take() { |
| let elapsed_time = zx::Time::get_monotonic() - start_time; |
| self.cobalt_api.log_elapsed_time( |
| wlan_metrics_registry::CLIENT_CONNECTIONS_ENABLED_DURATION_METRIC_ID, |
| (), |
| elapsed_time.into_micros(), |
| ); |
| } |
| |
| 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: Some(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; |
| match phy_manager |
| .create_all_client_ifaces(CreateClientIfacesReason::StartClientConnections) |
| .await |
| { |
| Ok(_) => { |
| // Send an update to the update listener indicating that client connections are now |
| // enabled. |
| let update = listener::ClientStateUpdate { |
| state: Some(fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsEnabled), |
| networks: vec![], |
| }; |
| if let Err(e) = self |
| .client_update_sender |
| .unbounded_send(listener::Message::NotifyListeners(update)) |
| { |
| error!("Failed to send state update: {:?}", e) |
| }; |
| } |
| Err((_, phy_manager_error)) => { |
| return Err(format_err!( |
| "could not start client connection {:?}", |
| phy_manager_error |
| )); |
| } |
| } |
| |
| if self.clients_enabled_time.is_none() { |
| self.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| } |
| 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: Vec<u8>, |
| 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 elapsed_time = zx::Time::get_monotonic() - start_time; |
| self.cobalt_api.log_elapsed_time( |
| wlan_metrics_registry::ACCESS_POINT_ENABLED_DURATION_METRIC_ID, |
| (), |
| elapsed_time.into_micros(), |
| ); |
| } |
| |
| let fut = async move { |
| 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 elapsed_time = zx::Time::get_monotonic() - start_time; |
| self.cobalt_api.log_elapsed_time( |
| wlan_metrics_registry::ACCESS_POINT_ENABLED_DURATION_METRIC_ID, |
| (), |
| elapsed_time.into_micros(), |
| ); |
| } |
| } |
| |
| 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 list of DriverFeatures contains one that indicates WPA3 support. |
| pub fn wpa3_in_features(driver_features: &Vec<fidl_fuchsia_wlan_common::DriverFeature>) -> bool { |
| driver_features.iter().any(|feature| { |
| feature == &fidl_fuchsia_wlan_common::DriverFeature::SaeDriverAuth |
| || feature == &fidl_fuchsia_wlan_common::DriverFeature::SaeSmeAuth |
| }) |
| } |
| |
| async fn initiate_network_selection( |
| iface_manager: &IfaceManagerService, |
| iface_manager_client: Arc<Mutex<dyn IfaceManagerApi + Send>>, |
| network_selector: Arc<NetworkSelector>, |
| network_selection_futures: &mut FuturesUnordered< |
| BoxFuture<'static, Option<client_types::ConnectionCandidate>>, |
| >, |
| ) { |
| if !iface_manager.idle_clients().is_empty() |
| && iface_manager.saved_networks.known_network_count().await > 0 |
| && network_selection_futures.is_empty() |
| { |
| info!("Initiating network selection for idle client interface."); |
| let fut = async move { |
| let ignore_list = vec![]; |
| network_selector |
| .find_best_connection_candidate(iface_manager_client.clone(), &ignore_list) |
| .await |
| }; |
| network_selection_futures.push(fut.boxed()); |
| } |
| } |
| |
| async fn handle_network_selection_results( |
| network_selection_result: Option<client_types::ConnectionCandidate>, |
| iface_manager: &mut IfaceManagerService, |
| reconnect_monitor_interval: &mut i64, |
| connectivity_monitor_timer: &mut fasync::Interval, |
| ) { |
| if let Some(connection_candidate) = network_selection_result { |
| *reconnect_monitor_interval = 1; |
| |
| let connect_req = client_types::ConnectRequest { |
| target: connection_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_req.clone()).await |
| { |
| warn!("Could not reconnect iface {}: {:?}", iface_id, e); |
| } |
| } |
| } |
| } else { |
| info!("No saved networks available to reconnect to"); |
| *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)); |
| } |
| |
| async fn handle_terminated_state_machine( |
| terminated_fsm: StateMachineMetadata, |
| iface_manager: &mut IfaceManagerService, |
| iface_manager_client: Arc<Mutex<dyn IfaceManagerApi + Send>>, |
| selector: Arc<NetworkSelector>, |
| network_selection_futures: &mut FuturesUnordered< |
| BoxFuture<'static, Option<client_types::ConnectionCandidate>>, |
| >, |
| ) { |
| match terminated_fsm.role { |
| fidl_fuchsia_wlan_device::MacRole::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_device::MacRole::Client => { |
| iface_manager.record_idle_client(terminated_fsm.iface_id); |
| initiate_network_selection( |
| &iface_manager, |
| iface_manager_client.clone(), |
| selector.clone(), |
| network_selection_futures, |
| ) |
| .await; |
| } |
| fidl_fuchsia_wlan_device::MacRole::Mesh => { |
| // Not yet supported. |
| 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"); |
| } |
| } |
| |
| pub(crate) async fn serve_iface_manager_requests( |
| mut iface_manager: IfaceManagerService, |
| iface_manager_client: Arc<Mutex<dyn IfaceManagerApi + Send>>, |
| network_selector: Arc<NetworkSelector>, |
| mut requests: mpsc::Receiver<IfaceManagerRequest>, |
| ) -> Result<Void, 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(); |
| |
| // Scans will be initiated to perform network selection if clients become disconnected. |
| let mut network_selection_futures = FuturesUnordered::new(); |
| |
| // 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 { |
| 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, |
| iface_manager_client.clone(), |
| network_selector.clone(), |
| &mut network_selection_futures, |
| ).await; |
| }, |
| () = connectivity_monitor_timer.select_next_some() => { |
| initiate_network_selection( |
| &iface_manager, |
| iface_manager_client.clone(), |
| network_selector.clone(), |
| &mut network_selection_futures |
| ).await; |
| }, |
| op = operation_futures.select_next_some() => match op { |
| IfaceManagerOperation::ConfigureStateMachine => {}, |
| IfaceManagerOperation::SetCountry(previous_state) => { |
| restore_state_after_setting_country_code( |
| &mut iface_manager, |
| previous_state |
| ).await; |
| } |
| }, |
| network_selection_result = network_selection_futures.select_next_some() => { |
| handle_network_selection_results( |
| network_selection_result, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer |
| ).await; |
| }, |
| req = requests.select_next_some() => { |
| match req { |
| IfaceManagerRequest::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 |
| }; |
| operation_futures.push(disconnect_fut.boxed()); |
| } |
| IfaceManagerRequest::Connect(ConnectRequest { request, responder }) => { |
| if responder.send(iface_manager.connect(request).await).is_err() { |
| error!("could not respond to ConnectRequest"); |
| } |
| } |
| IfaceManagerRequest::RecordIdleIface(RecordIdleIfaceRequest { iface_id, responder } ) => { |
| if responder.send(iface_manager.record_idle_client(iface_id)).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 }) => { |
| if responder.send(iface_manager.handle_removed_iface(iface_id).await).is_err() { |
| error!("could not respond to RemoveIfaceRequest"); |
| } |
| } |
| IfaceManagerRequest::Scan(ScanRequest { scan_request, responder }) => { |
| if responder.send(iface_manager.scan( scan_request).await).is_err() { |
| error!("could not respond to ScanRequest"); |
| } |
| } |
| IfaceManagerRequest::GetScanProxy(ScanProxyRequest { responder }) => { |
| if responder.send(iface_manager.get_sme_proxy_for_scan().await).is_err() { |
| error!("could not respond to ScanRequest"); |
| } |
| } |
| IfaceManagerRequest::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 |
| }; |
| operation_futures.push(stop_client_connections_fut.boxed()); |
| } |
| 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::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 |
| }; |
| operation_futures.push(stop_ap_fut.boxed()); |
| } |
| IfaceManagerRequest::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 |
| }; |
| operation_futures.push(stop_all_aps_fut.boxed()); |
| } |
| IfaceManagerRequest::HasWpa3Iface(HasWpa3IfaceRequest { responder }) => { |
| if responder.send(iface_manager.has_wpa3_capable_client().await).is_err() { |
| error!("could not respond to HasWpa3IfaceRequest"); |
| } |
| |
| } |
| IfaceManagerRequest::SetCountry(req) => { |
| let regulatory_fut = |
| initiate_set_country(&mut iface_manager, req); |
| operation_futures.push(regulatory_fut); |
| } |
| }; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| access_point::types, |
| client::{scan::ScanResultUpdate, types as client_types}, |
| config_management::{Credential, NetworkIdentifier, SecurityType}, |
| mode_management::phy_manager::{self, PhyManagerError}, |
| regulatory_manager::REGION_CODE_LEN, |
| util::{ |
| logger::set_logger_for_test, |
| testing::{ |
| create_mock_cobalt_sender, create_mock_cobalt_sender_and_receiver, |
| generate_random_bss_desc, poll_sme_req, |
| }, |
| }, |
| }, |
| async_trait::async_trait, |
| eui48::MacAddress, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_cobalt::CobaltEvent, |
| fidl_fuchsia_stash as fidl_stash, |
| fuchsia_async::Executor, |
| fuchsia_inspect::{self as inspect}, |
| futures::{ |
| channel::mpsc, |
| stream::{StreamExt, StreamFuture}, |
| task::Poll, |
| TryFutureExt, TryStreamExt, |
| }, |
| pin_utils::pin_mut, |
| rand::{distributions::Alphanumeric, thread_rng, Rng}, |
| tempfile::TempDir, |
| test_case::test_case, |
| wlan_common::{ |
| assert_variant, |
| channel::{Cbw, Phy}, |
| 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. |
| pub static TEST_SSID: &str = "test_ssid"; |
| pub static TEST_PASSWORD: &str = "test_password"; |
| |
| /// Produces wlan network configuration objects to be used in tests. |
| pub fn create_connect_request(ssid: &str, password: &str) -> client_types::ConnectRequest { |
| let network = ap_types::NetworkIdentifier { |
| ssid: ssid.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }; |
| let credential = Credential::Password(password.as_bytes().to_vec()); |
| |
| client_types::ConnectRequest { |
| target: client_types::ConnectionCandidate { |
| network, |
| credential, |
| observed_in_passive_scan: Some(true), |
| bss: generate_random_bss_desc(), |
| multiple_bss_candidates: Some(true), |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| } |
| } |
| |
| /// Holds all of the boilerplate required for testing IfaceManager. |
| /// * DeviceServiceProxy and DeviceServiceRequestStream |
| /// * 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 device_service_proxy: fidl_fuchsia_wlan_device_service::DeviceServiceProxy, |
| pub device_service_stream: fidl_fuchsia_wlan_device_service::DeviceServiceRequestStream, |
| pub client_update_sender: listener::ClientListenerMessageSender, |
| pub client_update_receiver: mpsc::UnboundedReceiver<listener::ClientListenerMessage>, |
| pub ap_update_sender: listener::ApListenerMessageSender, |
| pub ap_update_receiver: mpsc::UnboundedReceiver<listener::ApMessage>, |
| pub saved_networks: Arc<SavedNetworksManager>, |
| pub network_selector: Arc<NetworkSelector>, |
| pub node: inspect::Node, |
| pub cobalt_api: CobaltSender, |
| pub cobalt_receiver: mpsc::Receiver<CobaltEvent>, |
| } |
| |
| fn rand_string() -> String { |
| thread_rng().sample_iter(&Alphanumeric).take(20).collect() |
| } |
| |
| /// Create a TestValues for a unit test. |
| pub fn test_setup(exec: &mut Executor) -> TestValues { |
| set_logger_for_test(); |
| let (proxy, requests) = |
| create_proxy::<fidl_fuchsia_wlan_device_service::DeviceServiceMarker>() |
| .expect("failed to create SeviceService proxy"); |
| let stream = 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()) |
| .expect("failed to create saved networks manager."); |
| let saved_networks = Arc::new(saved_networks); |
| let inspector = inspect::Inspector::new(); |
| let node = inspector.root().create_child("phy_manager"); |
| let (cobalt_api, cobalt_receiver) = create_mock_cobalt_sender_and_receiver(); |
| let network_selector = Arc::new(NetworkSelector::new( |
| saved_networks.clone(), |
| cobalt_api.clone(), |
| inspector.root().create_child("network_selection"), |
| )); |
| |
| TestValues { |
| device_service_proxy: proxy, |
| device_service_stream: stream, |
| client_update_sender: client_sender, |
| client_update_receiver: client_receiver, |
| ap_update_sender: ap_sender, |
| ap_update_receiver: ap_receiver, |
| saved_networks: saved_networks, |
| node: node, |
| network_selector, |
| cobalt_api, |
| cobalt_receiver, |
| } |
| } |
| |
| /// Creates a new PhyManagerPtr for tests. |
| fn create_empty_phy_manager( |
| device_service: fidl_fuchsia_wlan_device_service::DeviceServiceProxy, |
| node: inspect::Node, |
| ) -> Arc<Mutex<dyn PhyManagerApi + Send>> { |
| Arc::new(Mutex::new(phy_manager::PhyManager::new(device_service, node))) |
| } |
| |
| #[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, |
| } |
| |
| #[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(vec![TEST_CLIENT_IFACE_ID]) |
| } 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: MacAddress) { |
| 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() |
| } |
| } |
| |
| struct FakeClient { |
| disconnect_ok: bool, |
| is_alive: bool, |
| expected_connect_request: Option<client_types::ConnectRequest>, |
| } |
| |
| impl FakeClient { |
| fn new() -> Self { |
| FakeClient { disconnect_ok: true, is_alive: true, expected_connect_request: None } |
| } |
| } |
| |
| #[async_trait] |
| impl client_fsm::ClientApi for FakeClient { |
| fn connect( |
| &mut self, |
| request: client_types::ConnectRequest, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), Error> { |
| assert_eq!(Some(request), self.expected_connect_request); |
| let _ = responder.send(()); |
| 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, |
| driver_features: Vec::new(), |
| }; |
| 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, |
| }; |
| 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.device_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api.clone(), |
| ); |
| |
| if configured { |
| client_container.config = Some(ap_types::NetworkIdentifier { |
| ssid: TEST_SSID.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::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: &str, password: &str) -> ap_fsm::ApConfig { |
| let radio_config = RadioConfig::new(Phy::Ht, Cbw::Cbw20, 6); |
| ap_fsm::ApConfig { |
| id: ap_types::NetworkIdentifier { |
| ssid: ssid.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::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, |
| }; |
| 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.device_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api.clone(), |
| ); |
| |
| iface_manager.aps.push(ap_container); |
| iface_manager |
| } |
| |
| /// Tests the case where the only available client iface is one that has been configured. The |
| /// Client SME proxy associated with the configured iface should be used for scanning. |
| #[test] |
| fn test_scan_with_configured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| |
| // Scan and ensure that the response is handled properly |
| let scan_fut = iface_manager.scan(fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| )); |
| |
| pin_mut!(scan_fut); |
| let _scan_proxy = match exec.run_until_stalled(&mut scan_fut) { |
| Poll::Ready(proxy) => proxy, |
| Poll::Pending => panic!("no scan was requested"), |
| }; |
| } |
| |
| /// Tests the case where the only available client iface is one that has is unconfigured. The |
| /// Client SME proxy associated with the unconfigured iface should be used for scanning. |
| #[test] |
| fn test_scan_with_unconfigured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create an unconfigured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| create_iface_manager_with_client(&test_values, false); |
| |
| // Scan and ensure that the response is handled properly |
| let scan_fut = iface_manager.scan(fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| )); |
| |
| pin_mut!(scan_fut); |
| let _scan_proxy = match exec.run_until_stalled(&mut scan_fut) { |
| Poll::Ready(proxy) => proxy, |
| Poll::Pending => panic!("no scan was requested"), |
| }; |
| } |
| |
| /// Tests the scan behavior in the case where no client ifaces are present. Ensures that the |
| /// scan call results in an error. |
| #[test] |
| fn test_scan_with_no_ifaces() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.device_service_proxy.clone(), test_values.node); |
| |
| // Create and IfaceManager and issue a scan request. |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| let scan_fut = iface_manager.scan(fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| )); |
| |
| // Ensure that the scan request results in an error. |
| pin_mut!(scan_fut); |
| assert_variant!(exec.run_until_stalled(&mut scan_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where a scan request cannot be made of the SME proxy. |
| #[test] |
| fn test_scan_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| |
| // Drop the SME's serving end so that the request to scan will fail. |
| drop(next_sme_req); |
| |
| // Scan and ensure that an error is returned. |
| let scan_fut = iface_manager.scan(fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| )); |
| |
| pin_mut!(scan_fut); |
| assert_variant!(exec.run_until_stalled(&mut scan_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Tests the case where a scan is requested, the PhyManager provides a client iface ID, but |
| /// when the IfaceManager attempts to create an SME proxy, the creation fails. |
| #[test] |
| fn test_scan_sme_creation_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create an IfaceManager and drop its client. |
| let test_values = test_setup(&mut exec); |
| let (mut iface_manager, _next_sme_req) = |
| 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.device_service_stream); |
| |
| // Scan and ensure that an error is returned. |
| let scan_fut = iface_manager.scan(fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| )); |
| |
| pin_mut!(scan_fut); |
| assert_variant!(exec.run_until_stalled(&mut scan_fut), Poll::Ready(Err(_))); |
| } |
| |
| /// Move stash requests forward so that a save request can progress. |
| fn process_stash_write( |
| mut exec: &mut fuchsia_async::Executor, |
| mut 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(&mut exec, &mut stash_server); |
| } |
| |
| fn process_stash_delete( |
| mut exec: &mut fuchsia_async::Executor, |
| mut 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(&mut exec, &mut stash_server); |
| } |
| |
| fn process_stash_flush( |
| exec: &mut fuchsia_async::Executor, |
| 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(&mut Ok(())).expect("failed to send stash response"); |
| } |
| ); |
| } |
| |
| fn run_state_machine_futures( |
| exec: &mut fuchsia_async::Executor, |
| 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. |
| #[test] |
| fn test_connect_with_configured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let other_test_ssid = "other_ssid_connecting"; |
| |
| // 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_request = create_connect_request(other_test_ssid, TEST_PASSWORD); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_request: Some(connect_request.clone()), |
| })); |
| |
| // Ask the IfaceManager to connect. |
| let config = connect_request; |
| let connect_response_fut = { |
| let connect_fut = iface_manager.connect(config); |
| pin_mut!(connect_fut); |
| |
| // Run the connect request to completion. |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(receiver) => receiver.into_future(), |
| 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 oneshot has been acked. |
| pin_mut!(connect_response_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_response_fut), Poll::Ready(Ok(()))); |
| |
| // Verify that the ClientIfaceContainer has the correct config. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!( |
| iface_manager.clients[0].config, |
| Some( |
| NetworkIdentifier::new(other_test_ssid.as_bytes().to_vec(), SecurityType::Wpa) |
| .into() |
| ) |
| ); |
| } |
| |
| /// Tests the case where connect is called while the only available interface is currently |
| /// unconfigured. |
| #[test] |
| fn test_connect_with_unconfigured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, mut _sme_stream) = |
| create_iface_manager_with_client(&test_values, false); |
| |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Add credentials for the test network to the saved networks. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let save_network_fut = test_values.saved_networks.store(network_id.clone(), credential); |
| pin_mut!(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_response_fut = { |
| let config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let connect_fut = iface_manager.connect(config); |
| pin_mut!(connect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Pending); |
| |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| |
| sme |
| } |
| ); |
| _sme_stream = sme_server.into_stream().unwrap().into_future(); |
| |
| pin_mut!(connect_fut); |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(receiver) => receiver.into_future(), |
| 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); |
| 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, TEST_SSID.as_bytes().to_vec()); |
| assert_eq!(req.credential, fidl_fuchsia_wlan_sme::Credential::Password(TEST_PASSWORD.as_bytes().to_vec())); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_fuchsia_wlan_sme::ConnectResultCode::Success) |
| .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 oneshot has been acked. |
| pin_mut!(connect_response_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_response_fut), Poll::Ready(Ok(()))); |
| } |
| |
| // 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(network_id.into())); |
| } |
| |
| /// Tests the case where connect is called for a WPA3 connection an there is an unconfigured |
| /// WPA3 iface available. |
| #[test_case(fidl_fuchsia_wlan_common::DriverFeature::SaeDriverAuth)] |
| #[test_case(fidl_fuchsia_wlan_common::DriverFeature::SaeSmeAuth)] |
| fn test_connect_with_unconfigured_wpa3_iface( |
| wpa3_feature: fidl_fuchsia_wlan_common::DriverFeature, |
| ) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| 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); |
| iface_manager.clients[0].driver_features.push(wpa3_feature); |
| |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Add credentials for the test network to the saved networks. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), SecurityType::Wpa3); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let save_network_fut = |
| test_values.saved_networks.store(network_id.clone(), credential.clone()); |
| pin_mut!(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_response_fut = { |
| let config = client_types::ConnectRequest { |
| target: client_types::ConnectionCandidate { |
| network: network_id.clone().into(), |
| credential, |
| observed_in_passive_scan: Some(true), |
| bss: generate_random_bss_desc(), |
| multiple_bss_candidates: Some(true), |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| }; |
| let connect_fut = iface_manager.connect(config); |
| pin_mut!(connect_fut); |
| |
| // Expect that we have requested a client SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut connect_fut), Poll::Pending); |
| |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| |
| sme |
| } |
| ); |
| _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(receiver) => receiver.into_future(), |
| 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); |
| 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, TEST_SSID.as_bytes().to_vec()); |
| assert_eq!(req.credential, fidl_fuchsia_wlan_sme::Credential::Password(TEST_PASSWORD.as_bytes().to_vec())); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_fuchsia_wlan_sme::ConnectResultCode::Success) |
| .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 oneshot has been acked. |
| pin_mut!(connect_response_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_response_fut), Poll::Ready(Ok(()))); |
| } |
| |
| // 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(network_id.into())); |
| } |
| |
| /// 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. |
| #[test] |
| fn test_connect_wpa3_no_wpa3_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| 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 network = ap_types::NetworkIdentifier { |
| ssid: b"some_ssid".to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa3, |
| }; |
| let credential = Credential::Password(b"some_password".to_vec()); |
| |
| let config = client_types::ConnectRequest { |
| target: client_types::ConnectionCandidate { |
| network, |
| credential, |
| observed_in_passive_scan: Some(true), |
| bss: generate_random_bss_desc(), |
| multiple_bss_candidates: None, |
| }, |
| reason: client_types::ConnectReason::FidlConnectRequest, |
| }; |
| let connect_fut = iface_manager.connect(config); |
| |
| // Verify that the request to connect results in an error. |
| pin_mut!(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. |
| #[test] |
| fn test_connect_wpa3_with_configured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| // Build the connect request for connecting to the WPA3 network. |
| let network = ap_types::NetworkIdentifier { |
| ssid: b"some_wpa3_network".to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa3, |
| }; |
| let credential = Credential::Password(b"password".to_vec()); |
| let connect_request = client_types::ConnectRequest { |
| target: client_types::ConnectionCandidate { |
| network: network.clone(), |
| credential, |
| observed_in_passive_scan: Some(true), |
| bss: generate_random_bss_desc(), |
| multiple_bss_candidates: Some(true), |
| }, |
| 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, |
| })); |
| |
| // 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_request: Some(connect_request.clone()), |
| })); |
| |
| // Ask the IfaceManager to connect to a new network. |
| let connect_response_fut = { |
| let connect_fut = iface_manager.connect(connect_request); |
| pin_mut!(connect_fut); |
| |
| // Run the connect request to completion. |
| match exec.run_until_stalled(&mut connect_fut) { |
| Poll::Ready(connect_result) => match connect_result { |
| Ok(receiver) => receiver.into_future(), |
| 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 oneshot has been acked. |
| pin_mut!(connect_response_fut); |
| assert_variant!(exec.run_until_stalled(&mut connect_response_fut), Poll::Ready(Ok(()))); |
| |
| // Verify that the ClientIfaceContainer has the correct config. |
| assert_eq!(iface_manager.clients.len(), 1); |
| assert_eq!(iface_manager.clients[0].config, Some(network)); |
| } |
| |
| /// Tests the case where connect is called, but no client ifaces exist. |
| #[test] |
| fn test_connect_with_no_ifaces() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.device_service_proxy.clone(), test_values.node); |
| |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // Call connect on the IfaceManager |
| let config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let connect_fut = iface_manager.connect(config); |
| |
| // Verify that the request to connect results in an error. |
| pin_mut!(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. |
| #[test] |
| fn test_connect_sme_creation_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.device_service_stream); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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()); |
| |
| // Ask the IfaceManager to connect and make sure that it fails. |
| let config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let connect_fut = iface_manager.connect(config); |
| |
| pin_mut!(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. |
| #[test] |
| fn test_disconnect_configured_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.as_bytes().to_vec(), 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.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }; |
| let disconnect_fut = iface_manager |
| .disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Ensure that disconnect returns a successful response. |
| pin_mut!(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. |
| #[test] |
| fn test_disconnect_nonexistent_config() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.as_bytes().to_vec(), 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: "nonexistent_ssid".as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }; |
| let disconnect_fut = iface_manager |
| .disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Ensure that the request returns immediately. |
| pin_mut!(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. |
| #[test] |
| fn test_disconnect_no_clients() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // Call disconnect on the IfaceManager |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: "nonexistent_ssid".as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }; |
| let disconnect_fut = |
| iface_manager.disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| // Verify that disconnect returns immediately. |
| pin_mut!(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. |
| #[test] |
| fn test_disconnect_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_request: None, |
| })); |
| |
| // Call disconnect on the IfaceManager |
| let network_id = ap_types::NetworkIdentifier { |
| ssid: TEST_SSID.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }; |
| let disconnect_fut = |
| iface_manager.disconnect(network_id, client_types::DisconnectReason::NetworkUnsaved); |
| |
| pin_mut!(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. |
| #[test] |
| fn test_stop_connected_client() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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, |
| ); |
| pin_mut!(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: Some(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); |
| }); |
| |
| // Ensure a metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| |
| // Ensure the client connections enabled time was reset to None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Call stop_client_connections when the only available client is unconfigured. |
| #[test] |
| fn test_stop_unconfigured_client() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // Create a PhyManager with one known client. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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, |
| ); |
| pin_mut!(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()); |
| |
| // Ensure a metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| |
| // Ensure the client connections enabled time was reset to None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where stop_client_connections is called, but there are no client ifaces. |
| #[test] |
| fn test_stop_no_clients() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create and empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| iface_manager.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| |
| // 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. |
| pin_mut!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure a metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| |
| // Ensure the client connections enabled time was reset to None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where client connections are stopped, but stopping one of the client state |
| /// machines fails. |
| #[test] |
| fn test_stop_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| iface_manager.clients[0].client_state_machine = Some(Box::new(FakeClient { |
| disconnect_ok: false, |
| is_alive: true, |
| expected_connect_request: None, |
| })); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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, |
| ); |
| pin_mut!(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()); |
| |
| // Regardless of whether or not stop succeeds or fails, a metric should be logged. |
| // Ensure a metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| |
| // Ensure the client connections enabled time was reset to None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where the IfaceManager fails to tear down all of the client ifaces. |
| #[test] |
| fn test_stop_iface_destruction_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| let (mut iface_manager, _) = create_iface_manager_with_client(&test_values, true); |
| iface_manager.clients_enabled_time = Some(zx::Time::get_monotonic()); |
| 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, |
| })); |
| |
| // Create a PhyManager with a single, known client iface. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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, |
| ); |
| pin_mut!(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()); |
| |
| // Regardless of whether or not stop succeeds or fails, a metric should be logged. |
| // Ensure a metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| |
| // Ensure the client connections enabled time was reset to None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where StopClientConnections is called when the client interfaces are already |
| /// stopped. |
| #[test] |
| fn test_stop_client_when_already_stopped() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // 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. |
| pin_mut!(stop_fut); |
| assert_variant!(exec.run_until_stalled(&mut stop_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure no metric was logged. |
| assert_variant!(test_values.cobalt_receiver.try_next(), Err(_)); |
| |
| // Ensure the client connections enabled is None. |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where an existing iface is marked as idle. |
| #[test] |
| fn test_mark_iface_idle() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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_request: 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. |
| #[test] |
| fn test_mark_active_iface_idle() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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. |
| #[test] |
| fn test_mark_nonexistent_iface_idle() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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. |
| #[test] |
| fn test_unconfigured_iface_idle_check() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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. |
| #[test] |
| fn test_configured_alive_iface_idle_check() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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. |
| #[test] |
| fn test_configured_dead_iface_idle_check() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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_request: None, |
| })); |
| |
| assert!(!iface_manager.idle_clients().is_empty()); |
| } |
| |
| /// Tests the case where not ifaces are present and has_idle_client is called. |
| #[test] |
| fn test_no_ifaces_idle_check() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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. |
| #[test] |
| fn test_start_clients_succeeds() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| { |
| let start_fut = iface_manager.start_client_connections(); |
| |
| // Ensure stop_client_connections returns immediately and is successful. |
| pin_mut!(start_fut); |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Ready(Ok(_))); |
| } |
| |
| // Ensure an update was sent |
| let client_state_update = listener::ClientStateUpdate { |
| state: Some(fidl_fuchsia_wlan_policy::WlanClientState::ConnectionsEnabled), |
| networks: vec![], |
| }; |
| assert_variant!( |
| test_values.client_update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| assert!(iface_manager.clients_enabled_time.is_some()); |
| } |
| |
| /// Tests the case where starting client connections fails. |
| #[test] |
| fn test_start_clients_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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, |
| })); |
| |
| { |
| let start_fut = iface_manager.start_client_connections(); |
| pin_mut!(start_fut); |
| assert_variant!(exec.run_until_stalled(&mut start_fut), Poll::Ready(Err(_))); |
| } |
| |
| assert!(iface_manager.clients_enabled_time.is_none()); |
| } |
| |
| /// Tests the case where the IfaceManager is able to request that the AP state machine start |
| /// the access point. |
| #[test] |
| fn test_start_ap_succeeds() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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); |
| |
| pin_mut!(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. |
| #[test] |
| fn test_start_ap_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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); |
| |
| pin_mut!(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. |
| #[test] |
| fn test_start_ap_no_ifaces() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // Call start_ap. |
| let config = create_ap_config(TEST_SSID, TEST_PASSWORD); |
| let fut = iface_manager.start_ap(config); |
| |
| pin_mut!(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. |
| #[test] |
| fn test_stop_ap_succeeds() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), TEST_PASSWORD.as_bytes().to_vec()); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where IfaceManager is requested to stop a config that is not accounted for. |
| #[test] |
| fn test_stop_ap_invalid_config() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), TEST_PASSWORD.as_bytes().to_vec()); |
| pin_mut!(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.cobalt_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. |
| #[test] |
| fn test_stop_ap_stop_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), TEST_PASSWORD.as_bytes().to_vec()); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where IfaceManager stops the AP state machine, but the request to exit |
| /// fails. |
| #[test] |
| fn test_stop_ap_exit_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), TEST_PASSWORD.as_bytes().to_vec()); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where stop is called on the IfaceManager, but there are no AP ifaces. |
| #[test] |
| fn test_stop_ap_no_ifaces() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| let fut = |
| iface_manager.stop_ap(TEST_SSID.as_bytes().to_vec(), TEST_PASSWORD.as_bytes().to_vec()); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| } |
| |
| /// Tests the case where stop_all_aps is called and it succeeds. |
| #[test] |
| fn test_stop_all_aps_succeeds() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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(); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where stop_all_aps is called and the request to stop fails for an iface. |
| #[test] |
| fn test_stop_all_aps_stop_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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(); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where stop_all_aps is called and the request to stop fails for an iface. |
| #[test] |
| fn test_stop_all_aps_exit_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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(); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| assert_variant!(test_values.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| /// Tests the case where stop_all_aps is called on the IfaceManager, but there are no AP |
| /// ifaces. |
| #[test] |
| fn test_stop_all_aps_no_ifaces() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let mut test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| let fut = iface_manager.stop_all_aps(); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(Ok(()))); |
| |
| // Ensure no metrics are logged. |
| assert_variant!(test_values.cobalt_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. |
| #[test] |
| fn test_start_ap_twice_then_stop() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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); |
| |
| pin_mut!(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.clone(); |
| |
| // Now issue a second start command. |
| let alternate_ssid = "some_other_ssid"; |
| let alternate_password = "some_other_password"; |
| let config = create_ap_config(alternate_ssid, alternate_password); |
| { |
| let fut = iface_manager.start_ap(config); |
| |
| pin_mut!(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.cobalt_receiver.try_next(), Err(_)); |
| |
| // Now issue a stop command. |
| { |
| let fut = iface_manager.stop_all_aps(); |
| pin_mut!(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.cobalt_receiver.try_next(), Ok(Some(_))); |
| } |
| |
| #[test] |
| fn test_recover_removed_client_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(fut); |
| |
| // Expect a DeviceService request an SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.device_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }))) => { |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK) |
| .expect("failed to send AP SME response."); |
| } |
| ); |
| |
| // Expected a DeviceService request to get driver features |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.device_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }))) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut response)) |
| .expect("Failed to send iface 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); |
| } |
| |
| #[test] |
| fn test_client_iface_recovery_fails() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(fut); |
| |
| // Expect a DeviceService request an SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.device_service_stream.next()), |
| Poll::Ready(Some(Ok(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }))) => { |
| responder |
| .send(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND) |
| .expect("failed to send AP SME 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!(iface_manager.clients.is_empty()); |
| } |
| |
| #[test] |
| fn test_do_not_recover_removed_ap_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(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()); |
| } |
| |
| #[test] |
| fn test_remove_nonexistent_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(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_device_service_req( |
| exec: &mut fuchsia_async::Executor, |
| next_req: &mut StreamFuture<fidl_fuchsia_wlan_device_service::DeviceServiceRequestStream>, |
| ) -> Poll<fidl_fuchsia_wlan_device_service::DeviceServiceRequest> { |
| 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") |
| }) |
| } |
| |
| #[test] |
| fn test_add_client_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_CLIENT_IFACE_ID); |
| pin_mut!(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 device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut 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_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| } |
| ); |
| |
| // Expect a QueryIface request for the new iface's driver features and respond to it. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut response)) |
| .expect("Sending iface 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_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_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); |
| } |
| |
| #[test] |
| fn test_add_ap_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_AP_IFACE_ID); |
| pin_mut!(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 device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Ap, |
| id: TEST_AP_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut 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_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetApSme { |
| iface_id: TEST_AP_IFACE_ID, sme: _, responder |
| }) => responder |
| ); |
| |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_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); |
| } |
| |
| #[test] |
| fn test_add_nonexistent_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let mut iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks, |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| { |
| // Notify the IfaceManager of a new interface. |
| let fut = iface_manager.handle_added_iface(TEST_AP_IFACE_ID); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an iface query and send back an error |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| responder |
| .send(fuchsia_zircon::sys::ZX_ERR_NOT_FOUND, None) |
| .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()); |
| } |
| |
| #[test] |
| fn test_add_existing_client_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an interface query and notify that it is a client. |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut 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); |
| } |
| |
| #[test] |
| fn test_add_existing_ap_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Expect an interface query and notify that it is a client. |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_AP_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Ap, |
| id: TEST_AP_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut 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::Executor, |
| network_selector: Arc<NetworkSelector>, |
| iface_manager: IfaceManagerService, |
| req: IfaceManagerRequest, |
| mut req_receiver: oneshot::Receiver<Result<T, Error>>, |
| device_service_stream: fidl_fuchsia_wlan_device_service::DeviceServiceRequestStream, |
| test_type: TestType, |
| ) { |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| |
| // Start the service loop |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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 device_service_fut = device_service_stream.into_future(); |
| assert_variant!(exec.run_until_stalled(&mut serve_fut), Poll::Pending); |
| match poll_device_service_req(exec, &mut device_service_fut) { |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, |
| sme: _, |
| responder, |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| } |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetApSme { |
| iface_id: TEST_AP_IFACE_ID, |
| sme: _, |
| responder, |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_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::Executor, |
| network_selector: Arc<NetworkSelector>, |
| iface_manager: IfaceManagerService, |
| req: IfaceManagerRequest, |
| mut req_receiver: oneshot::Receiver<()>, |
| test_type: TestType, |
| ) { |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| |
| // Start the service loop |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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_request: None}, |
| TestType::Pass; "successfully disconnects configured client")] |
| #[test_case(FakeClient {disconnect_ok: false, is_alive:true, expected_connect_request: None}, |
| TestType::Fail; "fails to disconnect configured client")] |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_request: None}, |
| TestType::ClientError; "client drops receiver")] |
| fn service_disconnect_test(fake_client: FakeClient, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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: fidl_fuchsia_wlan_policy::NetworkIdentifier { |
| ssid: TEST_SSID.as_bytes().to_vec(), |
| type_: fidl_fuchsia_wlan_policy::SecurityType::Wpa, |
| }, |
| reason: client_types::DisconnectReason::NetworkUnsaved, |
| responder: ack_sender, |
| }; |
| let req = IfaceManagerRequest::Disconnect(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| ack_receiver, |
| test_values.device_service_stream, |
| test_type, |
| ); |
| } |
| |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_request: Some(create_connect_request(TEST_SSID, TEST_PASSWORD))}, |
| TestType::Pass; "successfully connected a client")] |
| #[test_case(FakeClient {disconnect_ok: true, is_alive:true, expected_connect_request: Some(create_connect_request(TEST_SSID, TEST_PASSWORD))}, |
| TestType::ClientError; "client drops receiver")] |
| fn service_connect_test(fake_client: FakeClient, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_req = fake_client.expected_connect_request.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: connect_req, responder: ack_sender }; |
| let req = IfaceManagerRequest::Connect(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| ack_receiver, |
| test_values.device_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. |
| #[test] |
| fn test_service_record_idle_client() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_request: None, |
| })); |
| |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network_selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks, |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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))); |
| } |
| |
| #[test] |
| fn test_service_record_idle_client_response_failure() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network_selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks, |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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); |
| } |
| |
| #[test] |
| fn test_service_query_idle_client_response_failure() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = create_iface_manager_with_client(&test_values, true); |
| |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network_selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks, |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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); |
| } |
| |
| #[derive(Debug)] |
| struct FakeIfaceManagerRequester { |
| scan_proxy_requested: bool, |
| } |
| |
| impl FakeIfaceManagerRequester { |
| fn new() -> Self { |
| FakeIfaceManagerRequester { scan_proxy_requested: false } |
| } |
| } |
| |
| #[async_trait] |
| impl IfaceManagerApi for FakeIfaceManagerRequester { |
| async fn disconnect( |
| &mut self, |
| _network_id: types::NetworkIdentifier, |
| _reason: client_types::DisconnectReason, |
| ) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn connect( |
| &mut self, |
| _connect_req: client_types::ConnectRequest, |
| ) -> Result<oneshot::Receiver<()>, Error> { |
| unimplemented!() |
| } |
| |
| async fn record_idle_client(&mut self, _iface_id: u16) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn has_idle_client(&mut self) -> Result<bool, Error> { |
| unimplemented!() |
| } |
| |
| async fn handle_added_iface(&mut self, _iface_id: u16) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn handle_removed_iface(&mut self, _iface_id: u16) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn scan( |
| &mut self, |
| _scan_request: fidl_fuchsia_wlan_sme::ScanRequest, |
| ) -> Result<fidl_fuchsia_wlan_sme::ScanTransactionProxy, Error> { |
| unimplemented!() |
| } |
| |
| async fn get_sme_proxy_for_scan( |
| &mut self, |
| ) -> Result<fidl_fuchsia_wlan_sme::ClientSmeProxy, Error> { |
| self.scan_proxy_requested = true; |
| Err(format_err!("scan failed")) |
| } |
| |
| async fn stop_client_connections( |
| &mut self, |
| _reason: client_types::DisconnectReason, |
| ) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn start_client_connections(&mut self) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn start_ap( |
| &mut self, |
| _config: ap_fsm::ApConfig, |
| ) -> Result<oneshot::Receiver<()>, Error> { |
| unimplemented!() |
| } |
| |
| async fn stop_ap(&mut self, _ssid: Vec<u8>, _password: Vec<u8>) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn stop_all_aps(&mut self) -> Result<(), Error> { |
| unimplemented!() |
| } |
| |
| async fn has_wpa3_capable_client(&mut self) -> Result<bool, Error> { |
| Ok(true) |
| } |
| |
| async fn set_country( |
| &mut self, |
| _country_code: Option<[u8; REGION_CODE_LEN]>, |
| ) -> Result<(), Error> { |
| unimplemented!() |
| } |
| } |
| |
| #[test] |
| fn test_service_add_iface_succeeds() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // Create other components to run the service. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network_selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks, |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Create mpsc channel to handle requests. |
| let (mut sender, receiver) = mpsc::channel(1); |
| let serve_fut = serve_iface_manager_requests( |
| iface_manager, |
| iface_manager_client, |
| network_selector, |
| receiver, |
| ); |
| pin_mut!(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 device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut 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_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_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); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::QueryIface { |
| iface_id: TEST_CLIENT_IFACE_ID, responder |
| }) => { |
| let mut response = fidl_fuchsia_wlan_device_service::QueryIfaceResponse { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| id: TEST_CLIENT_IFACE_ID, |
| phy_id: 0, |
| phy_assigned_id: 0, |
| mac_addr: [0; 6], |
| driver_features: Vec::new(), |
| }; |
| responder |
| .send(fuchsia_zircon::sys::ZX_OK, Some(&mut response)) |
| .expect("Sending iface 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_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme: _, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_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")] |
| fn service_add_iface_negative_tests(test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| |
| // Create an empty PhyManager and IfaceManager. |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api, |
| ); |
| |
| // 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 stream so that querying the interface properties will fail. |
| drop(test_values.device_service_stream); |
| |
| run_service_test_with_unit_return( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| new_iface_receiver, |
| test_type, |
| ); |
| } |
| |
| #[test_case(TestType::Pass; "successfully removed iface")] |
| #[test_case(TestType::ClientError; "client dropped receiving end")] |
| fn service_remove_iface_test(test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| remove_iface_receiver, |
| test_type, |
| ); |
| } |
| |
| #[test_case(TestType::Pass; "successfully scanned")] |
| #[test_case(TestType::Fail; "failed to scanned")] |
| #[test_case(TestType::ClientError; "client drops receiver")] |
| fn service_scan_test(test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let test_values = test_setup(&mut exec); |
| let (iface_manager, _stream) = match test_type { |
| TestType::Pass | TestType::ClientError => { |
| let (iface_manager, stream) = create_iface_manager_with_client(&test_values, true); |
| (iface_manager, Some(stream)) |
| } |
| TestType::Fail => { |
| let phy_manager = phy_manager::PhyManager::new( |
| test_values.device_service_proxy.clone(), |
| test_values.node, |
| ); |
| let iface_manager = IfaceManagerService::new( |
| Arc::new(Mutex::new(phy_manager)), |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api, |
| ); |
| (iface_manager, None) |
| } |
| }; |
| |
| // Make a scan request. |
| let (scan_sender, scan_receiver) = oneshot::channel(); |
| let req = ScanRequest { |
| scan_request: fidl_fuchsia_wlan_sme::ScanRequest::Passive( |
| fidl_fuchsia_wlan_sme::PassiveScanRequest {}, |
| ), |
| responder: scan_sender, |
| }; |
| let req = IfaceManagerRequest::Scan(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| scan_receiver, |
| test_values.device_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 |
| }, |
| 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 |
| }, |
| 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 |
| }, |
| TestType::ClientError; |
| "client dropped receiver" |
| )] |
| fn service_start_client_connections_test(phy_manager: FakePhyManager, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api, |
| ); |
| |
| // 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, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| start_receiver, |
| test_values.device_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 |
| }, |
| 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 |
| }, |
| 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 |
| }, |
| TestType::ClientError; |
| "client dropped receiver" |
| )] |
| fn service_stop_client_connections_test(phy_manager: FakePhyManager, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api, |
| ); |
| |
| // 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::StopClientConnections(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.device_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")] |
| fn service_start_ap_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| start_receiver, |
| test_values.device_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")] |
| fn service_stop_ap_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), |
| password: TEST_PASSWORD.as_bytes().to_vec(), |
| responder: stop_sender, |
| }; |
| let req = IfaceManagerRequest::StopAp(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.device_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")] |
| fn service_stop_all_aps_test(fake_ap: FakeAp, test_type: TestType) { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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::StopAllAps(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| stop_receiver, |
| test_values.device_service_stream, |
| test_type, |
| ); |
| } |
| |
| /// Tests the case where the IfaceManager attempts to reconnect a client interface that has |
| /// disconnected. |
| #[test] |
| fn test_reconnect_disconnected_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_request: None, |
| })); |
| |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let save_network_fut = test_values.saved_networks.store(network_id, credential); |
| pin_mut!(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 config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let reconnect_fut = |
| iface_manager.attempt_client_reconnect(TEST_CLIENT_IFACE_ID, config); |
| pin_mut!(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 device_service_fut = test_values.device_service_stream.into_future(); |
| let sme_server = assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme, responder |
| }) => { |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| sme |
| } |
| ); |
| |
| // 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); |
| |
| // 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); |
| 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, TEST_SSID.as_bytes().to_vec()); |
| assert_eq!(req.credential, fidl_fuchsia_wlan_sme::Credential::Password(TEST_PASSWORD.as_bytes().to_vec())); |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_fuchsia_wlan_sme::ConnectResultCode::Success) |
| .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. |
| #[test] |
| fn test_reconnect_nonexistent_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create an empty IfaceManager |
| let test_values = test_setup(&mut exec); |
| let phy_manager = |
| create_empty_phy_manager(test_values.device_service_proxy.clone(), test_values.node); |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager, |
| test_values.client_update_sender, |
| test_values.ap_update_sender, |
| test_values.device_service_proxy, |
| test_values.saved_networks.clone(), |
| test_values.network_selector, |
| test_values.cobalt_api, |
| ); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), 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()); |
| |
| // Ask the IfaceManager to reconnect. |
| { |
| let config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let reconnect_fut = |
| iface_manager.attempt_client_reconnect(TEST_CLIENT_IFACE_ID, config); |
| pin_mut!(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. |
| #[test] |
| fn test_reconnect_connected_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_request: None, |
| })); |
| |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| test_values.saved_networks = Arc::new(saved_networks); |
| |
| // Update the saved networks with knowledge of the test SSID and credentials. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let save_network_fut = test_values.saved_networks.store(network_id, credential); |
| pin_mut!(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 config = create_connect_request(TEST_SSID, TEST_PASSWORD); |
| let reconnect_fut = |
| iface_manager.attempt_client_reconnect(TEST_CLIENT_IFACE_ID, config); |
| pin_mut!(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")] |
| fn test_initiate_network_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::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| |
| // Insert a saved network. |
| let network_id = NetworkIdentifier::new(TEST_SSID.as_bytes().to_vec(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let mut stash_server = { |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| 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()); |
| pin_mut!(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_request: None, |
| })); |
| |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| |
| // Create an empty FuturesUnordered to hold the network selection request. |
| let mut network_selection_futures = FuturesUnordered::< |
| BoxFuture<'static, Option<client_types::ConnectionCandidate>>, |
| >::new(); |
| |
| // Create a network selector to be used by the network selection request. |
| let selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks.clone(), |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Setup the test to prevent a network selection from happening for whatever reason was specified. |
| match test_type { |
| NetworkSelectionMissingAttribute::AllAttributesPresent => {} |
| 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_request: None, |
| })); |
| } |
| NetworkSelectionMissingAttribute::SavedNetwork => { |
| // Remove the saved network so that there are no known networks to connect to. |
| let remove_network_fut = test_values.saved_networks.remove(network_id, credential); |
| pin_mut!(remove_network_fut); |
| 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. |
| network_selection_futures.push(ready(None).boxed()); |
| } |
| } |
| |
| { |
| // Run the future to completion. |
| let fut = initiate_network_selection( |
| &iface_manager, |
| iface_manager_client.clone(), |
| selector, |
| &mut network_selection_futures, |
| ); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| // Run all network_selection futures to completion. |
| for mut network_selection_future in network_selection_futures.iter_mut() { |
| assert_variant!(exec.run_until_stalled(&mut network_selection_future), Poll::Ready(_)); |
| } |
| |
| // Check the outcome based on the expected failure mode. |
| let fut = iface_manager_client.lock(); |
| pin_mut!(fut); |
| let iface_manager_client = assert_variant!( |
| exec.run_until_stalled(&mut fut), |
| Poll::Ready(iface_manager_client) => iface_manager_client |
| ); |
| |
| // We are using a scan request issuance as a proxy to determine if the network selection |
| // module took action. |
| match test_type { |
| NetworkSelectionMissingAttribute::AllAttributesPresent => { |
| assert!(iface_manager_client.scan_proxy_requested); |
| } |
| NetworkSelectionMissingAttribute::IdleClient |
| | NetworkSelectionMissingAttribute::SavedNetwork |
| | NetworkSelectionMissingAttribute::NetworkSelectionInProgress => { |
| assert!(!iface_manager_client.scan_proxy_requested); |
| } |
| } |
| } |
| |
| #[test] |
| fn test_scan_result_backoff() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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_network_selection_results( |
| None, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| pin_mut!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| assert_eq!(reconnect_monitor_interval, expected_wait_times[i]); |
| } |
| } |
| |
| #[test] |
| fn test_reconnect_on_network_selection_results() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| |
| // Insert a saved network. |
| let ssid = TEST_SSID.as_bytes().to_vec(); |
| let network_id = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| 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, credential.clone()); |
| pin_mut!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| } |
| |
| // Create a network selector. |
| let selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks.clone(), |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Inject a scan result into the network selector. |
| { |
| let scan_results = vec![client_types::ScanResult { |
| ssid: ssid.clone(), |
| security_type_detailed: client_types::SecurityTypeDetailed::Wpa1, |
| entries: vec![client_types::Bss { |
| bssid: [20, 30, 40, 50, 60, 70], |
| rssi: -15, |
| timestamp_nanos: 0, |
| compatible: true, |
| observed_in_passive_scan: false, |
| snr_db: 20, |
| channel: fidl_fuchsia_wlan_common::WlanChan { |
| primary: 8, |
| cbw: fidl_fuchsia_wlan_common::Cbw::Cbw20, |
| secondary80: 0, |
| }, |
| bss_desc: None, |
| }], |
| compatibility: client_types::Compatibility::Supported, |
| }]; |
| let mut network_selector_updater = selector.generate_scan_result_updater(true); |
| let update_fut = network_selector_updater.update_scan_results(&scan_results); |
| pin_mut!(update_fut); |
| assert_variant!(exec.run_until_stalled(&mut update_fut), Poll::Ready(())); |
| } |
| |
| // 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_request: 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)); |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network = exec.run_singlethreaded( |
| selector.find_best_connection_candidate(iface_manager_client, &vec![]), |
| ); |
| assert!(network.is_some()); |
| |
| { |
| // Run reconnection attempt |
| let fut = handle_network_selection_results( |
| network, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| |
| pin_mut!(fut); |
| |
| // Expect a client SME proxy request |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let mut device_service_fut = test_values.device_service_stream.into_future(); |
| assert_variant!( |
| poll_device_service_req(&mut exec, &mut device_service_fut), |
| Poll::Ready(fidl_fuchsia_wlan_device_service::DeviceServiceRequest::GetClientSme { |
| iface_id: TEST_CLIENT_IFACE_ID, sme, responder |
| }) => { |
| // Send back a positive acknowledgement. |
| assert!(responder.send(fuchsia_zircon::sys::ZX_OK).is_ok()); |
| |
| sme |
| } |
| ); |
| |
| // 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()); |
| } |
| |
| #[test] |
| fn test_idle_client_remains_after_failed_reconnection() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // Create a configured ClientIfaceContainer. |
| let mut test_values = test_setup(&mut exec); |
| |
| // Insert a saved network. |
| let ssid = TEST_SSID.as_bytes().to_vec(); |
| let network_id = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| 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, credential.clone()); |
| pin_mut!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| } |
| |
| // Create a network selector. |
| let selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks.clone(), |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // Inject a scan result into the network selector. |
| { |
| let scan_results = vec![client_types::ScanResult { |
| ssid: ssid.clone(), |
| security_type_detailed: client_types::SecurityTypeDetailed::Wpa1, |
| entries: vec![client_types::Bss { |
| bssid: [20, 30, 40, 50, 60, 70], |
| rssi: -15, |
| timestamp_nanos: 0, |
| compatible: true, |
| observed_in_passive_scan: false, |
| snr_db: 20, |
| channel: fidl_fuchsia_wlan_common::WlanChan { |
| primary: 8, |
| cbw: fidl_fuchsia_wlan_common::Cbw::Cbw20, |
| secondary80: 0, |
| }, |
| bss_desc: None, |
| }], |
| compatibility: client_types::Compatibility::Supported, |
| }]; |
| let mut network_selector_updater = selector.generate_scan_result_updater(true); |
| let update_fut = network_selector_updater.update_scan_results(&scan_results); |
| pin_mut!(update_fut); |
| assert_variant!(exec.run_until_stalled(&mut update_fut), Poll::Ready(())); |
| } |
| |
| // 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_request: 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)); |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let network = exec.run_singlethreaded( |
| selector.find_best_connection_candidate(iface_manager_client, &vec![]), |
| ); |
| assert!(network.is_some()); |
| |
| { |
| // Run reconnection attempt |
| let fut = handle_network_selection_results( |
| network, |
| &mut iface_manager, |
| &mut reconnect_monitor_interval, |
| &mut connectivity_monitor_timer, |
| ); |
| |
| pin_mut!(fut); |
| |
| // Drop the device service stream so that the SME request fails. |
| drop(test_values.device_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()); |
| } |
| |
| #[test] |
| fn test_terminated_client() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| 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.as_bytes().to_vec(), SecurityType::Wpa); |
| let credential = Credential::Password(TEST_PASSWORD.as_bytes().to_vec()); |
| let temp_dir = TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join(rand_string()); |
| let tmp_path = temp_dir.path().join(rand_string()); |
| let (saved_networks, mut stash_server) = |
| exec.run_singlethreaded(SavedNetworksManager::new_and_stash_server(path, tmp_path)); |
| 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()); |
| pin_mut!(save_network_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_network_fut), Poll::Pending); |
| |
| process_stash_write(&mut exec, &mut stash_server); |
| } |
| |
| // Create a network selector. |
| let selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks.clone(), |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // 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_request: None, |
| })); |
| |
| // Create remaining boilerplate to call handle_terminated_state_machine. |
| let iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let mut network_selection_futures = FuturesUnordered::new(); |
| |
| let metadata = StateMachineMetadata { |
| role: fidl_fuchsia_wlan_device::MacRole::Client, |
| iface_id: TEST_CLIENT_IFACE_ID, |
| }; |
| |
| { |
| let fut = handle_terminated_state_machine( |
| metadata, |
| &mut iface_manager, |
| iface_manager_client, |
| selector, |
| &mut network_selection_futures, |
| ); |
| pin_mut!(fut); |
| |
| 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!(!network_selection_futures.is_empty()); |
| } |
| |
| #[test] |
| fn test_terminated_ap() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| let test_values = test_setup(&mut exec); |
| let selector = Arc::new(NetworkSelector::new( |
| test_values.saved_networks.clone(), |
| create_mock_cobalt_sender(), |
| inspect::Inspector::new().root().create_child("network_selector"), |
| )); |
| |
| // 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 iface_manager_client = Arc::new(Mutex::new(FakeIfaceManagerRequester::new())); |
| let mut network_selection_futures = FuturesUnordered::new(); |
| |
| let metadata = StateMachineMetadata { |
| role: fidl_fuchsia_wlan_device::MacRole::Ap, |
| iface_id: TEST_AP_IFACE_ID, |
| }; |
| |
| { |
| let fut = handle_terminated_state_machine( |
| metadata, |
| &mut iface_manager, |
| iface_manager_client, |
| selector, |
| &mut network_selection_futures, |
| ); |
| pin_mut!(fut); |
| |
| 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. |
| #[test] |
| fn test_has_wpa3_capable_client() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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, |
| })); |
| let fut = iface_manager.has_wpa3_capable_client(); |
| pin_mut!(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. |
| #[test] |
| fn test_has_no_wpa3_capable_iface() { |
| let mut exec = fuchsia_async::Executor::new().expect("failed to create an executor"); |
| |
| // 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, |
| })); |
| let fut = iface_manager.has_wpa3_capable_client(); |
| pin_mut!(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" |
| )] |
| 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::Executor::new().expect("failed to create an executor"); |
| 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, |
| })); |
| |
| let mut iface_manager = IfaceManagerService::new( |
| phy_manager.clone(), |
| test_values.client_update_sender.clone(), |
| test_values.ap_update_sender.clone(), |
| test_values.device_service_proxy.clone(), |
| test_values.saved_networks.clone(), |
| test_values.network_selector.clone(), |
| test_values.cobalt_api.clone(), |
| ); |
| |
| // 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::SetCountry(req); |
| |
| run_service_test( |
| &mut exec, |
| test_values.network_selector, |
| iface_manager, |
| req, |
| set_country_receiver, |
| test_values.device_service_stream, |
| test_type, |
| ); |
| |
| if destroy_iface_ok && set_country_ok { |
| let phy_manager_fut = phy_manager.lock(); |
| pin_mut!(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])) |
| } |
| ); |
| } |
| } |
| } |