Skip to main content

wlancfg_lib/client/
state_machine.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::client::roaming::lib::{PolicyRoamRequest, ROAMING_CHANNEL_BUFFER_SIZE};
6use crate::client::roaming::local_roam_manager::RoamManager;
7use crate::client::roaming::roam_monitor::RoamDataSender;
8use crate::client::types;
9use crate::config_management::{Credential, PastConnectionData, SavedNetworksManagerApi};
10use crate::mode_management::iface_manager_api::SmeForClientStateMachine;
11use crate::mode_management::{Defect, IfaceFailure};
12use crate::telemetry::{
13    AVERAGE_SCORE_DELTA_MINIMUM_DURATION, DisconnectInfo, METRICS_SHORT_CONNECT_DURATION,
14    TelemetryEvent, TelemetrySender,
15};
16use crate::util::listener::Message::NotifyListeners;
17use crate::util::listener::{ClientListenerMessageSender, ClientNetworkState, ClientStateUpdate};
18use crate::util::state_machine::{self, ExitReason, IntoStateExt, StateMachineStatusPublisher};
19use anyhow::format_err;
20use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
21use fidl_fuchsia_wlan_internal as fidl_internal;
22use fidl_fuchsia_wlan_policy as fidl_policy;
23use fidl_fuchsia_wlan_sme as fidl_sme;
24use fuchsia_async::{self as fasync, DurationExt};
25use fuchsia_inspect::Node as InspectNode;
26use fuchsia_inspect_contrib::inspect_insert;
27use fuchsia_inspect_contrib::log::WriteInspect;
28use futures::channel::{mpsc, oneshot};
29use futures::future::{Fuse, FutureExt};
30use futures::select;
31use futures::stream::{self, StreamExt, TryStreamExt};
32use log::{debug, error, info, warn};
33use std::borrow::Cow;
34use std::pin::Pin;
35use std::sync::Arc;
36use wlan_common::bss::BssDescription;
37use wlan_common::channel::{Bandwidth, Channel};
38use wlan_common::historical_list::HistoricalList;
39use wlan_common::sequestered::Sequestered;
40
41const MAX_CONNECTION_ATTEMPTS: u8 = 4; // arbitrarily chosen until we have some data
42const NUM_PAST_SCORES: usize = 91; // number of past periodic connection scores to store for metrics
43const PENDING_ROAM_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(3);
44
45type State = state_machine::State<ExitReason>;
46type ReqStream = stream::Fuse<mpsc::Receiver<ManualRequest>>;
47pub type TrackedSignals = HistoricalList<types::TimestampedSignal, NUM_PAST_SCORES>;
48
49struct PendingRoam {
50    pub request: PolicyRoamRequest,
51    pub timestamp: fasync::MonotonicInstant,
52    pub _wake_lease: Option<wlan_power_manager::WakeLease>,
53}
54impl From<PolicyRoamRequest> for PendingRoam {
55    fn from(request: PolicyRoamRequest) -> Self {
56        Self { request, timestamp: fasync::MonotonicInstant::now(), _wake_lease: None }
57    }
58}
59
60pub trait ClientApi {
61    fn connect(&mut self, selection: types::ConnectSelection) -> Result<(), anyhow::Error>;
62    fn disconnect(
63        &mut self,
64        reason: types::DisconnectReason,
65        responder: oneshot::Sender<()>,
66    ) -> Result<(), anyhow::Error>;
67
68    /// Queries the liveness of the channel used to control the client state machine.  If the
69    /// channel is not alive, this indicates that the client state machine has exited.
70    fn is_alive(&self) -> bool;
71}
72
73pub struct Client {
74    req_sender: mpsc::Sender<ManualRequest>,
75}
76
77impl Client {
78    pub fn new(req_sender: mpsc::Sender<ManualRequest>) -> Self {
79        Self { req_sender }
80    }
81}
82
83impl ClientApi for Client {
84    fn connect(&mut self, selection: types::ConnectSelection) -> Result<(), anyhow::Error> {
85        self.req_sender
86            .try_send(ManualRequest::Connect(Box::new(selection)))
87            .map_err(|e| format_err!("failed to send connect selection: {:?}", e))
88    }
89
90    fn disconnect(
91        &mut self,
92        reason: types::DisconnectReason,
93        responder: oneshot::Sender<()>,
94    ) -> Result<(), anyhow::Error> {
95        self.req_sender
96            .try_send(ManualRequest::Disconnect((reason, responder)))
97            .map_err(|e| format_err!("failed to send disconnect request: {:?}", e))
98    }
99
100    fn is_alive(&self) -> bool {
101        !self.req_sender.is_closed()
102    }
103}
104
105// TODO(https://fxbug.dev/324167674): fix.
106pub enum ManualRequest {
107    Connect(Box<types::ConnectSelection>),
108    Disconnect((types::DisconnectReason, oneshot::Sender<()>)),
109}
110
111#[derive(Clone, Debug, Default, PartialEq)]
112pub enum Status {
113    Disconnecting,
114    #[default]
115    Disconnected,
116    Connecting,
117    Connected {
118        channel: u8,
119        rssi: i8,
120        snr: i8,
121    },
122}
123
124impl Status {
125    fn from_ap_state(ap_state: &types::ApState) -> Self {
126        Status::Connected {
127            channel: ap_state.tracked.channel.primary,
128            rssi: ap_state.tracked.signal.rssi_dbm,
129            snr: ap_state.tracked.signal.snr_db,
130        }
131    }
132}
133
134impl WriteInspect for Status {
135    fn write_inspect<'a>(&self, writer: &InspectNode, key: impl Into<Cow<'a, str>>) {
136        match self {
137            Status::Connected { channel, rssi, snr } => {
138                inspect_insert!(writer, var key: {
139                    Connected: {
140                        channel: channel,
141                        rssi: rssi,
142                        snr: snr
143                    }
144                })
145            }
146            other => inspect_insert!(writer, var key: format!("{:?}", other)),
147        }
148    }
149}
150
151fn send_listener_state_update(
152    sender: &ClientListenerMessageSender,
153    network_update: Option<ClientNetworkState>,
154) {
155    let mut networks = vec![];
156    if let Some(network) = network_update {
157        networks.push(network)
158    }
159
160    let updates =
161        ClientStateUpdate { state: fidl_policy::WlanClientState::ConnectionsEnabled, networks };
162    match sender.clone().unbounded_send(NotifyListeners(updates)) {
163        Ok(_) => (),
164        Err(e) => error!("failed to send state update: {:?}", e),
165    };
166}
167
168pub async fn serve(
169    iface_id: u16,
170    proxy: SmeForClientStateMachine,
171    sme_event_stream: fidl_sme::ClientSmeEventStream,
172    req_stream: mpsc::Receiver<ManualRequest>,
173    update_sender: ClientListenerMessageSender,
174    saved_networks_manager: Arc<dyn SavedNetworksManagerApi>,
175    connect_selection: types::ConnectSelection,
176    telemetry_sender: TelemetrySender,
177    defect_sender: mpsc::Sender<Defect>,
178    roam_manager: RoamManager,
179    status_publisher: StateMachineStatusPublisher<Status>,
180    power_manager: Arc<dyn wlan_power_manager::PowerManager>,
181) {
182    let next_network = Some(ConnectingOptions { connect_selection, attempt_counter: 0 });
183    let disconnect_options = DisconnectingOptions {
184        disconnect_responder: None,
185        previous_network: None,
186        next_network,
187        reason: types::DisconnectReason::Startup,
188    };
189    let common_options = CommonStateOptions {
190        proxy,
191        req_stream: req_stream.fuse(),
192        update_sender,
193        saved_networks_manager,
194        telemetry_sender: telemetry_sender.clone(),
195        iface_id,
196        defect_sender,
197        roam_manager,
198        status_publisher: status_publisher.clone(),
199        power_manager,
200    };
201    let state_machine =
202        disconnecting_state(common_options, disconnect_options).into_state_machine();
203    let removal_watcher = sme_event_stream.map_ok(|_| ()).try_collect::<()>();
204    select! {
205        state_machine = state_machine.fuse() => {
206            match state_machine {
207                Err(ExitReason(Err(e))) => error!("Client state machine for iface #{} terminated with an error: {:?}",
208                    iface_id, e),
209                Err(ExitReason(Ok(_))) => info!("Client state machine for iface #{} exited gracefully",
210                    iface_id,),
211            }
212        }
213        removal_watcher = removal_watcher.fuse() => {
214            match removal_watcher {
215                Ok(()) => {
216                    info!("Device was unexpectedly removed.");
217                }
218                Err(e) => {
219                    info!("Error reading from Client SME channel of iface #{}: {:?}",
220                        iface_id, e);
221                }
222            }
223
224            telemetry_sender
225                .send(TelemetryEvent::Disconnected { track_subsequent_downtime: false, info: None });
226        },
227    }
228
229    status_publisher.publish_status(Status::Disconnected);
230}
231
232/// Common parameters passed to all states
233struct CommonStateOptions {
234    proxy: SmeForClientStateMachine,
235    req_stream: ReqStream,
236    update_sender: ClientListenerMessageSender,
237    saved_networks_manager: Arc<dyn SavedNetworksManagerApi>,
238    telemetry_sender: TelemetrySender,
239    iface_id: u16,
240    defect_sender: mpsc::Sender<Defect>,
241    roam_manager: RoamManager,
242    status_publisher: StateMachineStatusPublisher<Status>,
243    power_manager: Arc<dyn wlan_power_manager::PowerManager>,
244}
245
246impl CommonStateOptions {
247    async fn network_is_likely_hidden(&self, options: &ConnectingOptions) -> bool {
248        match self
249            .saved_networks_manager
250            .lookup(&options.connect_selection.target.network)
251            .await
252            .filter(|config| config.credential == options.connect_selection.target.credential)
253        {
254            Some(config) => config.is_hidden(),
255            None => {
256                error!("Could not lookup if connected network is hidden.");
257                false
258            }
259        }
260    }
261}
262
263pub type ConnectionStatsSender = mpsc::UnboundedSender<fidl_internal::SignalReportIndication>;
264pub type ConnectionStatsReceiver = mpsc::UnboundedReceiver<fidl_internal::SignalReportIndication>;
265
266fn handle_none_request() -> Result<State, ExitReason> {
267    Err(ExitReason(Err(format_err!("The stream of requests ended unexpectedly"))))
268}
269
270// These functions were introduced to resolve the following error:
271// ```
272// error[E0391]: cycle detected when evaluating trait selection obligation
273// `impl core::future::future::Future: std::marker::Send`
274// ```
275// which occurs when two functions that return an `impl Trait` call each other
276// in a cycle. (e.g. this case `connecting_state` calling `disconnecting_state`,
277// which calls `connecting_state`)
278fn to_disconnecting_state(
279    common_options: CommonStateOptions,
280    disconnecting_options: DisconnectingOptions,
281) -> State {
282    disconnecting_state(common_options, disconnecting_options).into_state()
283}
284fn to_connecting_state(
285    common_options: CommonStateOptions,
286    connecting_options: ConnectingOptions,
287) -> State {
288    connecting_state(common_options, connecting_options).into_state()
289}
290
291struct DisconnectingOptions {
292    disconnect_responder: Option<oneshot::Sender<()>>,
293    /// Information about the previously connected network, if there was one. Used to send out
294    /// listener updates.
295    previous_network: Option<(types::NetworkIdentifier, types::DisconnectStatus)>,
296    /// Configuration for the next network to connect to, after the disconnect is complete. If not
297    /// present, the state machine will proceed to IDLE.
298    next_network: Option<ConnectingOptions>,
299    reason: types::DisconnectReason,
300}
301/// The DISCONNECTING state requests an SME disconnect, then transitions to either:
302/// - the CONNECTING state if options.next_network is present
303/// - exit otherwise
304async fn disconnecting_state(
305    common_options: CommonStateOptions,
306    mut options: DisconnectingOptions,
307) -> Result<State, ExitReason> {
308    let _wake_lease =
309        common_options.power_manager.take_wake_lease("wlancfg-client-disconnecting").await;
310
311    // Log a message with the disconnect reason
312    match options.reason {
313        types::DisconnectReason::FailedToConnect
314        | types::DisconnectReason::Startup
315        | types::DisconnectReason::DisconnectDetectedFromSme => {
316            // These are either just noise or have separate logging, so keep the level at debug.
317            debug!("Disconnected due to {:?}", options.reason);
318        }
319        reason => {
320            info!("Disconnected due to {:?}", reason);
321        }
322    }
323
324    notify_on_disconnect_attempt(&common_options);
325
326    // TODO(https://fxbug.dev/42130926): either make this fire-and-forget in the SME, or spawn a thread for this,
327    // so we don't block on it
328    common_options
329        .proxy
330        .disconnect(types::convert_to_sme_disconnect_reason(options.reason))
331        .await
332        .map_err(|e| ExitReason(Err(e)))?;
333
334    notify_once_disconnected(&common_options, &mut options);
335
336    // Transition to next state
337    match options.next_network {
338        Some(next_network) => Ok(to_connecting_state(common_options, next_network)),
339        None => Err(ExitReason(Ok(()))),
340    }
341}
342
343fn notify_on_disconnect_attempt(common_options: &CommonStateOptions) {
344    common_options.status_publisher.publish_status(Status::Disconnecting);
345}
346
347fn notify_once_disconnected(
348    common_options: &CommonStateOptions,
349    options: &mut DisconnectingOptions,
350) {
351    common_options.status_publisher.publish_status(Status::Disconnected);
352
353    // Notify listeners if a disconnect request was sent, or ensure that listeners know client
354    // connections are enabled.
355    let networks =
356        options.previous_network.clone().map(|(network_identifier, status)| ClientNetworkState {
357            id: network_identifier,
358            state: types::ConnectionState::Disconnected,
359            status: Some(status),
360        });
361    send_listener_state_update(&common_options.update_sender, networks);
362
363    // Notify the caller that disconnect was sent to the SME once the final disconnected update has
364    // been sent.  This ensures that there will not be a race when the IfaceManager sends out a
365    // ConnectionsDisabled update.
366    #[allow(clippy::single_match, reason = "mass allow for https://fxbug.dev/381896734")]
367    match options.disconnect_responder.take() {
368        Some(responder) => responder.send(()).unwrap_or(()),
369        None => (),
370    }
371}
372
373struct ConnectingOptions {
374    connect_selection: types::ConnectSelection,
375    /// Count of previous consecutive failed connection attempts to this same network.
376    attempt_counter: u8,
377}
378
379async fn handle_connecting_error_and_retry(
380    common_options: CommonStateOptions,
381    options: ConnectingOptions,
382) -> Result<State, ExitReason> {
383    let _wake_lease =
384        common_options.power_manager.take_wake_lease("wlancfg-client-reconnect-retry").await;
385
386    // Check if the limit for connection attempts to this network has been
387    // exceeded.
388    let new_attempt_count = options.attempt_counter + 1;
389    if new_attempt_count >= MAX_CONNECTION_ATTEMPTS {
390        info!("Exceeded maximum connection attempts, will not retry");
391        send_listener_state_update(
392            &common_options.update_sender,
393            Some(ClientNetworkState {
394                id: options.connect_selection.target.network,
395                state: types::ConnectionState::Failed,
396                status: Some(types::DisconnectStatus::ConnectionFailed),
397            }),
398        );
399        Err(ExitReason(Ok(())))
400    } else {
401        // Limit not exceeded, retry after backing off.
402        let backoff_time = 400_i64 * i64::from(new_attempt_count);
403        info!("Will attempt to reconnect after {}ms backoff", backoff_time);
404        fasync::Timer::new(zx::MonotonicDuration::from_millis(backoff_time).after_now()).await;
405
406        let next_connecting_options = ConnectingOptions {
407            connect_selection: types::ConnectSelection {
408                reason: types::ConnectReason::RetryAfterFailedConnectAttempt,
409                ..options.connect_selection
410            },
411            attempt_counter: new_attempt_count,
412        };
413        let disconnecting_options = DisconnectingOptions {
414            disconnect_responder: None,
415            previous_network: None,
416            next_network: Some(next_connecting_options),
417            reason: types::DisconnectReason::FailedToConnect,
418        };
419        Ok(to_disconnecting_state(common_options, disconnecting_options))
420    }
421}
422
423#[allow(clippy::needless_return, reason = "mass allow for https://fxbug.dev/381896734")]
424/// The CONNECTING state requests an SME connect. It handles the SME connect response:
425/// - for a successful connection, transition to CONNECTED state
426/// - for a failed connection, retry connection by passing a next_network to the
427///   DISCONNECTING state, as long as there haven't been too many connection attempts
428#[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
429/// During this time, incoming ManualRequests are also monitored for:
430/// - duplicate connect requests are deduped
431/// - different connect requests are serviced by passing a next_network to the DISCONNECTING state
432/// - disconnect requests cause a transition to DISCONNECTING state
433async fn connecting_state(
434    mut common_options: CommonStateOptions,
435    options: ConnectingOptions,
436) -> Result<State, ExitReason> {
437    let _wake_lease =
438        common_options.power_manager.take_wake_lease("wlancfg-client-connecting").await;
439    debug!("Entering connecting state");
440    notify_on_connection_attempt(&common_options, &options);
441
442    // Release the sequestered BSS description. While considered a "black box" elsewhere, the state
443    // machine uses this by design to construct its AP state and to report telemetry.
444    let bss_description =
445        Sequestered::release(options.connect_selection.target.bss.bss_description.clone());
446    let ap_state = types::ApState::from(
447        BssDescription::try_from(bss_description.clone()).map_err(|error| {
448            // This only occurs if an invalid `BssDescription` is received from SME, which should
449            // never happen.
450            ExitReason(Err(
451                format_err!("Failed to convert BSS description from FIDL: {:?}", error,),
452            ))
453        })?,
454    );
455
456    let sme_connect_request = fidl_sme::ConnectRequest {
457        ssid: options.connect_selection.target.network.ssid.to_vec(),
458        bss_description,
459        multiple_bss_candidates: options.connect_selection.target.network_has_multiple_bss,
460        authentication: options.connect_selection.target.authenticator.clone().into(),
461        deprecated_scan_type: fidl_fuchsia_wlan_common::ScanType::Active,
462    };
463    let (sme_result, connect_txn_stream) = common_options
464        .proxy
465        .connect(&sme_connect_request)
466        .await
467        .map_err(|e| ExitReason(Err(format_err!("{:?}", e))))?;
468
469    notify_on_connection_result(&mut common_options, &options, ap_state.clone(), sme_result).await;
470
471    match (sme_result.code, sme_result.is_credential_rejected) {
472        (fidl_ieee80211::StatusCode::Success, _) => {
473            info!("Successfully connected to network");
474            let network_is_likely_hidden = common_options.network_is_likely_hidden(&options).await;
475            let connected_options = ConnectedOptions::new(
476                &mut common_options,
477                Box::new(ap_state.clone()),
478                options.connect_selection.target.network_has_multiple_bss,
479                options.connect_selection.target.network.clone(),
480                options.connect_selection.target.credential.clone(),
481                options.connect_selection.reason,
482                connect_txn_stream,
483                network_is_likely_hidden,
484            );
485            Ok(connected_state(common_options, connected_options).into_state())
486        }
487        (code, true) => {
488            info!("Failed to connect: {:?}. Will not retry because of credential error.", code);
489            return Err(ExitReason(Ok(())));
490        }
491        (code, _) => {
492            info!("Failed to connect: {:?}", code);
493            handle_connecting_error_and_retry(common_options, options).await
494        }
495    }
496}
497
498fn notify_on_connection_attempt(common_options: &CommonStateOptions, options: &ConnectingOptions) {
499    common_options.status_publisher.publish_status(Status::Connecting);
500
501    if options.attempt_counter > 0 {
502        info!(
503            "Retrying connection, {} attempts remaining",
504            MAX_CONNECTION_ATTEMPTS - options.attempt_counter
505        );
506    }
507
508    // Send a "Connecting" update to listeners, unless this is a retry
509    if options.attempt_counter == 0 {
510        send_listener_state_update(
511            &common_options.update_sender,
512            Some(ClientNetworkState {
513                id: options.connect_selection.target.network.clone(),
514                state: types::ConnectionState::Connecting,
515                status: None,
516            }),
517        );
518    };
519}
520
521async fn notify_on_connection_result(
522    common_options: &mut CommonStateOptions,
523    options: &ConnectingOptions,
524    ap_state: types::ApState,
525    sme_result: fidl_sme::ConnectResult,
526) {
527    common_options.status_publisher.publish_status(Status::from_ap_state(&ap_state));
528
529    // Report the connect result to the saved networks manager.
530    common_options
531        .saved_networks_manager
532        .record_connect_result(
533            options.connect_selection.target.network.clone(),
534            &options.connect_selection.target.credential,
535            ap_state.clone().original().bssid,
536            sme_result,
537            options.connect_selection.target.bss.observation,
538        )
539        .await;
540
541    let network_is_likely_hidden = common_options.network_is_likely_hidden(options).await;
542    common_options.telemetry_sender.send(TelemetryEvent::ConnectResult {
543        ap_state,
544        result: sme_result,
545        policy_connect_reason: Some(options.connect_selection.reason),
546        multiple_bss_candidates: options.connect_selection.target.network_has_multiple_bss,
547        iface_id: common_options.iface_id,
548        network_is_likely_hidden,
549    });
550
551    if sme_result.code == fidl_ieee80211::StatusCode::Success {
552        send_listener_state_update(
553            &common_options.update_sender,
554            Some(ClientNetworkState {
555                id: options.connect_selection.target.network.clone(),
556                state: types::ConnectionState::Connected,
557                status: None,
558            }),
559        );
560    } else if sme_result.is_credential_rejected {
561        send_listener_state_update(
562            &common_options.update_sender,
563            Some(ClientNetworkState {
564                id: options.connect_selection.target.network.clone(),
565                state: types::ConnectionState::Failed,
566                status: Some(types::DisconnectStatus::CredentialsFailed),
567            }),
568        );
569    } else {
570        // Defects should be logged for connection failures that are not due to bad credentials.
571        if let Err(e) =
572            common_options.defect_sender.try_send(Defect::Iface(IfaceFailure::ConnectionFailure {
573                iface_id: common_options.iface_id,
574            }))
575        {
576            warn!("Failed to log connection failure: {}", e);
577        }
578    }
579}
580
581struct ConnectedOptions {
582    // Keep track of the BSSID we are connected in order to record connection information for
583    // future network selection.
584    ap_state: Box<types::ApState>,
585    multiple_bss_candidates: bool,
586    network_identifier: types::NetworkIdentifier,
587    credential: Credential,
588    ess_connect_reason: types::ConnectReason,
589    connect_txn_stream: fidl_sme::ConnectTransactionEventStream,
590    network_is_likely_hidden: bool,
591    ess_connect_start_time: fasync::MonotonicInstant,
592    bss_connect_start_time: fasync::MonotonicInstant,
593    initial_signal: types::Signal,
594    tracked_signals: TrackedSignals,
595    roam_monitor_sender: RoamDataSender,
596    roam_request_receiver: mpsc::Receiver<PolicyRoamRequest>,
597    post_connect_metric_timer: Pin<Box<fasync::Timer>>,
598    bss_connect_duration_metric_timer: Pin<Box<fasync::Timer>>,
599    pending_roam: Option<PendingRoam>,
600    pending_roam_timer: Pin<Box<Fuse<fasync::Timer>>>,
601}
602impl ConnectedOptions {
603    pub fn new(
604        common_options: &mut CommonStateOptions,
605        ap_state: Box<types::ApState>,
606        multiple_bss_candidates: bool,
607        network_identifier: types::NetworkIdentifier,
608        credential: Credential,
609        ess_connect_reason: types::ConnectReason,
610        connect_txn_stream: fidl_sme::ConnectTransactionEventStream,
611        network_is_likely_hidden: bool,
612    ) -> Self {
613        // Tracked signals
614        let mut past_signals = TrackedSignals::new();
615        let initial_signal = ap_state.tracked.signal;
616        past_signals.add(types::TimestampedSignal {
617            time: fasync::MonotonicInstant::now(),
618            signal: initial_signal,
619        });
620
621        // Initialize roam monitor with roam manager service.
622        let (roam_request_sender, roam_request_receiver) =
623            mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
624        let roam_monitor_sender = common_options.roam_manager.initialize_roam_monitor(
625            (*ap_state).clone(),
626            network_identifier.clone(),
627            credential.clone(),
628            roam_request_sender,
629        );
630        Self {
631            ap_state,
632            multiple_bss_candidates,
633            network_identifier,
634            credential,
635            ess_connect_reason,
636            connect_txn_stream,
637            network_is_likely_hidden,
638            ess_connect_start_time: fasync::MonotonicInstant::now(),
639            bss_connect_start_time: fasync::MonotonicInstant::now(),
640            initial_signal,
641            tracked_signals: TrackedSignals::new(),
642            roam_monitor_sender,
643            roam_request_receiver,
644            post_connect_metric_timer: Box::pin(fasync::Timer::new(
645                AVERAGE_SCORE_DELTA_MINIMUM_DURATION.after_now(),
646            )),
647            bss_connect_duration_metric_timer: Box::pin(fasync::Timer::new(
648                METRICS_SHORT_CONNECT_DURATION.after_now(),
649            )),
650            pending_roam: None,
651            pending_roam_timer: Box::pin(Fuse::terminated()),
652        }
653    }
654}
655/// The CONNECTED state monitors the SME status. It handles the SME status response:
656/// - if still connected to the correct network, no action
657/// - if disconnected, retry connection by passing a next_network to the
658///   DISCONNECTING state
659#[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
660/// During this time, incoming ManualRequests are also monitored for:
661/// - duplicate connect requests are deduped
662/// - different connect requests are serviced by passing a next_network to the DISCONNECTING state
663/// - disconnect requests cause a transition to DISCONNECTING state
664async fn connected_state(
665    mut common_options: CommonStateOptions,
666    mut options: ConnectedOptions,
667) -> Result<State, ExitReason> {
668    debug!("Entering connected state");
669    loop {
670        select! {
671            event = options.connect_txn_stream.next() => match event {
672                Some(Ok(event)) => {
673                    let is_sme_idle = match event {
674                        fidl_sme::ConnectTransactionEvent::OnDisconnect { info: fidl_info } => {
675                            notify_when_disconnect_detected(
676                                &common_options,
677                                &options,
678                                fidl_info,
679                            ).await;
680
681                            !fidl_info.is_sme_reconnecting
682                        }
683                        fidl_sme::ConnectTransactionEvent::OnConnectResult { result } => {
684                            let connected = result.code == fidl_ieee80211::StatusCode::Success;
685                            if connected {
686                                // This OnConnectResult should be for SME reconnecting to the same
687                                // AP, so keep the same SignalData but reset the connect start time
688                                // to track as a new connection.
689                                options.ess_connect_start_time = fasync::MonotonicInstant::now();
690                                options.bss_connect_start_time = fasync::MonotonicInstant::now();
691                            }
692                            notify_when_reconnect_detected(&common_options, &options, result);
693                            !connected
694                        }
695                        fidl_sme::ConnectTransactionEvent::OnRoamResult { result } => {
696                            if let Err(error) = handle_roam_result(&mut common_options, &mut options, &result).await {
697                                error!("Error handling roam result: {:?}. Cannot proceed with connection.", error);
698                                true
699                            } else {
700                                let connected = result.status_code == fidl_ieee80211::StatusCode::Success || result.original_association_maintained;
701                                !connected
702                            }
703                        }
704                        fidl_sme::ConnectTransactionEvent::OnSignalReport { ind } => {
705                            if let Some(rssi) = ind.rssi_dbm {
706                                options.ap_state.tracked.signal.rssi_dbm = rssi;
707                            }
708                            if let Some(snr) = ind.snr_db {
709                                options.ap_state.tracked.signal.snr_db = snr;
710                            }
711                            // Update list of signals only if RSSI or SNR was present.
712                            if ind.rssi_dbm.is_some() || ind.snr_db.is_some() {
713                                options.tracked_signals.add(types::TimestampedSignal {
714                                    time: fasync::MonotonicInstant::now(),
715                                    signal: options.ap_state.tracked.signal,
716                                });
717                            }
718
719                            notify_on_signal_report(
720                                &common_options,
721                                &mut options,
722                                ind
723                            );
724                            false
725                        }
726                        fidl_sme::ConnectTransactionEvent::OnRssiThresholdBreached { .. } => false,
727                        fidl_sme::ConnectTransactionEvent::OnChannelSwitched { info } => {
728                            info!(
729                                "OnChannelSwitch received. Previous channel: {:?}, new channel: {:?}.",
730                                options.ap_state.tracked.channel.primary, info.new_primary_channel
731                            );
732
733                            let cbw = match Bandwidth::from_fidl(info.bandwidth, info.vht_secondary_80_channel.number) {
734                                Ok(cbw) => cbw,
735                                Err(e) => {
736                                    // In the event that the CBW is invalid, reuse the previous CBW
737                                    // in determining the client's channel to preserve any legacy
738                                    // behavior.
739                                    error!("Invalid CBW in ChannelSwitchInfo: {}", e);
740                                    options.ap_state.tracked.channel.bandwidth
741                                }
742                            };
743                            options.ap_state.tracked.channel = Channel::new(
744                                info.new_primary_channel.number,
745                                cbw,
746                                info.new_primary_channel.band
747                            );
748
749                            // Re-initialize roam monitor for new channel
750                            let (sender, roam_request_receiver) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
751                            options.roam_request_receiver = roam_request_receiver;
752                            options.roam_monitor_sender =
753                                common_options.roam_manager.initialize_roam_monitor(
754                                    (*options.ap_state).clone(),
755                                    options.network_identifier.clone(),
756                                    options.credential.clone(),
757                                    sender
758                                );
759                            notify_on_channel_switch(&common_options, &options, info);
760                            false
761                        }
762                    };
763
764                    if is_sme_idle {
765                        info!("Idle sme detected.");
766                        let options = DisconnectingOptions {
767                            disconnect_responder: None,
768                            previous_network: Some((
769                                options.network_identifier.clone(),
770                                types::DisconnectStatus::ConnectionFailed
771                            )),
772                            next_network: None,
773                            reason: types::DisconnectReason::DisconnectDetectedFromSme,
774                        };
775                        return Ok(disconnecting_state(common_options, options).into_state());
776                    }
777                }
778                _ => {
779                    info!("SME dropped ConnectTransaction channel. Exiting state machine");
780                    return Err(ExitReason(Err(format_err!("Failed to receive ConnectTransactionEvent for SME status"))));
781                }
782            },
783            req = common_options.req_stream.next() => {
784                match req {
785                    Some(ManualRequest::Disconnect((reason, responder))) => {
786                        debug!("Disconnect requested");
787                        notify_on_manual_disconnect_request_received(
788                            &common_options,
789                            &options,
790                            reason,
791                        ).await;
792
793                        let options = DisconnectingOptions {
794                            disconnect_responder: Some(responder),
795                            previous_network: Some((
796                                options.network_identifier.clone(),
797                                types::DisconnectStatus::ConnectionStopped
798                            )),
799                            next_network: None,
800                            reason,
801                        };
802                        return Ok(disconnecting_state(common_options, options).into_state());
803                    }
804                    Some(ManualRequest::Connect(box_connect_selection)) => {
805                        let new_connect_selection = *box_connect_selection;
806                        // Check if it's the same network as we're currently connected to. If yes, reply immediately
807                        if new_connect_selection.target.network
808                            == options.network_identifier {
809                            info!("Received connection request for current network, deduping");
810                            continue
811                        }
812
813                        let reason = convert_manual_connect_to_disconnect_reason(
814                            &new_connect_selection.reason
815                        ).unwrap_or_else(|_| {
816                            error!("Unexpected connection reason: {:?}", new_connect_selection.reason);
817                            types::DisconnectReason::Unknown
818                        });
819                        notify_on_manual_connect_request_received(
820                            &common_options,
821                            &options,
822                            reason,
823                        ).await;
824
825                        let options = DisconnectingOptions {
826                            disconnect_responder: None,
827                            previous_network: Some((
828                                options.network_identifier,
829                                types::DisconnectStatus::ConnectionStopped
830                            )),
831                            next_network: Some(ConnectingOptions {
832                                connect_selection: new_connect_selection.clone(),
833                                attempt_counter: 0,
834                            }),
835                            reason
836                        };
837                        info!("Connection to new network requested, disconnecting from current network");
838                        return Ok(disconnecting_state(common_options, options).into_state())
839                    }
840                    None => return handle_none_request(),
841                };
842            },
843            () = &mut options.post_connect_metric_timer => {
844                common_options.telemetry_sender.send(TelemetryEvent::PostConnectionSignals {
845                        connect_time: options.bss_connect_start_time,
846                        signal_at_connect: options.initial_signal,
847                        signals: options.tracked_signals.clone()
848                });
849            },
850            () = &mut options.bss_connect_duration_metric_timer => {
851                // Log the average connection score metric for a long duration BSS connection.
852                common_options.telemetry_sender.send(TelemetryEvent::LongDurationSignals{
853                    signals: options.tracked_signals.get_before(fasync::MonotonicInstant::now())
854                });
855            },
856            () = &mut options.pending_roam_timer => {
857                error!("Pending roam request has timed out without a response from SME, cannot proceed with connection");
858                options.pending_roam = None;
859                notify_on_roam_error_and_exit(&common_options, &options).await;
860                let options = DisconnectingOptions {
861                    disconnect_responder: None,
862                    previous_network: Some((
863                        options.network_identifier.clone(),
864                        types::DisconnectStatus::ConnectionFailed
865                    )),
866                    next_network: None,
867                    // TODO(b/405151253): Add a disconnect reason for errors attempting to roam.
868                    reason: types::DisconnectReason::DisconnectDetectedFromSme,
869                };
870                return Ok(disconnecting_state(common_options, options).into_state());
871            }
872            roam_request = options.roam_request_receiver.select_next_some() => {
873                if let Some(pending_roam) = &options.pending_roam {
874                    info!("Already pending a roam result for a requested roam to BSSID: {:?}", pending_roam.request.candidate.bss.bssid);
875                } else {
876                    let wake_lease = common_options.power_manager.take_wake_lease("wlancfg-client-roaming").await;
877                    let _ = common_options
878                    .proxy
879                    .roam(&roam_request.clone().into())
880                    .inspect_err(|e| {
881                        error!("Error sending sme roam request: {}", e);
882                    });
883                    options.pending_roam = Some(PendingRoam {
884                        request: roam_request.clone(),
885                        timestamp: fasync::MonotonicInstant::now(),
886                        _wake_lease: wake_lease,
887                    });
888                    options.pending_roam_timer.set(fasync::Timer::new(PENDING_ROAM_TIMEOUT.after_now()).fuse());
889                    common_options.telemetry_sender.send(TelemetryEvent::PolicyRoamAttempt {
890                        request: roam_request,
891                        connected_duration: fasync::MonotonicInstant::now() - options.bss_connect_start_time,
892                    });
893                }
894            }
895        }
896    }
897}
898
899async fn notify_when_disconnect_detected(
900    common_options: &CommonStateOptions,
901    options: &ConnectedOptions,
902    fidl_info: fidl_sme::DisconnectInfo,
903) {
904    log_disconnect_to_telemetry(common_options, options, fidl_info, true).await;
905    log_disconnect_to_config_manager(
906        common_options,
907        options,
908        types::DisconnectReason::DisconnectDetectedFromSme,
909    )
910    .await
911}
912
913fn notify_when_reconnect_detected(
914    common_options: &CommonStateOptions,
915    options: &ConnectedOptions,
916    result: fidl_sme::ConnectResult,
917) {
918    common_options.telemetry_sender.send(TelemetryEvent::ConnectResult {
919        iface_id: common_options.iface_id,
920        result,
921        policy_connect_reason: None,
922        // It's not necessarily true that there are still multiple BSS
923        // candidates in the network at this point in time, but we use the
924        // heuristic that if previously there were multiple BSS's, then
925        // it likely remains the same.
926        multiple_bss_candidates: options.multiple_bss_candidates,
927        ap_state: (*options.ap_state).clone(),
928        network_is_likely_hidden: options.network_is_likely_hidden,
929    });
930}
931
932fn notify_on_signal_report(
933    common_options: &CommonStateOptions,
934    options: &mut ConnectedOptions,
935    ind: fidl_internal::SignalReportIndication,
936) {
937    // Update reported state.
938    common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
939
940    // Send signal report metrics
941    common_options.telemetry_sender.send(TelemetryEvent::OnSignalReport { ind: ind.clone() });
942
943    // Forward signal report data to roam monitor.
944    let _ = options
945        .roam_monitor_sender
946        .send_signal_report_ind(ind)
947        .inspect_err(|e| error!("Error handling signal report: {}", e));
948}
949
950fn notify_on_channel_switch(
951    common_options: &CommonStateOptions,
952    options: &ConnectedOptions,
953    info: fidl_internal::ChannelSwitchInfo,
954) {
955    common_options.telemetry_sender.send(TelemetryEvent::OnChannelSwitched { info });
956    // Update reported state.
957    common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
958}
959
960async fn notify_on_manual_disconnect_request_received(
961    common_options: &CommonStateOptions,
962    options: &ConnectedOptions,
963    reason: types::DisconnectReason,
964) {
965    let fidl_info = fidl_sme::DisconnectInfo {
966        is_sme_reconnecting: false,
967        disconnect_source: fidl_sme::DisconnectSource::User(
968            types::convert_to_sme_disconnect_reason(reason),
969        ),
970    };
971    log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
972    log_disconnect_to_config_manager(common_options, options, reason).await
973}
974
975async fn notify_on_manual_connect_request_received(
976    common_options: &CommonStateOptions,
977    options: &ConnectedOptions,
978    reason: types::DisconnectReason,
979) {
980    let fidl_info = fidl_sme::DisconnectInfo {
981        is_sme_reconnecting: false,
982        disconnect_source: fidl_sme::DisconnectSource::User(
983            types::convert_to_sme_disconnect_reason(reason),
984        ),
985    };
986    log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
987    log_disconnect_to_config_manager(common_options, options, reason).await
988}
989
990/// On a roam success:
991///   - logs a disconnect from the original BSS to saved networks manager
992///   - updates the state machine internal state (see update_internal_state_on_roam_success)
993///
994/// On a roam failure and original association not maintained:
995///   - logs a disconnect from the original BSS to saved networks manager
996///   - logs a disconnect to telemetry
997///
998/// Then the connect attempt, roam result, and any defects are logged (see notify_on_roam_result)
999async fn handle_roam_result(
1000    common_options: &mut CommonStateOptions,
1001    options: &mut ConnectedOptions,
1002    result: &fidl_sme::RoamResult,
1003) -> Result<(), anyhow::Error> {
1004    // TODO(b/399649753): Distinguish between policy-initaited and firmware-initiated RoamResults
1005    // here, when possible. Only correlate policy-initiated RoamResults with pending roams.
1006
1007    // Verify that the received roam result matches the pending roam request. If there is not a
1008    // pending roam request, or the pending request is for another BSS, the connection cannot
1009    // proceed.
1010    let pending_roam = match options.pending_roam.take() {
1011        None => {
1012            notify_on_roam_error_and_exit(common_options, options).await;
1013            return Err(format_err!(
1014                "Roam result unexpectedly received without a pending roam request."
1015            ));
1016        }
1017        Some(pending_roam) => {
1018            if pending_roam.request.candidate.bss.bssid != result.bssid.into() {
1019                notify_on_roam_error_and_exit(common_options, options).await;
1020                return Err(format_err!(
1021                    "Roam result received for bssid {:?} while awaiting result for bssid {:?}.",
1022                    result.bssid,
1023                    pending_roam.request.candidate.bss.bssid
1024                ));
1025            }
1026            pending_roam
1027        }
1028    };
1029    options.pending_roam_timer.set(Fuse::terminated());
1030
1031    let roam_succeeded = result.status_code == fidl_ieee80211::StatusCode::Success;
1032    let original_ap_state = options.ap_state.clone();
1033    if roam_succeeded {
1034        // Record BSS disconnect to config manager. We do not report a disconnect to
1035        // telemetry, since we are still connected to the same ESS.
1036        log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1037            .await;
1038        // Update internals of connected state to proceed with connection.
1039        update_internal_state_on_roam_success(common_options, options, result)?;
1040        info!("Roam succeeded!");
1041    } else if !result.original_association_maintained {
1042        // RoamResult should always include disconnect_info on failure, but this check prevents
1043        // issues if it's missing.
1044        let sme_disconnect_info = match result.disconnect_info {
1045            Some(ref info) => *info.clone(),
1046            None => {
1047                warn!("RoamResult failure does not contain SME disconnect info.");
1048                fidl_sme::DisconnectInfo {
1049                    is_sme_reconnecting: false,
1050                    disconnect_source: fidl_sme::DisconnectSource::Mlme(
1051                        fidl_sme::DisconnectCause {
1052                            reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1053                            mlme_event_name:
1054                                fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1055                        },
1056                    ),
1057                }
1058            }
1059        };
1060        // Record a disconnect to both config manager and telemetry.
1061        log_disconnect_to_telemetry(common_options, options, sme_disconnect_info, true).await;
1062        log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1063            .await;
1064        info!("Roam attempt failed, original association not maintained, disconnecting");
1065    }
1066    notify_on_roam_result(common_options, options, result, *original_ap_state, pending_roam).await;
1067    Ok(())
1068}
1069
1070/// Called for each roam result, regardless of success/failure, after any required
1071/// internal state updates have been made. This function:
1072///   - Records the connect attempt to the target BSS with saved networks manager
1073///   - Logs the roam result to telemetry
1074///   - Publishes the current ap state (which may have been updates)
1075///   - Logs a connect failure defect, if applicable.
1076async fn notify_on_roam_result(
1077    common_options: &mut CommonStateOptions,
1078    options: &mut ConnectedOptions,
1079    result: &fidl_sme::RoamResult,
1080    original_ap_state: types::ApState,
1081    pending_roam: PendingRoam,
1082) {
1083    // Record connect result to config manager, regardless of status.
1084    common_options
1085        .saved_networks_manager
1086        .record_connect_result(
1087            options.network_identifier.clone(),
1088            &options.credential,
1089            result.bssid.into(),
1090            fidl_sme::ConnectResult {
1091                code: result.status_code,
1092                is_credential_rejected: result.is_credential_rejected,
1093                is_reconnect: match result.disconnect_info {
1094                    Some(ref info) => info.is_sme_reconnecting,
1095                    None => false,
1096                },
1097            },
1098            types::ScanObservation::Unknown,
1099        )
1100        .await;
1101
1102    // Log policy-initiated roam result to telemetry once state is up to date.
1103    common_options.telemetry_sender.send(TelemetryEvent::PolicyInitiatedRoamResult {
1104        iface_id: common_options.iface_id,
1105        result: result.clone(),
1106        updated_ap_state: (*options.ap_state).clone(),
1107        original_ap_state: Box::new(original_ap_state),
1108        request: Box::new(pending_roam.request.clone()),
1109        request_time: pending_roam.timestamp,
1110        result_time: fasync::MonotonicInstant::now(),
1111    });
1112
1113    // Publish state.
1114    common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
1115
1116    // Log defect on connect failure.
1117    if result.status_code != fidl_ieee80211::StatusCode::Success
1118        && !result.is_credential_rejected
1119        && let Err(e) =
1120            common_options.defect_sender.try_send(Defect::Iface(IfaceFailure::ConnectionFailure {
1121                iface_id: common_options.iface_id,
1122            }))
1123    {
1124        warn!("Failed to log connection failure: {}", e);
1125    }
1126}
1127
1128async fn notify_on_roam_error_and_exit(
1129    common_options: &CommonStateOptions,
1130    options: &ConnectedOptions,
1131) {
1132    // TODO(b/405151253): Add a disconnect reason for errors attempting to roam.
1133    let fidl_info = fidl_sme::DisconnectInfo {
1134        is_sme_reconnecting: false,
1135        disconnect_source: fidl_sme::DisconnectSource::User(
1136            fidl_sme::UserDisconnectReason::Unknown,
1137        ),
1138    };
1139    log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
1140    log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1141        .await;
1142
1143    // Clear the network from the updates stream, since this state machine will exit.
1144    let networks = Some(ClientNetworkState {
1145        id: options.network_identifier.clone(),
1146        state: types::ConnectionState::Disconnected,
1147        status: Some(types::DisconnectStatus::ConnectionFailed),
1148    });
1149    send_listener_state_update(&common_options.update_sender, networks);
1150}
1151
1152async fn log_disconnect_to_telemetry(
1153    common_options: &CommonStateOptions,
1154    options: &ConnectedOptions,
1155    fidl_info: fidl_sme::DisconnectInfo,
1156    track_subsequent_downtime: bool,
1157) {
1158    let now = fasync::MonotonicInstant::now();
1159    let info = DisconnectInfo {
1160        iface_id: common_options.iface_id,
1161        connected_duration: now - options.ess_connect_start_time,
1162        is_sme_reconnecting: fidl_info.is_sme_reconnecting,
1163        disconnect_source: fidl_info.disconnect_source,
1164        previous_connect_reason: options.ess_connect_reason,
1165        ap_state: (*options.ap_state).clone(),
1166        signals: options.tracked_signals.clone(),
1167    };
1168    common_options
1169        .telemetry_sender
1170        .send(TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) });
1171}
1172
1173async fn log_disconnect_to_config_manager(
1174    common_options: &CommonStateOptions,
1175    options: &ConnectedOptions,
1176    reason: types::DisconnectReason,
1177) {
1178    let curr_time = fasync::MonotonicInstant::now();
1179    let uptime = curr_time - options.bss_connect_start_time;
1180    let data = PastConnectionData::new(
1181        options.ap_state.original().bssid,
1182        curr_time,
1183        uptime,
1184        reason,
1185        options.ap_state.tracked.signal,
1186        // TODO: record average phy rate over connection once available
1187        0,
1188    );
1189    common_options
1190        .saved_networks_manager
1191        .record_disconnect(&options.network_identifier.clone(), &options.credential, data)
1192        .await;
1193}
1194
1195/// Updates all internal state following a roam to a new BSS. This includes updating the ap state,
1196/// restarting metrics timers, clearing signal tracking, and re-initializing a new roam monitor.
1197fn update_internal_state_on_roam_success(
1198    common_options: &mut CommonStateOptions,
1199    options: &mut ConnectedOptions,
1200    result: &fidl_sme::RoamResult,
1201) -> Result<(), anyhow::Error> {
1202    // Update internal state, to proceed with connection.
1203    let bss_description = match result.bss_description {
1204        Some(ref bss_description) => bss_description,
1205        None => {
1206            return Err(format_err!("RoamResult is missing BSS description from FIDL"));
1207        }
1208    };
1209    let ap_state = types::ApState::from(
1210        BssDescription::try_from(*bss_description.clone()).map_err(|error| {
1211            // This only occurs if an invalid `BssDescription` is received from SME, which should
1212            // never happen.
1213            format_err!("Failed to convert BSS description from FIDL: {:?}", error,)
1214        })?,
1215    );
1216    *options.ap_state = ap_state;
1217    options.bss_connect_start_time = fasync::MonotonicInstant::now();
1218    options.tracked_signals = TrackedSignals::new();
1219    options.initial_signal = options.ap_state.tracked.signal;
1220    options.tracked_signals.add(types::TimestampedSignal {
1221        time: fasync::MonotonicInstant::now(),
1222        signal: options.initial_signal,
1223    });
1224    options.post_connect_metric_timer =
1225        Box::pin(fasync::Timer::new(AVERAGE_SCORE_DELTA_MINIMUM_DURATION.after_now()));
1226    options.bss_connect_duration_metric_timer =
1227        Box::pin(fasync::Timer::new(METRICS_SHORT_CONNECT_DURATION.after_now()));
1228    // Re-initialize roam monitor for new BSS
1229    let (sender, roam_receiver) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
1230    options.roam_request_receiver = roam_receiver;
1231    options.roam_monitor_sender = common_options.roam_manager.initialize_roam_monitor(
1232        (*options.ap_state).clone(),
1233        options.network_identifier.clone(),
1234        options.credential.clone(),
1235        sender,
1236    );
1237    Ok(())
1238}
1239
1240#[allow(clippy::result_unit_err, reason = "mass allow for https://fxbug.dev/381896734")]
1241/// Get the disconnect reason corresponding to the connect reason. Return an error if the connect
1242/// reason does not correspond to a manual connect.
1243pub fn convert_manual_connect_to_disconnect_reason(
1244    reason: &types::ConnectReason,
1245) -> Result<types::DisconnectReason, ()> {
1246    match reason {
1247        types::ConnectReason::FidlConnectRequest => Ok(types::DisconnectReason::FidlConnectRequest),
1248        types::ConnectReason::ProactiveNetworkSwitch => {
1249            Ok(types::DisconnectReason::ProactiveNetworkSwitch)
1250        }
1251        types::ConnectReason::RetryAfterDisconnectDetected
1252        | types::ConnectReason::RetryAfterFailedConnectAttempt
1253        | types::ConnectReason::RegulatoryChangeReconnect
1254        | types::ConnectReason::IdleInterfaceAutoconnect
1255        | types::ConnectReason::NewSavedNetworkAutoconnect => Err(()),
1256    }
1257}
1258
1259#[cfg(test)]
1260mod tests {
1261    use super::*;
1262    use crate::client::roaming::lib::{PolicyRoamRequest, RoamTriggerData};
1263    use crate::client::roaming::local_roam_manager::RoamServiceRequest;
1264    use crate::config_management::{network_config, new_past_connection_list};
1265    use crate::util::listener;
1266    use crate::util::state_machine::{StateMachineStatusReader, status_publisher_and_reader};
1267    use crate::util::testing::{
1268        ConnectResultRecord, ConnectionRecord, FakeSavedNetworksManager,
1269        generate_connect_selection, generate_disconnect_info, generate_policy_roam_request,
1270        generate_random_scanned_candidate, poll_sme_req, random_connection_data,
1271    };
1272    use assert_matches::assert_matches;
1273    use fidl::endpoints::{create_proxy, create_proxy_and_stream};
1274    use fidl::prelude::*;
1275    use fidl_fuchsia_wlan_policy as fidl_policy;
1276    use futures::Future;
1277    use futures::task::Poll;
1278    use ieee80211::MacAddrBytes;
1279    use std::pin::pin;
1280    use wlan_common::random_fidl_bss_description;
1281    use wlan_metrics_registry::PolicyDisconnectionMigratedMetricDimensionReason;
1282
1283    struct TestValues {
1284        common_options: CommonStateOptions,
1285        sme_req_stream: fidl_sme::ClientSmeRequestStream,
1286        saved_networks_manager: Arc<FakeSavedNetworksManager>,
1287        client_req_sender: mpsc::Sender<ManualRequest>,
1288        update_receiver: mpsc::UnboundedReceiver<listener::ClientListenerMessage>,
1289        telemetry_receiver: mpsc::Receiver<TelemetryEvent>,
1290        defect_receiver: mpsc::Receiver<Defect>,
1291        roam_service_request_receiver: mpsc::Receiver<RoamServiceRequest>,
1292        status_reader: StateMachineStatusReader<Status>,
1293        power_manager: Arc<wlan_power_manager_testing::TestPowerManager>,
1294    }
1295
1296    fn test_setup() -> TestValues {
1297        let (client_req_sender, client_req_stream) = mpsc::channel(1);
1298        let (update_sender, update_receiver) = mpsc::unbounded();
1299        let (sme_proxy, sme_server) = create_proxy::<fidl_sme::ClientSmeMarker>();
1300        let sme_req_stream = sme_server.into_stream();
1301        let saved_networks = FakeSavedNetworksManager::new();
1302        let saved_networks_manager = Arc::new(saved_networks);
1303        let (telemetry_sender, telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1304        let telemetry_sender = TelemetrySender::new(telemetry_sender);
1305        let (defect_sender, defect_receiver) = mpsc::channel(100);
1306        let (roam_service_request_sender, roam_service_request_receiver) = mpsc::channel(100);
1307        let roam_manager = RoamManager::new(roam_service_request_sender);
1308        let (status_publisher, status_reader) = status_publisher_and_reader::<Status>();
1309        let power_manager = Arc::new(wlan_power_manager_testing::TestPowerManager::new());
1310
1311        TestValues {
1312            common_options: CommonStateOptions {
1313                proxy: SmeForClientStateMachine::new(sme_proxy, 0, defect_sender.clone()),
1314                req_stream: client_req_stream.fuse(),
1315                update_sender,
1316                saved_networks_manager: saved_networks_manager.clone(),
1317                telemetry_sender,
1318                iface_id: 1,
1319                defect_sender,
1320                roam_manager,
1321                status_publisher,
1322                power_manager: power_manager.clone(),
1323            },
1324            sme_req_stream,
1325            saved_networks_manager,
1326            client_req_sender,
1327            update_receiver,
1328            telemetry_receiver,
1329            defect_receiver,
1330            roam_service_request_receiver,
1331            status_reader,
1332            power_manager,
1333        }
1334    }
1335
1336    #[allow(clippy::needless_return, reason = "mass allow for https://fxbug.dev/381896734")]
1337    async fn run_state_machine(fut: impl Future<Output = Result<State, ExitReason>> + 'static) {
1338        let state_machine = fut.into_state_machine();
1339        select! {
1340            _state_machine = state_machine.fuse() => return,
1341        }
1342    }
1343
1344    #[fuchsia::test]
1345    fn connecting_state_successfully_connects() {
1346        let mut exec = fasync::TestExecutor::new();
1347        let mut test_values = test_setup();
1348
1349        let connect_selection = generate_connect_selection();
1350        let bss_description =
1351            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1352
1353        // Store the network in the saved_networks_manager, so we can record connection success
1354        let save_fut = test_values.saved_networks_manager.store(
1355            connect_selection.target.network.clone(),
1356            connect_selection.target.credential.clone(),
1357        );
1358        let mut save_fut = pin!(save_fut);
1359        assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
1360
1361        // Check that the saved networks manager has the expected initial data
1362        let saved_network = exec
1363            .run_singlethreaded(
1364                test_values
1365                    .saved_networks_manager
1366                    .lookup(&connect_selection.target.network.clone()),
1367            )
1368            .expect("failed to lookup network");
1369        assert!(!saved_network.has_ever_connected);
1370        assert!(saved_network.hidden_probability > 0.0);
1371
1372        let connecting_options =
1373            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1374        let initial_state = connecting_state(test_values.common_options, connecting_options);
1375        let fut = run_state_machine(initial_state);
1376        let mut fut = pin!(fut);
1377        let sme_fut = test_values.sme_req_stream.into_future();
1378        let mut sme_fut = pin!(sme_fut);
1379
1380        // Run the state machine
1381        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1382
1383        // Ensure a connect request is sent to the SME
1384        let connect_txn_handle = assert_matches!(
1385            poll_sme_req(&mut exec, &mut sme_fut),
1386            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1387                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1388                assert_eq!(req.bss_description, bss_description);
1389                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1390                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1391                // Send connection response.
1392                let (_stream, ctrl) = txn.expect("connect txn unused")
1393                    .into_stream_and_control_handle();
1394                ctrl
1395            }
1396        );
1397        connect_txn_handle
1398            .send_on_connect_result(&fake_successful_connect_result())
1399            .expect("failed to send connection completion");
1400
1401        // Check for a connecting update
1402        let client_state_update = ClientStateUpdate {
1403            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1404            networks: vec![ClientNetworkState {
1405                id: connect_selection.target.network.clone(),
1406                state: fidl_policy::ConnectionState::Connecting,
1407                status: None,
1408            }],
1409        };
1410        assert_matches!(
1411            test_values.update_receiver.try_recv(),
1412            Ok(listener::Message::NotifyListeners(updates)) => {
1413            assert_eq!(updates, client_state_update);
1414        });
1415
1416        // Progress the state machine
1417        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1418
1419        // Check for a connect update
1420        let client_state_update = ClientStateUpdate {
1421            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1422            networks: vec![ClientNetworkState {
1423                id: connect_selection.target.network.clone(),
1424                state: fidl_policy::ConnectionState::Connected,
1425                status: None,
1426            }],
1427        };
1428        assert_matches!(
1429            test_values.update_receiver.try_recv(),
1430            Ok(listener::Message::NotifyListeners(updates)) => {
1431            assert_eq!(updates, client_state_update);
1432        });
1433
1434        // Check that the connection was recorded to SavedNetworksManager
1435        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1436            let expected_connect_result = ConnectResultRecord {
1437                 id: connect_selection.target.network.clone(),
1438                 credential: connect_selection.target.credential.clone(),
1439                 bssid: types::Bssid::from(bss_description.bssid),
1440                 connect_result: fake_successful_connect_result(),
1441                 scan_type: connect_selection.target.bss.observation,
1442            };
1443            assert_eq!(data, &expected_connect_result);
1444        });
1445
1446        // Progress the state machine
1447        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1448
1449        // Ensure no further updates were sent to listeners
1450        assert_matches!(
1451            exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1452            Poll::Pending
1453        );
1454    }
1455
1456    #[fuchsia::test]
1457    fn connecting_state_times_out() {
1458        let mut exec = fasync::TestExecutor::new();
1459        let mut test_values = test_setup();
1460
1461        let connect_selection = generate_connect_selection();
1462        let bss_description =
1463            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1464
1465        // Store the network in the saved_networks_manager
1466        let save_fut = test_values.saved_networks_manager.store(
1467            connect_selection.target.network.clone(),
1468            connect_selection.target.credential.clone(),
1469        );
1470        let mut save_fut = pin!(save_fut);
1471        assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
1472
1473        // Prepare state machine
1474        let connecting_options =
1475            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1476        let initial_state = connecting_state(test_values.common_options, connecting_options);
1477        let fut = run_state_machine(initial_state);
1478        let mut fut = pin!(fut);
1479        let sme_fut = test_values.sme_req_stream.into_future();
1480        let mut sme_fut = pin!(sme_fut);
1481
1482        // Run the state machine
1483        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1484
1485        // Ensure a connect request is sent to the SME
1486        let connect_txn_handle = assert_matches!(
1487            poll_sme_req(&mut exec, &mut sme_fut),
1488            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1489                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1490                assert_eq!(req.bss_description, bss_description);
1491                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1492                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1493                let (_stream, ctrl) = txn.expect("connect txn unused")
1494                    .into_stream_and_control_handle();
1495                ctrl
1496            }
1497        );
1498
1499        // Check for a connecting update
1500        let client_state_update = ClientStateUpdate {
1501            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1502            networks: vec![ClientNetworkState {
1503                id: connect_selection.target.network.clone(),
1504                state: fidl_policy::ConnectionState::Connecting,
1505                status: None,
1506            }],
1507        };
1508        assert_matches!(
1509            test_values.update_receiver.try_recv(),
1510            Ok(listener::Message::NotifyListeners(updates)) => {
1511            assert_eq!(updates, client_state_update);
1512        });
1513
1514        // Respond with a SignalReport, which should not unblock connecting_state
1515        connect_txn_handle
1516            .send_on_signal_report(&fidl_internal::SignalReportIndication {
1517                rssi_dbm: Some(-25),
1518                snr_db: Some(30),
1519                tx_rate_500kbps: Some(0),
1520                ..Default::default()
1521            })
1522            .expect("failed to send singal report");
1523
1524        // Run the state machine. Should still be pending
1525        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1526
1527        // Wake up the next timer, which is the timeout for the connect request.
1528        assert!(exec.wake_next_timer().is_some());
1529
1530        // State machine should exit.
1531        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1532    }
1533
1534    #[fuchsia::test]
1535    fn connecting_state_successfully_scans_and_connects() {
1536        let mut exec = fasync::TestExecutor::new_with_fake_time();
1537        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(123));
1538        let mut test_values = test_setup();
1539
1540        let connect_selection = generate_connect_selection();
1541        let bss_description =
1542            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1543
1544        // Set how the SavedNetworksManager should respond to lookup_compatible for the scan.
1545        let expected_config = network_config::NetworkConfig::new(
1546            connect_selection.target.network.clone(),
1547            connect_selection.target.credential.clone(),
1548            connect_selection.target.saved_network_info.has_ever_connected,
1549            None,
1550        )
1551        .expect("failed to create network config");
1552        test_values.saved_networks_manager.set_lookup_compatible_response(vec![expected_config]);
1553
1554        let connecting_options =
1555            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1556        let initial_state = connecting_state(test_values.common_options, connecting_options);
1557        let fut = run_state_machine(initial_state);
1558        let mut fut = pin!(fut);
1559
1560        // Run the state machine
1561        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1562
1563        // Ensure a connect request is sent to the SME
1564        let sme_fut = test_values.sme_req_stream.into_future();
1565        let mut sme_fut = pin!(sme_fut);
1566        let time_to_connect = zx::MonotonicDuration::from_seconds(30);
1567        let connect_txn_handle = assert_matches!(
1568            poll_sme_req(&mut exec, &mut sme_fut),
1569            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1570                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1571                assert_eq!(req.bss_description, bss_description.clone());
1572                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1573                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1574                // Send connection response.
1575                exec.set_fake_time(fasync::MonotonicInstant::after(time_to_connect));
1576                let (_stream, ctrl) = txn.expect("connect txn unused")
1577                    .into_stream_and_control_handle();
1578                ctrl
1579            }
1580        );
1581        connect_txn_handle
1582            .send_on_connect_result(&fake_successful_connect_result())
1583            .expect("failed to send connection completion");
1584
1585        // Check for a connecting update
1586        let client_state_update = ClientStateUpdate {
1587            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1588            networks: vec![ClientNetworkState {
1589                id: connect_selection.target.network.clone(),
1590                state: fidl_policy::ConnectionState::Connecting,
1591                status: None,
1592            }],
1593        };
1594        assert_matches!(
1595            test_values.update_receiver.try_recv(),
1596            Ok(listener::Message::NotifyListeners(updates)) => {
1597            assert_eq!(updates, client_state_update);
1598        });
1599
1600        // Progress the state machine
1601        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1602
1603        // Check for a connect update
1604        let client_state_update = ClientStateUpdate {
1605            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1606            networks: vec![ClientNetworkState {
1607                id: connect_selection.target.network.clone(),
1608                state: fidl_policy::ConnectionState::Connected,
1609                status: None,
1610            }],
1611        };
1612        assert_matches!(
1613            test_values.update_receiver.try_recv(),
1614            Ok(listener::Message::NotifyListeners(updates)) => {
1615            assert_eq!(updates, client_state_update);
1616        });
1617
1618        // Check that the saved networks manager has the connection result recorded
1619        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1620            let expected_connect_result = ConnectResultRecord {
1621                 id: connect_selection.target.network.clone(),
1622                 credential: connect_selection.target.credential.clone(),
1623                 bssid: types::Bssid::from(bss_description.bssid),
1624                 connect_result: fake_successful_connect_result(),
1625                 scan_type: connect_selection.target.bss.observation,
1626            };
1627            assert_eq!(data, &expected_connect_result);
1628        });
1629
1630        // Check that connected telemetry event is sent
1631        assert_matches!(
1632            test_values.telemetry_receiver.try_recv(),
1633            Ok(TelemetryEvent::ConnectResult { iface_id: 1, policy_connect_reason, result, multiple_bss_candidates, ap_state, network_is_likely_hidden: _ }) => {
1634                assert_eq!(bss_description, ap_state.original().clone().into());
1635                assert_eq!(multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1636                assert_eq!(policy_connect_reason, Some(connect_selection.reason));
1637                assert_eq!(result, fake_successful_connect_result());
1638            }
1639        );
1640
1641        // Progress the state machine
1642        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1643
1644        // Ensure no further updates were sent to listeners
1645        assert_matches!(
1646            exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1647            Poll::Pending
1648        );
1649
1650        // Verify the Connected status was set.
1651        let status = test_values.status_reader.read_status().expect("failed to read status");
1652        assert_matches!(status, Status::Connected { .. });
1653
1654        // Send a disconnect and check that the connection data is correctly recorded
1655        let is_sme_reconnecting = false;
1656        let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
1657        connect_txn_handle
1658            .send_on_disconnect(&fidl_disconnect_info)
1659            .expect("failed to send disconnection event");
1660        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1661
1662        // Verify roam monitor request was sent.
1663        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
1664            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
1665        });
1666
1667        // Run the state machine
1668        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1669
1670        let expected_recorded_connection = ConnectionRecord {
1671            id: connect_selection.target.network.clone(),
1672            credential: connect_selection.target.credential.clone(),
1673            data: PastConnectionData {
1674                bssid: types::Bssid::from(bss_description.bssid),
1675                disconnect_time: fasync::MonotonicInstant::now(),
1676                connection_uptime: zx::MonotonicDuration::from_minutes(0),
1677                disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
1678                signal_at_disconnect: types::Signal {
1679                    rssi_dbm: bss_description.rssi_dbm,
1680                    snr_db: bss_description.snr_db,
1681                },
1682                // TODO: record average phy rate over connection once available
1683                average_tx_rate: 0,
1684            },
1685        };
1686        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [data] => {
1687            assert_eq!(data, &expected_recorded_connection);
1688        });
1689    }
1690
1691    #[fuchsia::test]
1692    fn connecting_state_fails_to_connect_and_retries() {
1693        let mut exec = fasync::TestExecutor::new();
1694        let mut test_values = test_setup();
1695
1696        let connect_selection = generate_connect_selection();
1697        let bss_description =
1698            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1699
1700        let connecting_options =
1701            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1702        let initial_state = connecting_state(test_values.common_options, connecting_options);
1703        let fut = run_state_machine(initial_state);
1704        let mut fut = pin!(fut);
1705        let sme_fut = test_values.sme_req_stream.into_future();
1706        let mut sme_fut = pin!(sme_fut);
1707
1708        // Run the state machine
1709        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1710
1711        // Ensure a connect request is sent to the SME
1712        let mut connect_txn_handle = assert_matches!(
1713            poll_sme_req(&mut exec, &mut sme_fut),
1714            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1715                assert_eq!(req.ssid, connect_selection.target.network.ssid.to_vec());
1716                 // Send connection response.
1717                let (_stream, ctrl) = txn.expect("connect txn unused")
1718                    .into_stream_and_control_handle();
1719                ctrl
1720            }
1721        );
1722        let connect_result = fidl_sme::ConnectResult {
1723            code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1724            ..fake_successful_connect_result()
1725        };
1726        connect_txn_handle
1727            .send_on_connect_result(&connect_result)
1728            .expect("failed to send connection completion");
1729
1730        // Check for a connecting update
1731        let client_state_update = ClientStateUpdate {
1732            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1733            networks: vec![ClientNetworkState {
1734                id: types::NetworkIdentifier {
1735                    ssid: connect_selection.target.network.ssid.clone(),
1736                    security_type: types::SecurityType::Wpa2,
1737                },
1738                state: fidl_policy::ConnectionState::Connecting,
1739                status: None,
1740            }],
1741        };
1742        assert_matches!(
1743            test_values.update_receiver.try_recv(),
1744            Ok(listener::Message::NotifyListeners(updates)) => {
1745            assert_eq!(updates, client_state_update);
1746        });
1747
1748        // Progress the state machine
1749        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1750        assert!(exec.wake_next_timer().is_some());
1751        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1752
1753        // Check that connect result telemetry event is sent
1754        assert_matches!(
1755            test_values.telemetry_receiver.try_recv(),
1756            Ok(TelemetryEvent::ConnectResult { iface_id: 1, policy_connect_reason, result, multiple_bss_candidates, ap_state, network_is_likely_hidden: _ }) => {
1757                assert_eq!(bss_description, ap_state.original().clone().into());
1758                assert_eq!(multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1759                assert_eq!(policy_connect_reason, Some(connect_selection.reason));
1760                assert_eq!(result, connect_result);
1761            }
1762        );
1763
1764        // Ensure a disconnect request is sent to the SME
1765        assert_matches!(
1766            poll_sme_req(&mut exec, &mut sme_fut),
1767            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::FailedToConnect }) => {
1768                responder.send().expect("could not send sme response");
1769            }
1770        );
1771
1772        // Progress the state machine
1773        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1774
1775        // Ensure a connect request is sent to the SME
1776        connect_txn_handle = assert_matches!(
1777            poll_sme_req(&mut exec, &mut sme_fut),
1778            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1779                assert_eq!(req.ssid, connect_selection.target.network.ssid.to_vec());
1780                assert_eq!(req.bss_description, Sequestered::release(connect_selection.target.bss.bss_description));
1781                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1782                 // Send connection response.
1783                let (_stream, ctrl) = txn.expect("connect txn unused")
1784                    .into_stream_and_control_handle();
1785                ctrl
1786            }
1787        );
1788        let connect_result = fake_successful_connect_result();
1789        connect_txn_handle
1790            .send_on_connect_result(&connect_result)
1791            .expect("failed to send connection completion");
1792
1793        // Progress the state machine
1794        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1795
1796        // Empty update sent to NotifyListeners (which in this case, will not actually be sent.)
1797        assert_matches!(
1798            test_values.update_receiver.try_recv(),
1799            Ok(listener::Message::NotifyListeners(ClientStateUpdate {
1800                state: fidl_policy::WlanClientState::ConnectionsEnabled,
1801                networks
1802            })) => {
1803                assert!(networks.is_empty());
1804            }
1805        );
1806
1807        // A defect should be logged.
1808        assert_matches!(
1809            test_values.defect_receiver.try_recv(),
1810            Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
1811        );
1812
1813        // Check for a connected update
1814        let client_state_update = ClientStateUpdate {
1815            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1816            networks: vec![ClientNetworkState {
1817                id: types::NetworkIdentifier {
1818                    ssid: connect_selection.target.network.ssid.clone(),
1819                    security_type: types::SecurityType::Wpa2,
1820                },
1821                state: fidl_policy::ConnectionState::Connected,
1822                status: None,
1823            }],
1824        };
1825        assert_matches!(
1826            test_values.update_receiver.try_recv(),
1827            Ok(listener::Message::NotifyListeners(updates)) => {
1828            assert_eq!(updates, client_state_update);
1829        });
1830
1831        // Progress the state machine
1832        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1833
1834        // Ensure no further updates were sent to listeners
1835        assert_matches!(
1836            exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1837            Poll::Pending
1838        );
1839    }
1840
1841    #[fuchsia::test]
1842    fn connecting_state_fails_to_connect_at_max_retries() {
1843        let mut exec = fasync::TestExecutor::new();
1844        let mut test_values = test_setup();
1845
1846        let connect_selection = generate_connect_selection();
1847        let bss_description =
1848            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1849
1850        // save network to check that failed connect is recorded
1851        assert!(
1852            exec.run_singlethreaded(test_values.saved_networks_manager.store(
1853                connect_selection.target.network.clone(),
1854                connect_selection.target.credential.clone()
1855            ),)
1856                .expect("Failed to save network")
1857                .is_none()
1858        );
1859
1860        let connecting_options = ConnectingOptions {
1861            connect_selection: connect_selection.clone(),
1862            attempt_counter: MAX_CONNECTION_ATTEMPTS - 1,
1863        };
1864        let initial_state = connecting_state(test_values.common_options, connecting_options);
1865        let fut = run_state_machine(initial_state);
1866        let mut fut = pin!(fut);
1867        let sme_fut = test_values.sme_req_stream.into_future();
1868        let mut sme_fut = pin!(sme_fut);
1869
1870        // Run the state machine
1871        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1872
1873        // Ensure a connect request is sent to the SME
1874        assert_matches!(
1875            poll_sme_req(&mut exec, &mut sme_fut),
1876            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1877                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1878                assert_eq!(req.bss_description, bss_description.clone());
1879                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1880                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1881                 // Send connection response.
1882                let (_stream, ctrl) = txn.expect("connect txn unused")
1883                    .into_stream_and_control_handle();
1884                let connect_result = fidl_sme::ConnectResult {
1885                    code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1886                    ..fake_successful_connect_result()
1887                };
1888                ctrl
1889                    .send_on_connect_result(&connect_result)
1890                    .expect("failed to send connection completion");
1891            }
1892        );
1893
1894        // After failing to reconnect, the state machine should exit so that the state machine
1895        // monitor can attempt to reconnect the interface.
1896        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1897
1898        // Check for a connect update
1899        let client_state_update = ClientStateUpdate {
1900            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1901            networks: vec![ClientNetworkState {
1902                id: connect_selection.target.network.clone(),
1903                state: fidl_policy::ConnectionState::Failed,
1904                status: Some(fidl_policy::DisconnectStatus::ConnectionFailed),
1905            }],
1906        };
1907        assert_matches!(
1908            test_values.update_receiver.try_recv(),
1909            Ok(listener::Message::NotifyListeners(updates)) => {
1910            assert_eq!(updates, client_state_update);
1911        });
1912
1913        // Check that failure was recorded in SavedNetworksManager
1914        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1915            let connect_result = fidl_sme::ConnectResult {
1916                code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1917                is_credential_rejected: false,
1918                is_reconnect: false,
1919            };
1920            let expected_connect_result = ConnectResultRecord {
1921                 id: connect_selection.target.network.clone(),
1922                 credential: connect_selection.target.credential.clone(),
1923                 bssid: types::Bssid::from(bss_description.bssid),
1924                 connect_result,
1925                 scan_type: connect_selection.target.bss.observation,
1926            };
1927            assert_eq!(data, &expected_connect_result);
1928        });
1929
1930        // A defect should be logged.
1931        assert_matches!(
1932            test_values.defect_receiver.try_recv(),
1933            Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
1934        );
1935    }
1936
1937    #[fuchsia::test]
1938    fn connecting_state_fails_to_connect_with_bad_credentials() {
1939        let mut exec = fasync::TestExecutor::new();
1940        let mut test_values = test_setup();
1941
1942        let connect_selection = generate_connect_selection();
1943        let bss_description =
1944            Sequestered::release(connect_selection.target.bss.bss_description.clone());
1945
1946        assert!(
1947            exec.run_singlethreaded(test_values.saved_networks_manager.store(
1948                connect_selection.target.network.clone(),
1949                connect_selection.target.credential.clone()
1950            ),)
1951                .expect("Failed to save network")
1952                .is_none()
1953        );
1954
1955        let connecting_options = ConnectingOptions {
1956            connect_selection: connect_selection.clone(),
1957            attempt_counter: MAX_CONNECTION_ATTEMPTS - 1,
1958        };
1959        let initial_state = connecting_state(test_values.common_options, connecting_options);
1960        let fut = run_state_machine(initial_state);
1961        let mut fut = pin!(fut);
1962        let sme_fut = test_values.sme_req_stream.into_future();
1963        let mut sme_fut = pin!(sme_fut);
1964
1965        // Run the state machine
1966        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1967
1968        // Ensure a connect request is sent to the SME
1969        assert_matches!(
1970            poll_sme_req(&mut exec, &mut sme_fut),
1971            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1972                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1973                assert_eq!(req.bss_description, bss_description.clone());
1974                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1975                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1976                 // Send connection response.
1977                let (_stream, ctrl) = txn.expect("connect txn unused")
1978                    .into_stream_and_control_handle();
1979                let connect_result = fidl_sme::ConnectResult {
1980                    code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1981                    is_credential_rejected: true,
1982                    ..fake_successful_connect_result()
1983                };
1984                ctrl
1985                    .send_on_connect_result(&connect_result)
1986                    .expect("failed to send connection completion");
1987            }
1988        );
1989
1990        // The state machine should exit when bad credentials are detected so that the state
1991        // machine monitor can try to connect to another network.
1992        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1993
1994        // Check for a connect update
1995        let client_state_update = ClientStateUpdate {
1996            state: fidl_policy::WlanClientState::ConnectionsEnabled,
1997            networks: vec![ClientNetworkState {
1998                id: connect_selection.target.network.clone(),
1999                state: fidl_policy::ConnectionState::Failed,
2000                status: Some(fidl_policy::DisconnectStatus::CredentialsFailed),
2001            }],
2002        };
2003        assert_matches!(
2004            test_values.update_receiver.try_recv(),
2005            Ok(listener::Message::NotifyListeners(updates)) => {
2006            assert_eq!(updates, client_state_update);
2007        });
2008
2009        // Check that failure was recorded to SavedNetworksManager
2010        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
2011            let connect_result = fidl_sme::ConnectResult {
2012                code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
2013                is_credential_rejected: true,
2014                is_reconnect: false,
2015            };
2016            let expected_connect_result = ConnectResultRecord {
2017                 id: connect_selection.target.network.clone(),
2018                 credential: connect_selection.target.credential.clone(),
2019                 bssid: types::Bssid::from(bss_description.bssid),
2020                 connect_result,
2021                 scan_type: connect_selection.target.bss.observation,
2022            };
2023            assert_eq!(data, &expected_connect_result);
2024        });
2025
2026        // No defect should have been observed.
2027        assert_matches!(test_values.defect_receiver.try_recv(), Err(e) if e.is_closed());
2028    }
2029
2030    #[fuchsia::test]
2031    fn connecting_state_gets_duplicate_connect_selection() {
2032        let mut exec = fasync::TestExecutor::new();
2033        let mut test_values = test_setup();
2034
2035        let connect_selection = generate_connect_selection();
2036        let bss_description =
2037            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2038
2039        let connecting_options =
2040            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2041        let initial_state = connecting_state(test_values.common_options, connecting_options);
2042        let fut = run_state_machine(initial_state);
2043        let mut fut = pin!(fut);
2044        let sme_fut = test_values.sme_req_stream.into_future();
2045        let mut sme_fut = pin!(sme_fut);
2046
2047        // Run the state machine
2048        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2049
2050        // Check for a connecting update
2051        let client_state_update = ClientStateUpdate {
2052            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2053            networks: vec![ClientNetworkState {
2054                id: types::NetworkIdentifier {
2055                    ssid: connect_selection.target.network.ssid.clone(),
2056                    security_type: types::SecurityType::Wpa2,
2057                },
2058                state: fidl_policy::ConnectionState::Connecting,
2059                status: None,
2060            }],
2061        };
2062        assert_matches!(
2063            test_values.update_receiver.try_recv(),
2064            Ok(listener::Message::NotifyListeners(updates)) => {
2065            assert_eq!(updates, client_state_update);
2066        });
2067
2068        // Send a duplicate connect request
2069        let mut client = Client::new(test_values.client_req_sender);
2070        let duplicate_request = types::ConnectSelection {
2071            // this incoming request should be deduped regardless of the reason
2072            reason: types::ConnectReason::ProactiveNetworkSwitch,
2073            ..connect_selection.clone()
2074        };
2075        client.connect(duplicate_request).expect("failed to make request");
2076
2077        // Progress the state machine
2078        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2079
2080        // Ensure a connect request is sent to the SME
2081        let connect_txn_handle = assert_matches!(
2082            poll_sme_req(&mut exec, &mut sme_fut),
2083            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
2084                assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
2085                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
2086                assert_eq!(req.bss_description, bss_description);
2087                assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
2088                 // Send connection response.
2089                let (_stream, ctrl) = txn.expect("connect txn unused")
2090                    .into_stream_and_control_handle();
2091                ctrl
2092            }
2093        );
2094        connect_txn_handle
2095            .send_on_connect_result(&fake_successful_connect_result())
2096            .expect("failed to send connection completion");
2097
2098        // Progress the state machine
2099        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2100
2101        // Check for a connect update
2102        let client_state_update = ClientStateUpdate {
2103            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2104            networks: vec![ClientNetworkState {
2105                id: connect_selection.target.network.clone(),
2106                state: fidl_policy::ConnectionState::Connected,
2107                status: None,
2108            }],
2109        };
2110        assert_matches!(
2111            test_values.update_receiver.try_recv(),
2112            Ok(listener::Message::NotifyListeners(updates)) => {
2113            assert_eq!(updates, client_state_update);
2114        });
2115
2116        // Progress the state machine
2117        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2118
2119        // Ensure no further updates were sent to listeners
2120        assert_matches!(
2121            exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
2122            Poll::Pending
2123        );
2124    }
2125
2126    #[fuchsia::test]
2127    fn connecting_state_has_broken_sme() {
2128        let mut exec = fasync::TestExecutor::new();
2129        let test_values = test_setup();
2130
2131        let connect_selection = generate_connect_selection();
2132
2133        let connecting_options =
2134            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2135        let initial_state = connecting_state(test_values.common_options, connecting_options);
2136        let fut = run_state_machine(initial_state);
2137        let mut fut = pin!(fut);
2138
2139        // Break the SME by dropping the server end of the SME stream, so it causes an error
2140        drop(test_values.sme_req_stream);
2141
2142        // Ensure the state machine exits
2143        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2144    }
2145
2146    #[fuchsia::test]
2147    fn connected_state_gets_disconnect_request() {
2148        let mut exec = fasync::TestExecutor::new_with_fake_time();
2149        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2150
2151        let mut test_values = test_setup();
2152        let mut telemetry_receiver = test_values.telemetry_receiver;
2153        let connect_selection = generate_connect_selection();
2154        let bss_description =
2155            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2156        let init_ap_state =
2157            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2158
2159        let (connect_txn_proxy, _connect_txn_stream) =
2160            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2161        let options = ConnectedOptions::new(
2162            &mut test_values.common_options,
2163            Box::new(init_ap_state.clone()),
2164            connect_selection.target.network_has_multiple_bss,
2165            connect_selection.target.network.clone(),
2166            connect_selection.target.credential.clone(),
2167            connect_selection.reason,
2168            connect_txn_proxy.take_event_stream(),
2169            false,
2170        );
2171        let initial_state = connected_state(test_values.common_options, options);
2172        let fut = run_state_machine(initial_state);
2173        let mut fut = pin!(fut);
2174        let sme_fut = test_values.sme_req_stream.into_future();
2175        let mut sme_fut = pin!(sme_fut);
2176
2177        let disconnect_time =
2178            fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2179
2180        // Run the state machine
2181        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2182
2183        // Verify roam monitor request was sent.
2184        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2185            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2186        });
2187
2188        // Run the state machine
2189        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2190
2191        // Run forward to get post connection signals metrics
2192        exec.set_fake_time(fasync::MonotonicInstant::after(
2193            AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2194        ));
2195        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2196        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2197            assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2198        });
2199
2200        // Run forward to get long duration signals metrics
2201        exec.set_fake_time(fasync::MonotonicInstant::after(
2202            METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2203        ));
2204        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2205        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2206            assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2207        });
2208
2209        // Run forward to disconnect time
2210        exec.set_fake_time(disconnect_time);
2211        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2212
2213        // Send a disconnect request
2214        let mut client = Client::new(test_values.client_req_sender);
2215        let (sender, mut receiver) = oneshot::channel();
2216        client
2217            .disconnect(types::DisconnectReason::FidlStopClientConnectionsRequest, sender)
2218            .expect("failed to make request");
2219
2220        // Run the state machine
2221        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2222
2223        // Respond to the SME disconnect
2224        assert_matches!(
2225            poll_sme_req(&mut exec, &mut sme_fut),
2226            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::FidlStopClientConnectionsRequest }) => {
2227                responder.send().expect("could not send sme response");
2228            }
2229        );
2230
2231        // Once the disconnect is processed, the state machine should exit.
2232        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2233
2234        // Check for a disconnect update and the responder
2235        let client_state_update = ClientStateUpdate {
2236            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2237            networks: vec![ClientNetworkState {
2238                id: connect_selection.target.network.clone(),
2239                state: fidl_policy::ConnectionState::Disconnected,
2240                status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
2241            }],
2242        };
2243        assert_matches!(
2244            test_values.update_receiver.try_recv(),
2245            Ok(listener::Message::NotifyListeners(updates)) => {
2246            assert_eq!(updates, client_state_update);
2247        });
2248        assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(())));
2249
2250        // Disconnect telemetry event sent
2251        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2252            assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2253                assert!(!track_subsequent_downtime);
2254                assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2255                    assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2256                    assert!(!is_sme_reconnecting);
2257                    assert_eq!(disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::FidlStopClientConnectionsRequest));
2258                    assert_eq!(previous_connect_reason, connect_selection.reason);
2259                    assert_eq!(ap_state, init_ap_state.clone());
2260                });
2261            });
2262        });
2263
2264        // The disconnect should have been recorded for the saved network config.
2265        let expected_recorded_connection = ConnectionRecord {
2266            id: connect_selection.target.network.clone(),
2267            credential: connect_selection.target.credential.clone(),
2268            data: PastConnectionData {
2269                bssid: init_ap_state.original().bssid,
2270                disconnect_time,
2271                connection_uptime: zx::MonotonicDuration::from_hours(12),
2272                disconnect_reason: types::DisconnectReason::FidlStopClientConnectionsRequest,
2273                signal_at_disconnect: types::Signal {
2274                    rssi_dbm: bss_description.rssi_dbm,
2275                    snr_db: bss_description.snr_db,
2276                },
2277                // TODO: record average phy rate over connection once available
2278                average_tx_rate: 0,
2279            },
2280        };
2281        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2282            assert_eq!(connection_data, &expected_recorded_connection);
2283        });
2284    }
2285
2286    #[fuchsia::test]
2287    fn connected_state_records_unexpected_disconnect() {
2288        let mut exec = fasync::TestExecutor::new_with_fake_time();
2289        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2290
2291        let mut test_values = test_setup();
2292        let mut telemetry_receiver = test_values.telemetry_receiver;
2293
2294        let connect_selection = generate_connect_selection();
2295        let bss_description =
2296            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2297        let init_ap_state =
2298            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2299
2300        // Save the network in order to later record the disconnect to it.
2301        let save_fut = test_values.saved_networks_manager.store(
2302            connect_selection.target.network.clone(),
2303            connect_selection.target.credential.clone(),
2304        );
2305        let mut save_fut = pin!(save_fut);
2306        assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
2307
2308        let (connect_txn_proxy, connect_txn_stream) =
2309            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2310        let connect_txn_handle = connect_txn_stream.control_handle();
2311        let options = ConnectedOptions::new(
2312            &mut test_values.common_options,
2313            Box::new(init_ap_state.clone()),
2314            connect_selection.target.network_has_multiple_bss,
2315            connect_selection.target.network.clone(),
2316            connect_selection.target.credential.clone(),
2317            connect_selection.reason,
2318            connect_txn_proxy.take_event_stream(),
2319            false,
2320        );
2321
2322        // Start the state machine in the connected state.
2323        let initial_state = connected_state(test_values.common_options, options);
2324        let fut = run_state_machine(initial_state);
2325        let mut fut = pin!(fut);
2326        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2327
2328        // Verify roam monitor request was sent.
2329        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2330            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2331        });
2332
2333        // Run the state machine
2334        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2335
2336        let disconnect_time =
2337            fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2338        exec.set_fake_time(disconnect_time);
2339
2340        // SME notifies Policy of disconnection
2341        let fidl_disconnect_info = generate_disconnect_info(false);
2342        connect_txn_handle
2343            .send_on_disconnect(&fidl_disconnect_info)
2344            .expect("failed to send disconnection event");
2345        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2346
2347        // The disconnect should have been recorded for the saved network config.
2348        let expected_recorded_connection = ConnectionRecord {
2349            id: connect_selection.target.network.clone(),
2350            credential: connect_selection.target.credential.clone(),
2351            data: PastConnectionData {
2352                bssid: init_ap_state.original().bssid,
2353                disconnect_time,
2354                connection_uptime: zx::MonotonicDuration::from_hours(12),
2355                disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
2356                signal_at_disconnect: types::Signal {
2357                    rssi_dbm: bss_description.rssi_dbm,
2358                    snr_db: bss_description.snr_db,
2359                },
2360                // TODO: record average phy rate over connection once available
2361                average_tx_rate: 0,
2362            },
2363        };
2364        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2365            assert_eq!(connection_data, &expected_recorded_connection);
2366        });
2367
2368        // Disconnect telemetry event sent
2369        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2370            assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2371                assert!(track_subsequent_downtime);
2372                assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2373                    assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2374                    assert!(!is_sme_reconnecting);
2375                    assert_eq!(disconnect_source, fidl_disconnect_info.disconnect_source);
2376                    assert_eq!(previous_connect_reason, connect_selection.reason);
2377                    assert_eq!(ap_state, init_ap_state);
2378                });
2379            });
2380        });
2381    }
2382
2383    #[fuchsia::test]
2384    fn connected_state_reconnect_resets_connected_duration() {
2385        let mut exec = fasync::TestExecutor::new_with_fake_time();
2386        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2387
2388        let mut test_values = test_setup();
2389        let mut telemetry_receiver = test_values.telemetry_receiver;
2390
2391        let connect_selection = generate_connect_selection();
2392        let bss_description =
2393            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2394        let ap_state =
2395            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2396
2397        let (connect_txn_proxy, connect_txn_stream) =
2398            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2399        let connect_txn_handle = connect_txn_stream.control_handle();
2400        let options = ConnectedOptions::new(
2401            &mut test_values.common_options,
2402            Box::new(ap_state.clone()),
2403            connect_selection.target.network_has_multiple_bss,
2404            connect_selection.target.network.clone(),
2405            connect_selection.target.credential.clone(),
2406            connect_selection.reason,
2407            connect_txn_proxy.take_event_stream(),
2408            false,
2409        );
2410        let initial_state = connected_state(test_values.common_options, options);
2411        let fut = run_state_machine(initial_state);
2412        let mut fut = pin!(fut);
2413
2414        let disconnect_time =
2415            fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2416
2417        // Run the state machine
2418        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2419
2420        // Verify roam monitor request was sent.
2421        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2422            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2423        });
2424
2425        // Run the state machine
2426        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2427
2428        // Run forward to get post connection score metrics
2429        exec.set_fake_time(fasync::MonotonicInstant::after(
2430            AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2431        ));
2432        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2433        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2434            assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2435        });
2436
2437        // Run forward to get long duration signals metrics
2438        exec.set_fake_time(fasync::MonotonicInstant::after(
2439            METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2440        ));
2441        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2442        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2443            assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2444        });
2445
2446        // Run forward to disconnect time
2447        exec.set_fake_time(disconnect_time);
2448        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2449
2450        // SME notifies Policy of disconnection with SME-initiated reconnect
2451        let is_sme_reconnecting = true;
2452        let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
2453        connect_txn_handle
2454            .send_on_disconnect(&fidl_disconnect_info)
2455            .expect("failed to send disconnection event");
2456        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2457
2458        // Disconnect telemetry event sent
2459        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2460            assert_matches!(event, TelemetryEvent::Disconnected { info: Some(info), .. } => {
2461                assert_eq!(info.connected_duration, zx::MonotonicDuration::from_hours(12));
2462            });
2463        });
2464
2465        // SME notifies Policy of reconnection successful
2466        exec.set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(1)));
2467        let connect_result =
2468            fidl_sme::ConnectResult { is_reconnect: true, ..fake_successful_connect_result() };
2469        connect_txn_handle
2470            .send_on_connect_result(&connect_result)
2471            .expect("failed to send connect result event");
2472
2473        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2474        assert_matches!(telemetry_receiver.try_recv(), Ok(TelemetryEvent::ConnectResult { .. }));
2475
2476        // SME notifies Policy of another disconnection
2477        exec.set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(2)));
2478        let is_sme_reconnecting = false;
2479        let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
2480        connect_txn_handle
2481            .send_on_disconnect(&fidl_disconnect_info)
2482            .expect("failed to send disconnection event");
2483        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2484
2485        // Another disconnect telemetry event sent
2486        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2487            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
2488                assert_eq!(info.unwrap().connected_duration, zx::MonotonicDuration::from_hours(2));
2489            });
2490        });
2491    }
2492
2493    #[fuchsia::test]
2494    fn connected_state_records_unexpected_disconnect_unspecified_bss() {
2495        let mut exec = fasync::TestExecutor::new_with_fake_time();
2496        let connection_attempt_time = fasync::MonotonicInstant::from_nanos(0);
2497        exec.set_fake_time(connection_attempt_time);
2498        let mut test_values = test_setup();
2499
2500        let connect_selection = generate_connect_selection();
2501        let bss_description =
2502            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2503
2504        // Setup for network selection in the connecting state to select the intended network.
2505        let expected_config = network_config::NetworkConfig::new(
2506            connect_selection.target.network.clone(),
2507            connect_selection.target.credential.clone(),
2508            false,
2509            None,
2510        )
2511        .expect("failed to create network config");
2512        test_values.saved_networks_manager.set_lookup_compatible_response(vec![expected_config]);
2513
2514        let connecting_options =
2515            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2516        let initial_state = connecting_state(test_values.common_options, connecting_options);
2517        let state_fut = run_state_machine(initial_state);
2518        let mut state_fut = pin!(state_fut);
2519        let sme_fut = test_values.sme_req_stream.into_future();
2520        let mut sme_fut = pin!(sme_fut);
2521
2522        // Run the state machine
2523        assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2524
2525        // Run the state machine
2526        assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2527
2528        let time_to_connect = zx::MonotonicDuration::from_seconds(10);
2529        exec.set_fake_time(fasync::MonotonicInstant::after(time_to_connect));
2530
2531        // Process connect request sent to SME
2532        let connect_txn_handle = assert_matches!(
2533            poll_sme_req(&mut exec, &mut sme_fut),
2534            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req: _, txn, control_handle: _ }) => {
2535                 // Send connection response.
2536                let (_stream, ctrl) = txn.expect("connect txn unused")
2537                    .into_stream_and_control_handle();
2538                ctrl
2539            }
2540        );
2541        connect_txn_handle
2542            .send_on_connect_result(&fake_successful_connect_result())
2543            .expect("failed to send connection completion");
2544        assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2545
2546        // SME notifies Policy of disconnection.
2547        let disconnect_time = fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(5));
2548        exec.set_fake_time(disconnect_time);
2549        let is_sme_reconnecting = false;
2550        connect_txn_handle
2551            .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2552            .expect("failed to send disconnection event");
2553        assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2554
2555        // Verify roam monitor request was sent.
2556        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2557            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2558        });
2559
2560        assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2561        // The connection data should have been recorded at disconnect.
2562        let expected_recorded_connection = ConnectionRecord {
2563            id: connect_selection.target.network.clone(),
2564            credential: connect_selection.target.credential.clone(),
2565            data: PastConnectionData {
2566                bssid: types::Bssid::from(bss_description.bssid),
2567                disconnect_time,
2568                connection_uptime: zx::MonotonicDuration::from_hours(5),
2569                disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
2570                signal_at_disconnect: types::Signal {
2571                    rssi_dbm: bss_description.rssi_dbm,
2572                    snr_db: bss_description.snr_db,
2573                },
2574                average_tx_rate: 0,
2575            },
2576        };
2577        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2578            assert_eq!(connection_data, &expected_recorded_connection);
2579        });
2580    }
2581
2582    #[fuchsia::test]
2583    fn connected_state_gets_duplicate_connect_selection() {
2584        let mut exec = fasync::TestExecutor::new_with_fake_time();
2585        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2586        let mut test_values = test_setup();
2587        let mut telemetry_receiver = test_values.telemetry_receiver;
2588
2589        let connect_selection = generate_connect_selection();
2590        let bss_description =
2591            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2592        let ap_state =
2593            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2594
2595        let (connect_txn_proxy, _connect_txn_stream) =
2596            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2597        let options = ConnectedOptions::new(
2598            &mut test_values.common_options,
2599            Box::new(ap_state.clone()),
2600            connect_selection.target.network_has_multiple_bss,
2601            connect_selection.target.network.clone(),
2602            connect_selection.target.credential.clone(),
2603            connect_selection.reason,
2604            connect_txn_proxy.take_event_stream(),
2605            false,
2606        );
2607        let initial_state = connected_state(test_values.common_options, options);
2608        let fut = run_state_machine(initial_state);
2609        let mut fut = pin!(fut);
2610        let sme_fut = test_values.sme_req_stream.into_future();
2611        let mut sme_fut = pin!(sme_fut);
2612
2613        // Send another duplicate request
2614        let mut client = Client::new(test_values.client_req_sender);
2615        client.connect(connect_selection.clone()).expect("failed to make request");
2616
2617        // Run the state machine
2618        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2619
2620        // Ensure nothing was sent to the SME
2621        assert_matches!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending);
2622
2623        // No telemetry event is sent
2624        assert_matches!(telemetry_receiver.try_recv(), Err(_));
2625    }
2626
2627    #[fuchsia::test]
2628    fn connected_state_gets_different_connect_selection() {
2629        let mut exec = fasync::TestExecutor::new_with_fake_time();
2630        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2631
2632        let mut test_values = test_setup();
2633        let mut telemetry_receiver = test_values.telemetry_receiver;
2634
2635        let first_connect_selection = generate_connect_selection();
2636        let first_bss_desc =
2637            Sequestered::release(first_connect_selection.target.bss.bss_description.clone());
2638        let first_ap_state =
2639            types::ApState::from(BssDescription::try_from(first_bss_desc.clone()).unwrap());
2640        let second_connect_selection = types::ConnectSelection {
2641            reason: types::ConnectReason::ProactiveNetworkSwitch,
2642            ..generate_connect_selection()
2643        };
2644
2645        let (connect_txn_proxy, _connect_txn_stream) =
2646            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2647        let options = ConnectedOptions::new(
2648            &mut test_values.common_options,
2649            Box::new(first_ap_state.clone()),
2650            first_connect_selection.target.network_has_multiple_bss,
2651            first_connect_selection.target.network.clone(),
2652            first_connect_selection.target.credential.clone(),
2653            first_connect_selection.reason,
2654            connect_txn_proxy.take_event_stream(),
2655            false,
2656        );
2657        let initial_state = connected_state(test_values.common_options, options);
2658        let fut = run_state_machine(initial_state);
2659        let mut fut = pin!(fut);
2660        let sme_fut = test_values.sme_req_stream.into_future();
2661        let mut sme_fut = pin!(sme_fut);
2662
2663        let disconnect_time =
2664            fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2665
2666        // Run the state machine
2667        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2668
2669        // Verify roam monitor request was sent.
2670        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2671            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2672        });
2673
2674        // Run the state machine
2675        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2676
2677        // Run forward to get post connection signals metrics
2678        exec.set_fake_time(fasync::MonotonicInstant::after(
2679            AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2680        ));
2681        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2682        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2683            assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2684        });
2685
2686        // Run forward to get long duration signals metrics
2687        exec.set_fake_time(fasync::MonotonicInstant::after(
2688            METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2689        ));
2690        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2691        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2692            assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2693        });
2694
2695        // Run forward to disconnect time
2696        exec.set_fake_time(disconnect_time);
2697        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2698
2699        // Send a different connect request
2700        let mut client = Client::new(test_values.client_req_sender);
2701        client.connect(second_connect_selection.clone()).expect("failed to make request");
2702
2703        // Run the state machine
2704        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2705
2706        // There should be 2 requests to the SME stacked up
2707        // First SME request: disconnect
2708        assert_matches!(
2709            poll_sme_req(&mut exec, &mut sme_fut),
2710            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch }) => {
2711                responder.send().expect("could not send sme response");
2712            }
2713        );
2714        // Progress the state machine
2715        // TODO(https://fxbug.dev/42130926): remove this once the disconnect request is fire-and-forget
2716        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2717        // Second SME request: connect to the second network
2718        let connect_txn_handle = assert_matches!(
2719            poll_sme_req(&mut exec, &mut sme_fut),
2720            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
2721                assert_eq!(req.ssid, second_connect_selection.target.network.ssid.clone().to_vec());
2722                 // Send connection response.
2723                let (_stream, ctrl) = txn.expect("connect txn unused")
2724                    .into_stream_and_control_handle();
2725                ctrl
2726            }
2727        );
2728        connect_txn_handle
2729            .send_on_connect_result(&fake_successful_connect_result())
2730            .expect("failed to send connection completion");
2731        // Progress the state machine
2732        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2733
2734        // Check for a disconnect update
2735        let client_state_update = ClientStateUpdate {
2736            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2737            networks: vec![ClientNetworkState {
2738                id: first_connect_selection.target.network.clone(),
2739                state: fidl_policy::ConnectionState::Disconnected,
2740                status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
2741            }],
2742        };
2743        assert_matches!(
2744            test_values.update_receiver.try_recv(),
2745            Ok(listener::Message::NotifyListeners(updates)) => {
2746            assert_eq!(updates, client_state_update);
2747        });
2748
2749        // Disconnect telemetry event sent
2750        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2751            assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2752                assert!(!track_subsequent_downtime);
2753                assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2754                    assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2755                    assert!(!is_sme_reconnecting);
2756                    assert_eq!(disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch));
2757                    assert_eq!(previous_connect_reason, first_connect_selection.reason);
2758                    assert_eq!(ap_state, first_ap_state.clone());
2759                });
2760            });
2761        });
2762
2763        // Check for a connecting update
2764        let client_state_update = ClientStateUpdate {
2765            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2766            networks: vec![ClientNetworkState {
2767                id: types::NetworkIdentifier {
2768                    ssid: second_connect_selection.target.network.ssid.clone(),
2769                    security_type: types::SecurityType::Wpa2,
2770                },
2771                state: fidl_policy::ConnectionState::Connecting,
2772                status: None,
2773            }],
2774        };
2775        assert_matches!(
2776            test_values.update_receiver.try_recv(),
2777            Ok(listener::Message::NotifyListeners(updates)) => {
2778            assert_eq!(updates, client_state_update);
2779        });
2780        // Check for a connected update
2781        let client_state_update = ClientStateUpdate {
2782            state: fidl_policy::WlanClientState::ConnectionsEnabled,
2783            networks: vec![ClientNetworkState {
2784                id: types::NetworkIdentifier {
2785                    ssid: second_connect_selection.target.network.ssid.clone(),
2786                    security_type: types::SecurityType::Wpa2,
2787                },
2788                state: fidl_policy::ConnectionState::Connected,
2789                status: None,
2790            }],
2791        };
2792        assert_matches!(
2793            test_values.update_receiver.try_recv(),
2794            Ok(listener::Message::NotifyListeners(updates)) => {
2795            assert_eq!(updates, client_state_update);
2796        });
2797
2798        // Progress the state machine
2799        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2800
2801        // Ensure no further updates were sent to listeners
2802        assert_matches!(
2803            exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
2804            Poll::Pending
2805        );
2806
2807        // Check that the first connection was recorded
2808        let expected_recorded_connection = ConnectionRecord {
2809            id: first_connect_selection.target.network.clone(),
2810            credential: first_connect_selection.target.credential.clone(),
2811            data: PastConnectionData {
2812                bssid: types::Bssid::from(first_bss_desc.bssid),
2813                disconnect_time,
2814                connection_uptime: zx::MonotonicDuration::from_hours(12),
2815                disconnect_reason: types::DisconnectReason::ProactiveNetworkSwitch,
2816                signal_at_disconnect: types::Signal {
2817                    rssi_dbm: first_bss_desc.rssi_dbm,
2818                    snr_db: first_bss_desc.snr_db,
2819                },
2820                // TODO: record average phy rate over connection once available
2821                average_tx_rate: 0,
2822            },
2823        };
2824        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2825            assert_eq!(connection_data, &expected_recorded_connection);
2826        });
2827    }
2828
2829    #[fuchsia::test]
2830    fn connected_state_notified_of_network_disconnect_no_sme_reconnect_short_uptime_no_retry() {
2831        let mut exec = fasync::TestExecutor::new_with_fake_time();
2832        let mut test_values = test_setup();
2833
2834        let connect_selection = generate_connect_selection();
2835        let bss_description =
2836            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2837        let ap_state =
2838            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2839
2840        let (connect_txn_proxy, connect_txn_stream) =
2841            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2842        let connect_txn_handle = connect_txn_stream.control_handle();
2843        let options = ConnectedOptions::new(
2844            &mut test_values.common_options,
2845            Box::new(ap_state.clone()),
2846            connect_selection.target.network_has_multiple_bss,
2847            connect_selection.target.network.clone(),
2848            connect_selection.target.credential.clone(),
2849            connect_selection.reason,
2850            connect_txn_proxy.take_event_stream(),
2851            false,
2852        );
2853        let initial_state = connected_state(test_values.common_options, options);
2854        let fut = run_state_machine(initial_state);
2855        let mut fut = pin!(fut);
2856        let sme_fut = test_values.sme_req_stream.into_future();
2857        let mut sme_fut = pin!(sme_fut);
2858
2859        // Run the state machine
2860        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2861
2862        // Verify roam monitor request was sent.
2863        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2864            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2865        });
2866
2867        // Run the state machine
2868        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2869
2870        // SME notifies Policy of disconnection.
2871        let is_sme_reconnecting = false;
2872        connect_txn_handle
2873            .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2874            .expect("failed to send disconnection event");
2875
2876        // Run the state machine
2877        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2878
2879        // Check for a disconnect request to SME
2880        assert_matches!(
2881            poll_sme_req(&mut exec, &mut sme_fut),
2882            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
2883                responder.send().expect("could not send sme response");
2884            }
2885        );
2886
2887        // The state machine should exit since there is no attempt to reconnect.
2888        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2889    }
2890
2891    #[fuchsia::test]
2892    fn connected_state_notified_of_network_disconnect_sme_reconnect_successfully() {
2893        let mut exec = fasync::TestExecutor::new();
2894        let mut test_values = test_setup();
2895
2896        let connect_selection = generate_connect_selection();
2897        let bss_description =
2898            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2899        let ap_state =
2900            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2901
2902        let (connect_txn_proxy, connect_txn_stream) =
2903            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2904        let connect_txn_handle = connect_txn_stream.control_handle();
2905        let options = ConnectedOptions::new(
2906            &mut test_values.common_options,
2907            Box::new(ap_state.clone()),
2908            connect_selection.target.network_has_multiple_bss,
2909            connect_selection.target.network.clone(),
2910            connect_selection.target.credential.clone(),
2911            connect_selection.reason,
2912            connect_txn_proxy.take_event_stream(),
2913            false,
2914        );
2915        let initial_state = connected_state(test_values.common_options, options);
2916        let fut = run_state_machine(initial_state);
2917        let mut fut = pin!(fut);
2918
2919        // Run the state machine
2920        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2921
2922        // SME notifies Policy of disconnection
2923        let is_sme_reconnecting = true;
2924        connect_txn_handle
2925            .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2926            .expect("failed to send disconnection event");
2927
2928        // Run the state machine
2929        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2930
2931        // SME notifies Policy that reconnects succeeds
2932        let connect_result =
2933            fidl_sme::ConnectResult { is_reconnect: true, ..fake_successful_connect_result() };
2934        connect_txn_handle
2935            .send_on_connect_result(&connect_result)
2936            .expect("failed to send reconnection result");
2937
2938        // Run the state machine
2939        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2940
2941        // Check there were no state updates
2942        assert_matches!(test_values.update_receiver.try_recv(), Err(_));
2943    }
2944
2945    #[fuchsia::test]
2946    fn connected_state_notified_of_network_disconnect_sme_reconnect_unsuccessfully() {
2947        let mut exec = fasync::TestExecutor::new_with_fake_time();
2948        let mut test_values = test_setup();
2949        let connect_selection = generate_connect_selection();
2950        let bss_description =
2951            Sequestered::release(connect_selection.target.bss.bss_description.clone());
2952        let ap_state =
2953            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2954
2955        // Set the start time of the connection
2956        let start_time = fasync::MonotonicInstant::now();
2957        exec.set_fake_time(start_time);
2958
2959        let (connect_txn_proxy, connect_txn_stream) =
2960            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2961        let connect_txn_handle = connect_txn_stream.control_handle();
2962        let options = ConnectedOptions::new(
2963            &mut test_values.common_options,
2964            Box::new(ap_state.clone()),
2965            connect_selection.target.network_has_multiple_bss,
2966            connect_selection.target.network.clone(),
2967            connect_selection.target.credential.clone(),
2968            connect_selection.reason,
2969            connect_txn_proxy.take_event_stream(),
2970            false,
2971        );
2972        let initial_state = connected_state(test_values.common_options, options);
2973        let fut = run_state_machine(initial_state);
2974        let mut fut = pin!(fut);
2975        let sme_fut = test_values.sme_req_stream.into_future();
2976        let mut sme_fut = pin!(sme_fut);
2977
2978        // Run the state machine
2979        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2980
2981        // Verify roam monitor request was sent.
2982        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2983            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2984        });
2985
2986        // Run the state machine
2987        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2988
2989        // Set time to indicate a decent uptime before the disconnect so the AP is retried
2990        exec.set_fake_time(start_time + fasync::MonotonicDuration::from_hours(24));
2991
2992        // SME notifies Policy of disconnection
2993        let is_sme_reconnecting = true;
2994        connect_txn_handle
2995            .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2996            .expect("failed to send disconnection event");
2997
2998        // Run the state machine
2999        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3000
3001        // SME notifies Policy that reconnects fails
3002        let connect_result = fidl_sme::ConnectResult {
3003            code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
3004            is_reconnect: true,
3005            ..fake_successful_connect_result()
3006        };
3007        connect_txn_handle
3008            .send_on_connect_result(&connect_result)
3009            .expect("failed to send reconnection result");
3010
3011        // Run the state machine
3012        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3013
3014        // Check for an SME disconnect request
3015        assert_matches!(
3016            poll_sme_req(&mut exec, &mut sme_fut),
3017            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
3018                responder.send().expect("could not send sme response");
3019            }
3020        );
3021
3022        // The state machine should exit since there is no policy attempt to reconnect.
3023        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3024
3025        // Check for a disconnect update
3026        let client_state_update = ClientStateUpdate {
3027            state: fidl_policy::WlanClientState::ConnectionsEnabled,
3028            networks: vec![ClientNetworkState {
3029                id: connect_selection.target.network.clone(),
3030                state: fidl_policy::ConnectionState::Disconnected,
3031                status: Some(fidl_policy::DisconnectStatus::ConnectionFailed),
3032            }],
3033        };
3034        assert_matches!(
3035            test_values.update_receiver.try_recv(),
3036            Ok(listener::Message::NotifyListeners(updates)) => {
3037            assert_eq!(updates, client_state_update);
3038        });
3039    }
3040
3041    #[fuchsia::test]
3042    fn connected_state_on_signal_report() {
3043        let mut exec = fasync::TestExecutor::new_with_fake_time();
3044        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3045
3046        let mut test_values = test_setup();
3047
3048        // Verify the status is initialized to default.
3049        let status = test_values.status_reader.read_status().expect("failed to read status");
3050        assert_matches!(status, Status::Disconnected);
3051
3052        // Set initial RSSI and SNR values
3053        let mut connect_selection = generate_connect_selection();
3054        let init_rssi = -40;
3055        let init_snr = 30;
3056        connect_selection.target.bss.signal =
3057            types::Signal { rssi_dbm: init_rssi, snr_db: init_snr };
3058
3059        let mut bss_description =
3060            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3061        bss_description.rssi_dbm = init_rssi;
3062        bss_description.snr_db = init_snr;
3063        connect_selection.target.bss.bss_description = bss_description.clone().into();
3064
3065        let ap_state =
3066            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3067
3068        // Add a PastConnectionData for the connected network to be send in BSS quality data.
3069        let mut past_connections = new_past_connection_list();
3070        let mut past_connection_data = random_connection_data();
3071        past_connection_data.bssid = ieee80211::Bssid::from(bss_description.bssid);
3072        past_connections.add(past_connection_data);
3073        let mut saved_networks_manager = FakeSavedNetworksManager::new();
3074        saved_networks_manager.past_connections_response = past_connections.clone();
3075        test_values.common_options.saved_networks_manager = Arc::new(saved_networks_manager);
3076
3077        // Set up the state machine, starting at the connected state.
3078        let (connect_txn_proxy, connect_txn_stream) =
3079            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3080        let options = ConnectedOptions::new(
3081            &mut test_values.common_options,
3082            Box::new(ap_state.clone()),
3083            connect_selection.target.network_has_multiple_bss,
3084            connect_selection.target.network.clone(),
3085            connect_selection.target.credential.clone(),
3086            connect_selection.reason,
3087            connect_txn_proxy.take_event_stream(),
3088            false,
3089        );
3090        let initial_state = connected_state(test_values.common_options, options);
3091
3092        let connect_txn_handle = connect_txn_stream.control_handle();
3093        let fut = run_state_machine(initial_state);
3094        let mut fut = pin!(fut);
3095        let sme_fut = test_values.sme_req_stream.into_future();
3096        let mut sme_fut = pin!(sme_fut);
3097
3098        // Run the state machine
3099        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3100
3101        let request = test_values
3102            .roam_service_request_receiver
3103            .try_recv()
3104            .expect("error receiving roam service request");
3105        assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor{ mut roam_trigger_data_receiver, .. } => {
3106            // Run the state machine
3107            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3108
3109            // Send the first signal report from SME
3110            let rssi_1 = -50;
3111            let snr_1 = 25;
3112            let fidl_signal_report =
3113                fidl_internal::SignalReportIndication {
3114                    rssi_dbm: Some(rssi_1),
3115                    snr_db: Some(snr_1),
3116                    tx_rate_500kbps: Some(0),
3117                    ..Default::default()
3118                };
3119            connect_txn_handle
3120                .send_on_signal_report(&fidl_signal_report)
3121                .expect("failed to send signal report");
3122            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3123
3124            // Do a quick check that state machine does not exist and there's no disconnect to SME
3125            assert_matches!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending);
3126
3127            // Verify telemetry event
3128            assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3129                assert_matches!(event, TelemetryEvent::OnSignalReport { .. });
3130            });
3131
3132            // Verify that signal report is sent to the roam monitor
3133            assert_matches!(roam_trigger_data_receiver.try_recv(), Ok(RoamTriggerData::SignalReportInd(_)));
3134
3135            // Verify that the status is updated.
3136            let status = test_values.status_reader.read_status().expect("failed to read status");
3137            assert_eq!(
3138                status,
3139                Status::Connected {
3140                    rssi: rssi_1,
3141                    snr: snr_1,
3142                    channel: ap_state.tracked.channel.primary
3143                }
3144            );
3145
3146            // Send a second signal report with higher RSSI and SNR than the previous reports.
3147            let rssi_2 = -30;
3148            let snr_2 = 35;
3149            let fidl_signal_report =
3150                fidl_internal::SignalReportIndication {
3151                    rssi_dbm: Some(rssi_2),
3152                    snr_db: Some(snr_2),
3153                    tx_rate_500kbps: Some(0),
3154                    ..Default::default()
3155                };
3156            connect_txn_handle
3157                .send_on_signal_report(&fidl_signal_report)
3158                .expect("failed to send signal report");
3159            assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3160
3161            // Verify telemetry events;
3162            assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3163                assert_matches!(event, TelemetryEvent::OnSignalReport { .. });
3164            });
3165
3166            // Verify that signal report is sent to the roam monitor
3167            assert_matches!(roam_trigger_data_receiver.try_recv(), Ok(RoamTriggerData::SignalReportInd(_)));
3168        });
3169    }
3170
3171    #[fuchsia::test]
3172    fn connected_state_on_channel_switched() {
3173        let mut exec = fasync::TestExecutor::new_with_fake_time();
3174        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3175
3176        let mut test_values = test_setup();
3177        let mut telemetry_receiver = test_values.telemetry_receiver;
3178
3179        let connect_selection = generate_connect_selection();
3180        let bss_description =
3181            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3182        let ap_state =
3183            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3184
3185        // Set up the state machine, starting at the connected state.
3186        let (connect_txn_proxy, connect_txn_stream) =
3187            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3188        let options = ConnectedOptions::new(
3189            &mut test_values.common_options,
3190            Box::new(ap_state.clone()),
3191            connect_selection.target.network_has_multiple_bss,
3192            connect_selection.target.network.clone(),
3193            connect_selection.target.credential.clone(),
3194            connect_selection.reason,
3195            connect_txn_proxy.take_event_stream(),
3196            false,
3197        );
3198        let initial_state = connected_state(test_values.common_options, options);
3199
3200        let connect_txn_handle = connect_txn_stream.control_handle();
3201        let fut = run_state_machine(initial_state);
3202        let mut fut = pin!(fut);
3203
3204        // Run the state machine
3205        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3206
3207        // Verify roam monitor request was sent.
3208        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3209            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3210                assert_eq!(ap_state.tracked.channel.primary, bss_description.primary.number)
3211            });
3212        });
3213
3214        // Run the state machine
3215        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3216
3217        let channel_switch_info = fidl_internal::ChannelSwitchInfo {
3218            new_primary_channel: fidl_ieee80211::ChannelNumber {
3219                band: fidl_ieee80211::WlanBand::TwoGhz,
3220                number: 10,
3221            },
3222            bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw20,
3223            vht_secondary_80_channel: fidl_ieee80211::ChannelNumber {
3224                band: fidl_ieee80211::WlanBand::TwoGhz,
3225                number: 0,
3226            },
3227        };
3228        connect_txn_handle
3229            .send_on_channel_switched(&channel_switch_info)
3230            .expect("failed to send signal report");
3231        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3232
3233        // Verify telemetry event
3234        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3235            assert_matches!(event, TelemetryEvent::OnChannelSwitched { info } => {
3236                assert_eq!(info, channel_switch_info);
3237            });
3238        });
3239
3240        // Verify the roam monitor was re-initialized with the new channel
3241        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3242            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3243                assert_eq!(ap_state.tracked.channel.primary, 10)
3244            });
3245        });
3246
3247        // Have SME notify Policy of disconnection so we can see whether the channel in the
3248        // BssDescription has changed.
3249        let is_sme_reconnecting = false;
3250        let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
3251        connect_txn_handle
3252            .send_on_disconnect(&fidl_disconnect_info)
3253            .expect("failed to send disconnection event");
3254        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3255
3256        // Verify telemetry event
3257        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3258            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3259                assert_eq!(info.unwrap().ap_state.tracked.channel.primary, 10);
3260            });
3261        });
3262    }
3263
3264    #[fuchsia::test]
3265    fn connected_state_on_roam_selection() {
3266        let mut exec = fasync::TestExecutor::new_with_fake_time();
3267        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3268
3269        let mut test_values = test_setup();
3270        let sme_fut = test_values.sme_req_stream.into_future();
3271        let mut sme_fut = pin!(sme_fut);
3272
3273        // Set up the state machine, starting at the connected state.
3274        let connect_selection = generate_connect_selection();
3275        let bss_description =
3276            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3277        let ap_state =
3278            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3279        let (connect_txn_proxy, _connect_txn_stream) =
3280            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3281        let options = ConnectedOptions::new(
3282            &mut test_values.common_options,
3283            Box::new(ap_state.clone()),
3284            connect_selection.target.network_has_multiple_bss,
3285            connect_selection.target.network.clone(),
3286            connect_selection.target.credential.clone(),
3287            connect_selection.reason,
3288            connect_txn_proxy.take_event_stream(),
3289            false,
3290        );
3291        let initial_state = connected_state(test_values.common_options, options);
3292        let fut = run_state_machine(initial_state);
3293        let mut fut = pin!(fut);
3294
3295        // Run the state machine.
3296        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3297
3298        // Verify roam monitor selection was sent.
3299        let mut roam_sender;
3300        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3301            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { roam_request_sender, .. } => {
3302                roam_sender = roam_request_sender;
3303            });
3304        });
3305
3306        // Send a roam request to state machine.
3307        let roam_candidate = generate_random_scanned_candidate();
3308        roam_sender
3309            .try_send(PolicyRoamRequest { candidate: roam_candidate.clone(), reasons: vec![] })
3310            .unwrap();
3311
3312        // Run the state machine
3313        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3314
3315        // Verify state machine issues a roam to SME.
3316        assert_matches!(
3317            poll_sme_req(&mut exec, &mut sme_fut),
3318            Poll::Ready(fidl_sme::ClientSmeRequest::Roam{ req, ..}) => {
3319                assert_eq!(req.bss_description, Sequestered::release(roam_candidate.clone().bss.bss_description));
3320            }
3321        );
3322
3323        // Verify roam attempt telemetry event.
3324        assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3325            assert_matches!(event, TelemetryEvent::PolicyRoamAttempt { request, connected_duration } => {
3326                assert_eq!(request.candidate, roam_candidate);
3327                assert_eq!(request.reasons, vec![]);
3328                assert_eq!(connected_duration, zx::Duration::from_minutes(0));
3329
3330            });
3331        });
3332
3333        let calls = test_values.power_manager.calls.lock();
3334        assert!(calls.contains(&"wlancfg-client-roaming".to_string()));
3335    }
3336
3337    #[fuchsia::test]
3338    fn connected_state_on_roam_result_success() {
3339        let mut exec = fasync::TestExecutor::new_with_fake_time();
3340        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3341
3342        let mut test_values = test_setup();
3343        let mut telemetry_receiver = test_values.telemetry_receiver;
3344
3345        let connect_selection = generate_connect_selection();
3346        let bss_description =
3347            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3348        let ap_state =
3349            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3350
3351        // Set up the state machine, starting at the connected state.
3352        let (connect_txn_proxy, connect_txn_stream) =
3353            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3354        let mut options = ConnectedOptions::new(
3355            &mut test_values.common_options,
3356            Box::new(ap_state.clone()),
3357            connect_selection.target.network_has_multiple_bss,
3358            connect_selection.target.network.clone(),
3359            connect_selection.target.credential.clone(),
3360            connect_selection.reason,
3361            connect_txn_proxy.take_event_stream(),
3362            false,
3363        );
3364        // Set the pending roam request, so we don't immediately exit when a result comes in.
3365        let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3366        options.pending_roam = Some(policy_request.clone().into());
3367        let initial_state = connected_state(test_values.common_options, options);
3368
3369        let connect_txn_handle = connect_txn_stream.control_handle();
3370        let fut = run_state_machine(initial_state);
3371        let mut fut = pin!(fut);
3372
3373        // Run the state machine
3374        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3375
3376        // Verify roam monitor request was sent.
3377        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3378            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3379        });
3380
3381        // Send a successful roam result
3382        let bss_desc = random_fidl_bss_description!();
3383        let roam_result = fidl_sme::RoamResult {
3384            bssid: [1, 1, 1, 1, 1, 1],
3385            status_code: fidl_ieee80211::StatusCode::Success,
3386            original_association_maintained: false,
3387            bss_description: Some(Box::new(bss_desc.clone())),
3388            disconnect_info: None,
3389            is_credential_rejected: false,
3390        };
3391        connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3392        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3393
3394        // Verify the roam monitor was re-initialized with the new BSS
3395        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3396            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3397                assert_eq!(ap_state.original().bssid.to_array(), bss_desc.bssid);
3398            });
3399        });
3400
3401        // Verify a disconnect was logged to saved networks manager
3402        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3403            assert_eq!(id, &connect_selection.target.network.clone());
3404            assert_eq!(credential, &connect_selection.target.credential.clone());
3405            assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3406                assert_eq!(bssid, &connect_selection.target.bss.bssid);
3407                assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3408            })
3409        });
3410
3411        // Verify the successful connect result was logged to saved networks manager
3412        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3413            let expected_connect_result = ConnectResultRecord {
3414                 id: connect_selection.target.network.clone(),
3415                 credential: connect_selection.target.credential.clone(),
3416                 bssid: types::Bssid::from(roam_result.bssid),
3417                 connect_result: fidl_sme::ConnectResult {
3418                    code: roam_result.status_code,
3419                    is_credential_rejected: roam_result.is_credential_rejected,
3420                    is_reconnect: false,
3421                 },
3422                 scan_type: types::ScanObservation::Unknown,
3423            };
3424            assert_eq!(data, &expected_connect_result);
3425        });
3426
3427        // Verify telemetry event for roam result
3428        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3429            assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3430                assert_eq!(result, roam_result);
3431            });
3432        });
3433
3434        // Explicitly verify there is _not_ a disconnect metric logged, since we have not exited the
3435        // ESS.
3436        assert_matches!(telemetry_receiver.try_recv(), Err(_));
3437
3438        // Run time forward past the timeout for the pending roam request.
3439        exec.set_fake_time(fasync::MonotonicInstant::after(
3440            PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3441        ));
3442
3443        // Run the state machine and verify that it does NOT fire the pending timer and exit.
3444        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3445    }
3446
3447    #[fuchsia::test]
3448    fn connected_state_on_roam_result_failed_original_association_maintained() {
3449        let mut exec = fasync::TestExecutor::new_with_fake_time();
3450        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3451
3452        let mut test_values = test_setup();
3453        let mut telemetry_receiver = test_values.telemetry_receiver;
3454
3455        let connect_selection = generate_connect_selection();
3456        let bss_description =
3457            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3458        let ap_state =
3459            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3460
3461        // Set up the state machine, starting at the connected state.
3462        let (connect_txn_proxy, connect_txn_stream) =
3463            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3464        let mut options = ConnectedOptions::new(
3465            &mut test_values.common_options,
3466            Box::new(ap_state.clone()),
3467            connect_selection.target.network_has_multiple_bss,
3468            connect_selection.target.network.clone(),
3469            connect_selection.target.credential.clone(),
3470            connect_selection.reason,
3471            connect_txn_proxy.take_event_stream(),
3472            false,
3473        );
3474        // Set the pending roam request, so we don't immediately exit when a result comes in.
3475        let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3476        options.pending_roam = Some(policy_request.clone().into());
3477        let initial_state = connected_state(test_values.common_options, options);
3478
3479        let connect_txn_handle = connect_txn_stream.control_handle();
3480        let fut = run_state_machine(initial_state);
3481        let mut fut = pin!(fut);
3482
3483        // Run the state machine
3484        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3485
3486        // Verify roam monitor request was sent.
3487        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3488            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3489        });
3490
3491        // Send a failed roam result, where the original association was maintained.
3492        let roam_result = fidl_sme::RoamResult {
3493            bssid: [1, 1, 1, 1, 1, 1],
3494            status_code: fidl_ieee80211::StatusCode::JoinFailure,
3495            original_association_maintained: true,
3496            bss_description: Some(Box::new(bss_description.clone())),
3497            disconnect_info: None,
3498            is_credential_rejected: false,
3499        };
3500        connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3501        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3502
3503        // Verify the failed connect result was logged to saved networks manager
3504        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3505            let expected_connect_result = ConnectResultRecord {
3506                 id: connect_selection.target.network.clone(),
3507                 credential: connect_selection.target.credential.clone(),
3508                 bssid: types::Bssid::from(roam_result.bssid),
3509                 connect_result: fidl_sme::ConnectResult {
3510                    code: roam_result.status_code,
3511                    is_credential_rejected: roam_result.is_credential_rejected,
3512                    is_reconnect: false,
3513                 },
3514                 scan_type: types::ScanObservation::Unknown,
3515            };
3516            assert_eq!(data, &expected_connect_result);
3517        });
3518
3519        // A defect should be logged.
3520        assert_matches!(
3521            test_values.defect_receiver.try_recv(),
3522            Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
3523        );
3524
3525        // Verify telemetry event for roam result
3526        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3527            assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3528                assert_eq!(result, roam_result);
3529            });
3530        });
3531
3532        // Explicitly verify there is _not_ a disconnect metric logged, since we have not exited the
3533        // ESS.
3534        assert_matches!(telemetry_receiver.try_recv(), Err(_));
3535
3536        // Verify the roam monitor was _not_ re-initialized.
3537        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Err(_));
3538
3539        // Run time forward past the timeout for the pending roam request.
3540        exec.set_fake_time(fasync::MonotonicInstant::after(
3541            PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3542        ));
3543
3544        // Run the state machine and verify that it does NOT fire the pending timer and exit.
3545        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3546    }
3547
3548    #[fuchsia::test]
3549    fn connected_state_on_roam_result_failed_and_disconnected() {
3550        let mut exec = fasync::TestExecutor::new_with_fake_time();
3551        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3552
3553        let mut test_values = test_setup();
3554        let mut telemetry_receiver = test_values.telemetry_receiver;
3555        let sme_fut = test_values.sme_req_stream.into_future();
3556        let mut sme_fut = pin!(sme_fut);
3557
3558        let connect_selection = generate_connect_selection();
3559        let bss_description =
3560            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3561        let ap_state =
3562            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3563
3564        // Set up the state machine, starting at the connected state.
3565        let (connect_txn_proxy, connect_txn_stream) =
3566            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3567        let mut options = ConnectedOptions::new(
3568            &mut test_values.common_options,
3569            Box::new(ap_state.clone()),
3570            connect_selection.target.network_has_multiple_bss,
3571            connect_selection.target.network.clone(),
3572            connect_selection.target.credential.clone(),
3573            connect_selection.reason,
3574            connect_txn_proxy.take_event_stream(),
3575            false,
3576        );
3577        // Set the pending roam request, so we don't immediately exit when a result comes in.
3578        let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3579        options.pending_roam = Some(policy_request.clone().into());
3580        let initial_state = connected_state(test_values.common_options, options);
3581
3582        let connect_txn_handle = connect_txn_stream.control_handle();
3583        let fut = run_state_machine(initial_state);
3584        let mut fut = pin!(fut);
3585
3586        // Run the state machine
3587        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3588
3589        // Verify roam monitor request was sent.
3590        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3591            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3592        });
3593
3594        // Send a failed roam result, where the original association was *NOT* maintained.
3595        let disconnect_info = fidl_sme::DisconnectInfo {
3596            is_sme_reconnecting: false,
3597            disconnect_source: fidl_sme::DisconnectSource::Ap(fidl_sme::DisconnectCause {
3598                mlme_event_name: fidl_sme::DisconnectMlmeEventName::DisassociateIndication,
3599                reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
3600            }),
3601        };
3602        let roam_result = fidl_sme::RoamResult {
3603            bssid: [1, 1, 1, 1, 1, 1],
3604            status_code: fidl_ieee80211::StatusCode::JoinFailure,
3605            original_association_maintained: false,
3606            bss_description: None,
3607            disconnect_info: Some(Box::new(disconnect_info)),
3608            is_credential_rejected: false,
3609        };
3610        connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3611        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3612
3613        // Verify the failed connect result was logged to saved networks manager
3614        assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3615            let expected_connect_result = ConnectResultRecord {
3616                 id: connect_selection.target.network.clone(),
3617                 credential: connect_selection.target.credential.clone(),
3618                 bssid: types::Bssid::from(roam_result.bssid),
3619                 connect_result: fidl_sme::ConnectResult {
3620                    code: roam_result.status_code,
3621                    is_credential_rejected: roam_result.is_credential_rejected,
3622                    is_reconnect: false,
3623                 },
3624                 scan_type: types::ScanObservation::Unknown,
3625            };
3626            assert_eq!(data, &expected_connect_result);
3627        });
3628
3629        // Verify a disconnect was logged to saved networks manager
3630        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3631            assert_eq!(id, &connect_selection.target.network.clone());
3632            assert_eq!(credential, &connect_selection.target.credential.clone());
3633            assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3634                assert_eq!(bssid, &connect_selection.target.bss.bssid);
3635                assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3636            })
3637        });
3638
3639        // Verify telemetry event for disconnect
3640        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3641            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3642                assert_eq!(info.unwrap().disconnect_source, disconnect_info.disconnect_source);
3643            });
3644        });
3645
3646        // Verify telemetry event for roam result
3647        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3648            assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3649                assert_eq!(result, roam_result);
3650            });
3651        });
3652
3653        // A defect should be logged.
3654        assert_matches!(
3655            test_values.defect_receiver.try_recv(),
3656            Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
3657        );
3658
3659        // Check for an SME disconnect request
3660        assert_matches!(
3661            poll_sme_req(&mut exec, &mut sme_fut),
3662            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect { .. })
3663        );
3664
3665        // Run time forward past the timeout for the pending roam request.
3666        exec.set_fake_time(fasync::MonotonicInstant::after(
3667            PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3668        ));
3669
3670        // Run the state machine and verify that it does NOT fire the pending timer and exit.
3671        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3672    }
3673    #[fuchsia::test]
3674    fn connected_state_on_unexpected_policy_roam_result_disconnects() {
3675        let mut exec = fasync::TestExecutor::new_with_fake_time();
3676        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3677
3678        let mut test_values = test_setup();
3679        let mut telemetry_receiver = test_values.telemetry_receiver;
3680        let sme_fut = test_values.sme_req_stream.into_future();
3681        let mut sme_fut = pin!(sme_fut);
3682
3683        let connect_selection = generate_connect_selection();
3684        let bss_description =
3685            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3686        let ap_state =
3687            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3688
3689        // Set up the state machine, starting at the connected state.
3690        let (connect_txn_proxy, connect_txn_stream) =
3691            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3692        let mut options = ConnectedOptions::new(
3693            &mut test_values.common_options,
3694            Box::new(ap_state.clone()),
3695            connect_selection.target.network_has_multiple_bss,
3696            connect_selection.target.network.clone(),
3697            connect_selection.target.credential.clone(),
3698            connect_selection.reason,
3699            connect_txn_proxy.take_event_stream(),
3700            false,
3701        );
3702        // Set the pending roam request for some BSSID.
3703        let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3704        options.pending_roam = Some(policy_request.into());
3705        let initial_state = connected_state(test_values.common_options, options);
3706
3707        let connect_txn_handle = connect_txn_stream.control_handle();
3708        let fut = run_state_machine(initial_state);
3709        let mut fut = pin!(fut);
3710
3711        // Run the state machine
3712        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3713
3714        // Verify roam monitor request was sent.
3715        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3716            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3717        });
3718
3719        // Send a roam result with a BSSID that does NOT match the roam request.
3720        let bss_desc = random_fidl_bss_description!();
3721        let roam_result = fidl_sme::RoamResult {
3722            bssid: [2, 2, 2, 2, 2, 2],
3723            status_code: fidl_ieee80211::StatusCode::Success,
3724            original_association_maintained: false,
3725            bss_description: Some(Box::new(bss_desc.clone())),
3726            disconnect_info: None,
3727            is_credential_rejected: false,
3728        };
3729        connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3730
3731        // Run forward the state machine.
3732        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3733
3734        // Verify a disconnect request is sent to SME.
3735        assert_matches!(
3736            poll_sme_req(&mut exec, &mut sme_fut),
3737            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
3738                responder.send().expect("could not send sme response");
3739            }
3740        );
3741
3742        // Ensure the state machine exits once the disconnect is processed.
3743        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3744
3745        // Verify a disconnect was logged to saved networks manager
3746        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3747            assert_eq!(id, &connect_selection.target.network.clone());
3748            assert_eq!(credential, &connect_selection.target.credential.clone());
3749            assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3750                assert_eq!(bssid, &connect_selection.target.bss.bssid);
3751                assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3752            })
3753        });
3754
3755        // Verify a disconnect event was logged to telemetry
3756        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3757            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3758                assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3759            });
3760        });
3761
3762        // Verify a disconnected listener update is sent.
3763        assert_matches!(
3764            test_values.update_receiver.try_recv(),
3765            Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3766                state: fidl_policy::WlanClientState::ConnectionsEnabled,
3767                networks
3768            })) => {
3769                assert_eq!(networks.len(), 1);
3770                assert_eq!(networks[0].id, connect_selection.target.network);
3771                assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3772                assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3773            }
3774        );
3775    }
3776
3777    #[fuchsia::test]
3778    fn connected_state_on_unexpected_policy_roam_result_exits_on_broken_sme() {
3779        let mut exec = fasync::TestExecutor::new_with_fake_time();
3780        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3781
3782        let mut test_values = test_setup();
3783        let mut telemetry_receiver = test_values.telemetry_receiver;
3784
3785        let connect_selection = generate_connect_selection();
3786        let bss_description =
3787            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3788        let ap_state =
3789            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3790
3791        // Set up the state machine, starting at the connected state.
3792        let (connect_txn_proxy, connect_txn_stream) =
3793            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3794        let mut options = ConnectedOptions::new(
3795            &mut test_values.common_options,
3796            Box::new(ap_state.clone()),
3797            connect_selection.target.network_has_multiple_bss,
3798            connect_selection.target.network.clone(),
3799            connect_selection.target.credential.clone(),
3800            connect_selection.reason,
3801            connect_txn_proxy.take_event_stream(),
3802            false,
3803        );
3804        // Set the pending roam request for some BSSID.
3805        let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3806        options.pending_roam = Some(policy_request.into());
3807        let initial_state = connected_state(test_values.common_options, options);
3808
3809        let connect_txn_handle = connect_txn_stream.control_handle();
3810        let fut = run_state_machine(initial_state);
3811        let mut fut = pin!(fut);
3812
3813        // Run the state machine
3814        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3815
3816        // Verify roam monitor request was sent.
3817        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3818            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3819        });
3820
3821        // Break the SME by dropping the server end of the SME stream, so it causes an error
3822        drop(test_values.sme_req_stream);
3823
3824        // Send a roam result with a BSSID that does NOT match the roam request.
3825        let bss_desc = random_fidl_bss_description!();
3826        let roam_result = fidl_sme::RoamResult {
3827            bssid: [2, 2, 2, 2, 2, 2],
3828            status_code: fidl_ieee80211::StatusCode::Success,
3829            original_association_maintained: false,
3830            bss_description: Some(Box::new(bss_desc.clone())),
3831            disconnect_info: None,
3832            is_credential_rejected: false,
3833        };
3834        connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3835
3836        // Ensure the state machine exits.
3837        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3838
3839        // Verify a disconnect was logged to saved networks manager
3840        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3841            assert_eq!(id, &connect_selection.target.network.clone());
3842            assert_eq!(credential, &connect_selection.target.credential.clone());
3843            assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3844                assert_eq!(bssid, &connect_selection.target.bss.bssid);
3845                assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3846            })
3847        });
3848
3849        // Verify a disconnect event was logged to telemetry
3850        assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3851            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3852                assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3853            });
3854        });
3855
3856        // Verify a disconnected listener update is sent.
3857        assert_matches!(
3858            test_values.update_receiver.try_recv(),
3859            Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3860                state: fidl_policy::WlanClientState::ConnectionsEnabled,
3861                networks
3862            })) => {
3863                assert_eq!(networks.len(), 1);
3864                assert_eq!(networks[0].id, connect_selection.target.network);
3865                assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3866                assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3867            }
3868        );
3869    }
3870
3871    #[fuchsia::test]
3872    fn connected_state_pending_roam_expires() {
3873        let mut exec = fasync::TestExecutor::new_with_fake_time();
3874
3875        let mut test_values = test_setup();
3876        let sme_fut = test_values.sme_req_stream.into_future();
3877        let mut sme_fut = pin!(sme_fut);
3878
3879        let connect_selection = generate_connect_selection();
3880        let bss_description =
3881            Sequestered::release(connect_selection.target.bss.bss_description.clone());
3882        let ap_state =
3883            types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3884
3885        let (connect_txn_proxy, _connect_txn_stream) =
3886            create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3887        let options = ConnectedOptions::new(
3888            &mut test_values.common_options,
3889            Box::new(ap_state.clone()),
3890            connect_selection.target.network_has_multiple_bss,
3891            connect_selection.target.network.clone(),
3892            connect_selection.target.credential.clone(),
3893            connect_selection.reason,
3894            connect_txn_proxy.take_event_stream(),
3895            false,
3896        );
3897        // Set the pending roam request for some BSSID.
3898        let initial_state = connected_state(test_values.common_options, options);
3899        let fut = run_state_machine(initial_state);
3900        let mut fut = pin!(fut);
3901
3902        // Verify roam monitor init was sent.
3903        let mut roam_sender;
3904        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3905            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { roam_request_sender, .. } => {
3906                roam_sender = roam_request_sender;
3907            });
3908        });
3909
3910        // Run the state machine
3911        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3912
3913        // Send a roam request to state machine.
3914        let roam_candidate = generate_random_scanned_candidate();
3915        roam_sender
3916            .try_send(PolicyRoamRequest { candidate: roam_candidate.clone(), reasons: vec![] })
3917            .unwrap();
3918
3919        // Run the state machine
3920        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3921
3922        // Verify state machine issues a roam to SME.
3923        assert_matches!(
3924            poll_sme_req(&mut exec, &mut sme_fut),
3925            Poll::Ready(fidl_sme::ClientSmeRequest::Roam{ req, ..}) => {
3926                assert_eq!(req.bss_description, Sequestered::release(roam_candidate.clone().bss.bss_description));
3927            }
3928        );
3929
3930        // Verify roam attempt telemetry event.
3931        assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3932            assert_matches!(event, TelemetryEvent::PolicyRoamAttempt { request, .. } => {
3933                assert_eq!(request.candidate,
3934                    roam_candidate);
3935                assert_eq!(request.reasons, vec![]);
3936            });
3937        });
3938
3939        // Run time forward past the timeout for the pending roam request.
3940        exec.set_fake_time(fasync::MonotonicInstant::after(
3941            PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3942        ));
3943
3944        // Run forward the state machine.
3945        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3946
3947        // Verify a disconnect request is sent to SME.
3948        assert_matches!(
3949            poll_sme_req(&mut exec, &mut sme_fut),
3950            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
3951                responder.send().expect("could not send sme response");
3952            }
3953        );
3954
3955        // Ensure the state machine exits once the disconnect is processed.
3956        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3957
3958        // Verify a disconnect was logged to saved networks manager
3959        assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3960            assert_eq!(id, &connect_selection.target.network.clone());
3961            assert_eq!(credential, &connect_selection.target.credential.clone());
3962            assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3963                assert_eq!(bssid, &connect_selection.target.bss.bssid);
3964                assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3965            })
3966        });
3967
3968        // Verify a disconnect event was logged to telemetry
3969        assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3970            assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3971                assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3972            });
3973        });
3974
3975        // Verify a disconnected listener update is sent.
3976        assert_matches!(
3977            test_values.update_receiver.try_recv(),
3978            Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3979                state: fidl_policy::WlanClientState::ConnectionsEnabled,
3980                networks
3981            })) => {
3982                assert_eq!(networks.len(), 1);
3983                assert_eq!(networks[0].id, connect_selection.target.network);
3984                assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3985                assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3986            }
3987        );
3988    }
3989
3990    #[fuchsia::test]
3991    fn disconnecting_state_completes_and_exits() {
3992        let mut exec = fasync::TestExecutor::new();
3993        let mut test_values = test_setup();
3994
3995        let (sender, _) = oneshot::channel();
3996        let disconnecting_options = DisconnectingOptions {
3997            disconnect_responder: Some(sender),
3998            previous_network: None,
3999            next_network: None,
4000            reason: types::DisconnectReason::RegulatoryRegionChange,
4001        };
4002        let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4003        let fut = run_state_machine(initial_state);
4004        let mut fut = pin!(fut);
4005        let sme_fut = test_values.sme_req_stream.into_future();
4006        let mut sme_fut = pin!(sme_fut);
4007
4008        // Run the state machine
4009        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4010
4011        // Ensure a disconnect request is sent to the SME
4012        assert_matches!(
4013            poll_sme_req(&mut exec, &mut sme_fut),
4014            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::RegulatoryRegionChange }) => {
4015                responder.send().expect("could not send sme response");
4016            }
4017        );
4018
4019        // Ensure the state machine exits once the disconnect is processed.
4020        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4021
4022        // The state machine should have sent a listener update
4023        assert_matches!(
4024            test_values.update_receiver.try_recv(),
4025            Ok(listener::Message::NotifyListeners(ClientStateUpdate {
4026                state: fidl_policy::WlanClientState::ConnectionsEnabled,
4027                networks
4028            })) => {
4029                assert!(networks.is_empty());
4030            }
4031        );
4032    }
4033
4034    #[fuchsia::test]
4035    fn disconnecting_state_completes_disconnect_to_connecting() {
4036        let mut exec = fasync::TestExecutor::new();
4037        let mut test_values = test_setup();
4038
4039        let previous_connect_selection = generate_connect_selection();
4040        let next_connect_selection = generate_connect_selection();
4041
4042        let bss_description =
4043            Sequestered::release(next_connect_selection.target.bss.bss_description.clone());
4044
4045        let (disconnect_sender, mut disconnect_receiver) = oneshot::channel();
4046        let connecting_options = ConnectingOptions {
4047            connect_selection: next_connect_selection.clone(),
4048            attempt_counter: 0,
4049        };
4050        // Include both a "previous" and "next" network
4051        let disconnecting_options = DisconnectingOptions {
4052            disconnect_responder: Some(disconnect_sender),
4053            previous_network: Some((
4054                previous_connect_selection.target.network.clone(),
4055                fidl_policy::DisconnectStatus::ConnectionStopped,
4056            )),
4057            next_network: Some(connecting_options),
4058            reason: types::DisconnectReason::ProactiveNetworkSwitch,
4059        };
4060        let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4061        let fut = run_state_machine(initial_state);
4062        let mut fut = pin!(fut);
4063        let sme_fut = test_values.sme_req_stream.into_future();
4064        let mut sme_fut = pin!(sme_fut);
4065
4066        // Run the state machine
4067        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4068
4069        // Ensure a disconnect request is sent to the SME
4070        assert_matches!(
4071            poll_sme_req(&mut exec, &mut sme_fut),
4072            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch }) => {
4073                responder.send().expect("could not send sme response");
4074            }
4075        );
4076
4077        // Progress the state machine
4078        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4079
4080        // Check for a disconnect update and the disconnect responder
4081        let client_state_update = ClientStateUpdate {
4082            state: fidl_policy::WlanClientState::ConnectionsEnabled,
4083            networks: vec![ClientNetworkState {
4084                id: previous_connect_selection.target.network.clone(),
4085                state: fidl_policy::ConnectionState::Disconnected,
4086                status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
4087            }],
4088        };
4089        assert_matches!(
4090            test_values.update_receiver.try_recv(),
4091            Ok(listener::Message::NotifyListeners(updates)) => {
4092            assert_eq!(updates, client_state_update);
4093        });
4094
4095        assert_matches!(exec.run_until_stalled(&mut disconnect_receiver), Poll::Ready(Ok(())));
4096
4097        // Ensure a connect request is sent to the SME
4098        assert_matches!(
4099            poll_sme_req(&mut exec, &mut sme_fut),
4100            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
4101                assert_eq!(req.ssid, next_connect_selection.target.network.ssid.clone().to_vec());
4102                assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
4103                assert_eq!(req.bss_description, bss_description.clone());
4104                assert_eq!(req.multiple_bss_candidates, next_connect_selection.target.network_has_multiple_bss);
4105                 // Send connection response.
4106                let (_stream, ctrl) = txn.expect("connect txn unused")
4107                    .into_stream_and_control_handle();
4108                ctrl
4109                    .send_on_connect_result(&fake_successful_connect_result())
4110                    .expect("failed to send connection completion");
4111            }
4112        );
4113    }
4114
4115    #[fuchsia::test]
4116    fn disconnecting_state_has_broken_sme() {
4117        let mut exec = fasync::TestExecutor::new();
4118        let test_values = test_setup();
4119
4120        let (sender, mut receiver) = oneshot::channel();
4121        let disconnecting_options = DisconnectingOptions {
4122            disconnect_responder: Some(sender),
4123            previous_network: None,
4124            next_network: None,
4125            reason: types::DisconnectReason::NetworkConfigUpdated,
4126        };
4127        let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4128        let fut = run_state_machine(initial_state);
4129        let mut fut = pin!(fut);
4130
4131        // Break the SME by dropping the server end of the SME stream, so it causes an error
4132        drop(test_values.sme_req_stream);
4133
4134        // Ensure the state machine exits
4135        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4136
4137        // Expect the responder to have an error
4138        assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Err(_)));
4139    }
4140
4141    #[fuchsia::test]
4142    fn serve_loop_handles_startup() {
4143        let mut exec = fasync::TestExecutor::new();
4144        let test_values = test_setup();
4145        let sme_proxy = test_values.common_options.proxy;
4146        let sme_event_stream = sme_proxy.take_event_stream();
4147        let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4148        let sme_fut = test_values.sme_req_stream.into_future();
4149        let mut sme_fut = pin!(sme_fut);
4150
4151        // Create a connect request so that the state machine does not immediately exit.
4152        let connect_selection = generate_connect_selection();
4153
4154        let fut = serve(
4155            0,
4156            sme_proxy,
4157            sme_event_stream,
4158            client_req_stream,
4159            test_values.common_options.update_sender,
4160            test_values.common_options.saved_networks_manager,
4161            connect_selection,
4162            test_values.common_options.telemetry_sender,
4163            test_values.common_options.defect_sender,
4164            test_values.common_options.roam_manager,
4165            test_values.common_options.status_publisher,
4166            test_values.power_manager.clone(),
4167        );
4168        let mut fut = pin!(fut);
4169
4170        // Run the state machine so it sends the initial SME disconnect request.
4171        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4172        assert_matches!(
4173            poll_sme_req(&mut exec, &mut sme_fut),
4174            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4175                responder.send().expect("could not send sme response");
4176            }
4177        );
4178
4179        // Run the future again and ensure that it has not exited after receiving the response.
4180        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4181    }
4182
4183    #[fuchsia::test]
4184    fn serve_loop_handles_sme_disappearance() {
4185        let mut exec = fasync::TestExecutor::new();
4186        let mut test_values = test_setup();
4187        let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4188
4189        // Make our own SME proxy for this test
4190        let (sme_proxy, sme_server) = create_proxy::<fidl_sme::ClientSmeMarker>();
4191        let (sme_req_stream, sme_control_handle) = sme_server.into_stream_and_control_handle();
4192
4193        let sme_fut = sme_req_stream.into_future();
4194        let mut sme_fut = pin!(sme_fut);
4195
4196        let sme_event_stream = sme_proxy.take_event_stream();
4197
4198        // Create a connect request so that the state machine does not immediately exit.
4199        let connect_selection = generate_connect_selection();
4200
4201        let fut = serve(
4202            0,
4203            SmeForClientStateMachine::new(
4204                sme_proxy,
4205                0,
4206                test_values.common_options.defect_sender.clone(),
4207            ),
4208            sme_event_stream,
4209            client_req_stream,
4210            test_values.common_options.update_sender,
4211            test_values.common_options.saved_networks_manager,
4212            connect_selection,
4213            test_values.common_options.telemetry_sender,
4214            test_values.common_options.defect_sender,
4215            test_values.common_options.roam_manager,
4216            test_values.common_options.status_publisher,
4217            test_values.power_manager.clone(),
4218        );
4219        let mut fut = pin!(fut);
4220
4221        // Run the state machine so it sends the initial SME disconnect request.
4222        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4223        assert_matches!(
4224            poll_sme_req(&mut exec, &mut sme_fut),
4225            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4226                responder.send().expect("could not send sme response");
4227            }
4228        );
4229
4230        // Run the future again and ensure that it has not exited after receiving the response.
4231        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4232
4233        sme_control_handle.shutdown_with_epitaph(zx::Status::UNAVAILABLE);
4234
4235        // Ensure the state machine has no further actions and is exited
4236        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4237
4238        // Verify that a disconnect event was logged on exit.
4239        let mut telemetry_events = Vec::new();
4240        while let Ok(event) = test_values.telemetry_receiver.try_recv() {
4241            telemetry_events.push(event)
4242        }
4243
4244        assert_matches!(
4245            telemetry_events.last(),
4246            Some(&TelemetryEvent::Disconnected { track_subsequent_downtime: false, info: None })
4247        );
4248    }
4249
4250    #[fuchsia::test]
4251    fn serve_loop_handles_disconnect() {
4252        let mut exec = fasync::TestExecutor::new();
4253        let mut test_values = test_setup();
4254        let sme_proxy = test_values.common_options.proxy;
4255        let sme_event_stream = sme_proxy.take_event_stream();
4256        let (client_req_sender, client_req_stream) = mpsc::channel(1);
4257        let sme_fut = test_values.sme_req_stream.into_future();
4258        let mut sme_fut = pin!(sme_fut);
4259
4260        // Create a connect request so that the state machine does not immediately exit.
4261        let connect_selection = generate_connect_selection();
4262        let fut = serve(
4263            0,
4264            sme_proxy,
4265            sme_event_stream,
4266            client_req_stream,
4267            test_values.common_options.update_sender,
4268            test_values.common_options.saved_networks_manager,
4269            connect_selection,
4270            test_values.common_options.telemetry_sender,
4271            test_values.common_options.defect_sender,
4272            test_values.common_options.roam_manager,
4273            test_values.common_options.status_publisher,
4274            test_values.power_manager.clone(),
4275        );
4276        let mut fut = pin!(fut);
4277
4278        // Run the state machine so it sends the initial SME disconnect request.
4279        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4280        assert_matches!(
4281            poll_sme_req(&mut exec, &mut sme_fut),
4282            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4283                responder.send().expect("could not send sme response");
4284            }
4285        );
4286
4287        // Run the future again and ensure that it has not exited after receiving the response.
4288        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4289
4290        // Absorb the connect request.
4291        let connect_txn_handle = assert_matches!(
4292            poll_sme_req(&mut exec, &mut sme_fut),
4293            Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req: _, txn, control_handle: _ }) => {
4294                // Send connection response.
4295                let (_stream, ctrl) = txn.expect("connect txn unused")
4296                    .into_stream_and_control_handle();
4297                ctrl
4298            }
4299        );
4300        connect_txn_handle
4301            .send_on_connect_result(&fake_successful_connect_result())
4302            .expect("failed to send connection completion");
4303        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4304
4305        // Verify roam monitor request was sent.
4306        assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
4307            assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
4308        });
4309
4310        // Run the state machine
4311        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4312
4313        // Send a disconnect request
4314        let mut client = Client::new(client_req_sender);
4315        let (sender, mut receiver) = oneshot::channel();
4316        client
4317            .disconnect(
4318                PolicyDisconnectionMigratedMetricDimensionReason::NetworkConfigUpdated,
4319                sender,
4320            )
4321            .expect("failed to make request");
4322
4323        // Run the state machine so that it handles the disconnect message.
4324        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4325        assert_matches!(
4326            poll_sme_req(&mut exec, &mut sme_fut),
4327            Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::NetworkConfigUpdated }) => {
4328                responder.send().expect("could not send sme response");
4329            }
4330        );
4331
4332        // The state machine should exit following the disconnect request.
4333        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4334
4335        // Expect the responder to be acknowledged
4336        assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(())));
4337    }
4338
4339    #[fuchsia::test]
4340    fn serve_loop_handles_state_machine_error() {
4341        let mut exec = fasync::TestExecutor::new();
4342        let test_values = test_setup();
4343        let sme_proxy = test_values.common_options.proxy;
4344        let sme_event_stream = sme_proxy.take_event_stream();
4345        let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4346
4347        // Set the status to something non-disconnected so that we can verify that the state is set
4348        // when the state machine exits.
4349        test_values.common_options.status_publisher.publish_status(Status::Connecting);
4350
4351        // Create a connect request so that the state machine does not immediately exit.
4352        let connect_selection = generate_connect_selection();
4353
4354        let fut = serve(
4355            0,
4356            sme_proxy,
4357            sme_event_stream,
4358            client_req_stream,
4359            test_values.common_options.update_sender,
4360            test_values.common_options.saved_networks_manager,
4361            connect_selection,
4362            test_values.common_options.telemetry_sender,
4363            test_values.common_options.defect_sender,
4364            test_values.common_options.roam_manager,
4365            test_values.common_options.status_publisher.clone(),
4366            test_values.power_manager.clone(),
4367        );
4368        let mut fut = pin!(fut);
4369
4370        // Drop the server end of the SME stream, so it causes an error
4371        drop(test_values.sme_req_stream);
4372
4373        // Ensure the state machine exits
4374        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4375
4376        // Verify that the state has been set to disconnected.
4377        let status = test_values.status_reader.read_status().expect("could not get reader");
4378        assert_eq!(status, Status::Disconnected);
4379    }
4380
4381    fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
4382        fidl_sme::ConnectResult {
4383            code: fidl_ieee80211::StatusCode::Success,
4384            is_credential_rejected: false,
4385            is_reconnect: false,
4386        }
4387    }
4388
4389    #[fuchsia::test]
4390    fn disconnecting_sets_status() {
4391        let mut exec = fasync::TestExecutor::new();
4392        let test_values = test_setup();
4393
4394        let (sender, _) = oneshot::channel();
4395        let disconnecting_options = DisconnectingOptions {
4396            disconnect_responder: Some(sender),
4397            previous_network: None,
4398            next_network: None,
4399            reason: types::DisconnectReason::RegulatoryRegionChange,
4400        };
4401
4402        // Run the disconnecting state machine.
4403        let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4404        let fut = run_state_machine(initial_state);
4405        let mut fut = pin!(fut);
4406        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4407
4408        // Verify the disconnecting state has been reported.
4409        let status = test_values.status_reader.read_status().expect("could not get reader");
4410        assert_eq!(status, Status::Disconnecting);
4411    }
4412
4413    #[fuchsia::test]
4414    fn connecting_sets_status() {
4415        let mut exec = fasync::TestExecutor::new_with_fake_time();
4416        let connection_attempt_time = fasync::MonotonicInstant::from_nanos(0);
4417        exec.set_fake_time(connection_attempt_time);
4418        let test_values = test_setup();
4419
4420        let connect_selection = generate_connect_selection();
4421        let connecting_options =
4422            ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
4423
4424        // Run the connecting state
4425        let initial_state = connecting_state(test_values.common_options, connecting_options);
4426        let fut = run_state_machine(initial_state);
4427        let mut fut = pin!(fut);
4428        assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4429
4430        // Verify the status was set.
4431        let status = test_values.status_reader.read_status().expect("failed to read status");
4432        assert_matches!(status, Status::Connecting);
4433    }
4434
4435    struct InspectTestValues {
4436        exec: fasync::TestExecutor,
4437        inspector: fuchsia_inspect::Inspector,
4438        _node: fuchsia_inspect::Node,
4439        status_node: fuchsia_inspect_contrib::nodes::BoundedListNode,
4440    }
4441
4442    impl InspectTestValues {
4443        fn new(exec: fasync::TestExecutor) -> Self {
4444            let inspector = fuchsia_inspect::Inspector::default();
4445            let _node = inspector.root().create_child("node");
4446            let status_node =
4447                fuchsia_inspect_contrib::nodes::BoundedListNode::new(_node.clone_weak(), 1);
4448
4449            Self { exec, inspector, _node, status_node }
4450        }
4451
4452        fn log_status(&mut self, status: Status) -> fuchsia_inspect::reader::DiagnosticsHierarchy {
4453            fuchsia_inspect_contrib::inspect_log!(self.status_node, "status" => status);
4454            let read_fut = fuchsia_inspect::reader::read(&self.inspector);
4455            let mut read_fut = pin!(read_fut);
4456            assert_matches!(
4457                self.exec.run_until_stalled(&mut read_fut),
4458                Poll::Ready(Ok(hierarchy)) => hierarchy
4459            )
4460        }
4461    }
4462
4463    #[fuchsia::test]
4464    fn test_disconnecting_status_inspect_log() {
4465        let exec = fasync::TestExecutor::new_with_fake_time();
4466        let mut test_values = InspectTestValues::new(exec);
4467        let hierarchy = test_values.log_status(Status::Disconnecting);
4468        diagnostics_assertions::assert_data_tree!(
4469            @executor test_values.exec,
4470            hierarchy,
4471            root: contains {
4472                node: contains {
4473                    "0": contains {
4474                        status: "Disconnecting"
4475                    }
4476                }
4477        });
4478    }
4479
4480    #[fuchsia::test]
4481    fn test_disconnected_status_inspect_log() {
4482        let exec = fasync::TestExecutor::new_with_fake_time();
4483        let mut test_values = InspectTestValues::new(exec);
4484        let hierarchy = test_values.log_status(Status::Disconnected);
4485        diagnostics_assertions::assert_data_tree!(
4486            @executor test_values.exec,
4487            hierarchy,
4488            root: contains {
4489                node: contains {
4490                    "0": contains {
4491                        status: "Disconnected"
4492                    }
4493                }
4494        });
4495    }
4496
4497    #[fuchsia::test]
4498    fn test_connecting_status_inspect_log() {
4499        let exec = fasync::TestExecutor::new_with_fake_time();
4500        let mut test_values = InspectTestValues::new(exec);
4501        let hierarchy = test_values.log_status(Status::Connecting);
4502        diagnostics_assertions::assert_data_tree!(
4503            @executor test_values.exec,
4504            hierarchy,
4505            root: contains {
4506                node: contains {
4507                    "0": contains {
4508                        status: "Connecting"
4509                    }
4510                }
4511        });
4512    }
4513
4514    #[fuchsia::test]
4515    fn test_connected_status_inspect_log() {
4516        let exec = fasync::TestExecutor::new_with_fake_time();
4517        let mut test_values = InspectTestValues::new(exec);
4518        let hierarchy = test_values.log_status(Status::Connected { channel: 1, rssi: 2, snr: 3 });
4519        diagnostics_assertions::assert_data_tree!(
4520            @executor test_values.exec,
4521            hierarchy,
4522            root: contains {
4523                node: contains {
4524                    "0": contains {
4525                        status: contains {
4526                            Connected: { channel: 1_u64, rssi: 2_i64, snr: 3_i64 }
4527                        }
4528                    }
4529                }
4530        });
4531    }
4532
4533    #[fuchsia::test]
4534    fn test_state_machine_wake_leases() {
4535        let mut exec = fasync::TestExecutor::new();
4536        let test_values = test_setup();
4537
4538        let disconnecting_options = DisconnectingOptions {
4539            disconnect_responder: None,
4540            previous_network: None,
4541            next_network: None,
4542            reason: types::DisconnectReason::Startup,
4543        };
4544        let mut fut = pin!(disconnecting_state(test_values.common_options, disconnecting_options));
4545        assert!(exec.run_until_stalled(&mut fut).is_pending());
4546        let calls = test_values.power_manager.calls.lock();
4547        assert!(calls.contains(&"wlancfg-client-disconnecting".to_string()));
4548    }
4549}