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