| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::access_point::types; |
| use crate::mode_management::{Defect, IfaceFailure}; |
| use crate::telemetry::{TelemetryEvent, TelemetrySender}; |
| use crate::util::listener::Message::NotifyListeners; |
| use crate::util::listener::{ |
| ApListenerMessageSender, ApStateUpdate, ApStatesUpdate, ConnectedClientInformation, |
| }; |
| use crate::util::state_machine::{self, ExitReason, IntoStateExt}; |
| use anyhow::format_err; |
| use fidl_fuchsia_wlan_sme as fidl_sme; |
| use fuchsia_async::{self as fasync, DurationExt}; |
| use fuchsia_sync::Mutex; |
| use fuchsia_zircon::{self as zx, DurationNum}; |
| use futures::channel::{mpsc, oneshot}; |
| use futures::future::FutureExt; |
| use futures::select; |
| use futures::stream::{self, Fuse, FuturesUnordered, StreamExt, TryStreamExt}; |
| use std::convert::Infallible; |
| use std::sync::Arc; |
| use tracing::{info, warn}; |
| use wlan_common::channel::{Cbw, Channel}; |
| use wlan_common::RadioConfig; |
| |
| const AP_STATUS_INTERVAL_SEC: i64 = 10; |
| |
| // If a scan is occurring on a PHY and that same PHY is asked to start an AP, the request to start |
| // the AP will likely fail. Scans are allowed a maximum to 10s to complete. The timeout is |
| // defined in //src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.h as |
| // |
| // #define BRCMF_ESCAN_TIMER_INTERVAL_MS 10000 /* E-Scan timeout */ |
| // |
| // As such, a minimum of 10s worth of retries should be allowed when starting the soft AP. Allow |
| // 12s worth of retries to ensure adequate time for the scan to finish. |
| const AP_START_RETRY_INTERVAL: i64 = 2; |
| const AP_START_MAX_RETRIES: u16 = 6; |
| |
| type State = state_machine::State<ExitReason>; |
| type ReqStream = stream::Fuse<mpsc::Receiver<ManualRequest>>; |
| |
| pub trait AccessPointApi { |
| fn start( |
| &mut self, |
| request: ApConfig, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), anyhow::Error>; |
| fn stop(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error>; |
| fn exit(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error>; |
| } |
| |
| pub struct AccessPoint { |
| req_sender: mpsc::Sender<ManualRequest>, |
| } |
| |
| impl AccessPoint { |
| pub fn new(req_sender: mpsc::Sender<ManualRequest>) -> Self { |
| Self { req_sender } |
| } |
| } |
| |
| impl AccessPointApi for AccessPoint { |
| fn start( |
| &mut self, |
| request: ApConfig, |
| responder: oneshot::Sender<()>, |
| ) -> Result<(), anyhow::Error> { |
| self.req_sender |
| .try_send(ManualRequest::Start((request, responder))) |
| .map_err(|e| format_err!("failed to send start request: {:?}", e)) |
| } |
| |
| fn stop(&mut self, responder: oneshot::Sender<()>) -> Result<(), anyhow::Error> { |
| self.req_sender |
| .try_send(ManualRequest::Stop(responder)) |
| .map_err(|e| format_err!("failed to send stop 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 { |
| Start((ApConfig, oneshot::Sender<()>)), |
| Stop(oneshot::Sender<()>), |
| Exit(oneshot::Sender<()>), |
| } |
| |
| // To avoid printing PII, only allow Debug in tests, runtime logging should use Display |
| #[cfg_attr(test, derive(Debug))] |
| #[derive(Clone, PartialEq)] |
| pub struct ApConfig { |
| pub id: types::NetworkIdentifier, |
| pub credential: Vec<u8>, |
| pub radio_config: RadioConfig, |
| pub mode: types::ConnectivityMode, |
| pub band: types::OperatingBand, |
| } |
| |
| impl From<ApConfig> for fidl_sme::ApConfig { |
| fn from(config: ApConfig) -> Self { |
| fidl_sme::ApConfig { |
| ssid: config.id.ssid.to_vec(), |
| password: config.credential, |
| radio_cfg: config.radio_config.into(), |
| } |
| } |
| } |
| |
| struct ApStateTrackerInner { |
| state: Option<ApStateUpdate>, |
| sender: ApListenerMessageSender, |
| } |
| |
| impl ApStateTrackerInner { |
| fn send_update(&mut self) -> Result<(), anyhow::Error> { |
| let updates = match self.state.clone() { |
| Some(state) => ApStatesUpdate { access_points: [state].to_vec() }, |
| None => ApStatesUpdate { access_points: [].to_vec() }, |
| }; |
| |
| self.sender |
| .clone() |
| .unbounded_send(NotifyListeners(updates)) |
| .map_err(|e| format_err!("failed to send state update: {}", e)) |
| } |
| } |
| |
| struct ApStateTracker { |
| inner: Mutex<ApStateTrackerInner>, |
| } |
| |
| impl ApStateTracker { |
| fn new(sender: ApListenerMessageSender) -> Self { |
| ApStateTracker { inner: Mutex::new(ApStateTrackerInner { state: None, sender }) } |
| } |
| |
| fn reset_state(&self, state: ApStateUpdate) -> Result<(), anyhow::Error> { |
| let mut inner = self.inner.lock(); |
| inner.state = Some(state); |
| inner.send_update() |
| } |
| |
| fn consume_sme_status_update( |
| &self, |
| cbw: Cbw, |
| update: fidl_sme::Ap, |
| ) -> Result<(), anyhow::Error> { |
| let mut inner = self.inner.lock(); |
| |
| if let Some(ref mut state) = inner.state { |
| let channel = Channel::new(update.channel, cbw); |
| let frequency = match channel.get_center_freq() { |
| Ok(frequency) => Some(frequency as u32), |
| Err(e) => { |
| info!("failed to convert channel to frequency: {}", e); |
| None |
| } |
| }; |
| |
| let client_info = Some(ConnectedClientInformation { count: update.num_clients as u8 }); |
| |
| if frequency != state.frequency || client_info != state.clients { |
| state.frequency = frequency; |
| state.clients = client_info; |
| inner.send_update()?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn update_operating_state( |
| &self, |
| new_state: types::OperatingState, |
| ) -> Result<(), anyhow::Error> { |
| let mut inner = self.inner.lock(); |
| |
| // If there is a new operating state, update the existing operating state if present. |
| if let Some(ref mut state) = inner.state { |
| if state.state != new_state { |
| state.state = new_state; |
| } |
| inner.send_update()?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn set_stopped_state(&self) -> Result<(), anyhow::Error> { |
| let mut inner = self.inner.lock(); |
| inner.state = None; |
| inner.send_update() |
| } |
| } |
| |
| struct CommonStateDependencies { |
| iface_id: u16, |
| proxy: fidl_sme::ApSmeProxy, |
| req_stream: ReqStream, |
| state_tracker: Arc<ApStateTracker>, |
| telemetry_sender: TelemetrySender, |
| defect_sender: mpsc::UnboundedSender<Defect>, |
| } |
| |
| pub async fn serve( |
| iface_id: u16, |
| proxy: fidl_sme::ApSmeProxy, |
| sme_event_stream: fidl_sme::ApSmeEventStream, |
| req_stream: Fuse<mpsc::Receiver<ManualRequest>>, |
| message_sender: ApListenerMessageSender, |
| telemetry_sender: TelemetrySender, |
| defect_sender: mpsc::UnboundedSender<Defect>, |
| ) { |
| let state_tracker = Arc::new(ApStateTracker::new(message_sender)); |
| let deps = CommonStateDependencies { |
| iface_id, |
| proxy, |
| req_stream, |
| state_tracker: state_tracker.clone(), |
| telemetry_sender, |
| defect_sender, |
| }; |
| let state_machine = stopped_state(deps).into_state_machine(); |
| let removal_watcher = sme_event_stream.map_ok(|_| ()).try_collect::<()>(); |
| select! { |
| state_machine = state_machine.fuse() => { |
| match state_machine { |
| Ok(v) => { |
| // This should never happen because the `Infallible` type should be impossible |
| // to create. |
| let _: Infallible = v; |
| unreachable!() |
| } |
| Err(ExitReason(Ok(()))) => info!("AP state machine for iface #{} exited", iface_id), |
| Err(ExitReason(Err(e))) => { |
| info!("AP state machine for iface #{} terminated with an error: {}", iface_id, e) |
| } |
| } |
| }, |
| removal_watcher = removal_watcher.fuse() => { |
| match removal_watcher { |
| Ok(()) => info!("AP interface was unexpectedly removed: {}", iface_id), |
| Err(e) => { |
| info!("Error reading from AP SME channel of iface #{}: {}", iface_id, e); |
| } |
| } |
| let _ = state_tracker.update_operating_state(types::OperatingState::Failed); |
| } |
| } |
| } |
| |
| fn perform_manual_request( |
| deps: CommonStateDependencies, |
| req: Option<ManualRequest>, |
| ) -> Result<State, ExitReason> { |
| match req { |
| Some(ManualRequest::Start((req, responder))) => { |
| Ok(starting_state(deps, req, AP_START_MAX_RETRIES, Some(responder)).into_state()) |
| } |
| Some(ManualRequest::Stop(responder)) => Ok(stopping_state(deps, responder).into_state()), |
| Some(ManualRequest::Exit(responder)) => { |
| responder.send(()).unwrap_or_else(|_| ()); |
| Err(ExitReason(Ok(()))) |
| } |
| None => { |
| // It is possible that the state machine will be cleaned up before it has the |
| // opportunity to realize that the SME is no longer functional. In this scenario, |
| // listeners need to be notified of the failure. |
| deps.state_tracker |
| .update_operating_state(types::OperatingState::Failed) |
| .map_err(|e| ExitReason(Err(e)))?; |
| |
| return Err(ExitReason(Err(format_err!( |
| "The stream of user requests ended unexpectedly" |
| )))); |
| } |
| } |
| } |
| |
| // This intermediate state supresses a compiler warning on detection of a cycle. |
| fn transition_to_starting( |
| deps: CommonStateDependencies, |
| req: ApConfig, |
| remaining_retries: u16, |
| responder: Option<oneshot::Sender<()>>, |
| ) -> Result<State, ExitReason> { |
| Ok(starting_state(deps, req, remaining_retries, responder).into_state()) |
| } |
| |
| /// In the starting state, a request to ApSmeProxy::Start is made. If the start request fails, |
| /// the state machine exits with an error. On success, the state machine transitions into the |
| /// started state to monitor the SME. |
| /// |
| /// The starting state can be entered in the following ways. |
| /// 1. When the state machine is stopped and it is asked to start an AP. |
| /// 2. When the state machine is started and the AP fails. |
| /// 3. When retrying a failed start attempt. |
| /// |
| /// The starting state can be exited in the following ways. |
| /// 1. If stopping the AP SME fails, exit the state machine. The stop operation should be a very |
| /// brief interaction with the firmware. Failure can only occur if the SME layer times out the |
| /// operation or the driver crashes. Either scenario should be considered fatal. |
| /// 2. If the start request fails because the AP state machine cannot communicate with the SME, |
| /// the state machine will exit with an error. |
| /// 3. If the start request fails due to an error reported by the SME, it's possible that a client |
| /// interface associated with the same PHY is scanning. In this case, allow the operation to be |
| /// retried by transitioning back through the starting state. Once the retries are exhausted, |
| /// exit the state machine with an error. |
| /// 4. When the start request finishes, transition into the started state. |
| async fn starting_state( |
| mut deps: CommonStateDependencies, |
| req: ApConfig, |
| remaining_retries: u16, |
| responder: Option<oneshot::Sender<()>>, |
| ) -> Result<State, ExitReason> { |
| // Send a stop request to ensure that the AP begins in an unstarting state. |
| let stop_result = match deps.proxy.stop().await { |
| Ok(fidl_sme::StopApResultCode::Success) => Ok(()), |
| Ok(code) => Err(format_err!("Unexpected StopApResultCode: {:?}", code)), |
| Err(e) => Err(format_err!("Failed to send a stop command to wlanstack: {}", e)), |
| }; |
| |
| // If the stop operation failed, send a failure update and exit the state machine. |
| if stop_result.is_err() { |
| deps.state_tracker |
| .reset_state(ApStateUpdate::new( |
| req.id.clone(), |
| types::OperatingState::Failed, |
| req.mode, |
| req.band, |
| )) |
| .map_err(|e| ExitReason(Err(e)))?; |
| |
| stop_result.map_err(|e| ExitReason(Err(e)))?; |
| } |
| |
| // If the stop operation was successful, update all listeners that the AP is stopped. |
| deps.state_tracker.set_stopped_state().map_err(|e| ExitReason(Err(e)))?; |
| |
| // Update all listeners that a new AP is starting if this is the first attempt to start the AP. |
| if remaining_retries == AP_START_MAX_RETRIES { |
| deps.state_tracker |
| .reset_state(ApStateUpdate::new( |
| req.id.clone(), |
| types::OperatingState::Starting, |
| req.mode, |
| req.band, |
| )) |
| .map_err(|e| ExitReason(Err(e)))?; |
| } |
| |
| let ap_config = fidl_sme::ApConfig::from(req.clone()); |
| let start_result = match deps.proxy.start(&ap_config).await { |
| Ok(fidl_sme::StartApResultCode::Success) => { |
| deps.telemetry_sender.send(TelemetryEvent::StartApResult(Ok(()))); |
| Ok(()) |
| } |
| Ok(code) => { |
| // Log a metric indicating that starting the AP failed. |
| deps.telemetry_sender.send(TelemetryEvent::StartApResult(Err(()))); |
| if let Err(e) = |
| deps.defect_sender.unbounded_send(Defect::Iface(IfaceFailure::ApStartFailure { |
| iface_id: deps.iface_id, |
| })) |
| { |
| warn!("Failed to log AP start defect: {}", e) |
| } |
| |
| // For any non-Success response, attempt to retry the start operation. A successful |
| // stop operation followed by an unsuccessful start operation likely indicates that the |
| // PHY associated with this AP interface is busy scanning. A future attempt to start |
| // may succeed. |
| if remaining_retries > 0 { |
| let retry_timer = fasync::Timer::new(AP_START_RETRY_INTERVAL.seconds().after_now()); |
| |
| // To ensure that the state machine remains responsive, process any incoming |
| // requests while waiting for the timer to expire. |
| select! { |
| () = retry_timer.fuse() => { |
| return transition_to_starting( |
| deps, |
| req, |
| remaining_retries - 1, |
| responder, |
| ); |
| }, |
| req = deps.req_stream.next() => { |
| // If a new request comes in, clear out the current AP state. |
| deps.state_tracker |
| .set_stopped_state() |
| .map_err(|e| ExitReason(Err(e)))?; |
| return perform_manual_request( |
| deps, |
| req, |
| ); |
| } |
| } |
| } |
| |
| // Return an error if all retries have been exhausted. |
| Err(format_err!("Failed to start AP: {:?}", code)) |
| } |
| Err(e) => { |
| // If communicating with the SME fails, further attempts to start the AP are guaranteed |
| // to fail. |
| Err(format_err!("Failed to send a start command to wlanstack: {}", e)) |
| } |
| }; |
| |
| start_result.map_err(|e| { |
| // Send a failure notification. |
| if let Err(e) = deps.state_tracker.reset_state(ApStateUpdate::new( |
| req.id.clone(), |
| types::OperatingState::Failed, |
| req.mode, |
| req.band, |
| )) { |
| info!("Unable to notify listeners of AP start failure: {:?}", e); |
| } |
| ExitReason(Err(e)) |
| })?; |
| |
| match responder { |
| Some(responder) => responder.send(()).unwrap_or_else(|_| ()), |
| None => {} |
| } |
| |
| deps.state_tracker |
| .update_operating_state(types::OperatingState::Active) |
| .map_err(|e| ExitReason(Err(e)))?; |
| return Ok(started_state(deps, req).into_state()); |
| } |
| |
| /// In the stopping state, an ApSmeProxy::Stop is requested. Once the stop request has been |
| /// processed by the ApSmeProxy, all requests to stop the AP are acknowledged. The state machine |
| /// then transitions into the stopped state. |
| /// |
| /// The stopping state can be entered in the following ways. |
| /// 1. When a manual stop request is made when the state machine is in the started state. |
| /// |
| /// The stopping state can be exited in the following ways. |
| /// 1. When the request to stop the SME completes, the state machine will transition to the stopped |
| /// state. |
| /// 2. If an SME interaction fails, exits the state machine with an error. |
| async fn stopping_state( |
| deps: CommonStateDependencies, |
| responder: oneshot::Sender<()>, |
| ) -> Result<State, ExitReason> { |
| let result = match deps.proxy.stop().await { |
| Ok(fidl_sme::StopApResultCode::Success) => Ok(()), |
| Ok(code) => Err(format_err!("Unexpected StopApResultCode: {:?}", code)), |
| Err(e) => Err(format_err!("Failed to send a stop command to wlanstack: {}", e)), |
| }; |
| |
| // If the stop command fails, the SME is probably unusable. If the state is not updated before |
| // evaluating the stop result code, the AP state updates may end up with a lingering reference |
| // to a started or starting AP. |
| deps.state_tracker.set_stopped_state().map_err(|e| ExitReason(Err(e)))?; |
| result.map_err(|e| ExitReason(Err(e)))?; |
| |
| // Ack the request to stop the AP. |
| responder.send(()).unwrap_or_else(|_| ()); |
| |
| Ok(stopped_state(deps).into_state()) |
| } |
| |
| async fn stopped_state(mut deps: CommonStateDependencies) -> Result<State, ExitReason> { |
| // Wait for the next request from the caller |
| loop { |
| let req = deps.req_stream.next().await; |
| match req { |
| // Immediately reply to stop requests indicating that the AP is already stopped |
| Some(ManualRequest::Stop(responder)) => { |
| responder.send(()).unwrap_or_else(|_| ()); |
| } |
| // All other requests are handled manually |
| other => return perform_manual_request(deps, other), |
| } |
| } |
| } |
| |
| async fn started_state( |
| mut deps: CommonStateDependencies, |
| req: ApConfig, |
| ) -> Result<State, ExitReason> { |
| // Holds a pending status request. Request status immediately upon entering the started state. |
| let mut pending_status_req = FuturesUnordered::new(); |
| pending_status_req.push(deps.proxy.status()); |
| |
| let mut status_timer = |
| fasync::Interval::new(zx::Duration::from_seconds(AP_STATUS_INTERVAL_SEC)); |
| |
| // Channel bandwidth is required for frequency computation when reporting state updates. |
| let cbw = req.radio_config.channel.cbw; |
| |
| loop { |
| select! { |
| status_response = pending_status_req.select_next_some() => { |
| let status_response = match status_response { |
| Ok(status_response) => status_response, |
| Err(e) => { |
| // If querying AP status fails, notify listeners and exit the state |
| // machine. |
| deps.state_tracker.update_operating_state(types::OperatingState::Failed) |
| .map_err(|e| { ExitReason(Err(e)) })?; |
| |
| return Err(ExitReason(Err(anyhow::Error::from(e)))); |
| } |
| }; |
| |
| match status_response.running_ap { |
| Some(sme_state) => { |
| deps.state_tracker.consume_sme_status_update(cbw, *sme_state) |
| .map_err(|e| { ExitReason(Err(e)) })?; |
| } |
| None => { |
| deps.state_tracker.update_operating_state(types::OperatingState::Failed) |
| .map_err(|e| { ExitReason(Err(e)) })?; |
| |
| return transition_to_starting( |
| deps, |
| req, |
| AP_START_MAX_RETRIES, |
| None, |
| ); |
| } |
| } |
| }, |
| _ = status_timer.select_next_some() => { |
| if pending_status_req.is_empty() { |
| pending_status_req.push(deps.proxy.clone().status()); |
| } |
| }, |
| req = deps.req_stream.next() => { |
| return perform_manual_request( |
| deps, |
| req, |
| ); |
| }, |
| complete => { |
| panic!("AP state machine terminated unexpectedly"); |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::util::listener; |
| use fidl::endpoints::create_proxy; |
| use fidl_fuchsia_wlan_common as fidl_common; |
| use futures::stream::StreamFuture; |
| use futures::task::Poll; |
| use futures::Future; |
| use std::pin::pin; |
| use wlan_common::assert_variant; |
| |
| struct TestValues { |
| deps: CommonStateDependencies, |
| sme_req_stream: fidl_sme::ApSmeRequestStream, |
| ap_req_sender: mpsc::Sender<ManualRequest>, |
| update_receiver: mpsc::UnboundedReceiver<listener::ApMessage>, |
| telemetry_receiver: mpsc::Receiver<TelemetryEvent>, |
| defect_receiver: mpsc::UnboundedReceiver<Defect>, |
| } |
| |
| fn test_setup() -> TestValues { |
| let (ap_req_sender, ap_req_stream) = mpsc::channel(1); |
| let (update_sender, update_receiver) = mpsc::unbounded(); |
| let (sme_proxy, sme_server) = |
| create_proxy::<fidl_sme::ApSmeMarker>().expect("failed to create an sme channel"); |
| let sme_req_stream = sme_server.into_stream().expect("could not create SME request stream"); |
| let (telemetry_sender, telemetry_receiver) = mpsc::channel(100); |
| let telemetry_sender = TelemetrySender::new(telemetry_sender); |
| let (defect_sender, defect_receiver) = mpsc::unbounded(); |
| |
| let deps = CommonStateDependencies { |
| iface_id: 123, |
| proxy: sme_proxy, |
| req_stream: ap_req_stream.fuse(), |
| state_tracker: Arc::new(ApStateTracker::new(update_sender)), |
| telemetry_sender, |
| defect_sender, |
| }; |
| |
| TestValues { |
| deps, |
| sme_req_stream, |
| ap_req_sender, |
| update_receiver, |
| telemetry_receiver, |
| defect_receiver, |
| } |
| } |
| |
| fn create_network_id() -> types::NetworkIdentifier { |
| types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| } |
| } |
| |
| fn poll_sme_req( |
| exec: &mut fasync::TestExecutor, |
| next_sme_req: &mut StreamFuture<fidl_sme::ApSmeRequestStream>, |
| ) -> Poll<fidl_sme::ApSmeRequest> { |
| 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, |
| } |
| } |
| |
| #[fuchsia::test] |
| fn test_stop_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and ignore the status request |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 0, num_clients: 0 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // Issue a stop request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| ap.stop(sender).expect("failed to make stop request"); |
| |
| // Run the state machine and ensure that a stop request is issued by the SME proxy. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send SME stop response"); |
| } |
| ); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_exit_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and ignore the status request |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 0, num_clients: 0 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // Issue an exit request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| ap.exit(sender).expect("failed to make stop request"); |
| |
| // Expect the responder to be acknowledged and the state machine to exit. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_start_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and ignore the status request |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 0, num_clients: 0 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // Issue a start request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| ap.start(req, sender).expect("failed to make stop request"); |
| |
| // Expect that the state machine issues a stop request followed by a start 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::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Verify that the SME response is plumbed back to the caller. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_duplicate_status_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let mut state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.frequency = Some(2437); |
| state.clients = Some(ConnectedClientInformation { count: 0 }); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and send back an identical status. |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 6, num_clients: 0 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // Run the state machine and ensure no update has been sent. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_new_status_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let mut state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.frequency = Some(0); |
| state.clients = Some(ConnectedClientInformation { count: 0 }); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and send back an identical status. |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 0, num_clients: 1 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // Run the state machine and ensure an update has been sent. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Ready((Some(listener::Message::NotifyListeners(updates)), _)) => { |
| assert!(!updates.access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_sme_failure_during_started() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Drop the serving side of the SME so that a status request will result in an error. |
| drop(test_values.sme_req_stream); |
| |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| { |
| let mut state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.frequency = Some(0); |
| state.clients = Some(ConnectedClientInformation { count: 0 }); |
| test_values.deps.state_tracker.inner.lock().state = Some(state); |
| } |
| |
| // Run the started state and send back an identical status. |
| let fut = started_state(test_values.deps, req); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // The state machine should exit when it is unable to query status. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Verify that a failure notification is send to listeners. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Failed); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_stop_while_stopped() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| // Run the stopped state. |
| let fut = stopped_state(test_values.deps); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Issue a stop request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| ap.stop(sender).expect("failed to make stop request"); |
| |
| // Expect the responder to be acknowledged immediately. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_exit_while_stopped() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| // Run the stopped state. |
| let fut = stopped_state(test_values.deps); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Issue an exit request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, mut receiver) = oneshot::channel(); |
| ap.exit(sender).expect("failed to make stop request"); |
| |
| // Expect the responder to be acknowledged and the state machine to exit. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_start_while_stopped() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Run the stopped state. |
| let fut = stopped_state(test_values.deps); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Issue a start request. |
| let (sender, mut receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| ap.start(req, sender).expect("failed to make stop request"); |
| |
| // Expect that the state machine issues a stop request. |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // An empty update should be sent after stopping. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| |
| // The empty update should be quickly followed by a starting update. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| |
| // A start request should have been issues to the SME proxy. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Verify that the SME response is plumbed back to the caller. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(()))); |
| |
| // There should be a pending active state notification |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Active); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_exit_while_stopping() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| // Run the stopping state. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| let fut = stopping_state(test_values.deps, stop_sender); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Issue an exit request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (exit_sender, mut exit_receiver) = oneshot::channel(); |
| ap.exit(exit_sender).expect("failed to make stop request"); |
| |
| // While stopping is still in progress, exit and stop should not be responded to yet. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut exit_receiver), Poll::Pending); |
| |
| // Once stop AP request is finished, the state machine can terminate |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Ok(()))); |
| assert_variant!(exec.run_until_stalled(&mut exit_receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_stop_while_stopping() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Run the stopping state. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| let fut = stopping_state(test_values.deps, stop_sender); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Verify that no state update is ready yet. |
| assert_variant!(&mut test_values.update_receiver.try_next(), Err(_)); |
| |
| // Issue a stop request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (second_stop_sender, mut second_stop_receiver) = oneshot::channel(); |
| ap.stop(second_stop_sender).expect("failed to make stop request"); |
| |
| // Expect the stop request from the SME proxy |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Expect both responders to be acknowledged. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Ok(()))); |
| assert_variant!(exec.run_until_stalled(&mut second_stop_receiver), Poll::Ready(Ok(()))); |
| |
| // There should be a new update indicating that no AP's are active. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_start_while_stopping() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| |
| // Run the stopping state. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| let fut = stopping_state(test_values.deps, stop_sender); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Issue a start request. |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| ap.start(req, start_sender).expect("failed to make stop request"); |
| |
| // The state machine should not respond to the stop request yet until it's finished. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Pending); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| let stop_responder = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => responder |
| ); |
| |
| // The state machine should not send new request yet since stop is still unfinished |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // After SME sends response, the state machine can proceed |
| stop_responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Ok(()))); |
| |
| // Expect another stop request from the state machine entering the starting state. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Expect a start 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::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Expect the start responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Ok(()))); |
| } |
| |
| #[fuchsia::test] |
| fn test_sme_failure_while_stopping() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Drop the serving side of the SME so that the stop request will result in an error. |
| drop(test_values.sme_req_stream); |
| |
| // Run the stopping state. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| let fut = stopping_state(test_values.deps, stop_sender); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // The state machine should exit when it is unable to issue the stop command. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Err(_))); |
| |
| // There should be a new update indicating that no AP's are active. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_failed_result_code_while_stopping() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Run the stopping state. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| let fut = stopping_state(test_values.deps, stop_sender); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Verify that no state update is ready yet. |
| assert_variant!(&mut test_values.update_receiver.try_next(), Err(_)); |
| |
| // Expect the stop request from the SME proxy |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::InternalError).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // The state machine should exit. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Err(_))); |
| |
| // There should be a new update indicating that no AP's are active. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_stop_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state |
| let fut = starting_state(test_values.deps, req, 0, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Handle the initial disconnect request |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Wait for a start request, but don't reply to it yet. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let start_responder = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| } |
| ); |
| |
| // Issue a stop request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| ap.stop(stop_sender).expect("failed to make stop request"); |
| |
| // Run the state machine and ensure that a stop request is not issued by the SME proxy yet. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // After SME responds to start request, the state machine can continue |
| start_responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("could not send SME start response"); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Ok(()))); |
| |
| // When state machine goes to started state, it issues a status request. Ignore it. |
| let _status_responder = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => responder |
| ); |
| |
| // Stop request should be issued now to SME |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send SME stop response"); |
| } |
| ); |
| |
| // Expect the responder to be acknowledged |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Ok(()))); |
| |
| // The successful AP start should be logged. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Ok(())))) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_start_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state |
| let fut = starting_state(test_values.deps, req, 0, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Handle the initial disconnect request |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Wait for a start request, but don't reply to it. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let start_responder = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| } |
| ); |
| |
| // Issue a second start request. |
| let (second_start_sender, mut second_start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| ap.start(req, second_start_sender).expect("failed to make start request"); |
| |
| // Run the state machine and ensure that the first start request is still pending. |
| // Furthermore, no new start request is issued yet. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Pending); |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| |
| // Respond to the first start request |
| start_responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("failed to send start response"); |
| |
| // The first request should receive the acknowledgement, the second one shouldn't. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Ok(()))); |
| assert_variant!(exec.run_until_stalled(&mut second_start_receiver), Poll::Pending); |
| |
| // The state machine checks for status to make sure AP is started |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Status{ responder }) => { |
| let ap_info = fidl_sme::Ap { ssid: vec![], channel: 0, num_clients: 0 }; |
| let response = fidl_sme::ApStatusResponse { |
| running_ap: Some(Box::new(ap_info)) |
| }; |
| responder.send(&response).expect("could not send AP status response"); |
| } |
| ); |
| |
| // The state machine should transition back into the starting state and issue a stop |
| // request, due to the second start request. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send SME stop response"); |
| } |
| ); |
| |
| // The state machine should then issue a start 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::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("failed to send start response"); |
| } |
| ); |
| |
| // The second start request should receive the acknowledgement now. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut second_start_receiver), Poll::Ready(Ok(()))); |
| |
| // The successful AP start event should be logged. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Ok(())))) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_exit_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state |
| let fut = starting_state(test_values.deps, req, 0, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Handle the initial disconnect request |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| // Wait for a start request, but don't reply to it. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| let start_responder = assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| } |
| ); |
| |
| // Issue an exit request. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (exit_sender, mut exit_receiver) = oneshot::channel(); |
| ap.exit(exit_sender).expect("failed to make stop request"); |
| |
| // While starting is still in progress, exit and start should not be responded to yet. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Pending); |
| assert_variant!(exec.run_until_stalled(&mut exit_receiver), Poll::Pending); |
| |
| // Once start AP request is finished, the state machine can terminate |
| start_responder |
| .send(fidl_sme::StartApResultCode::Success) |
| .expect("could not send AP start response"); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut exit_receiver), Poll::Ready(Ok(()))); |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Ok(()))); |
| |
| // The AP start success event should be logged to telemetry. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Ok(())))) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_sme_breaks_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Drop the serving side of the SME so that client requests fail. |
| drop(test_values.sme_req_stream); |
| |
| let (start_sender, _start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state |
| let fut = starting_state(test_values.deps, req, 0, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Run the state machine and expect it to exit |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // No metric should be logged in this case and the sender should have been dropped. |
| assert_variant!(test_values.telemetry_receiver.try_next(), Ok(None)); |
| } |
| |
| #[fuchsia::test] |
| fn test_sme_fails_to_stop_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, _start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state |
| let fut = starting_state(test_values.deps, req, 0, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // Handle the initial disconnect request and send back a failure. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::TimedOut) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // The future should complete. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // There should also be a failed state update. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Failed); |
| }); |
| |
| // No metric should be logged in this case and the sender should have been dropped. |
| assert_variant!(test_values.telemetry_receiver.try_next(), Ok(None)); |
| } |
| |
| #[fuchsia::test] |
| fn test_sme_fails_to_start_while_starting() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Start off in the starting state with AP_START_MAX_RETRIES retry attempts. |
| let fut = starting_state(test_values.deps, req, AP_START_MAX_RETRIES, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // We'll need to inject some SME responses. |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| for retry_number in 0..(AP_START_MAX_RETRIES + 1) { |
| // Handle the initial stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // There should also be a stopped state update. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(_))) |
| ); |
| |
| // If this is the first attempt, there should be a starting notification, otherwise |
| // there should be no update. |
| if retry_number == 0 { |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| } else { |
| assert_variant!(test_values.update_receiver.try_next(), Err(_)); |
| } |
| |
| // Wait for a start request and send back a timeout. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::TimedOut) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| if retry_number < AP_START_MAX_RETRIES { |
| // The future should still be running. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Verify that no new message has been reported yet. |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Pending); |
| |
| // The state machine should then retry following the retry interval. |
| assert_variant!(exec.wake_next_timer(), Some(_)); |
| } |
| } |
| |
| // The future should complete. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // Verify that the start receiver got an error. |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Err(_))); |
| |
| // There should be a failure notification at the end of the retries. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Failed); |
| }); |
| |
| // A metric should be logged for the failure to start the AP. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Err(())))) |
| ); |
| |
| // A defect should be sent as well. |
| assert_variant!( |
| test_values.defect_receiver.try_next(), |
| Ok(Some(Defect::Iface(IfaceFailure::ApStartFailure { .. }))) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_stop_after_start_failure() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Insert a stop request to be processed after starting the AP fails. |
| let (stop_sender, mut stop_receiver) = oneshot::channel(); |
| test_values |
| .ap_req_sender |
| .try_send(ManualRequest::Stop(stop_sender)) |
| .expect("failed to request AP stop"); |
| |
| // Start off in the starting state with AP_START_MAX_RETRIES retry attempts. |
| let fut = starting_state(test_values.deps, req, AP_START_MAX_RETRIES, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // We'll need to inject some SME responses. |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| // Handle the initial stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // There should also be a stopped state update. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(_))) |
| ); |
| |
| // Followed by a starting update. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| |
| // Wait for a start request and send back a timeout. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::TimedOut) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // At this point, the state machine should pause before retrying the start request. It |
| // should also check to see if there are any incoming AP commands and find the initial stop |
| // request. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // A metric should be logged for the failure to start the AP. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Err(())))) |
| ); |
| |
| // A defect should be sent as well. |
| assert_variant!( |
| test_values.defect_receiver.try_next(), |
| Ok(Some(Defect::Iface(IfaceFailure::ApStartFailure { .. }))) |
| ); |
| |
| // The start sender will be dropped in this transition. |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Err(_))); |
| |
| // There should be a pending AP stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // The future should be parked in the stopped state. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // Verify that the stop receiver is acknowledged. |
| assert_variant!(exec.run_until_stalled(&mut stop_receiver), Poll::Ready(Ok(()))); |
| |
| // There should be a new update indicating that no AP's are active. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_start_after_start_failure() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, mut start_receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config: radio_config.clone(), |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Insert a stop request to be processed after starting the AP fails. |
| let mut requested_id = create_network_id(); |
| requested_id.ssid = types::Ssid::try_from("second_test_ssid").unwrap(); |
| |
| let requested_config = ApConfig { |
| id: requested_id.clone(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| let (start_response_sender, _) = oneshot::channel(); |
| test_values |
| .ap_req_sender |
| .try_send(ManualRequest::Start((requested_config, start_response_sender))) |
| .expect("failed to request AP stop"); |
| |
| // Start off in the starting state with AP_START_MAX_RETRIES retry attempts. |
| let fut = starting_state(test_values.deps, req, AP_START_MAX_RETRIES, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // We'll need to inject some SME responses. |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| // Handle the initial stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // There should also be a stopped state update. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(_))) |
| ); |
| |
| // Followed by a starting update. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| |
| // Wait for a start request and send back a timeout. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::TimedOut) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // At this point, the state machine should pause before retrying the start request. It |
| // should also check to see if there are any incoming AP commands and find the initial |
| // start request. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // A metric should be logged for the failure to start the AP. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Err(())))) |
| ); |
| |
| // A defect should be sent as well. |
| assert_variant!( |
| test_values.defect_receiver.try_next(), |
| Ok(Some(Defect::Iface(IfaceFailure::ApStartFailure { .. }))) |
| ); |
| |
| // The original start sender will be dropped in this transition. |
| assert_variant!(exec.run_until_stalled(&mut start_receiver), Poll::Ready(Err(_))); |
| |
| // There should be a pending AP stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // This should be followed by another start request that matches the requested config. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config, responder: _ }) => { |
| assert_eq!(config.ssid, requested_id.ssid); |
| } |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_exit_after_start_failure() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| let (start_sender, _) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let req = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| // Insert a stop request to be processed after starting the AP fails. |
| let (exit_sender, mut exit_receiver) = oneshot::channel(); |
| test_values |
| .ap_req_sender |
| .try_send(ManualRequest::Exit(exit_sender)) |
| .expect("failed to request AP stop"); |
| |
| // Start off in the starting state with AP_START_MAX_RETRIES retry attempts. |
| let fut = starting_state(test_values.deps, req, AP_START_MAX_RETRIES, Some(start_sender)); |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| |
| // We'll need to inject some SME responses. |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| // Handle the initial stop 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::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // There should also be a stopped state update. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(_))) |
| ); |
| |
| // Followed by a starting update. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| |
| // Wait for a start request and send back a timeout. |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Start{ config: _, responder }) => { |
| responder |
| .send(fidl_sme::StartApResultCode::TimedOut) |
| .expect("could not send AP stop response"); |
| } |
| ); |
| |
| // At this point, the state machine should pause before retrying the start request. It |
| // should also check to see if there are any incoming AP commands and find the initial exit |
| // request at which point it should exit. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| assert_variant!(exec.run_until_stalled(&mut exit_receiver), Poll::Ready(Ok(()))); |
| |
| // A metric should be logged for the failure to start the AP. |
| assert_variant!( |
| test_values.telemetry_receiver.try_next(), |
| Ok(Some(TelemetryEvent::StartApResult(Err(())))) |
| ); |
| |
| // A defect should be sent as well. |
| assert_variant!( |
| test_values.defect_receiver.try_next(), |
| Ok(Some(Defect::Iface(IfaceFailure::ApStartFailure { .. }))) |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_manual_start_causes_starting_notification() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| |
| // Create a start request and enter the state machine with a manual start request. |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let requested_config = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| |
| let (start_response_sender, _) = oneshot::channel(); |
| let manual_request = ManualRequest::Start((requested_config, start_response_sender)); |
| |
| let fut = async move { perform_manual_request(test_values.deps, Some(manual_request)) }; |
| let fut = run_state_machine(fut); |
| let mut fut = pin!(fut); |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // We should get a stop request |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| assert_variant!( |
| poll_sme_req(&mut exec, &mut sme_fut), |
| Poll::Ready(fidl_sme::ApSmeRequest::Stop{ responder }) => { |
| responder |
| .send(fidl_sme::StopApResultCode::Success) |
| .expect("could not send SME stop response"); |
| } |
| ); |
| |
| // We should then get a notification that the AP is inactive followed by a new starting |
| // notification. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(updates))) => { |
| assert!(updates.access_points.is_empty()); |
| }); |
| |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(mut updates))) => { |
| let update = updates.access_points.pop().expect("no new updates available."); |
| assert_eq!(update.state, types::OperatingState::Starting); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_serve_does_not_terminate_right_away() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| let sme_event_stream = test_values.deps.proxy.take_event_stream(); |
| let sme_fut = test_values.sme_req_stream.into_future(); |
| let mut sme_fut = pin!(sme_fut); |
| |
| let update_sender = test_values.deps.state_tracker.inner.lock().sender.clone(); |
| |
| let fut = serve( |
| 0, |
| test_values.deps.proxy, |
| sme_event_stream, |
| test_values.deps.req_stream, |
| update_sender, |
| test_values.deps.telemetry_sender, |
| test_values.deps.defect_sender, |
| ); |
| let mut fut = pin!(fut); |
| |
| // Run the state machine. No request is made initially. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| assert_variant!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending); |
| } |
| |
| #[fuchsia::test] |
| fn test_no_notification_when_sme_fails_while_stopped() { |
| let mut exec = fasync::TestExecutor::new(); |
| let test_values = test_setup(); |
| let sme_event_stream = test_values.deps.proxy.take_event_stream(); |
| let update_sender = test_values.deps.state_tracker.inner.lock().sender.clone(); |
| |
| let fut = serve( |
| 0, |
| test_values.deps.proxy, |
| sme_event_stream, |
| test_values.deps.req_stream, |
| update_sender, |
| test_values.deps.telemetry_sender, |
| test_values.deps.defect_sender, |
| ); |
| let mut fut = pin!(fut); |
| |
| // Cause the SME event stream to terminate. |
| drop(test_values.sme_req_stream); |
| |
| // Run the state machine and observe that it has terminated. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // There should be no notification of failure since no AP is actively running. |
| assert_variant!( |
| exec.run_until_stalled(&mut test_values.update_receiver.into_future()), |
| Poll::Pending |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_failure_notification_when_configured() { |
| let mut exec = fasync::TestExecutor::new(); |
| let mut test_values = test_setup(); |
| let sme_event_stream = test_values.deps.proxy.take_event_stream(); |
| let mut sme_fut = Box::pin(test_values.sme_req_stream.into_future()); |
| |
| let update_sender = test_values.deps.state_tracker.inner.lock().sender.clone(); |
| let fut = serve( |
| 0, |
| test_values.deps.proxy, |
| sme_event_stream, |
| test_values.deps.req_stream, |
| update_sender, |
| test_values.deps.telemetry_sender, |
| test_values.deps.defect_sender, |
| ); |
| let mut fut = pin!(fut); |
| |
| // Make a request to start the access point. |
| let mut ap = AccessPoint::new(test_values.ap_req_sender); |
| let (sender, _receiver) = oneshot::channel(); |
| let radio_config = RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, 6); |
| let config = ApConfig { |
| id: create_network_id(), |
| credential: vec![], |
| radio_config, |
| mode: types::ConnectivityMode::Unrestricted, |
| band: types::OperatingBand::Any, |
| }; |
| ap.start(config, sender).expect("failed to make start request"); |
| |
| // Expect that the state machine issues a stop request followed by a start 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::ApSmeRequest::Stop{ responder }) => { |
| responder.send(fidl_sme::StopApResultCode::Success).expect("could not send AP stop response"); |
| } |
| ); |
| |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending); |
| |
| // At this point, the state machine will have sent an empty notification and a starting |
| // notification. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(update))) => { |
| assert!(update.access_points.is_empty()); |
| } |
| ); |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(update))) => { |
| assert_eq!(update.access_points.len(), 1); |
| assert_eq!(update.access_points[0].state, types::OperatingState::Starting); |
| } |
| ); |
| |
| // Cause the SME event stream to terminate. |
| drop(sme_fut); |
| |
| // Run the state machine and observe that it has terminated. |
| assert_variant!(exec.run_until_stalled(&mut fut), Poll::Ready(())); |
| |
| // There should be a failure notification. |
| assert_variant!( |
| test_values.update_receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(update))) => { |
| assert_eq!(update.access_points.len(), 1); |
| assert_eq!(update.access_points[0].state, types::OperatingState::Failed); |
| } |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_state_tracker_reset() { |
| let _exec = fasync::TestExecutor::new(); |
| let (sender, mut receiver) = mpsc::unbounded(); |
| |
| // A new state tracker should initially have no state. |
| let state = ApStateTracker::new(sender); |
| { |
| assert!(state.inner.lock().state.is_none()); |
| } |
| |
| // And there should be no updates. |
| assert_variant!(receiver.try_next(), Err(_)); |
| |
| // Reset the state to starting and verify that the internal state has been updated. |
| let new_state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.reset_state(new_state).expect("failed to reset state"); |
| assert_variant!(state.inner.lock().state.as_ref(), Some(ApStateUpdate { |
| id: types::NetworkIdentifier { |
| ssid, |
| security_type: types::SecurityType::None, |
| }, |
| state: types::OperatingState::Starting, |
| mode: Some(types::ConnectivityMode::Unrestricted), |
| band: Some(types::OperatingBand::Any), |
| frequency: None, |
| clients: None, |
| }) => { |
| let expected_ssid = types::Ssid::try_from("test_ssid").unwrap(); |
| assert_eq!(ssid, &expected_ssid); |
| }); |
| |
| // Resetting the state should result in an update. |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points }))) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Starting); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, None); |
| assert_eq!(access_points[0].clients, None); |
| } |
| ); |
| } |
| |
| #[fuchsia::test] |
| fn test_state_tracker_consume_sme_update() { |
| let _exec = fasync::TestExecutor::new(); |
| let (sender, mut receiver) = mpsc::unbounded(); |
| let state = ApStateTracker::new(sender); |
| |
| // Reset the state to started and send an update. |
| let new_state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Active, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.reset_state(new_state).expect("failed to reset state"); |
| |
| // The update should note that the AP is active. |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Active); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, None); |
| assert_eq!(access_points[0].clients, None); |
| }); |
| |
| // Consume a status update and expect a new notification to be generated. |
| let ap_info = fidl_sme::Ap { |
| ssid: types::Ssid::try_from("test_ssid").unwrap().to_vec(), |
| channel: 6, |
| num_clients: 123, |
| }; |
| state |
| .consume_sme_status_update(Cbw::Cbw20, ap_info) |
| .expect("failure while updating SME status"); |
| |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Active); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, Some(2437)); |
| assert_eq!(access_points[0].clients, Some(ConnectedClientInformation { count: 123 })); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_state_tracker_update_operating_state() { |
| let _exec = fasync::TestExecutor::new(); |
| let (sender, mut receiver) = mpsc::unbounded(); |
| let state = ApStateTracker::new(sender); |
| |
| // Reset the state to started and send an update. |
| let new_state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Starting, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.reset_state(new_state).expect("failed to reset state"); |
| |
| // The update should note that the AP is starting. |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Starting); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, None); |
| assert_eq!(access_points[0].clients, None); |
| }); |
| |
| // Give another update that the state is starting and ensure that a notification is sent. |
| state |
| .update_operating_state(types::OperatingState::Starting) |
| .expect("failed to send duplicate update."); |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Starting); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, None); |
| assert_eq!(access_points[0].clients, None); |
| }); |
| |
| // Now update that the state is active and expect a notification to be generated. |
| state |
| .update_operating_state(types::OperatingState::Active) |
| .expect("failed to send active update."); |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert_eq!(access_points.len(), 1); |
| |
| let expected_id = types::NetworkIdentifier { |
| ssid: types::Ssid::try_from("test_ssid").unwrap(), |
| security_type: types::SecurityType::None, |
| }; |
| assert_eq!(access_points[0].id, expected_id); |
| assert_eq!(access_points[0].state, types::OperatingState::Active); |
| assert_eq!(access_points[0].mode, Some(types::ConnectivityMode::Unrestricted)); |
| assert_eq!(access_points[0].band, Some(types::OperatingBand::Any)); |
| assert_eq!(access_points[0].frequency, None); |
| assert_eq!(access_points[0].clients, None); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_state_tracker_set_stopped_state() { |
| let _exec = fasync::TestExecutor::new(); |
| let (sender, mut receiver) = mpsc::unbounded(); |
| let state = ApStateTracker::new(sender); |
| |
| // Set up some initial state. |
| { |
| let new_state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Active, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.inner.lock().state = Some(new_state); |
| } |
| |
| // Set the state to stopped and verify that the internal state information has been |
| // removed. |
| state.set_stopped_state().expect("failed to send stopped notification"); |
| { |
| assert!(state.inner.lock().state.is_none()); |
| } |
| |
| // Verify that an empty update has arrived. |
| assert_variant!( |
| receiver.try_next(), |
| Ok(Some(listener::Message::NotifyListeners(ApStatesUpdate { access_points })) |
| ) => { |
| assert!(access_points.is_empty()); |
| }); |
| } |
| |
| #[fuchsia::test] |
| fn test_state_tracker_failure_modes() { |
| let _exec = fasync::TestExecutor::new(); |
| let (sender, receiver) = mpsc::unbounded(); |
| let state = ApStateTracker::new(sender); |
| { |
| let new_state = ApStateUpdate::new( |
| create_network_id(), |
| types::OperatingState::Active, |
| types::ConnectivityMode::Unrestricted, |
| types::OperatingBand::Any, |
| ); |
| state.inner.lock().state = Some(new_state); |
| } |
| |
| // Currently, the only reason any of the state tracker methods might fail is because of a |
| // failure to enqueue a state change notification. Drop the receiving end to trigger this |
| // condition. |
| drop(receiver); |
| |
| let _ = state |
| .update_operating_state(types::OperatingState::Failed) |
| .expect_err("unexpectedly able to set operating state"); |
| let _ = state |
| .consume_sme_status_update( |
| Cbw::Cbw20, |
| fidl_sme::Ap { |
| ssid: types::Ssid::try_from("test_ssid").unwrap().to_vec(), |
| channel: 6, |
| num_clients: 123, |
| }, |
| ) |
| .expect_err("unexpectedly able to update SME status"); |
| let _ = state.set_stopped_state().expect_err("unexpectedly able to set stopped state"); |
| } |
| } |