| // 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::{ |
| client::{sme_credential_from_policy, types}, |
| config_management::{Credential, SavedNetworksManager}, |
| util::{ |
| listener::{ |
| ClientListenerMessageSender, ClientNetworkState, ClientStateUpdate, |
| Message::NotifyListeners, |
| }, |
| state_machine::{self, ExitReason, IntoStateExt}, |
| }, |
| }, |
| anyhow::format_err, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_wlan_sme as fidl_sme, fuchsia_async as fasync, fuchsia_zircon as zx, |
| futures::{ |
| channel::{mpsc, oneshot}, |
| future::FutureExt, |
| select, |
| stream::{self, FuturesUnordered, StreamExt, TryStreamExt}, |
| }, |
| log::{debug, error, info}, |
| pin_utils::pin_mut, |
| std::sync::Arc, |
| void::ResultVoidErrExt, |
| wlan_common::RadioConfig, |
| }; |
| |
| // TODO(53513): add Cobalt metrics |
| |
| const SME_STATUS_INTERVAL_SEC: i64 = 1; // this poll is very cheap, so we can do it frequently |
| const MAX_CONNECTION_ATTEMPTS: u8 = 4; // arbitrarily chosen until we have some data |
| |
| type State = state_machine::State<ExitReason>; |
| type ReqStream = stream::Fuse<mpsc::Receiver<ManualRequest>>; |
| |
| pub trait ClientApi { |
| fn connect( |
| &mut self, |
| request: ConnectRequest, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), anyhow::Error>; |
| fn disconnect(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error>; |
| fn exit(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error>; |
| } |
| |
| pub struct Client { |
| req_sender: mpsc::Sender<ManualRequest>, |
| } |
| |
| impl Client { |
| pub fn new(req_sender: mpsc::Sender<ManualRequest>) -> Self { |
| Self { req_sender } |
| } |
| } |
| |
| impl ClientApi for Client { |
| fn connect( |
| &mut self, |
| request: ConnectRequest, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), anyhow::Error> { |
| self.req_sender |
| .try_send(ManualRequest::Connect((request, responder))) |
| .map_err(|e| format_err!("failed to send connect request: {:?}", e)) |
| } |
| |
| fn disconnect(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error> { |
| self.req_sender |
| .try_send(ManualRequest::Disconnect(responder)) |
| .map_err(|e| format_err!("failed to send disconnect request: {:?}", e)) |
| } |
| |
| fn exit(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error> { |
| self.req_sender |
| .try_send(ManualRequest::Exit(responder)) |
| .map_err(|e| format_err!("failed to send exit request: {:?}", e)) |
| } |
| } |
| |
| pub enum ManualRequest { |
| Connect((ConnectRequest, oneshot::Sender<()>)), |
| Disconnect(oneshot::Sender<()>), |
| Exit(oneshot::Sender<()>), |
| } |
| #[derive(PartialEq, Debug)] |
| pub enum ClientStateMachineNotification { |
| Idle { iface_id: u16 }, |
| } |
| |
| fn send_listener_state_update( |
| sender: &ClientListenerMessageSender, |
| network_update: ClientNetworkState, |
| ) { |
| let updates = ClientStateUpdate { state: None, networks: [network_update].to_vec() }; |
| match sender.clone().unbounded_send(NotifyListeners(updates)) { |
| Ok(_) => (), |
| Err(e) => error!("failed to send state update: {:?}", e), |
| }; |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct ConnectRequest { |
| pub network: types::NetworkIdentifier, |
| pub credential: Credential, |
| } |
| |
| pub async fn serve( |
| iface_id: u16, |
| proxy: fidl_sme::ClientSmeProxy, |
| sme_event_stream: fidl_sme::ClientSmeEventStream, |
| req_stream: mpsc::Receiver<ManualRequest>, |
| update_sender: ClientListenerMessageSender, |
| iface_manager_sender: mpsc::Sender<ClientStateMachineNotification>, |
| saved_networks_manager: Arc<SavedNetworksManager>, |
| ) { |
| let disconnect_options = DisconnectingOptions { |
| disconnect_responder: None, |
| previous_network: None, |
| next_network: None, |
| }; |
| let common_options = CommonStateOptions { |
| proxy: proxy, |
| req_stream: req_stream.fuse(), |
| update_sender: update_sender, |
| iface_id: iface_id, |
| iface_manager_sender: iface_manager_sender, |
| saved_networks_manager: saved_networks_manager, |
| }; |
| let state_machine = |
| disconnecting_state(common_options, disconnect_options).into_state_machine(); |
| let removal_watcher = sme_event_stream.map_ok(|_| ()).try_collect::<()>(); |
| select! { |
| state_machine = state_machine.fuse() => { |
| match state_machine.void_unwrap_err() { |
| ExitReason(Err(e)) => info!("Client state machine for iface #{} terminated with an error: {:?}", |
| iface_id, e), |
| ExitReason(Ok(_)) => info!("Client state machine for iface #{} exited gracefully", |
| iface_id,), |
| } |
| } |
| removal_watcher = removal_watcher.fuse() => if let Err(e) = removal_watcher { |
| info!("Error reading from Client SME channel of iface #{}: {:?}", |
| iface_id, e); |
| }, |
| } |
| } |
| |
| /// Common parameters passed to all states |
| struct CommonStateOptions { |
| proxy: fidl_sme::ClientSmeProxy, |
| req_stream: ReqStream, |
| update_sender: ClientListenerMessageSender, |
| iface_id: u16, |
| iface_manager_sender: mpsc::Sender<ClientStateMachineNotification>, |
| saved_networks_manager: Arc<SavedNetworksManager>, |
| } |
| |
| async fn handle_exit_or_none_request(req: Option<ManualRequest>) -> Result<State, ExitReason> { |
| match req { |
| Some(ManualRequest::Exit(responder)) => { |
| responder.send(()).unwrap_or_else(|_| ()); |
| Err(ExitReason(Ok(()))) |
| } |
| None => { |
| return Err(ExitReason(Err(format_err!( |
| "The stream of user requests ended unexpectedly" |
| )))) |
| } |
| Some(ManualRequest::Disconnect(_)) => { |
| return Err(ExitReason(Err(format_err!( |
| "Unexpected disconnect request passed in to exit handler" |
| )))) |
| } |
| Some(ManualRequest::Connect(_)) => { |
| return Err(ExitReason(Err(format_err!( |
| "Unexpected connect request passed in to exit handler" |
| )))) |
| } |
| } |
| } |
| |
| // These functions were introduced to resolve the following error: |
| // ``` |
| // error[E0391]: cycle detected when evaluating trait selection obligation |
| // `impl core::future::future::Future: std::marker::Send` |
| // ``` |
| // which occurs when two functions that return an `impl Trait` call each other |
| // in a cycle. (e.g. this case `connecting_state` calling `disconnecting_state`, |
| // which calls `connecting_state`) |
| fn to_disconnecting_state( |
| common_options: CommonStateOptions, |
| disconnecting_options: DisconnectingOptions, |
| ) -> State { |
| disconnecting_state(common_options, disconnecting_options).into_state() |
| } |
| fn to_connecting_state( |
| common_options: CommonStateOptions, |
| connecting_options: ConnectingOptions, |
| ) -> State { |
| connecting_state(common_options, connecting_options).into_state() |
| } |
| |
| struct DisconnectingOptions { |
| disconnect_responder: Option<oneshot::Sender<()>>, |
| /// Information about the previously connected network, if there was one. Used to send out |
| /// listener updates. |
| previous_network: Option<(types::NetworkIdentifier, types::DisconnectStatus)>, |
| /// Configuration for the next network to connect to, after the disconnect is complete. If not |
| /// present, the state machine will proceed to IDLE. |
| next_network: Option<ConnectingOptions>, |
| } |
| /// The DISCONNECTING state requests an SME disconnect, then transitions to either: |
| /// - the CONNECTING state if options.next_network is present |
| /// - the IDLE state otherwise |
| async fn disconnecting_state( |
| common_options: CommonStateOptions, |
| options: DisconnectingOptions, |
| ) -> Result<State, ExitReason> { |
| debug!("Entering disconnecting state"); |
| |
| // TODO(53505): either make this fire-and-forget in the SME, or spawn a thread for this, |
| // so we don't block on it |
| common_options.proxy.disconnect().await.map_err(|e| { |
| ExitReason(Err(format_err!("Failed to send command to wlanstack: {:?}", e))) |
| })?; |
| |
| // Notify the caller that disconnect was sent to the SME |
| match options.disconnect_responder { |
| Some(responder) => responder.send(()).unwrap_or_else(|_| ()), |
| None => (), |
| } |
| // Notify listeners of disconnection |
| match options.previous_network { |
| Some((network_identifier, status)) => send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: network_identifier, |
| state: types::ConnectionState::Disconnected, |
| status: Some(status), |
| }, |
| ), |
| None => (), |
| } |
| |
| // Transition to next state |
| match options.next_network { |
| Some(next_network) => Ok(to_connecting_state(common_options, next_network)), |
| None => Ok(idle_state(common_options).into_state()), |
| } |
| } |
| |
| /// The IDLE state notifies the iface manager that we are idle, then awaits |
| /// further ManualRequests. |
| async fn idle_state(mut common_options: CommonStateOptions) -> Result<State, ExitReason> { |
| debug!("Entering idle state"); |
| |
| // Inform the iface manager that we are idle |
| common_options |
| .iface_manager_sender |
| .try_send(ClientStateMachineNotification::Idle { iface_id: common_options.iface_id }) |
| .map_err(|e| { |
| ExitReason(Err(format_err!("Failed to send notice to ifaceManager: {:?}", e))) |
| })?; |
| |
| // Wait for the next request from the caller |
| loop { |
| match common_options.req_stream.next().await { |
| // Immediately reply to disconnect requests indicating that the client is already stopped |
| Some(ManualRequest::Disconnect(responder)) => { |
| responder.send(()).unwrap_or_else(|_| ()); |
| } |
| Some(ManualRequest::Connect((connect_request, responder))) => { |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(responder), |
| connect_request: connect_request, |
| attempt_counter: 0, |
| }; |
| return Ok(to_connecting_state(common_options, connecting_options)); |
| } |
| exit_or_none => return handle_exit_or_none_request(exit_or_none).await, |
| } |
| } |
| } |
| |
| async fn wait_until_connected( |
| txn: fidl_sme::ConnectTransactionProxy, |
| ) -> Result<fidl_sme::ConnectResultCode, anyhow::Error> { |
| let mut stream = txn.take_event_stream(); |
| while let Some(event) = stream.try_next().await? { |
| match event { |
| fidl_sme::ConnectTransactionEvent::OnFinished { code } => return Ok(code), |
| } |
| } |
| Err(format_err!("Server closed the ConnectTransaction channel before sending a response")) |
| } |
| |
| struct ConnectingOptions { |
| connect_responder: Option<oneshot::Sender<()>>, |
| connect_request: ConnectRequest, |
| /// Count of previous consecutive failed connection attempts to this same network. |
| attempt_counter: u8, |
| } |
| |
| /// The CONNECTING state requests an SME connect. It handles the SME connect response: |
| /// - for a successful connection, transition to CONNECTED state |
| /// - for a failed connection, retry connection by passing a next_network to the |
| /// DISCONNECTING state, as long as there haven't been too many connection attempts |
| /// During this time, incoming ManualRequests are also monitored for: |
| /// - duplicate connect requests are deduped |
| /// - different connect requests are serviced by passing a next_network to the DISCONNECTING state |
| /// - disconnect requests cause a transition to DISCONNECTING state |
| async fn connecting_state( |
| mut common_options: CommonStateOptions, |
| options: ConnectingOptions, |
| ) -> Result<State, ExitReason> { |
| debug!("Entering connecting state"); |
| if options.attempt_counter > 0 { |
| info!( |
| "Retrying connection, {} attempts remaining", |
| MAX_CONNECTION_ATTEMPTS - options.attempt_counter |
| ); |
| } |
| |
| // Send a connect request to the SME |
| let (connect_txn, remote) = create_proxy() |
| .map_err(|e| ExitReason(Err(format_err!("Failed to create proxy: {:?}", e))))?; |
| let mut sme_connect_request = fidl_sme::ConnectRequest { |
| ssid: options.connect_request.network.ssid.clone(), |
| credential: sme_credential_from_policy(&options.connect_request.credential), |
| radio_cfg: RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl(), |
| deprecated_scan_type: fidl_fuchsia_wlan_common::ScanType::Active, |
| }; |
| common_options.proxy.connect(&mut sme_connect_request, Some(remote)).map_err(|e| { |
| ExitReason(Err(format_err!("Failed to send command to wlanstack: {:?}", e))) |
| })?; |
| let pending_connect_request = wait_until_connected(connect_txn).fuse(); |
| pin_mut!(pending_connect_request); |
| |
| // Send a "Connecting" update to listeners, unless this is a retry |
| if options.attempt_counter == 0 { |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network.clone(), |
| state: types::ConnectionState::Connecting, |
| status: None, |
| }, |
| ); |
| }; |
| |
| // Let the responder know we've successfully started this connection attempt |
| match options.connect_responder { |
| Some(responder) => responder.send(()).unwrap_or_else(|_| ()), |
| None => {} |
| } |
| |
| loop { |
| select! { |
| // Monitor the SME connection attempt |
| connected = pending_connect_request => { |
| let code = connected.map_err({ |
| |e| ExitReason(Err(format_err!("failed to send connect to sme: {:?}", e))) |
| })?; |
| // Notify the saved networks manager |
| common_options.saved_networks_manager.record_connect_result( |
| options.connect_request.network.clone().into(), |
| &options.connect_request.credential, |
| code |
| ).await; |
| match code { |
| fidl_sme::ConnectResultCode::Success => { |
| info!("Successfully connected to network"); |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network.clone(), |
| state: types::ConnectionState::Connected, |
| status: None |
| }, |
| ); |
| return Ok( |
| connected_state(common_options, options.connect_request).into_state() |
| ); |
| }, |
| fidl_sme::ConnectResultCode::CredentialRejected | fidl_sme::ConnectResultCode::WrongCredentialType => { |
| info!("Failed to connect. Will not retry because of credential error: {:?}", code); |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network, |
| state: types::ConnectionState::Failed, |
| status: Some(types::DisconnectStatus::CredentialsFailed), |
| }, |
| ); |
| return Ok(idle_state(common_options).into_state()); |
| }, |
| other => { |
| info!("Failed to connect: {:?}", other); |
| // Check if the limit for connection attempts to this network has been |
| // exceeded. |
| let new_attempt_count = options.attempt_counter + 1; |
| if new_attempt_count >= MAX_CONNECTION_ATTEMPTS { |
| info!("Exceeded maximum connection attempts, will not retry"); |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network, |
| state: types::ConnectionState::Failed, |
| status: Some(types::DisconnectStatus::ConnectionFailed) |
| }, |
| ); |
| return Ok(idle_state(common_options).into_state()); |
| } else { |
| // Limit not exceeded, retry. |
| let next_connecting_options = ConnectingOptions { |
| connect_responder: None, |
| connect_request: options.connect_request, |
| attempt_counter: new_attempt_count, |
| }; |
| let disconnecting_options = DisconnectingOptions { |
| disconnect_responder: None, |
| previous_network: None, |
| next_network: Some(next_connecting_options) |
| }; |
| return Ok(to_disconnecting_state(common_options, disconnecting_options)); |
| } |
| } |
| }; |
| }, |
| // Monitor incoming ManualRequests |
| new_req = common_options.req_stream.next() => { |
| match new_req { |
| Some(ManualRequest::Disconnect(responder)) => { |
| info!("Cancelling pending connect due to disconnect request"); |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network, |
| state: types::ConnectionState::Disconnected, |
| status: Some(types::DisconnectStatus::ConnectionStopped) |
| }, |
| ); |
| let options = DisconnectingOptions { |
| disconnect_responder: Some(responder), |
| previous_network: None, |
| next_network: None, |
| }; |
| return Ok(to_disconnecting_state(common_options, options)); |
| } |
| Some(ManualRequest::Connect((new_connect_request, new_responder))) => { |
| // Check if it's the same network as we're currently connected to. |
| // If yes, dedupe the request by adding the responder to our list of |
| // responders. |
| if (new_connect_request.network == options.connect_request.network) { |
| info!("Received duplicate connection request, deduping"); |
| new_responder.send(()).unwrap_or_else(|_| ()); |
| } else { |
| info!("Cancelling pending connect due to new connection request"); |
| send_listener_state_update( |
| &common_options.update_sender, |
| ClientNetworkState { |
| id: options.connect_request.network, |
| state: types::ConnectionState::Disconnected, |
| status: Some(types::DisconnectStatus::ConnectionStopped) |
| }, |
| ); |
| let next_connecting_options = ConnectingOptions { |
| connect_responder: Some(new_responder), |
| connect_request: new_connect_request, |
| attempt_counter: 0, |
| }; |
| let disconnecting_options = DisconnectingOptions { |
| disconnect_responder: None, |
| previous_network: None, |
| next_network: Some(next_connecting_options), |
| }; |
| return Ok(to_disconnecting_state(common_options, disconnecting_options)); |
| } |
| } |
| exit_or_none => return handle_exit_or_none_request(exit_or_none).await, |
| }; |
| }, |
| } |
| } |
| } |
| |
| /// The CONNECTED state monitors the SME status. It handles the SME status response: |
| /// - if still connected to the correct network, no action |
| /// - if disconnected, retry connection by passing a next_network to the |
| /// DISCONNECTING state |
| /// During this time, incoming ManualRequests are also monitored for: |
| /// - duplicate connect requests are deduped |
| /// - different connect requests are serviced by passing a next_network to the DISCONNECTING state |
| /// - disconnect requests cause a transition to DISCONNECTING state |
| async fn connected_state( |
| mut common_options: CommonStateOptions, |
| current_network: ConnectRequest, |
| ) -> Result<State, ExitReason> { |
| debug!("Entering connected state"); |
| |
| // TODO(57237): replace this poll with a notification from wlanstack in the ConnectTxn |
| // Holds a pending SME status request. Request status immediately upon entering the started state. |
| let mut pending_status_req = FuturesUnordered::new(); |
| pending_status_req.push(common_options.proxy.status()); |
| |
| let mut status_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(SME_STATUS_INTERVAL_SEC)); |
| |
| loop { |
| select! { |
| status_response = pending_status_req.select_next_some() => { |
| let status_response = status_response.map_err(|e| { |
| ExitReason(Err(format_err!("failed to get sme status: {:?}", e))) |
| })?; |
| match status_response.connected_to { |
| Some(bss_info) => { |
| // TODO(53545): send some stats to the saved network manager |
| if (bss_info.ssid != current_network.network.ssid) { |
| error!("Currently connected SSID changed unexpectedly"); |
| return Err(ExitReason(Err(format_err!("Currently connected SSID changed unexpectedly")))); |
| } |
| } |
| None => { |
| let next_connecting_options = ConnectingOptions { |
| connect_responder: None, |
| connect_request: current_network.clone(), |
| attempt_counter: 0, |
| }; |
| let options = DisconnectingOptions { |
| disconnect_responder: None, |
| previous_network: Some((current_network.clone().network, types::DisconnectStatus::ConnectionFailed)), |
| next_network: Some(next_connecting_options) |
| }; |
| info!("Detected disconnection from network"); |
| return Ok(disconnecting_state(common_options, options).into_state()); |
| } |
| } |
| }, |
| _ = status_timer.select_next_some() => { |
| if pending_status_req.is_empty() { |
| pending_status_req.push(common_options.proxy.clone().status()); |
| } |
| }, |
| req = common_options.req_stream.next() => { |
| match req { |
| Some(ManualRequest::Disconnect(responder)) => { |
| debug!("Disconnect requested"); |
| let options = DisconnectingOptions { |
| disconnect_responder: Some(responder), |
| previous_network: Some((current_network.network, types::DisconnectStatus::ConnectionStopped)), |
| next_network: None |
| }; |
| return Ok(disconnecting_state(common_options, options).into_state()); |
| } |
| Some(ManualRequest::Connect((new_connect_request, new_responder))) => { |
| // Check if it's the same network as we're currently connected to. If yes, reply immediately |
| if (new_connect_request.network == current_network.network) { |
| info!("Connection requested for current network, deduping request"); |
| new_responder.send(()).unwrap_or_else(|_| ()); |
| } else { |
| let next_connecting_options = ConnectingOptions { |
| connect_responder: Some(new_responder), |
| connect_request: new_connect_request, |
| attempt_counter: 0, |
| }; |
| let options = DisconnectingOptions { |
| disconnect_responder: None, |
| previous_network: Some((current_network.network, types::DisconnectStatus::ConnectionStopped)), |
| next_network: Some(next_connecting_options) |
| }; |
| info!("Connection to new network requested, disconnecting from current network"); |
| return Ok(disconnecting_state(common_options,options).into_state()) |
| } |
| } |
| exit_or_none => return handle_exit_or_none_request(exit_or_none).await, |
| }; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| config_management::network_config::{self, FailureReason}, |
| util::{cobalt::create_mock_cobalt_sender, listener, logger::set_logger_for_test}, |
| }, |
| fidl_fuchsia_stash as fidl_stash, fidl_fuchsia_wlan_policy as fidl_policy, fuchsia_zircon, |
| futures::{stream::StreamFuture, task::Poll, Future}, |
| rand::{distributions::Alphanumeric, thread_rng, Rng}, |
| std::time::SystemTime, |
| wlan_common::assert_variant, |
| }; |
| |
| struct TestValues { |
| common_options: CommonStateOptions, |
| sme_req_stream: fidl_sme::ClientSmeRequestStream, |
| client_req_sender: mpsc::Sender<ManualRequest>, |
| update_receiver: mpsc::UnboundedReceiver<listener::ClientListenerMessage>, |
| iface_manager_stream: mpsc::Receiver<ClientStateMachineNotification>, |
| } |
| |
| async fn test_setup() -> TestValues { |
| set_logger_for_test(); |
| let (client_req_sender, client_req_stream) = mpsc::channel(1); |
| let (iface_manager_sender, iface_manager_stream) = mpsc::channel(1); |
| let (update_sender, update_receiver) = mpsc::unbounded(); |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ClientSmeMarker>().expect("failed to create an sme channel"); |
| let sme_req_stream = sme_server.into_stream().expect("could not create SME request stream"); |
| let saved_networks_manager = Arc::new( |
| SavedNetworksManager::new_for_test() |
| .await |
| .expect("Failed to create saved networks manager"), |
| ); |
| |
| TestValues { |
| common_options: CommonStateOptions { |
| iface_id: 20, |
| iface_manager_sender: iface_manager_sender, |
| proxy: sme_proxy, |
| req_stream: client_req_stream.fuse(), |
| update_sender: update_sender, |
| saved_networks_manager: saved_networks_manager, |
| }, |
| sme_req_stream, |
| client_req_sender, |
| update_receiver, |
| iface_manager_stream, |
| } |
| } |
| |
| fn poll_sme_req( |
| exec: &mut fasync::Executor, |
| next_sme_req: &mut StreamFuture<fidl_sme::ClientSmeRequestStream>, |
| ) -> Poll<fidl_sme::ClientSmeRequest> { |
| exec.run_until_stalled(next_sme_req).map(|(req, stream)| { |
| *next_sme_req = stream.into_future(); |
| req.expect("did not expect the SME request stream to end") |
| .expect("error polling SME request stream") |
| }) |
| } |
| |
| async fn run_state_machine( |
| fut: impl Future<Output = Result<State, ExitReason>> + Send + 'static, |
| ) { |
| let state_machine = fut.into_state_machine(); |
| select! { |
| state_machine = state_machine.fuse() => return, |
| } |
| } |
| |
| /// Move stash requests forward so that a save request can progress. |
| fn process_stash_write( |
| exec: &mut fasync::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::SetValue{..}))) |
| ); |
| 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"); |
| } |
| ); |
| } |
| |
| #[test] |
| fn idle_state_gets_exit_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let initial_state = idle_state(test_values.common_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send an exit request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.exit(sender).expect("failed to make request"); |
| |
| // Ensure the state machine has no further actions and is exited |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn idle_state_notifies_iface_manager() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| let test_iface_id = test_values.common_options.iface_id; |
| |
| let initial_state = idle_state(test_values.common_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure the iface manager got a notification |
| assert_variant!(test_values.iface_manager_stream.try_next(), Ok(Some(message)) => { |
| assert_eq!(message, ClientStateMachineNotification::Idle { |
| iface_id: test_iface_id |
| }) |
| }); |
| } |
| |
| #[test] |
| fn idle_state_gets_connect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let initial_state = idle_state(test_values.common_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send a connect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, _receiver) = oneshot::channel(); |
| let next_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| client.connect(connect_request.clone(), sender).expect("failed to make request"); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| } |
| |
| #[test] |
| fn idle_state_gets_disconnect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let initial_state = idle_state(test_values.common_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send a disconnect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, _receiver) = oneshot::channel(); |
| client.disconnect(sender).expect("failed to make request"); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure nothing was sent to the SME, since we shouldn't have |
| // transitioned to the disconnect state at all |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| } |
| |
| #[test] |
| fn connecting_state_gets_exit_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: "test".as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let connecting_options = ConnectingOptions { |
| connect_responder: None, |
| connect_request: connect_request, |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send an exit request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.exit(sender).expect("failed to make request"); |
| |
| // Ensure the state machine has no further actions and is exited |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| fn rand_string() -> String { |
| thread_rng().sample_iter(&Alphanumeric).take(20).collect() |
| } |
| |
| #[test] |
| fn connecting_state_successfully_connects() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| // Do test set up manually to get stash server |
| set_logger_for_test(); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let (update_sender, mut update_receiver) = mpsc::unbounded(); |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ClientSmeMarker>().expect("failed to create an sme channel"); |
| let sme_req_stream = sme_server.into_stream().expect("could not create SME request stream"); |
| let temp_dir = tempfile::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)); |
| let saved_networks_manager = Arc::new(saved_networks); |
| let next_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wep, |
| }, |
| credential: Credential::Password("Anything".as_bytes().to_vec()), |
| }; |
| |
| // Store the network in the saved_networks_manager, so we can record connection success |
| let save_fut = saved_networks_manager |
| .store(connect_request.network.clone().into(), connect_request.credential.clone()); |
| pin_mut!(save_fut); |
| assert_variant!(exec.run_until_stalled(&mut save_fut), Poll::Pending); |
| process_stash_write(&mut exec, &mut stash_server); |
| assert_variant!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(()))); |
| |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let common_options = CommonStateOptions { |
| iface_id: 20, |
| iface_manager_sender: iface_manager_sender, |
| proxy: sme_proxy, |
| req_stream: client_req_stream.fuse(), |
| update_sender: update_sender, |
| saved_networks_manager: saved_networks_manager.clone(), |
| }; |
| let initial_state = connecting_state(common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wep, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| process_stash_write(&mut exec, &mut stash_server); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Check for a connect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wep, |
| }, |
| state: fidl_policy::ConnectionState::Connected, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Check that the saved networks manager has the connection recorded |
| let saved_networks = exec.run_singlethreaded( |
| saved_networks_manager.lookup(connect_request.network.clone().into()), |
| ); |
| assert_eq!(true, saved_networks[0].has_ever_connected); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!(exec.run_until_stalled(&mut update_receiver.into_future()), Poll::Pending); |
| } |
| |
| #[test] |
| fn connecting_state_fails_to_connect_and_retries() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let next_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Failed) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a disconnect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a connected update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connected, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[test] |
| fn connecting_state_fails_to_connect_at_max_retries() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| // Don't use test_values() because of issue with KnownEssStore |
| set_logger_for_test(); |
| let (update_sender, mut update_receiver) = mpsc::unbounded(); |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ClientSmeMarker>().expect("failed to create an sme channel"); |
| let sme_req_stream = sme_server.into_stream().expect("could not create SME request stream"); |
| let stash_id = "connecting_state_fails_to_connect_at_max_retries"; |
| let temp_dir = tempfile::TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join("networks.json"); |
| let tmp_path = temp_dir.path().join("tmp.json"); |
| let saved_networks_manager = Arc::new( |
| exec.run_singlethreaded(SavedNetworksManager::new_with_stash_or_paths( |
| stash_id, |
| path, |
| tmp_path, |
| create_mock_cobalt_sender(), |
| )) |
| .expect("Failed to create saved networks manager"), |
| ); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let common_options = CommonStateOptions { |
| iface_id: 20, |
| iface_manager_sender: iface_manager_sender, |
| proxy: sme_proxy, |
| req_stream: client_req_stream.fuse(), |
| update_sender: update_sender, |
| saved_networks_manager: saved_networks_manager.clone(), |
| }; |
| |
| let next_network_ssid = "bar"; |
| let next_security_type = types::SecurityType::None; |
| let next_credential = Credential::None; |
| let next_network_identifier = types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: next_security_type, |
| }; |
| let config_net_id = |
| network_config::NetworkIdentifier::from(next_network_identifier.clone()); |
| // save network to check that failed connect is recorded |
| exec.run_singlethreaded( |
| saved_networks_manager.store(config_net_id.clone(), next_credential.clone()), |
| ) |
| .expect("Failed to save network"); |
| let before_recording = SystemTime::now(); |
| |
| let connect_request = |
| ConnectRequest { network: next_network_identifier, credential: next_credential }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: MAX_CONNECTION_ATTEMPTS - 1, |
| }; |
| let initial_state = connecting_state(common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Failed) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a connect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: next_security_type, |
| }, |
| state: fidl_policy::ConnectionState::Failed, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionFailed), |
| }], |
| }; |
| assert_variant!( |
| update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!(exec.run_until_stalled(&mut update_receiver.into_future()), Poll::Pending); |
| |
| // Ensure no further requests were sent to the SME |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // Check that failure was recorded in SavedNetworksManager |
| let mut configs = exec.run_singlethreaded(saved_networks_manager.lookup(config_net_id)); |
| let network_config = configs.pop().expect("Failed to get saved network"); |
| let mut failures = network_config.perf_stats.failure_list.get_recent(before_recording); |
| let connect_failure = failures.pop().expect("Saved network is missing failure reason"); |
| assert_eq!(connect_failure.reason, FailureReason::GeneralFailure); |
| } |
| |
| #[test] |
| fn connecting_state_fails_to_connect_with_bad_password() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| // Don't use test_values() because of issue with KnownEssStore |
| set_logger_for_test(); |
| let (update_sender, mut update_receiver) = mpsc::unbounded(); |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ClientSmeMarker>().expect("failed to create an sme channel"); |
| let sme_req_stream = sme_server.into_stream().expect("could not create SME request stream"); |
| let stash_id = "connecting_state_fails_to_connect_with_bad_password"; |
| let temp_dir = tempfile::TempDir::new().expect("failed to create temporary directory"); |
| let path = temp_dir.path().join("networks.json"); |
| let tmp_path = temp_dir.path().join("tmp.json"); |
| let saved_networks_manager = Arc::new( |
| exec.run_singlethreaded(SavedNetworksManager::new_with_stash_or_paths( |
| stash_id, |
| path, |
| tmp_path, |
| create_mock_cobalt_sender(), |
| )) |
| .expect("Failed to create saved networks manager"), |
| ); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let common_options = CommonStateOptions { |
| iface_id: 20, |
| iface_manager_sender: iface_manager_sender, |
| proxy: sme_proxy, |
| req_stream: client_req_stream.fuse(), |
| update_sender: update_sender, |
| saved_networks_manager: saved_networks_manager.clone(), |
| }; |
| |
| let next_network_ssid = "bar"; |
| let next_network_identifier = types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }; |
| let config_net_id = |
| network_config::NetworkIdentifier::from(next_network_identifier.clone()); |
| let next_credential = Credential::Password("password".as_bytes().to_vec()); |
| // save network to check that failed connect is recorded |
| let saved_networks_manager = common_options.saved_networks_manager.clone(); |
| exec.run_singlethreaded( |
| saved_networks_manager.store(config_net_id.clone(), next_credential.clone()), |
| ) |
| .expect("Failed to save network"); |
| let before_recording = SystemTime::now(); |
| |
| let connect_request = |
| ConnectRequest { network: next_network_identifier, credential: next_credential }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: MAX_CONNECTION_ATTEMPTS - 1, |
| }; |
| let initial_state = connecting_state(common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::CredentialRejected) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a connect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Failed, |
| status: Some(fidl_policy::DisconnectStatus::CredentialsFailed), |
| }], |
| }; |
| assert_variant!( |
| update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!(exec.run_until_stalled(&mut update_receiver.into_future()), Poll::Pending); |
| |
| // Ensure no further requests were sent to the SME |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // Check that failure was recorded in SavedNetworksManager |
| let mut configs = exec.run_singlethreaded(saved_networks_manager.lookup(config_net_id)); |
| let network_config = configs.pop().expect("Failed to get saved network"); |
| let mut failures = network_config.perf_stats.failure_list.get_recent(before_recording); |
| let connect_failure = failures.pop().expect("Saved network is missing failure reason"); |
| assert_eq!(connect_failure.reason, FailureReason::CredentialRejected); |
| } |
| |
| #[test] |
| fn connecting_state_gets_duplicate_connect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let next_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Send a duplicate connect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (connect_sender2, mut connect_receiver2) = oneshot::channel(); |
| client.connect(connect_request.clone(), connect_sender2).expect("failed to make request"); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver2), Poll::Ready(Ok(()))); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.clone().credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a connect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(next_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connected, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[test] |
| fn connecting_state_gets_different_connect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let first_network_ssid = "foo"; |
| let second_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: first_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(first_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Send a different connect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (connect_sender2, mut connect_receiver2) = oneshot::channel(); |
| let connect_request2 = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: second_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| client.connect(connect_request2.clone(), connect_sender2).expect("failed to make request"); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(first_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionStopped), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // There should be 3 requests to the SME stacked up |
| // First SME request: connect to the first network |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn: _, control_handle: _ }) => { |
| assert_eq!(req.ssid, first_network_ssid.as_bytes().to_vec()); |
| // Don't bother sending response, listener is gone |
| } |
| ); |
| // Second SME request: disconnect |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| // Progress the state machine |
| // TODO(53505): remove this once the disconnect request is fire-and-forget |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| // Third SME request: connect to the second network |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, second_network_ssid.as_bytes().to_vec()); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver2), Poll::Ready(Ok(()))); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(second_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| // Check for a connected update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(second_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connected, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[test] |
| fn connecting_state_gets_disconnect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let first_network_ssid = "foo"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: first_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Ok(()))); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(first_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Send a disconnect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (disconnect_sender, mut disconnect_receiver) = oneshot::channel(); |
| client.disconnect(disconnect_sender).expect("failed to make request"); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(first_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionStopped), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // There should be 2 requests to the SME stacked up |
| // First SME request: connect to the first network |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn: _, control_handle: _ }) => { |
| assert_eq!(req.ssid, first_network_ssid.as_bytes().to_vec()); |
| // Don't bother sending response, listener is gone |
| } |
| ); |
| // Second SME request: disconnect |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check the disconnect responder |
| assert_variant!(exec.run_until_stalled(&mut disconnect_receiver), Poll::Ready(Ok(()))); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[test] |
| fn connecting_state_has_broken_sme() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let first_network_ssid = "foo"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: first_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, mut connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let initial_state = connecting_state(test_values.common_options, connecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| |
| // Break the SME by dropping the server end of the SME stream, so it causes an error |
| drop(test_values.sme_req_stream); |
| |
| // Ensure the state machine exits |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to have an error |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver), Poll::Ready(Err(_))); |
| } |
| |
| #[test] |
| fn connected_state_gets_exit_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: "test".as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let initial_state = connected_state(test_values.common_options, connect_request); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send an exit request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.exit(sender).expect("failed to make request"); |
| |
| // Ensure the state machine has no further actions and is exited |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn connected_state_gets_disconnect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let network_ssid = "test"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let initial_state = connected_state(test_values.common_options, connect_request); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Clear the SME status request |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => { |
| responder.send(&mut fidl_sme::ClientStatusResponse{ |
| connecting_to_ssid: vec![], |
| connected_to: Some(Box::new(fidl_sme::BssInfo{ |
| bssid: [0, 0, 0, 0, 0, 0], |
| ssid: network_ssid.as_bytes().to_vec(), |
| rx_dbm: 0, |
| snr_db: 0, |
| channel: 0, |
| protection: fidl_sme::Protection::Unknown, |
| compatible: true, |
| })) |
| }).expect("could not send sme response"); |
| } |
| ); |
| |
| // Send a disconnect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.disconnect(sender).expect("failed to make request"); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Respond to the SME disconnect |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update and the responder |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionStopped), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn connected_state_gets_duplicate_connect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let network_ssid = "test"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let initial_state = connected_state(test_values.common_options, connect_request.clone()); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Clear the SME status request |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => { |
| responder.send(&mut fidl_sme::ClientStatusResponse{ |
| connecting_to_ssid: vec![], |
| connected_to: Some(Box::new(fidl_sme::BssInfo{ |
| bssid: [0, 0, 0, 0, 0, 0], |
| ssid: network_ssid.as_bytes().to_vec(), |
| rx_dbm: 0, |
| snr_db: 0, |
| channel: 0, |
| protection: fidl_sme::Protection::Unknown, |
| compatible: true, |
| })) |
| }).expect("could not send sme response"); |
| } |
| ); |
| |
| // Send another duplicate request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.connect(connect_request.clone(), sender).expect("failed to make request"); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure nothing was sent to the SME |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn connected_state_gets_different_connect_request() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let first_network_ssid = "foo"; |
| let second_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: first_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let initial_state = connected_state(test_values.common_options, connect_request.clone()); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Clear the SME status request |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => { |
| responder.send(&mut fidl_sme::ClientStatusResponse{ |
| connecting_to_ssid: vec![], |
| connected_to: Some(Box::new(fidl_sme::BssInfo{ |
| bssid: [0, 0, 0, 0, 0, 0], |
| ssid: first_network_ssid.as_bytes().to_vec(), |
| rx_dbm: 0, |
| snr_db: 0, |
| channel: 0, |
| protection: fidl_sme::Protection::Unknown, |
| compatible: true, |
| })) |
| }).expect("could not send sme response"); |
| } |
| ); |
| |
| // Send a different connect request |
| let mut client = Client::new(test_values.client_req_sender); |
| let (connect_sender2, mut connect_receiver2) = oneshot::channel(); |
| let connect_request2 = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: second_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| client.connect(connect_request2.clone(), connect_sender2).expect("failed to make request"); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // There should be 2 requests to the SME stacked up |
| // First SME request: disconnect |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| // Progress the state machine |
| // TODO(53505): remove this once the disconnect request is fire-and-forget |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| // Second SME request: connect to the second network |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, second_network_ssid.as_bytes().to_vec()); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(first_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionStopped), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Check the responder was acknowledged |
| assert_variant!(exec.run_until_stalled(&mut connect_receiver2), Poll::Ready(Ok(()))); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(second_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| // Check for a connected update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(second_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connected, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[test] |
| fn connected_state_detects_network_disconnect() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let network_ssid = "foo"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let initial_state = connected_state(test_values.common_options, connect_request.clone()); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Respond to the SME status request with a disconnected status |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => { |
| responder.send(&mut fidl_sme::ClientStatusResponse{ |
| connecting_to_ssid: vec![], |
| connected_to: None |
| }).expect("could not send sme response"); |
| } |
| ); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for an SME disconnect request |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| // Progress the state machine |
| // TODO(53505): remove this once the disconnect request is fire-and-forget |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionFailed), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| |
| // Check for an SME request to reconnect |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, network_ssid.as_bytes().to_vec()); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| |
| // Check for a connecting update |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Connecting, |
| status: None, |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| } |
| |
| #[test] |
| fn disconnecting_state_completes_disconnect_to_idle() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let (sender, mut receiver) = oneshot::channel(); |
| let disconnecting_options = DisconnectingOptions { |
| disconnect_responder: Some(sender), |
| previous_network: None, |
| next_network: None, |
| }; |
| let initial_state = disconnecting_state(test_values.common_options, disconnecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a disconnect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Ensure the state machine has no further actions |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure no further requests were sent to the SME |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // Ensure no updates were sent to listeners |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn disconnecting_state_completes_disconnect_to_connecting() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let mut test_values = exec.run_singlethreaded(test_setup()); |
| |
| let previous_network_ssid = "foo"; |
| let next_network_ssid = "bar"; |
| let connect_request = ConnectRequest { |
| network: types::NetworkIdentifier { |
| ssid: next_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| credential: Credential::None, |
| }; |
| let (connect_sender, _connect_receiver) = oneshot::channel(); |
| let connecting_options = ConnectingOptions { |
| connect_responder: Some(connect_sender), |
| connect_request: connect_request.clone(), |
| attempt_counter: 0, |
| }; |
| let (disconnect_sender, mut disconnect_receiver) = oneshot::channel(); |
| // Include both a "previous" and "next" network |
| let disconnecting_options = DisconnectingOptions { |
| disconnect_responder: Some(disconnect_sender), |
| previous_network: Some(( |
| types::NetworkIdentifier { |
| ssid: previous_network_ssid.as_bytes().to_vec(), |
| type_: types::SecurityType::Wpa2, |
| }, |
| fidl_policy::DisconnectStatus::ConnectionStopped, |
| )), |
| next_network: Some(connecting_options), |
| }; |
| let initial_state = disconnecting_state(test_values.common_options, disconnecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| // Run the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Ensure a disconnect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Progress the state machine |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Check for a disconnect update and the disconnect responder |
| let client_state_update = ClientStateUpdate { |
| state: None, |
| networks: vec![ClientNetworkState { |
| id: fidl_policy::NetworkIdentifier { |
| ssid: String::from(previous_network_ssid).into_bytes(), |
| type_: fidl_policy::SecurityType::Wpa2, |
| }, |
| state: fidl_policy::ConnectionState::Disconnected, |
| status: Some(fidl_policy::DisconnectStatus::ConnectionStopped), |
| }], |
| }; |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert_eq!(updates, client_state_update); |
| }); |
| assert_variant!(exec.run_until_stalled(&mut disconnect_receiver), Poll::Ready(Ok(()))); |
| |
| // Ensure a connect request is sent to the SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => { |
| assert_eq!(req.ssid, next_network_ssid.as_bytes().to_vec()); |
| assert_eq!(req.credential, sme_credential_from_policy(&connect_request.credential)); |
| assert_eq!(req.radio_cfg, RadioConfig { phy: None, cbw: None, primary_chan: None }.to_fidl()); |
| assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active); |
| // Send connection response. |
| let (_stream, ctrl) = txn.expect("connect txn unused") |
| .into_stream_and_control_handle().expect("error accessing control handle"); |
| ctrl.send_on_finished(fidl_sme::ConnectResultCode::Success) |
| .expect("failed to send connection completion"); |
| } |
| ); |
| } |
| |
| #[test] |
| fn disconnecting_state_has_broken_sme() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| |
| let (sender, mut receiver) = oneshot::channel(); |
| let disconnecting_options = DisconnectingOptions { |
| disconnect_responder: Some(sender), |
| previous_network: None, |
| next_network: None, |
| }; |
| let initial_state = disconnecting_state(test_values.common_options, disconnecting_options); |
| let fut = run_state_machine(initial_state); |
| pin_mut!(fut); |
| |
| // Break the SME by dropping the server end of the SME stream, so it causes an error |
| drop(test_values.sme_req_stream); |
| |
| // Ensure the state machine exits |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to have an error |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Err(_))); |
| } |
| |
| #[test] |
| fn serve_loop_handles_startup() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| let sme_proxy = test_values.common_options.proxy; |
| let sme_event_stream = sme_proxy.take_event_stream(); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| let fut = serve( |
| 0, |
| sme_proxy, |
| sme_event_stream, |
| client_req_stream, |
| test_values.common_options.update_sender, |
| iface_manager_sender, |
| test_values.common_options.saved_networks_manager, |
| ); |
| pin_mut!(fut); |
| |
| // Run the state machine so it sends the initial SME disconnect request. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Run the future again and ensure that it has not exited after receiving the response. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| } |
| |
| #[test] |
| fn serve_loop_handles_sme_disappearance() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| |
| // Make our own SME proxy for this test |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ClientSmeMarker>().expect("failed to create an sme channel"); |
| let (sme_req_stream, sme_control_handle) = sme_server |
| .into_stream_and_control_handle() |
| .expect("could not create SME request stream"); |
| |
| let sme_fut = sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| let sme_event_stream = sme_proxy.take_event_stream(); |
| |
| let fut = serve( |
| 0, |
| sme_proxy, |
| sme_event_stream, |
| client_req_stream, |
| test_values.common_options.update_sender, |
| iface_manager_sender, |
| test_values.common_options.saved_networks_manager, |
| ); |
| pin_mut!(fut); |
| |
| // Run the state machine so it sends the initial SME disconnect request. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Run the future again and ensure that it has not exited after receiving the response. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| sme_control_handle.shutdown_with_epitaph(fuchsia_zircon::Status::UNAVAILABLE); |
| |
| // Ensure the state machine has no further actions and is exited |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| |
| #[test] |
| fn serve_loop_handles_exit() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| let sme_proxy = test_values.common_options.proxy; |
| let sme_event_stream = sme_proxy.take_event_stream(); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let (client_req_sender, client_req_stream) = mpsc::channel(1); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| pin_mut!(sme_fut); |
| |
| let fut = serve( |
| 0, |
| sme_proxy, |
| sme_event_stream, |
| client_req_stream, |
| test_values.common_options.update_sender, |
| iface_manager_sender, |
| test_values.common_options.saved_networks_manager, |
| ); |
| pin_mut!(fut); |
| |
| // Run the state machine so it sends the initial SME disconnect request. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder }) => { |
| responder.send().expect("could not send sme response"); |
| } |
| ); |
| |
| // Run the future again and ensure that it has not exited after receiving the response. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Send an exit request |
| let mut client = Client::new(client_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| client.exit(sender).expect("failed to make request"); |
| |
| // Ensure the state machine has no further actions and is exited |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[test] |
| fn serve_loop_handles_state_machine_error() { |
| let mut exec = fasync::Executor::new().expect("failed to create an executor"); |
| let test_values = exec.run_singlethreaded(test_setup()); |
| let sme_proxy = test_values.common_options.proxy; |
| let sme_event_stream = sme_proxy.take_event_stream(); |
| let (iface_manager_sender, _iface_manager_stream) = mpsc::channel(1); |
| let (_client_req_sender, client_req_stream) = mpsc::channel(1); |
| |
| let fut = serve( |
| 0, |
| sme_proxy, |
| sme_event_stream, |
| client_req_stream, |
| test_values.common_options.update_sender, |
| iface_manager_sender, |
| test_values.common_options.saved_networks_manager, |
| ); |
| pin_mut!(fut); |
| |
| // Drop the server end of the SME stream, so it causes an error |
| drop(test_values.sme_req_stream); |
| |
| // Ensure the state machine exits |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| } |
| } |