1use crate::client::roaming::lib::{PolicyRoamRequest, ROAMING_CHANNEL_BUFFER_SIZE};
6use crate::client::roaming::local_roam_manager::RoamManager;
7use crate::client::roaming::roam_monitor::RoamDataSender;
8use crate::client::types;
9use crate::config_management::{Credential, PastConnectionData, SavedNetworksManagerApi};
10use crate::mode_management::iface_manager_api::SmeForClientStateMachine;
11use crate::mode_management::{Defect, IfaceFailure};
12use crate::telemetry::{
13 AVERAGE_SCORE_DELTA_MINIMUM_DURATION, DisconnectInfo, METRICS_SHORT_CONNECT_DURATION,
14 TelemetryEvent, TelemetrySender,
15};
16use crate::util::listener::Message::NotifyListeners;
17use crate::util::listener::{ClientListenerMessageSender, ClientNetworkState, ClientStateUpdate};
18use crate::util::state_machine::{self, ExitReason, IntoStateExt, StateMachineStatusPublisher};
19use anyhow::format_err;
20use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
21use fidl_fuchsia_wlan_internal as fidl_internal;
22use fidl_fuchsia_wlan_policy as fidl_policy;
23use fidl_fuchsia_wlan_sme as fidl_sme;
24use fuchsia_async::{self as fasync, DurationExt};
25use fuchsia_inspect::Node as InspectNode;
26use fuchsia_inspect_contrib::inspect_insert;
27use fuchsia_inspect_contrib::log::WriteInspect;
28use futures::channel::{mpsc, oneshot};
29use futures::future::{Fuse, FutureExt};
30use futures::select;
31use futures::stream::{self, StreamExt, TryStreamExt};
32use log::{debug, error, info, warn};
33use std::borrow::Cow;
34use std::pin::Pin;
35use std::sync::Arc;
36use wlan_common::bss::BssDescription;
37use wlan_common::channel::{Bandwidth, Channel};
38use wlan_common::historical_list::HistoricalList;
39use wlan_common::sequestered::Sequestered;
40
41const MAX_CONNECTION_ATTEMPTS: u8 = 4; const NUM_PAST_SCORES: usize = 91; const PENDING_ROAM_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(3);
44
45type State = state_machine::State<ExitReason>;
46type ReqStream = stream::Fuse<mpsc::Receiver<ManualRequest>>;
47pub type TrackedSignals = HistoricalList<types::TimestampedSignal, NUM_PAST_SCORES>;
48
49#[derive(Clone)]
50struct PendingRoam {
51 pub request: PolicyRoamRequest,
52 pub timestamp: fasync::MonotonicInstant,
53}
54impl From<PolicyRoamRequest> for PendingRoam {
55 fn from(request: PolicyRoamRequest) -> Self {
56 Self { request, timestamp: fasync::MonotonicInstant::now() }
57 }
58}
59
60pub trait ClientApi {
61 fn connect(&mut self, selection: types::ConnectSelection) -> Result<(), anyhow::Error>;
62 fn disconnect(
63 &mut self,
64 reason: types::DisconnectReason,
65 responder: oneshot::Sender<()>,
66 ) -> Result<(), anyhow::Error>;
67
68 fn is_alive(&self) -> bool;
71}
72
73pub struct Client {
74 req_sender: mpsc::Sender<ManualRequest>,
75}
76
77impl Client {
78 pub fn new(req_sender: mpsc::Sender<ManualRequest>) -> Self {
79 Self { req_sender }
80 }
81}
82
83impl ClientApi for Client {
84 fn connect(&mut self, selection: types::ConnectSelection) -> Result<(), anyhow::Error> {
85 self.req_sender
86 .try_send(ManualRequest::Connect(Box::new(selection)))
87 .map_err(|e| format_err!("failed to send connect selection: {:?}", e))
88 }
89
90 fn disconnect(
91 &mut self,
92 reason: types::DisconnectReason,
93 responder: oneshot::Sender<()>,
94 ) -> Result<(), anyhow::Error> {
95 self.req_sender
96 .try_send(ManualRequest::Disconnect((reason, responder)))
97 .map_err(|e| format_err!("failed to send disconnect request: {:?}", e))
98 }
99
100 fn is_alive(&self) -> bool {
101 !self.req_sender.is_closed()
102 }
103}
104
105pub enum ManualRequest {
107 Connect(Box<types::ConnectSelection>),
108 Disconnect((types::DisconnectReason, oneshot::Sender<()>)),
109}
110
111#[derive(Clone, Debug, Default, PartialEq)]
112pub enum Status {
113 Disconnecting,
114 #[default]
115 Disconnected,
116 Connecting,
117 Connected {
118 channel: u8,
119 rssi: i8,
120 snr: i8,
121 },
122}
123
124impl Status {
125 fn from_ap_state(ap_state: &types::ApState) -> Self {
126 Status::Connected {
127 channel: ap_state.tracked.channel.primary,
128 rssi: ap_state.tracked.signal.rssi_dbm,
129 snr: ap_state.tracked.signal.snr_db,
130 }
131 }
132}
133
134impl WriteInspect for Status {
135 fn write_inspect<'a>(&self, writer: &InspectNode, key: impl Into<Cow<'a, str>>) {
136 match self {
137 Status::Connected { channel, rssi, snr } => {
138 inspect_insert!(writer, var key: {
139 Connected: {
140 channel: channel,
141 rssi: rssi,
142 snr: snr
143 }
144 })
145 }
146 other => inspect_insert!(writer, var key: format!("{:?}", other)),
147 }
148 }
149}
150
151fn send_listener_state_update(
152 sender: &ClientListenerMessageSender,
153 network_update: Option<ClientNetworkState>,
154) {
155 let mut networks = vec![];
156 if let Some(network) = network_update {
157 networks.push(network)
158 }
159
160 let updates =
161 ClientStateUpdate { state: fidl_policy::WlanClientState::ConnectionsEnabled, networks };
162 match sender.clone().unbounded_send(NotifyListeners(updates)) {
163 Ok(_) => (),
164 Err(e) => error!("failed to send state update: {:?}", e),
165 };
166}
167
168pub async fn serve(
169 iface_id: u16,
170 proxy: SmeForClientStateMachine,
171 sme_event_stream: fidl_sme::ClientSmeEventStream,
172 req_stream: mpsc::Receiver<ManualRequest>,
173 update_sender: ClientListenerMessageSender,
174 saved_networks_manager: Arc<dyn SavedNetworksManagerApi>,
175 connect_selection: Option<types::ConnectSelection>,
176 telemetry_sender: TelemetrySender,
177 defect_sender: mpsc::Sender<Defect>,
178 roam_manager: RoamManager,
179 status_publisher: StateMachineStatusPublisher<Status>,
180) {
181 let next_network = connect_selection
182 .map(|selection| ConnectingOptions { connect_selection: selection, attempt_counter: 0 });
183 let disconnect_options = DisconnectingOptions {
184 disconnect_responder: None,
185 previous_network: None,
186 next_network,
187 reason: types::DisconnectReason::Startup,
188 };
189 let common_options = CommonStateOptions {
190 proxy,
191 req_stream: req_stream.fuse(),
192 update_sender,
193 saved_networks_manager,
194 telemetry_sender: telemetry_sender.clone(),
195 iface_id,
196 defect_sender,
197 roam_manager,
198 status_publisher: status_publisher.clone(),
199 };
200 let state_machine =
201 disconnecting_state(common_options, disconnect_options).into_state_machine();
202 let removal_watcher = sme_event_stream.map_ok(|_| ()).try_collect::<()>();
203 select! {
204 state_machine = state_machine.fuse() => {
205 match state_machine {
206 Err(ExitReason(Err(e))) => error!("Client state machine for iface #{} terminated with an error: {:?}",
207 iface_id, e),
208 Err(ExitReason(Ok(_))) => info!("Client state machine for iface #{} exited gracefully",
209 iface_id,),
210 }
211 }
212 removal_watcher = removal_watcher.fuse() => {
213 match removal_watcher {
214 Ok(()) => {
215 info!("Device was unexpectedly removed.");
216 }
217 Err(e) => {
218 info!("Error reading from Client SME channel of iface #{}: {:?}",
219 iface_id, e);
220 }
221 }
222
223 telemetry_sender
224 .send(TelemetryEvent::Disconnected { track_subsequent_downtime: false, info: None });
225 },
226 }
227
228 status_publisher.publish_status(Status::Disconnected);
229}
230
231struct CommonStateOptions {
233 proxy: SmeForClientStateMachine,
234 req_stream: ReqStream,
235 update_sender: ClientListenerMessageSender,
236 saved_networks_manager: Arc<dyn SavedNetworksManagerApi>,
237 telemetry_sender: TelemetrySender,
238 iface_id: u16,
239 defect_sender: mpsc::Sender<Defect>,
240 roam_manager: RoamManager,
241 status_publisher: StateMachineStatusPublisher<Status>,
242}
243
244impl CommonStateOptions {
245 async fn network_is_likely_hidden(&self, options: &ConnectingOptions) -> bool {
246 match self
247 .saved_networks_manager
248 .lookup(&options.connect_selection.target.network)
249 .await
250 .filter(|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
268fn 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 previous_network: Option<(types::NetworkIdentifier, types::DisconnectStatus)>,
294 next_network: Option<ConnectingOptions>,
297 reason: types::DisconnectReason,
298}
299async fn disconnecting_state(
303 common_options: CommonStateOptions,
304 mut options: DisconnectingOptions,
305) -> Result<State, ExitReason> {
306 match options.reason {
308 types::DisconnectReason::FailedToConnect
309 | types::DisconnectReason::Startup
310 | types::DisconnectReason::DisconnectDetectedFromSme => {
311 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 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 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 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 #[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 attempt_counter: u8,
372}
373
374async fn handle_connecting_error_and_retry(
375 common_options: CommonStateOptions,
376 options: ConnectingOptions,
377) -> Result<State, ExitReason> {
378 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 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#[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
421async 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 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 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 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 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 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 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: TrackedSignals,
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 let mut past_signals = TrackedSignals::new();
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 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: TrackedSignals::new(),
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#[allow(clippy::doc_lazy_continuation, reason = "mass allow for https://fxbug.dev/381896734")]
650async 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 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 options.ap_state.tracked.signal = ind.into();
697
698 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_primary_channel
715 );
716
717 let cbw = match Bandwidth::from_fidl(info.bandwidth, info.vht_secondary_80_channel.number) {
718 Ok(cbw) => cbw,
719 Err(e) => {
720 error!("Invalid CBW in ChannelSwitchInfo: {}", e);
724 options.ap_state.tracked.channel.bandwidth
725 }
726 };
727 options.ap_state.tracked.channel = Channel::new(
728 info.new_primary_channel.number,
729 cbw,
730 info.new_primary_channel.band
731 );
732
733 let (sender, roam_request_receiver) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
735 options.roam_request_receiver = roam_request_receiver;
736 options.roam_monitor_sender =
737 common_options.roam_manager.initialize_roam_monitor(
738 (*options.ap_state).clone(),
739 options.network_identifier.clone(),
740 options.credential.clone(),
741 sender
742 );
743 notify_on_channel_switch(&common_options, &options, info);
744 false
745 }
746 };
747
748 if is_sme_idle {
749 info!("Idle sme detected.");
750 let options = DisconnectingOptions {
751 disconnect_responder: None,
752 previous_network: Some((
753 options.network_identifier.clone(),
754 types::DisconnectStatus::ConnectionFailed
755 )),
756 next_network: None,
757 reason: types::DisconnectReason::DisconnectDetectedFromSme,
758 };
759 return Ok(disconnecting_state(common_options, options).into_state());
760 }
761 }
762 _ => {
763 info!("SME dropped ConnectTransaction channel. Exiting state machine");
764 return Err(ExitReason(Err(format_err!("Failed to receive ConnectTransactionEvent for SME status"))));
765 }
766 },
767 req = common_options.req_stream.next() => {
768 match req {
769 Some(ManualRequest::Disconnect((reason, responder))) => {
770 debug!("Disconnect requested");
771 notify_on_manual_disconnect_request_received(
772 &common_options,
773 &options,
774 reason,
775 ).await;
776
777 let options = DisconnectingOptions {
778 disconnect_responder: Some(responder),
779 previous_network: Some((
780 options.network_identifier.clone(),
781 types::DisconnectStatus::ConnectionStopped
782 )),
783 next_network: None,
784 reason,
785 };
786 return Ok(disconnecting_state(common_options, options).into_state());
787 }
788 Some(ManualRequest::Connect(box_connect_selection)) => {
789 let new_connect_selection = *box_connect_selection;
790 if new_connect_selection.target.network
792 == options.network_identifier {
793 info!("Received connection request for current network, deduping");
794 continue
795 }
796
797 let reason = convert_manual_connect_to_disconnect_reason(
798 &new_connect_selection.reason
799 ).unwrap_or_else(|_| {
800 error!("Unexpected connection reason: {:?}", new_connect_selection.reason);
801 types::DisconnectReason::Unknown
802 });
803 notify_on_manual_connect_request_received(
804 &common_options,
805 &options,
806 reason,
807 ).await;
808
809 let options = DisconnectingOptions {
810 disconnect_responder: None,
811 previous_network: Some((
812 options.network_identifier,
813 types::DisconnectStatus::ConnectionStopped
814 )),
815 next_network: Some(ConnectingOptions {
816 connect_selection: new_connect_selection.clone(),
817 attempt_counter: 0,
818 }),
819 reason
820 };
821 info!("Connection to new network requested, disconnecting from current network");
822 return Ok(disconnecting_state(common_options, options).into_state())
823 }
824 None => return handle_none_request(),
825 };
826 },
827 () = &mut options.post_connect_metric_timer => {
828 common_options.telemetry_sender.send(TelemetryEvent::PostConnectionSignals {
829 connect_time: options.bss_connect_start_time,
830 signal_at_connect: options.initial_signal,
831 signals: options.tracked_signals.clone()
832 });
833 },
834 () = &mut options.bss_connect_duration_metric_timer => {
835 common_options.telemetry_sender.send(TelemetryEvent::LongDurationSignals{
837 signals: options.tracked_signals.get_before(fasync::MonotonicInstant::now())
838 });
839 },
840 () = &mut options.pending_roam_timer => {
841 error!("Pending roam request has timed out without a response from SME, cannot proceed with connection");
842 notify_on_roam_error_and_exit(&common_options, &options).await;
843 let options = DisconnectingOptions {
844 disconnect_responder: None,
845 previous_network: Some((
846 options.network_identifier.clone(),
847 types::DisconnectStatus::ConnectionFailed
848 )),
849 next_network: None,
850 reason: types::DisconnectReason::DisconnectDetectedFromSme,
852 };
853 return Ok(disconnecting_state(common_options, options).into_state());
854 }
855 roam_request = options.roam_request_receiver.select_next_some() => {
856 if let Some(pending_roam) = &options.pending_roam {
857 info!("Already pending a roam result for a requested roam to BSSID: {:?}", pending_roam.request.candidate.bss.bssid);
858 } else {
859 let _ = common_options
860 .proxy
861 .roam(&roam_request.clone().into())
862 .inspect_err(|e| {
863 error!("Error sending sme roam request: {}", e);
864 });
865 options.pending_roam = Some(roam_request.clone().into());
866 options.pending_roam_timer.set(fasync::Timer::new(PENDING_ROAM_TIMEOUT.after_now()).fuse());
867 common_options.telemetry_sender.send(TelemetryEvent::PolicyRoamAttempt {
868 request: roam_request,
869 connected_duration: fasync::MonotonicInstant::now() - options.bss_connect_start_time,
870 });
871 }
872 }
873 }
874 }
875}
876
877async fn notify_when_disconnect_detected(
878 common_options: &CommonStateOptions,
879 options: &ConnectedOptions,
880 fidl_info: fidl_sme::DisconnectInfo,
881) {
882 log_disconnect_to_telemetry(common_options, options, fidl_info, true).await;
883 log_disconnect_to_config_manager(
884 common_options,
885 options,
886 types::DisconnectReason::DisconnectDetectedFromSme,
887 )
888 .await
889}
890
891fn notify_when_reconnect_detected(
892 common_options: &CommonStateOptions,
893 options: &ConnectedOptions,
894 result: fidl_sme::ConnectResult,
895) {
896 common_options.telemetry_sender.send(TelemetryEvent::ConnectResult {
897 iface_id: common_options.iface_id,
898 result,
899 policy_connect_reason: None,
900 multiple_bss_candidates: options.multiple_bss_candidates,
905 ap_state: (*options.ap_state).clone(),
906 network_is_likely_hidden: options.network_is_likely_hidden,
907 });
908}
909
910fn notify_on_signal_report(
911 common_options: &CommonStateOptions,
912 options: &mut ConnectedOptions,
913 ind: fidl_internal::SignalReportIndication,
914) {
915 common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
917
918 common_options.telemetry_sender.send(TelemetryEvent::OnSignalReport { ind });
920
921 let _ = options
923 .roam_monitor_sender
924 .send_signal_report_ind(ind)
925 .inspect_err(|e| error!("Error handling signal report: {}", e));
926}
927
928fn notify_on_channel_switch(
929 common_options: &CommonStateOptions,
930 options: &ConnectedOptions,
931 info: fidl_internal::ChannelSwitchInfo,
932) {
933 common_options.telemetry_sender.send(TelemetryEvent::OnChannelSwitched { info });
934 common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
936}
937
938async fn notify_on_manual_disconnect_request_received(
939 common_options: &CommonStateOptions,
940 options: &ConnectedOptions,
941 reason: types::DisconnectReason,
942) {
943 let fidl_info = fidl_sme::DisconnectInfo {
944 is_sme_reconnecting: false,
945 disconnect_source: fidl_sme::DisconnectSource::User(
946 types::convert_to_sme_disconnect_reason(reason),
947 ),
948 };
949 log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
950 log_disconnect_to_config_manager(common_options, options, reason).await
951}
952
953async fn notify_on_manual_connect_request_received(
954 common_options: &CommonStateOptions,
955 options: &ConnectedOptions,
956 reason: types::DisconnectReason,
957) {
958 let fidl_info = fidl_sme::DisconnectInfo {
959 is_sme_reconnecting: false,
960 disconnect_source: fidl_sme::DisconnectSource::User(
961 types::convert_to_sme_disconnect_reason(reason),
962 ),
963 };
964 log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
965 log_disconnect_to_config_manager(common_options, options, reason).await
966}
967
968async fn handle_roam_result(
978 common_options: &mut CommonStateOptions,
979 options: &mut ConnectedOptions,
980 result: &fidl_sme::RoamResult,
981) -> Result<(), anyhow::Error> {
982 let pending_roam = match options.pending_roam.take() {
989 None => {
990 notify_on_roam_error_and_exit(common_options, options).await;
991 return Err(format_err!(
992 "Roam result unexpectedly received without a pending roam request."
993 ));
994 }
995 Some(pending_roam) => {
996 if pending_roam.request.candidate.bss.bssid != result.bssid.into() {
997 notify_on_roam_error_and_exit(common_options, options).await;
998 return Err(format_err!(
999 "Roam result received for bssid {:?} while awaiting result for bssid {:?}.",
1000 result.bssid,
1001 pending_roam.request.candidate.bss.bssid
1002 ));
1003 }
1004 pending_roam
1005 }
1006 };
1007 options.pending_roam_timer.set(Fuse::terminated());
1008
1009 let roam_succeeded = result.status_code == fidl_ieee80211::StatusCode::Success;
1010 let original_ap_state = options.ap_state.clone();
1011 if roam_succeeded {
1012 log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1015 .await;
1016 update_internal_state_on_roam_success(common_options, options, result)?;
1018 info!("Roam succeeded!");
1019 } else if !result.original_association_maintained {
1020 let sme_disconnect_info = match result.disconnect_info {
1023 Some(ref info) => *info.clone(),
1024 None => {
1025 warn!("RoamResult failure does not contain SME disconnect info.");
1026 fidl_sme::DisconnectInfo {
1027 is_sme_reconnecting: false,
1028 disconnect_source: fidl_sme::DisconnectSource::Mlme(
1029 fidl_sme::DisconnectCause {
1030 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1031 mlme_event_name:
1032 fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1033 },
1034 ),
1035 }
1036 }
1037 };
1038 log_disconnect_to_telemetry(common_options, options, sme_disconnect_info, true).await;
1040 log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1041 .await;
1042 info!("Roam attempt failed, original association not maintained, disconnecting");
1043 }
1044 notify_on_roam_result(common_options, options, result, *original_ap_state, pending_roam).await;
1045 Ok(())
1046}
1047
1048async fn notify_on_roam_result(
1055 common_options: &mut CommonStateOptions,
1056 options: &mut ConnectedOptions,
1057 result: &fidl_sme::RoamResult,
1058 original_ap_state: types::ApState,
1059 pending_roam: PendingRoam,
1060) {
1061 common_options
1063 .saved_networks_manager
1064 .record_connect_result(
1065 options.network_identifier.clone(),
1066 &options.credential,
1067 result.bssid.into(),
1068 fidl_sme::ConnectResult {
1069 code: result.status_code,
1070 is_credential_rejected: result.is_credential_rejected,
1071 is_reconnect: match result.disconnect_info {
1072 Some(ref info) => info.is_sme_reconnecting,
1073 None => false,
1074 },
1075 },
1076 types::ScanObservation::Unknown,
1077 )
1078 .await;
1079
1080 common_options.telemetry_sender.send(TelemetryEvent::PolicyInitiatedRoamResult {
1082 iface_id: common_options.iface_id,
1083 result: result.clone(),
1084 updated_ap_state: (*options.ap_state).clone(),
1085 original_ap_state: Box::new(original_ap_state),
1086 request: Box::new(pending_roam.request.clone()),
1087 request_time: pending_roam.timestamp,
1088 result_time: fasync::MonotonicInstant::now(),
1089 });
1090
1091 common_options.status_publisher.publish_status(Status::from_ap_state(&options.ap_state));
1093
1094 if result.status_code != fidl_ieee80211::StatusCode::Success
1096 && !result.is_credential_rejected
1097 && let Err(e) =
1098 common_options.defect_sender.try_send(Defect::Iface(IfaceFailure::ConnectionFailure {
1099 iface_id: common_options.iface_id,
1100 }))
1101 {
1102 warn!("Failed to log connection failure: {}", e);
1103 }
1104}
1105
1106async fn notify_on_roam_error_and_exit(
1107 common_options: &CommonStateOptions,
1108 options: &ConnectedOptions,
1109) {
1110 let fidl_info = fidl_sme::DisconnectInfo {
1112 is_sme_reconnecting: false,
1113 disconnect_source: fidl_sme::DisconnectSource::User(
1114 fidl_sme::UserDisconnectReason::Unknown,
1115 ),
1116 };
1117 log_disconnect_to_telemetry(common_options, options, fidl_info, false).await;
1118 log_disconnect_to_config_manager(common_options, options, types::DisconnectReason::Unknown)
1119 .await;
1120
1121 let networks = Some(ClientNetworkState {
1123 id: options.network_identifier.clone(),
1124 state: types::ConnectionState::Disconnected,
1125 status: Some(types::DisconnectStatus::ConnectionFailed),
1126 });
1127 send_listener_state_update(&common_options.update_sender, networks);
1128}
1129
1130async fn log_disconnect_to_telemetry(
1131 common_options: &CommonStateOptions,
1132 options: &ConnectedOptions,
1133 fidl_info: fidl_sme::DisconnectInfo,
1134 track_subsequent_downtime: bool,
1135) {
1136 let now = fasync::MonotonicInstant::now();
1137 let info = DisconnectInfo {
1138 iface_id: common_options.iface_id,
1139 connected_duration: now - options.ess_connect_start_time,
1140 is_sme_reconnecting: fidl_info.is_sme_reconnecting,
1141 disconnect_source: fidl_info.disconnect_source,
1142 previous_connect_reason: options.ess_connect_reason,
1143 ap_state: (*options.ap_state).clone(),
1144 signals: options.tracked_signals.clone(),
1145 };
1146 common_options
1147 .telemetry_sender
1148 .send(TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) });
1149}
1150
1151async fn log_disconnect_to_config_manager(
1152 common_options: &CommonStateOptions,
1153 options: &ConnectedOptions,
1154 reason: types::DisconnectReason,
1155) {
1156 let curr_time = fasync::MonotonicInstant::now();
1157 let uptime = curr_time - options.bss_connect_start_time;
1158 let data = PastConnectionData::new(
1159 options.ap_state.original().bssid,
1160 curr_time,
1161 uptime,
1162 reason,
1163 options.ap_state.tracked.signal,
1164 0,
1166 );
1167 common_options
1168 .saved_networks_manager
1169 .record_disconnect(&options.network_identifier.clone(), &options.credential, data)
1170 .await;
1171}
1172
1173fn update_internal_state_on_roam_success(
1176 common_options: &mut CommonStateOptions,
1177 options: &mut ConnectedOptions,
1178 result: &fidl_sme::RoamResult,
1179) -> Result<(), anyhow::Error> {
1180 let bss_description = match result.bss_description {
1182 Some(ref bss_description) => bss_description,
1183 None => {
1184 return Err(format_err!("RoamResult is missing BSS description from FIDL"));
1185 }
1186 };
1187 let ap_state = types::ApState::from(
1188 BssDescription::try_from(*bss_description.clone()).map_err(|error| {
1189 format_err!("Failed to convert BSS description from FIDL: {:?}", error,)
1192 })?,
1193 );
1194 *options.ap_state = ap_state;
1195 options.bss_connect_start_time = fasync::MonotonicInstant::now();
1196 options.tracked_signals = TrackedSignals::new();
1197 options.initial_signal = options.ap_state.tracked.signal;
1198 options.tracked_signals.add(types::TimestampedSignal {
1199 time: fasync::MonotonicInstant::now(),
1200 signal: options.initial_signal,
1201 });
1202 options.post_connect_metric_timer =
1203 Box::pin(fasync::Timer::new(AVERAGE_SCORE_DELTA_MINIMUM_DURATION.after_now()));
1204 options.bss_connect_duration_metric_timer =
1205 Box::pin(fasync::Timer::new(METRICS_SHORT_CONNECT_DURATION.after_now()));
1206 let (sender, roam_receiver) = mpsc::channel(ROAMING_CHANNEL_BUFFER_SIZE);
1208 options.roam_request_receiver = roam_receiver;
1209 options.roam_monitor_sender = common_options.roam_manager.initialize_roam_monitor(
1210 (*options.ap_state).clone(),
1211 options.network_identifier.clone(),
1212 options.credential.clone(),
1213 sender,
1214 );
1215 Ok(())
1216}
1217
1218#[allow(clippy::result_unit_err, reason = "mass allow for https://fxbug.dev/381896734")]
1219pub fn convert_manual_connect_to_disconnect_reason(
1222 reason: &types::ConnectReason,
1223) -> Result<types::DisconnectReason, ()> {
1224 match reason {
1225 types::ConnectReason::FidlConnectRequest => Ok(types::DisconnectReason::FidlConnectRequest),
1226 types::ConnectReason::ProactiveNetworkSwitch => {
1227 Ok(types::DisconnectReason::ProactiveNetworkSwitch)
1228 }
1229 types::ConnectReason::RetryAfterDisconnectDetected
1230 | types::ConnectReason::RetryAfterFailedConnectAttempt
1231 | types::ConnectReason::RegulatoryChangeReconnect
1232 | types::ConnectReason::IdleInterfaceAutoconnect
1233 | types::ConnectReason::NewSavedNetworkAutoconnect => Err(()),
1234 }
1235}
1236
1237#[cfg(test)]
1238mod tests {
1239 use super::*;
1240 use crate::client::roaming::lib::{PolicyRoamRequest, RoamTriggerData};
1241 use crate::client::roaming::local_roam_manager::RoamServiceRequest;
1242 use crate::config_management::{network_config, new_past_connection_list};
1243 use crate::util::listener;
1244 use crate::util::state_machine::{StateMachineStatusReader, status_publisher_and_reader};
1245 use crate::util::testing::{
1246 ConnectResultRecord, ConnectionRecord, FakeSavedNetworksManager,
1247 generate_connect_selection, generate_disconnect_info, generate_policy_roam_request,
1248 generate_random_scanned_candidate, poll_sme_req, random_connection_data,
1249 };
1250 use assert_matches::assert_matches;
1251 use fidl::endpoints::{create_proxy, create_proxy_and_stream};
1252 use fidl::prelude::*;
1253 use fidl_fuchsia_wlan_policy as fidl_policy;
1254 use futures::Future;
1255 use futures::task::Poll;
1256 use ieee80211::MacAddrBytes;
1257 use std::pin::pin;
1258 use wlan_common::random_fidl_bss_description;
1259 use wlan_metrics_registry::PolicyDisconnectionMigratedMetricDimensionReason;
1260
1261 struct TestValues {
1262 common_options: CommonStateOptions,
1263 sme_req_stream: fidl_sme::ClientSmeRequestStream,
1264 saved_networks_manager: Arc<FakeSavedNetworksManager>,
1265 client_req_sender: mpsc::Sender<ManualRequest>,
1266 update_receiver: mpsc::UnboundedReceiver<listener::ClientListenerMessage>,
1267 telemetry_receiver: mpsc::Receiver<TelemetryEvent>,
1268 defect_receiver: mpsc::Receiver<Defect>,
1269 roam_service_request_receiver: mpsc::Receiver<RoamServiceRequest>,
1270 status_reader: StateMachineStatusReader<Status>,
1271 }
1272
1273 fn test_setup() -> TestValues {
1274 let (client_req_sender, client_req_stream) = mpsc::channel(1);
1275 let (update_sender, update_receiver) = mpsc::unbounded();
1276 let (sme_proxy, sme_server) = create_proxy::<fidl_sme::ClientSmeMarker>();
1277 let sme_req_stream = sme_server.into_stream();
1278 let saved_networks = FakeSavedNetworksManager::new();
1279 let saved_networks_manager = Arc::new(saved_networks);
1280 let (telemetry_sender, telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1281 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1282 let (defect_sender, defect_receiver) = mpsc::channel(100);
1283 let (roam_service_request_sender, roam_service_request_receiver) = mpsc::channel(100);
1284 let roam_manager = RoamManager::new(roam_service_request_sender);
1285 let (status_publisher, status_reader) = status_publisher_and_reader::<Status>();
1286
1287 TestValues {
1288 common_options: CommonStateOptions {
1289 proxy: SmeForClientStateMachine::new(sme_proxy, 0, defect_sender.clone()),
1290 req_stream: client_req_stream.fuse(),
1291 update_sender,
1292 saved_networks_manager: saved_networks_manager.clone(),
1293 telemetry_sender,
1294 iface_id: 1,
1295 defect_sender,
1296 roam_manager,
1297 status_publisher,
1298 },
1299 sme_req_stream,
1300 saved_networks_manager,
1301 client_req_sender,
1302 update_receiver,
1303 telemetry_receiver,
1304 defect_receiver,
1305 roam_service_request_receiver,
1306 status_reader,
1307 }
1308 }
1309
1310 #[allow(clippy::needless_return, reason = "mass allow for https://fxbug.dev/381896734")]
1311 async fn run_state_machine(fut: impl Future<Output = Result<State, ExitReason>> + 'static) {
1312 let state_machine = fut.into_state_machine();
1313 select! {
1314 _state_machine = state_machine.fuse() => return,
1315 }
1316 }
1317
1318 #[fuchsia::test]
1319 fn connecting_state_successfully_connects() {
1320 let mut exec = fasync::TestExecutor::new();
1321 let mut test_values = test_setup();
1322
1323 let connect_selection = generate_connect_selection();
1324 let bss_description =
1325 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1326
1327 let save_fut = test_values.saved_networks_manager.store(
1329 connect_selection.target.network.clone(),
1330 connect_selection.target.credential.clone(),
1331 );
1332 let mut save_fut = pin!(save_fut);
1333 assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
1334
1335 let saved_network = exec
1337 .run_singlethreaded(
1338 test_values
1339 .saved_networks_manager
1340 .lookup(&connect_selection.target.network.clone()),
1341 )
1342 .expect("failed to lookup network");
1343 assert!(!saved_network.has_ever_connected);
1344 assert!(saved_network.hidden_probability > 0.0);
1345
1346 let connecting_options =
1347 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1348 let initial_state = connecting_state(test_values.common_options, connecting_options);
1349 let fut = run_state_machine(initial_state);
1350 let mut fut = pin!(fut);
1351 let sme_fut = test_values.sme_req_stream.into_future();
1352 let mut sme_fut = pin!(sme_fut);
1353
1354 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1356
1357 let connect_txn_handle = assert_matches!(
1359 poll_sme_req(&mut exec, &mut sme_fut),
1360 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1361 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1362 assert_eq!(req.bss_description, bss_description);
1363 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1364 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1365 let (_stream, ctrl) = txn.expect("connect txn unused")
1367 .into_stream_and_control_handle();
1368 ctrl
1369 }
1370 );
1371 connect_txn_handle
1372 .send_on_connect_result(&fake_successful_connect_result())
1373 .expect("failed to send connection completion");
1374
1375 let client_state_update = ClientStateUpdate {
1377 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1378 networks: vec![ClientNetworkState {
1379 id: connect_selection.target.network.clone(),
1380 state: fidl_policy::ConnectionState::Connecting,
1381 status: None,
1382 }],
1383 };
1384 assert_matches!(
1385 test_values.update_receiver.try_recv(),
1386 Ok(listener::Message::NotifyListeners(updates)) => {
1387 assert_eq!(updates, client_state_update);
1388 });
1389
1390 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1392
1393 let client_state_update = ClientStateUpdate {
1395 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1396 networks: vec![ClientNetworkState {
1397 id: connect_selection.target.network.clone(),
1398 state: fidl_policy::ConnectionState::Connected,
1399 status: None,
1400 }],
1401 };
1402 assert_matches!(
1403 test_values.update_receiver.try_recv(),
1404 Ok(listener::Message::NotifyListeners(updates)) => {
1405 assert_eq!(updates, client_state_update);
1406 });
1407
1408 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1410 let expected_connect_result = ConnectResultRecord {
1411 id: connect_selection.target.network.clone(),
1412 credential: connect_selection.target.credential.clone(),
1413 bssid: types::Bssid::from(bss_description.bssid),
1414 connect_result: fake_successful_connect_result(),
1415 scan_type: connect_selection.target.bss.observation,
1416 };
1417 assert_eq!(data, &expected_connect_result);
1418 });
1419
1420 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1422
1423 assert_matches!(
1425 exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1426 Poll::Pending
1427 );
1428 }
1429
1430 #[fuchsia::test]
1431 fn connecting_state_times_out() {
1432 let mut exec = fasync::TestExecutor::new();
1433 let mut test_values = test_setup();
1434
1435 let connect_selection = generate_connect_selection();
1436 let bss_description =
1437 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1438
1439 let save_fut = test_values.saved_networks_manager.store(
1441 connect_selection.target.network.clone(),
1442 connect_selection.target.credential.clone(),
1443 );
1444 let mut save_fut = pin!(save_fut);
1445 assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
1446
1447 let connecting_options =
1449 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1450 let initial_state = connecting_state(test_values.common_options, connecting_options);
1451 let fut = run_state_machine(initial_state);
1452 let mut fut = pin!(fut);
1453 let sme_fut = test_values.sme_req_stream.into_future();
1454 let mut sme_fut = pin!(sme_fut);
1455
1456 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1458
1459 let connect_txn_handle = assert_matches!(
1461 poll_sme_req(&mut exec, &mut sme_fut),
1462 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1463 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1464 assert_eq!(req.bss_description, bss_description);
1465 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1466 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1467 let (_stream, ctrl) = txn.expect("connect txn unused")
1468 .into_stream_and_control_handle();
1469 ctrl
1470 }
1471 );
1472
1473 let client_state_update = ClientStateUpdate {
1475 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1476 networks: vec![ClientNetworkState {
1477 id: connect_selection.target.network.clone(),
1478 state: fidl_policy::ConnectionState::Connecting,
1479 status: None,
1480 }],
1481 };
1482 assert_matches!(
1483 test_values.update_receiver.try_recv(),
1484 Ok(listener::Message::NotifyListeners(updates)) => {
1485 assert_eq!(updates, client_state_update);
1486 });
1487
1488 connect_txn_handle
1490 .send_on_signal_report(&fidl_internal::SignalReportIndication {
1491 rssi_dbm: -25,
1492 snr_db: 30,
1493 tx_rate_500kbps: 0,
1494 })
1495 .expect("failed to send singal report");
1496
1497 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1499
1500 assert!(exec.wake_next_timer().is_some());
1502
1503 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1505 }
1506
1507 #[fuchsia::test]
1508 fn connecting_state_successfully_scans_and_connects() {
1509 let mut exec = fasync::TestExecutor::new_with_fake_time();
1510 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(123));
1511 let mut test_values = test_setup();
1512
1513 let connect_selection = generate_connect_selection();
1514 let bss_description =
1515 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1516
1517 let expected_config = network_config::NetworkConfig::new(
1519 connect_selection.target.network.clone(),
1520 connect_selection.target.credential.clone(),
1521 connect_selection.target.saved_network_info.has_ever_connected,
1522 None,
1523 )
1524 .expect("failed to create network config");
1525 test_values.saved_networks_manager.set_lookup_compatible_response(vec![expected_config]);
1526
1527 let connecting_options =
1528 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1529 let initial_state = connecting_state(test_values.common_options, connecting_options);
1530 let fut = run_state_machine(initial_state);
1531 let mut fut = pin!(fut);
1532
1533 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1535
1536 let sme_fut = test_values.sme_req_stream.into_future();
1538 let mut sme_fut = pin!(sme_fut);
1539 let time_to_connect = zx::MonotonicDuration::from_seconds(30);
1540 let connect_txn_handle = assert_matches!(
1541 poll_sme_req(&mut exec, &mut sme_fut),
1542 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1543 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1544 assert_eq!(req.bss_description, bss_description.clone());
1545 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1546 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1547 exec.set_fake_time(fasync::MonotonicInstant::after(time_to_connect));
1549 let (_stream, ctrl) = txn.expect("connect txn unused")
1550 .into_stream_and_control_handle();
1551 ctrl
1552 }
1553 );
1554 connect_txn_handle
1555 .send_on_connect_result(&fake_successful_connect_result())
1556 .expect("failed to send connection completion");
1557
1558 let client_state_update = ClientStateUpdate {
1560 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1561 networks: vec![ClientNetworkState {
1562 id: connect_selection.target.network.clone(),
1563 state: fidl_policy::ConnectionState::Connecting,
1564 status: None,
1565 }],
1566 };
1567 assert_matches!(
1568 test_values.update_receiver.try_recv(),
1569 Ok(listener::Message::NotifyListeners(updates)) => {
1570 assert_eq!(updates, client_state_update);
1571 });
1572
1573 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1575
1576 let client_state_update = ClientStateUpdate {
1578 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1579 networks: vec![ClientNetworkState {
1580 id: connect_selection.target.network.clone(),
1581 state: fidl_policy::ConnectionState::Connected,
1582 status: None,
1583 }],
1584 };
1585 assert_matches!(
1586 test_values.update_receiver.try_recv(),
1587 Ok(listener::Message::NotifyListeners(updates)) => {
1588 assert_eq!(updates, client_state_update);
1589 });
1590
1591 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1593 let expected_connect_result = ConnectResultRecord {
1594 id: connect_selection.target.network.clone(),
1595 credential: connect_selection.target.credential.clone(),
1596 bssid: types::Bssid::from(bss_description.bssid),
1597 connect_result: fake_successful_connect_result(),
1598 scan_type: connect_selection.target.bss.observation,
1599 };
1600 assert_eq!(data, &expected_connect_result);
1601 });
1602
1603 assert_matches!(
1605 test_values.telemetry_receiver.try_recv(),
1606 Ok(TelemetryEvent::ConnectResult { iface_id: 1, policy_connect_reason, result, multiple_bss_candidates, ap_state, network_is_likely_hidden: _ }) => {
1607 assert_eq!(bss_description, ap_state.original().clone().into());
1608 assert_eq!(multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1609 assert_eq!(policy_connect_reason, Some(connect_selection.reason));
1610 assert_eq!(result, fake_successful_connect_result());
1611 }
1612 );
1613
1614 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1616
1617 assert_matches!(
1619 exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1620 Poll::Pending
1621 );
1622
1623 let status = test_values.status_reader.read_status().expect("failed to read status");
1625 assert_matches!(status, Status::Connected { .. });
1626
1627 let is_sme_reconnecting = false;
1629 let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
1630 connect_txn_handle
1631 .send_on_disconnect(&fidl_disconnect_info)
1632 .expect("failed to send disconnection event");
1633 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1634
1635 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
1637 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
1638 });
1639
1640 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1642
1643 let expected_recorded_connection = ConnectionRecord {
1644 id: connect_selection.target.network.clone(),
1645 credential: connect_selection.target.credential.clone(),
1646 data: PastConnectionData {
1647 bssid: types::Bssid::from(bss_description.bssid),
1648 disconnect_time: fasync::MonotonicInstant::now(),
1649 connection_uptime: zx::MonotonicDuration::from_minutes(0),
1650 disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
1651 signal_at_disconnect: types::Signal {
1652 rssi_dbm: bss_description.rssi_dbm,
1653 snr_db: bss_description.snr_db,
1654 },
1655 average_tx_rate: 0,
1657 },
1658 };
1659 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [data] => {
1660 assert_eq!(data, &expected_recorded_connection);
1661 });
1662 }
1663
1664 #[fuchsia::test]
1665 fn connecting_state_fails_to_connect_and_retries() {
1666 let mut exec = fasync::TestExecutor::new();
1667 let mut test_values = test_setup();
1668
1669 let connect_selection = generate_connect_selection();
1670 let bss_description =
1671 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1672
1673 let connecting_options =
1674 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
1675 let initial_state = connecting_state(test_values.common_options, connecting_options);
1676 let fut = run_state_machine(initial_state);
1677 let mut fut = pin!(fut);
1678 let sme_fut = test_values.sme_req_stream.into_future();
1679 let mut sme_fut = pin!(sme_fut);
1680
1681 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1683
1684 let mut connect_txn_handle = assert_matches!(
1686 poll_sme_req(&mut exec, &mut sme_fut),
1687 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1688 assert_eq!(req.ssid, connect_selection.target.network.ssid.to_vec());
1689 let (_stream, ctrl) = txn.expect("connect txn unused")
1691 .into_stream_and_control_handle();
1692 ctrl
1693 }
1694 );
1695 let connect_result = fidl_sme::ConnectResult {
1696 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1697 ..fake_successful_connect_result()
1698 };
1699 connect_txn_handle
1700 .send_on_connect_result(&connect_result)
1701 .expect("failed to send connection completion");
1702
1703 let client_state_update = ClientStateUpdate {
1705 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1706 networks: vec![ClientNetworkState {
1707 id: types::NetworkIdentifier {
1708 ssid: connect_selection.target.network.ssid.clone(),
1709 security_type: types::SecurityType::Wpa2,
1710 },
1711 state: fidl_policy::ConnectionState::Connecting,
1712 status: None,
1713 }],
1714 };
1715 assert_matches!(
1716 test_values.update_receiver.try_recv(),
1717 Ok(listener::Message::NotifyListeners(updates)) => {
1718 assert_eq!(updates, client_state_update);
1719 });
1720
1721 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1723 assert!(exec.wake_next_timer().is_some());
1724 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1725
1726 assert_matches!(
1728 test_values.telemetry_receiver.try_recv(),
1729 Ok(TelemetryEvent::ConnectResult { iface_id: 1, policy_connect_reason, result, multiple_bss_candidates, ap_state, network_is_likely_hidden: _ }) => {
1730 assert_eq!(bss_description, ap_state.original().clone().into());
1731 assert_eq!(multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1732 assert_eq!(policy_connect_reason, Some(connect_selection.reason));
1733 assert_eq!(result, connect_result);
1734 }
1735 );
1736
1737 assert_matches!(
1739 poll_sme_req(&mut exec, &mut sme_fut),
1740 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::FailedToConnect }) => {
1741 responder.send().expect("could not send sme response");
1742 }
1743 );
1744
1745 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1747
1748 connect_txn_handle = assert_matches!(
1750 poll_sme_req(&mut exec, &mut sme_fut),
1751 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1752 assert_eq!(req.ssid, connect_selection.target.network.ssid.to_vec());
1753 assert_eq!(req.bss_description, Sequestered::release(connect_selection.target.bss.bss_description));
1754 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1755 let (_stream, ctrl) = txn.expect("connect txn unused")
1757 .into_stream_and_control_handle();
1758 ctrl
1759 }
1760 );
1761 let connect_result = fake_successful_connect_result();
1762 connect_txn_handle
1763 .send_on_connect_result(&connect_result)
1764 .expect("failed to send connection completion");
1765
1766 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1768
1769 assert_matches!(
1771 test_values.update_receiver.try_recv(),
1772 Ok(listener::Message::NotifyListeners(ClientStateUpdate {
1773 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1774 networks
1775 })) => {
1776 assert!(networks.is_empty());
1777 }
1778 );
1779
1780 assert_matches!(
1782 test_values.defect_receiver.try_recv(),
1783 Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
1784 );
1785
1786 let client_state_update = ClientStateUpdate {
1788 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1789 networks: vec![ClientNetworkState {
1790 id: types::NetworkIdentifier {
1791 ssid: connect_selection.target.network.ssid.clone(),
1792 security_type: types::SecurityType::Wpa2,
1793 },
1794 state: fidl_policy::ConnectionState::Connected,
1795 status: None,
1796 }],
1797 };
1798 assert_matches!(
1799 test_values.update_receiver.try_recv(),
1800 Ok(listener::Message::NotifyListeners(updates)) => {
1801 assert_eq!(updates, client_state_update);
1802 });
1803
1804 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1806
1807 assert_matches!(
1809 exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
1810 Poll::Pending
1811 );
1812 }
1813
1814 #[fuchsia::test]
1815 fn connecting_state_fails_to_connect_at_max_retries() {
1816 let mut exec = fasync::TestExecutor::new();
1817 let mut test_values = test_setup();
1818
1819 let connect_selection = generate_connect_selection();
1820 let bss_description =
1821 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1822
1823 assert!(
1825 exec.run_singlethreaded(test_values.saved_networks_manager.store(
1826 connect_selection.target.network.clone(),
1827 connect_selection.target.credential.clone()
1828 ),)
1829 .expect("Failed to save network")
1830 .is_none()
1831 );
1832
1833 let connecting_options = ConnectingOptions {
1834 connect_selection: connect_selection.clone(),
1835 attempt_counter: MAX_CONNECTION_ATTEMPTS - 1,
1836 };
1837 let initial_state = connecting_state(test_values.common_options, connecting_options);
1838 let fut = run_state_machine(initial_state);
1839 let mut fut = pin!(fut);
1840 let sme_fut = test_values.sme_req_stream.into_future();
1841 let mut sme_fut = pin!(sme_fut);
1842
1843 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1845
1846 assert_matches!(
1848 poll_sme_req(&mut exec, &mut sme_fut),
1849 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1850 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1851 assert_eq!(req.bss_description, bss_description.clone());
1852 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1853 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1854 let (_stream, ctrl) = txn.expect("connect txn unused")
1856 .into_stream_and_control_handle();
1857 let connect_result = fidl_sme::ConnectResult {
1858 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1859 ..fake_successful_connect_result()
1860 };
1861 ctrl
1862 .send_on_connect_result(&connect_result)
1863 .expect("failed to send connection completion");
1864 }
1865 );
1866
1867 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1870
1871 let client_state_update = ClientStateUpdate {
1873 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1874 networks: vec![ClientNetworkState {
1875 id: connect_selection.target.network.clone(),
1876 state: fidl_policy::ConnectionState::Failed,
1877 status: Some(fidl_policy::DisconnectStatus::ConnectionFailed),
1878 }],
1879 };
1880 assert_matches!(
1881 test_values.update_receiver.try_recv(),
1882 Ok(listener::Message::NotifyListeners(updates)) => {
1883 assert_eq!(updates, client_state_update);
1884 });
1885
1886 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1888 let connect_result = fidl_sme::ConnectResult {
1889 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1890 is_credential_rejected: false,
1891 is_reconnect: false,
1892 };
1893 let expected_connect_result = ConnectResultRecord {
1894 id: connect_selection.target.network.clone(),
1895 credential: connect_selection.target.credential.clone(),
1896 bssid: types::Bssid::from(bss_description.bssid),
1897 connect_result,
1898 scan_type: connect_selection.target.bss.observation,
1899 };
1900 assert_eq!(data, &expected_connect_result);
1901 });
1902
1903 assert_matches!(
1905 test_values.defect_receiver.try_recv(),
1906 Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
1907 );
1908 }
1909
1910 #[fuchsia::test]
1911 fn connecting_state_fails_to_connect_with_bad_credentials() {
1912 let mut exec = fasync::TestExecutor::new();
1913 let mut test_values = test_setup();
1914
1915 let connect_selection = generate_connect_selection();
1916 let bss_description =
1917 Sequestered::release(connect_selection.target.bss.bss_description.clone());
1918
1919 assert!(
1920 exec.run_singlethreaded(test_values.saved_networks_manager.store(
1921 connect_selection.target.network.clone(),
1922 connect_selection.target.credential.clone()
1923 ),)
1924 .expect("Failed to save network")
1925 .is_none()
1926 );
1927
1928 let connecting_options = ConnectingOptions {
1929 connect_selection: connect_selection.clone(),
1930 attempt_counter: MAX_CONNECTION_ATTEMPTS - 1,
1931 };
1932 let initial_state = connecting_state(test_values.common_options, connecting_options);
1933 let fut = run_state_machine(initial_state);
1934 let mut fut = pin!(fut);
1935 let sme_fut = test_values.sme_req_stream.into_future();
1936 let mut sme_fut = pin!(sme_fut);
1937
1938 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
1940
1941 assert_matches!(
1943 poll_sme_req(&mut exec, &mut sme_fut),
1944 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
1945 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
1946 assert_eq!(req.bss_description, bss_description.clone());
1947 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
1948 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
1949 let (_stream, ctrl) = txn.expect("connect txn unused")
1951 .into_stream_and_control_handle();
1952 let connect_result = fidl_sme::ConnectResult {
1953 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1954 is_credential_rejected: true,
1955 ..fake_successful_connect_result()
1956 };
1957 ctrl
1958 .send_on_connect_result(&connect_result)
1959 .expect("failed to send connection completion");
1960 }
1961 );
1962
1963 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
1966
1967 let client_state_update = ClientStateUpdate {
1969 state: fidl_policy::WlanClientState::ConnectionsEnabled,
1970 networks: vec![ClientNetworkState {
1971 id: connect_selection.target.network.clone(),
1972 state: fidl_policy::ConnectionState::Failed,
1973 status: Some(fidl_policy::DisconnectStatus::CredentialsFailed),
1974 }],
1975 };
1976 assert_matches!(
1977 test_values.update_receiver.try_recv(),
1978 Ok(listener::Message::NotifyListeners(updates)) => {
1979 assert_eq!(updates, client_state_update);
1980 });
1981
1982 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
1984 let connect_result = fidl_sme::ConnectResult {
1985 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1986 is_credential_rejected: true,
1987 is_reconnect: false,
1988 };
1989 let expected_connect_result = ConnectResultRecord {
1990 id: connect_selection.target.network.clone(),
1991 credential: connect_selection.target.credential.clone(),
1992 bssid: types::Bssid::from(bss_description.bssid),
1993 connect_result,
1994 scan_type: connect_selection.target.bss.observation,
1995 };
1996 assert_eq!(data, &expected_connect_result);
1997 });
1998
1999 assert_matches!(test_values.defect_receiver.try_recv(), Err(e) if e.is_closed());
2001 }
2002
2003 #[fuchsia::test]
2004 fn connecting_state_gets_duplicate_connect_selection() {
2005 let mut exec = fasync::TestExecutor::new();
2006 let mut test_values = test_setup();
2007
2008 let connect_selection = generate_connect_selection();
2009 let bss_description =
2010 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2011
2012 let connecting_options =
2013 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2014 let initial_state = connecting_state(test_values.common_options, connecting_options);
2015 let fut = run_state_machine(initial_state);
2016 let mut fut = pin!(fut);
2017 let sme_fut = test_values.sme_req_stream.into_future();
2018 let mut sme_fut = pin!(sme_fut);
2019
2020 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2022
2023 let client_state_update = ClientStateUpdate {
2025 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2026 networks: vec![ClientNetworkState {
2027 id: types::NetworkIdentifier {
2028 ssid: connect_selection.target.network.ssid.clone(),
2029 security_type: types::SecurityType::Wpa2,
2030 },
2031 state: fidl_policy::ConnectionState::Connecting,
2032 status: None,
2033 }],
2034 };
2035 assert_matches!(
2036 test_values.update_receiver.try_recv(),
2037 Ok(listener::Message::NotifyListeners(updates)) => {
2038 assert_eq!(updates, client_state_update);
2039 });
2040
2041 let mut client = Client::new(test_values.client_req_sender);
2043 let duplicate_request = types::ConnectSelection {
2044 reason: types::ConnectReason::ProactiveNetworkSwitch,
2046 ..connect_selection.clone()
2047 };
2048 client.connect(duplicate_request).expect("failed to make request");
2049
2050 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2052
2053 let connect_txn_handle = assert_matches!(
2055 poll_sme_req(&mut exec, &mut sme_fut),
2056 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
2057 assert_eq!(req.ssid, connect_selection.target.network.ssid.clone().to_vec());
2058 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
2059 assert_eq!(req.bss_description, bss_description);
2060 assert_eq!(req.multiple_bss_candidates, connect_selection.target.network_has_multiple_bss);
2061 let (_stream, ctrl) = txn.expect("connect txn unused")
2063 .into_stream_and_control_handle();
2064 ctrl
2065 }
2066 );
2067 connect_txn_handle
2068 .send_on_connect_result(&fake_successful_connect_result())
2069 .expect("failed to send connection completion");
2070
2071 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2073
2074 let client_state_update = ClientStateUpdate {
2076 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2077 networks: vec![ClientNetworkState {
2078 id: connect_selection.target.network.clone(),
2079 state: fidl_policy::ConnectionState::Connected,
2080 status: None,
2081 }],
2082 };
2083 assert_matches!(
2084 test_values.update_receiver.try_recv(),
2085 Ok(listener::Message::NotifyListeners(updates)) => {
2086 assert_eq!(updates, client_state_update);
2087 });
2088
2089 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2091
2092 assert_matches!(
2094 exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
2095 Poll::Pending
2096 );
2097 }
2098
2099 #[fuchsia::test]
2100 fn connecting_state_has_broken_sme() {
2101 let mut exec = fasync::TestExecutor::new();
2102 let test_values = test_setup();
2103
2104 let connect_selection = generate_connect_selection();
2105
2106 let connecting_options =
2107 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2108 let initial_state = connecting_state(test_values.common_options, connecting_options);
2109 let fut = run_state_machine(initial_state);
2110 let mut fut = pin!(fut);
2111
2112 drop(test_values.sme_req_stream);
2114
2115 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2117 }
2118
2119 #[fuchsia::test]
2120 fn connected_state_gets_disconnect_request() {
2121 let mut exec = fasync::TestExecutor::new_with_fake_time();
2122 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2123
2124 let mut test_values = test_setup();
2125 let mut telemetry_receiver = test_values.telemetry_receiver;
2126 let connect_selection = generate_connect_selection();
2127 let bss_description =
2128 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2129 let init_ap_state =
2130 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2131
2132 let (connect_txn_proxy, _connect_txn_stream) =
2133 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2134 let options = ConnectedOptions::new(
2135 &mut test_values.common_options,
2136 Box::new(init_ap_state.clone()),
2137 connect_selection.target.network_has_multiple_bss,
2138 connect_selection.target.network.clone(),
2139 connect_selection.target.credential.clone(),
2140 connect_selection.reason,
2141 connect_txn_proxy.take_event_stream(),
2142 false,
2143 );
2144 let initial_state = connected_state(test_values.common_options, options);
2145 let fut = run_state_machine(initial_state);
2146 let mut fut = pin!(fut);
2147 let sme_fut = test_values.sme_req_stream.into_future();
2148 let mut sme_fut = pin!(sme_fut);
2149
2150 let disconnect_time =
2151 fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2152
2153 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2155
2156 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2158 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2159 });
2160
2161 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2163
2164 exec.set_fake_time(fasync::MonotonicInstant::after(
2166 AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2167 ));
2168 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2169 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2170 assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2171 });
2172
2173 exec.set_fake_time(fasync::MonotonicInstant::after(
2175 METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2176 ));
2177 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2178 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2179 assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2180 });
2181
2182 exec.set_fake_time(disconnect_time);
2184 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2185
2186 let mut client = Client::new(test_values.client_req_sender);
2188 let (sender, mut receiver) = oneshot::channel();
2189 client
2190 .disconnect(types::DisconnectReason::FidlStopClientConnectionsRequest, sender)
2191 .expect("failed to make request");
2192
2193 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2195
2196 assert_matches!(
2198 poll_sme_req(&mut exec, &mut sme_fut),
2199 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::FidlStopClientConnectionsRequest }) => {
2200 responder.send().expect("could not send sme response");
2201 }
2202 );
2203
2204 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2206
2207 let client_state_update = ClientStateUpdate {
2209 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2210 networks: vec![ClientNetworkState {
2211 id: connect_selection.target.network.clone(),
2212 state: fidl_policy::ConnectionState::Disconnected,
2213 status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
2214 }],
2215 };
2216 assert_matches!(
2217 test_values.update_receiver.try_recv(),
2218 Ok(listener::Message::NotifyListeners(updates)) => {
2219 assert_eq!(updates, client_state_update);
2220 });
2221 assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(())));
2222
2223 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2225 assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2226 assert!(!track_subsequent_downtime);
2227 assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2228 assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2229 assert!(!is_sme_reconnecting);
2230 assert_eq!(disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::FidlStopClientConnectionsRequest));
2231 assert_eq!(previous_connect_reason, connect_selection.reason);
2232 assert_eq!(ap_state, init_ap_state.clone());
2233 });
2234 });
2235 });
2236
2237 let expected_recorded_connection = ConnectionRecord {
2239 id: connect_selection.target.network.clone(),
2240 credential: connect_selection.target.credential.clone(),
2241 data: PastConnectionData {
2242 bssid: init_ap_state.original().bssid,
2243 disconnect_time,
2244 connection_uptime: zx::MonotonicDuration::from_hours(12),
2245 disconnect_reason: types::DisconnectReason::FidlStopClientConnectionsRequest,
2246 signal_at_disconnect: types::Signal {
2247 rssi_dbm: bss_description.rssi_dbm,
2248 snr_db: bss_description.snr_db,
2249 },
2250 average_tx_rate: 0,
2252 },
2253 };
2254 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2255 assert_eq!(connection_data, &expected_recorded_connection);
2256 });
2257 }
2258
2259 #[fuchsia::test]
2260 fn connected_state_records_unexpected_disconnect() {
2261 let mut exec = fasync::TestExecutor::new_with_fake_time();
2262 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2263
2264 let mut test_values = test_setup();
2265 let mut telemetry_receiver = test_values.telemetry_receiver;
2266
2267 let connect_selection = generate_connect_selection();
2268 let bss_description =
2269 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2270 let init_ap_state =
2271 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2272
2273 let save_fut = test_values.saved_networks_manager.store(
2275 connect_selection.target.network.clone(),
2276 connect_selection.target.credential.clone(),
2277 );
2278 let mut save_fut = pin!(save_fut);
2279 assert_matches!(exec.run_until_stalled(&mut save_fut), Poll::Ready(Ok(None)));
2280
2281 let (connect_txn_proxy, connect_txn_stream) =
2282 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2283 let connect_txn_handle = connect_txn_stream.control_handle();
2284 let options = ConnectedOptions::new(
2285 &mut test_values.common_options,
2286 Box::new(init_ap_state.clone()),
2287 connect_selection.target.network_has_multiple_bss,
2288 connect_selection.target.network.clone(),
2289 connect_selection.target.credential.clone(),
2290 connect_selection.reason,
2291 connect_txn_proxy.take_event_stream(),
2292 false,
2293 );
2294
2295 let initial_state = connected_state(test_values.common_options, options);
2297 let fut = run_state_machine(initial_state);
2298 let mut fut = pin!(fut);
2299 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2300
2301 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2303 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2304 });
2305
2306 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2308
2309 let disconnect_time =
2310 fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2311 exec.set_fake_time(disconnect_time);
2312
2313 let fidl_disconnect_info = generate_disconnect_info(false);
2315 connect_txn_handle
2316 .send_on_disconnect(&fidl_disconnect_info)
2317 .expect("failed to send disconnection event");
2318 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2319
2320 let expected_recorded_connection = ConnectionRecord {
2322 id: connect_selection.target.network.clone(),
2323 credential: connect_selection.target.credential.clone(),
2324 data: PastConnectionData {
2325 bssid: init_ap_state.original().bssid,
2326 disconnect_time,
2327 connection_uptime: zx::MonotonicDuration::from_hours(12),
2328 disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
2329 signal_at_disconnect: types::Signal {
2330 rssi_dbm: bss_description.rssi_dbm,
2331 snr_db: bss_description.snr_db,
2332 },
2333 average_tx_rate: 0,
2335 },
2336 };
2337 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2338 assert_eq!(connection_data, &expected_recorded_connection);
2339 });
2340
2341 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2343 assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2344 assert!(track_subsequent_downtime);
2345 assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2346 assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2347 assert!(!is_sme_reconnecting);
2348 assert_eq!(disconnect_source, fidl_disconnect_info.disconnect_source);
2349 assert_eq!(previous_connect_reason, connect_selection.reason);
2350 assert_eq!(ap_state, init_ap_state);
2351 });
2352 });
2353 });
2354 }
2355
2356 #[fuchsia::test]
2357 fn connected_state_reconnect_resets_connected_duration() {
2358 let mut exec = fasync::TestExecutor::new_with_fake_time();
2359 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2360
2361 let mut test_values = test_setup();
2362 let mut telemetry_receiver = test_values.telemetry_receiver;
2363
2364 let connect_selection = generate_connect_selection();
2365 let bss_description =
2366 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2367 let ap_state =
2368 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2369
2370 let (connect_txn_proxy, connect_txn_stream) =
2371 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2372 let connect_txn_handle = connect_txn_stream.control_handle();
2373 let options = ConnectedOptions::new(
2374 &mut test_values.common_options,
2375 Box::new(ap_state.clone()),
2376 connect_selection.target.network_has_multiple_bss,
2377 connect_selection.target.network.clone(),
2378 connect_selection.target.credential.clone(),
2379 connect_selection.reason,
2380 connect_txn_proxy.take_event_stream(),
2381 false,
2382 );
2383 let initial_state = connected_state(test_values.common_options, options);
2384 let fut = run_state_machine(initial_state);
2385 let mut fut = pin!(fut);
2386
2387 let disconnect_time =
2388 fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2389
2390 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2392
2393 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2395 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2396 });
2397
2398 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2400
2401 exec.set_fake_time(fasync::MonotonicInstant::after(
2403 AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2404 ));
2405 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2406 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2407 assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2408 });
2409
2410 exec.set_fake_time(fasync::MonotonicInstant::after(
2412 METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2413 ));
2414 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2415 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2416 assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2417 });
2418
2419 exec.set_fake_time(disconnect_time);
2421 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2422
2423 let is_sme_reconnecting = true;
2425 let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
2426 connect_txn_handle
2427 .send_on_disconnect(&fidl_disconnect_info)
2428 .expect("failed to send disconnection event");
2429 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2430
2431 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2433 assert_matches!(event, TelemetryEvent::Disconnected { info: Some(info), .. } => {
2434 assert_eq!(info.connected_duration, zx::MonotonicDuration::from_hours(12));
2435 });
2436 });
2437
2438 exec.set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(1)));
2440 let connect_result =
2441 fidl_sme::ConnectResult { is_reconnect: true, ..fake_successful_connect_result() };
2442 connect_txn_handle
2443 .send_on_connect_result(&connect_result)
2444 .expect("failed to send connect result event");
2445
2446 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2447 assert_matches!(telemetry_receiver.try_recv(), Ok(TelemetryEvent::ConnectResult { .. }));
2448
2449 exec.set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(2)));
2451 let is_sme_reconnecting = false;
2452 let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
2453 connect_txn_handle
2454 .send_on_disconnect(&fidl_disconnect_info)
2455 .expect("failed to send disconnection event");
2456 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2457
2458 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2460 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
2461 assert_eq!(info.unwrap().connected_duration, zx::MonotonicDuration::from_hours(2));
2462 });
2463 });
2464 }
2465
2466 #[fuchsia::test]
2467 fn connected_state_records_unexpected_disconnect_unspecified_bss() {
2468 let mut exec = fasync::TestExecutor::new_with_fake_time();
2469 let connection_attempt_time = fasync::MonotonicInstant::from_nanos(0);
2470 exec.set_fake_time(connection_attempt_time);
2471 let mut test_values = test_setup();
2472
2473 let connect_selection = generate_connect_selection();
2474 let bss_description =
2475 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2476
2477 let expected_config = network_config::NetworkConfig::new(
2479 connect_selection.target.network.clone(),
2480 connect_selection.target.credential.clone(),
2481 false,
2482 None,
2483 )
2484 .expect("failed to create network config");
2485 test_values.saved_networks_manager.set_lookup_compatible_response(vec![expected_config]);
2486
2487 let connecting_options =
2488 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
2489 let initial_state = connecting_state(test_values.common_options, connecting_options);
2490 let state_fut = run_state_machine(initial_state);
2491 let mut state_fut = pin!(state_fut);
2492 let sme_fut = test_values.sme_req_stream.into_future();
2493 let mut sme_fut = pin!(sme_fut);
2494
2495 assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2497
2498 assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2500
2501 let time_to_connect = zx::MonotonicDuration::from_seconds(10);
2502 exec.set_fake_time(fasync::MonotonicInstant::after(time_to_connect));
2503
2504 let connect_txn_handle = assert_matches!(
2506 poll_sme_req(&mut exec, &mut sme_fut),
2507 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req: _, txn, control_handle: _ }) => {
2508 let (_stream, ctrl) = txn.expect("connect txn unused")
2510 .into_stream_and_control_handle();
2511 ctrl
2512 }
2513 );
2514 connect_txn_handle
2515 .send_on_connect_result(&fake_successful_connect_result())
2516 .expect("failed to send connection completion");
2517 assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2518
2519 let disconnect_time = fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(5));
2521 exec.set_fake_time(disconnect_time);
2522 let is_sme_reconnecting = false;
2523 connect_txn_handle
2524 .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2525 .expect("failed to send disconnection event");
2526 assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2527
2528 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2530 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2531 });
2532
2533 assert_matches!(exec.run_until_stalled(&mut state_fut), Poll::Pending);
2534 let expected_recorded_connection = ConnectionRecord {
2536 id: connect_selection.target.network.clone(),
2537 credential: connect_selection.target.credential.clone(),
2538 data: PastConnectionData {
2539 bssid: types::Bssid::from(bss_description.bssid),
2540 disconnect_time,
2541 connection_uptime: zx::MonotonicDuration::from_hours(5),
2542 disconnect_reason: types::DisconnectReason::DisconnectDetectedFromSme,
2543 signal_at_disconnect: types::Signal {
2544 rssi_dbm: bss_description.rssi_dbm,
2545 snr_db: bss_description.snr_db,
2546 },
2547 average_tx_rate: 0,
2548 },
2549 };
2550 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2551 assert_eq!(connection_data, &expected_recorded_connection);
2552 });
2553 }
2554
2555 #[fuchsia::test]
2556 fn connected_state_gets_duplicate_connect_selection() {
2557 let mut exec = fasync::TestExecutor::new_with_fake_time();
2558 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2559 let mut test_values = test_setup();
2560 let mut telemetry_receiver = test_values.telemetry_receiver;
2561
2562 let connect_selection = generate_connect_selection();
2563 let bss_description =
2564 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2565 let ap_state =
2566 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2567
2568 let (connect_txn_proxy, _connect_txn_stream) =
2569 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2570 let options = ConnectedOptions::new(
2571 &mut test_values.common_options,
2572 Box::new(ap_state.clone()),
2573 connect_selection.target.network_has_multiple_bss,
2574 connect_selection.target.network.clone(),
2575 connect_selection.target.credential.clone(),
2576 connect_selection.reason,
2577 connect_txn_proxy.take_event_stream(),
2578 false,
2579 );
2580 let initial_state = connected_state(test_values.common_options, options);
2581 let fut = run_state_machine(initial_state);
2582 let mut fut = pin!(fut);
2583 let sme_fut = test_values.sme_req_stream.into_future();
2584 let mut sme_fut = pin!(sme_fut);
2585
2586 let mut client = Client::new(test_values.client_req_sender);
2588 client.connect(connect_selection.clone()).expect("failed to make request");
2589
2590 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2592
2593 assert_matches!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending);
2595
2596 assert_matches!(telemetry_receiver.try_recv(), Err(_));
2598 }
2599
2600 #[fuchsia::test]
2601 fn connected_state_gets_different_connect_selection() {
2602 let mut exec = fasync::TestExecutor::new_with_fake_time();
2603 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
2604
2605 let mut test_values = test_setup();
2606 let mut telemetry_receiver = test_values.telemetry_receiver;
2607
2608 let first_connect_selection = generate_connect_selection();
2609 let first_bss_desc =
2610 Sequestered::release(first_connect_selection.target.bss.bss_description.clone());
2611 let first_ap_state =
2612 types::ApState::from(BssDescription::try_from(first_bss_desc.clone()).unwrap());
2613 let second_connect_selection = types::ConnectSelection {
2614 reason: types::ConnectReason::ProactiveNetworkSwitch,
2615 ..generate_connect_selection()
2616 };
2617
2618 let (connect_txn_proxy, _connect_txn_stream) =
2619 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2620 let options = ConnectedOptions::new(
2621 &mut test_values.common_options,
2622 Box::new(first_ap_state.clone()),
2623 first_connect_selection.target.network_has_multiple_bss,
2624 first_connect_selection.target.network.clone(),
2625 first_connect_selection.target.credential.clone(),
2626 first_connect_selection.reason,
2627 connect_txn_proxy.take_event_stream(),
2628 false,
2629 );
2630 let initial_state = connected_state(test_values.common_options, options);
2631 let fut = run_state_machine(initial_state);
2632 let mut fut = pin!(fut);
2633 let sme_fut = test_values.sme_req_stream.into_future();
2634 let mut sme_fut = pin!(sme_fut);
2635
2636 let disconnect_time =
2637 fasync::MonotonicInstant::after(zx::MonotonicDuration::from_hours(12));
2638
2639 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2641
2642 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2644 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2645 });
2646
2647 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2649
2650 exec.set_fake_time(fasync::MonotonicInstant::after(
2652 AVERAGE_SCORE_DELTA_MINIMUM_DURATION + zx::MonotonicDuration::from_seconds(1),
2653 ));
2654 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2655 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2656 assert_matches!(event, TelemetryEvent::PostConnectionSignals { .. });
2657 });
2658
2659 exec.set_fake_time(fasync::MonotonicInstant::after(
2661 METRICS_SHORT_CONNECT_DURATION + zx::MonotonicDuration::from_seconds(1),
2662 ));
2663 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2664 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2665 assert_matches!(event, TelemetryEvent::LongDurationSignals { .. });
2666 });
2667
2668 exec.set_fake_time(disconnect_time);
2670 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2671
2672 let mut client = Client::new(test_values.client_req_sender);
2674 client.connect(second_connect_selection.clone()).expect("failed to make request");
2675
2676 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2678
2679 assert_matches!(
2682 poll_sme_req(&mut exec, &mut sme_fut),
2683 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch }) => {
2684 responder.send().expect("could not send sme response");
2685 }
2686 );
2687 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2690 let connect_txn_handle = assert_matches!(
2692 poll_sme_req(&mut exec, &mut sme_fut),
2693 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
2694 assert_eq!(req.ssid, second_connect_selection.target.network.ssid.clone().to_vec());
2695 let (_stream, ctrl) = txn.expect("connect txn unused")
2697 .into_stream_and_control_handle();
2698 ctrl
2699 }
2700 );
2701 connect_txn_handle
2702 .send_on_connect_result(&fake_successful_connect_result())
2703 .expect("failed to send connection completion");
2704 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2706
2707 let client_state_update = ClientStateUpdate {
2709 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2710 networks: vec![ClientNetworkState {
2711 id: first_connect_selection.target.network.clone(),
2712 state: fidl_policy::ConnectionState::Disconnected,
2713 status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
2714 }],
2715 };
2716 assert_matches!(
2717 test_values.update_receiver.try_recv(),
2718 Ok(listener::Message::NotifyListeners(updates)) => {
2719 assert_eq!(updates, client_state_update);
2720 });
2721
2722 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
2724 assert_matches!(event, TelemetryEvent::Disconnected { track_subsequent_downtime, info: Some(info) } => {
2725 assert!(!track_subsequent_downtime);
2726 assert_matches!(info, DisconnectInfo {connected_duration, is_sme_reconnecting, disconnect_source, previous_connect_reason, ap_state, ..} => {
2727 assert_eq!(connected_duration, zx::MonotonicDuration::from_hours(12));
2728 assert!(!is_sme_reconnecting);
2729 assert_eq!(disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch));
2730 assert_eq!(previous_connect_reason, first_connect_selection.reason);
2731 assert_eq!(ap_state, first_ap_state.clone());
2732 });
2733 });
2734 });
2735
2736 let client_state_update = ClientStateUpdate {
2738 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2739 networks: vec![ClientNetworkState {
2740 id: types::NetworkIdentifier {
2741 ssid: second_connect_selection.target.network.ssid.clone(),
2742 security_type: types::SecurityType::Wpa2,
2743 },
2744 state: fidl_policy::ConnectionState::Connecting,
2745 status: None,
2746 }],
2747 };
2748 assert_matches!(
2749 test_values.update_receiver.try_recv(),
2750 Ok(listener::Message::NotifyListeners(updates)) => {
2751 assert_eq!(updates, client_state_update);
2752 });
2753 let client_state_update = ClientStateUpdate {
2755 state: fidl_policy::WlanClientState::ConnectionsEnabled,
2756 networks: vec![ClientNetworkState {
2757 id: types::NetworkIdentifier {
2758 ssid: second_connect_selection.target.network.ssid.clone(),
2759 security_type: types::SecurityType::Wpa2,
2760 },
2761 state: fidl_policy::ConnectionState::Connected,
2762 status: None,
2763 }],
2764 };
2765 assert_matches!(
2766 test_values.update_receiver.try_recv(),
2767 Ok(listener::Message::NotifyListeners(updates)) => {
2768 assert_eq!(updates, client_state_update);
2769 });
2770
2771 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2773
2774 assert_matches!(
2776 exec.run_until_stalled(&mut test_values.update_receiver.into_future()),
2777 Poll::Pending
2778 );
2779
2780 let expected_recorded_connection = ConnectionRecord {
2782 id: first_connect_selection.target.network.clone(),
2783 credential: first_connect_selection.target.credential.clone(),
2784 data: PastConnectionData {
2785 bssid: types::Bssid::from(first_bss_desc.bssid),
2786 disconnect_time,
2787 connection_uptime: zx::MonotonicDuration::from_hours(12),
2788 disconnect_reason: types::DisconnectReason::ProactiveNetworkSwitch,
2789 signal_at_disconnect: types::Signal {
2790 rssi_dbm: first_bss_desc.rssi_dbm,
2791 snr_db: first_bss_desc.snr_db,
2792 },
2793 average_tx_rate: 0,
2795 },
2796 };
2797 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [connection_data] => {
2798 assert_eq!(connection_data, &expected_recorded_connection);
2799 });
2800 }
2801
2802 #[fuchsia::test]
2803 fn connected_state_notified_of_network_disconnect_no_sme_reconnect_short_uptime_no_retry() {
2804 let mut exec = fasync::TestExecutor::new_with_fake_time();
2805 let mut test_values = test_setup();
2806
2807 let connect_selection = generate_connect_selection();
2808 let bss_description =
2809 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2810 let ap_state =
2811 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2812
2813 let (connect_txn_proxy, connect_txn_stream) =
2814 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2815 let connect_txn_handle = connect_txn_stream.control_handle();
2816 let options = ConnectedOptions::new(
2817 &mut test_values.common_options,
2818 Box::new(ap_state.clone()),
2819 connect_selection.target.network_has_multiple_bss,
2820 connect_selection.target.network.clone(),
2821 connect_selection.target.credential.clone(),
2822 connect_selection.reason,
2823 connect_txn_proxy.take_event_stream(),
2824 false,
2825 );
2826 let initial_state = connected_state(test_values.common_options, options);
2827 let fut = run_state_machine(initial_state);
2828 let mut fut = pin!(fut);
2829 let sme_fut = test_values.sme_req_stream.into_future();
2830 let mut sme_fut = pin!(sme_fut);
2831
2832 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2834
2835 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2837 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2838 });
2839
2840 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2842
2843 let is_sme_reconnecting = false;
2845 connect_txn_handle
2846 .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2847 .expect("failed to send disconnection event");
2848
2849 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2851
2852 assert_matches!(
2854 poll_sme_req(&mut exec, &mut sme_fut),
2855 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
2856 responder.send().expect("could not send sme response");
2857 }
2858 );
2859
2860 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2862 }
2863
2864 #[fuchsia::test]
2865 fn connected_state_notified_of_network_disconnect_sme_reconnect_successfully() {
2866 let mut exec = fasync::TestExecutor::new();
2867 let mut test_values = test_setup();
2868
2869 let connect_selection = generate_connect_selection();
2870 let bss_description =
2871 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2872 let ap_state =
2873 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2874
2875 let (connect_txn_proxy, connect_txn_stream) =
2876 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2877 let connect_txn_handle = connect_txn_stream.control_handle();
2878 let options = ConnectedOptions::new(
2879 &mut test_values.common_options,
2880 Box::new(ap_state.clone()),
2881 connect_selection.target.network_has_multiple_bss,
2882 connect_selection.target.network.clone(),
2883 connect_selection.target.credential.clone(),
2884 connect_selection.reason,
2885 connect_txn_proxy.take_event_stream(),
2886 false,
2887 );
2888 let initial_state = connected_state(test_values.common_options, options);
2889 let fut = run_state_machine(initial_state);
2890 let mut fut = pin!(fut);
2891
2892 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2894
2895 let is_sme_reconnecting = true;
2897 connect_txn_handle
2898 .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2899 .expect("failed to send disconnection event");
2900
2901 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2903
2904 let connect_result =
2906 fidl_sme::ConnectResult { is_reconnect: true, ..fake_successful_connect_result() };
2907 connect_txn_handle
2908 .send_on_connect_result(&connect_result)
2909 .expect("failed to send reconnection result");
2910
2911 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2913
2914 assert_matches!(test_values.update_receiver.try_recv(), Err(_));
2916 }
2917
2918 #[fuchsia::test]
2919 fn connected_state_notified_of_network_disconnect_sme_reconnect_unsuccessfully() {
2920 let mut exec = fasync::TestExecutor::new_with_fake_time();
2921 let mut test_values = test_setup();
2922 let connect_selection = generate_connect_selection();
2923 let bss_description =
2924 Sequestered::release(connect_selection.target.bss.bss_description.clone());
2925 let ap_state =
2926 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
2927
2928 let start_time = fasync::MonotonicInstant::now();
2930 exec.set_fake_time(start_time);
2931
2932 let (connect_txn_proxy, connect_txn_stream) =
2933 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
2934 let connect_txn_handle = connect_txn_stream.control_handle();
2935 let options = ConnectedOptions::new(
2936 &mut test_values.common_options,
2937 Box::new(ap_state.clone()),
2938 connect_selection.target.network_has_multiple_bss,
2939 connect_selection.target.network.clone(),
2940 connect_selection.target.credential.clone(),
2941 connect_selection.reason,
2942 connect_txn_proxy.take_event_stream(),
2943 false,
2944 );
2945 let initial_state = connected_state(test_values.common_options, options);
2946 let fut = run_state_machine(initial_state);
2947 let mut fut = pin!(fut);
2948 let sme_fut = test_values.sme_req_stream.into_future();
2949 let mut sme_fut = pin!(sme_fut);
2950
2951 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2953
2954 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
2956 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
2957 });
2958
2959 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2961
2962 exec.set_fake_time(start_time + fasync::MonotonicDuration::from_hours(24));
2964
2965 let is_sme_reconnecting = true;
2967 connect_txn_handle
2968 .send_on_disconnect(&generate_disconnect_info(is_sme_reconnecting))
2969 .expect("failed to send disconnection event");
2970
2971 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2973
2974 let connect_result = fidl_sme::ConnectResult {
2976 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
2977 is_reconnect: true,
2978 ..fake_successful_connect_result()
2979 };
2980 connect_txn_handle
2981 .send_on_connect_result(&connect_result)
2982 .expect("failed to send reconnection result");
2983
2984 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
2986
2987 assert_matches!(
2989 poll_sme_req(&mut exec, &mut sme_fut),
2990 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
2991 responder.send().expect("could not send sme response");
2992 }
2993 );
2994
2995 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
2997
2998 let client_state_update = ClientStateUpdate {
3000 state: fidl_policy::WlanClientState::ConnectionsEnabled,
3001 networks: vec![ClientNetworkState {
3002 id: connect_selection.target.network.clone(),
3003 state: fidl_policy::ConnectionState::Disconnected,
3004 status: Some(fidl_policy::DisconnectStatus::ConnectionFailed),
3005 }],
3006 };
3007 assert_matches!(
3008 test_values.update_receiver.try_recv(),
3009 Ok(listener::Message::NotifyListeners(updates)) => {
3010 assert_eq!(updates, client_state_update);
3011 });
3012 }
3013
3014 #[fuchsia::test]
3015 fn connected_state_on_signal_report() {
3016 let mut exec = fasync::TestExecutor::new_with_fake_time();
3017 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3018
3019 let mut test_values = test_setup();
3020
3021 let status = test_values.status_reader.read_status().expect("failed to read status");
3023 assert_matches!(status, Status::Disconnected);
3024
3025 let mut connect_selection = generate_connect_selection();
3027 let init_rssi = -40;
3028 let init_snr = 30;
3029 connect_selection.target.bss.signal =
3030 types::Signal { rssi_dbm: init_rssi, snr_db: init_snr };
3031
3032 let mut bss_description =
3033 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3034 bss_description.rssi_dbm = init_rssi;
3035 bss_description.snr_db = init_snr;
3036 connect_selection.target.bss.bss_description = bss_description.clone().into();
3037
3038 let ap_state =
3039 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3040
3041 let mut past_connections = new_past_connection_list();
3043 let mut past_connection_data = random_connection_data();
3044 past_connection_data.bssid = ieee80211::Bssid::from(bss_description.bssid);
3045 past_connections.add(past_connection_data);
3046 let mut saved_networks_manager = FakeSavedNetworksManager::new();
3047 saved_networks_manager.past_connections_response = past_connections.clone();
3048 test_values.common_options.saved_networks_manager = Arc::new(saved_networks_manager);
3049
3050 let (connect_txn_proxy, connect_txn_stream) =
3052 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3053 let options = ConnectedOptions::new(
3054 &mut test_values.common_options,
3055 Box::new(ap_state.clone()),
3056 connect_selection.target.network_has_multiple_bss,
3057 connect_selection.target.network.clone(),
3058 connect_selection.target.credential.clone(),
3059 connect_selection.reason,
3060 connect_txn_proxy.take_event_stream(),
3061 false,
3062 );
3063 let initial_state = connected_state(test_values.common_options, options);
3064
3065 let connect_txn_handle = connect_txn_stream.control_handle();
3066 let fut = run_state_machine(initial_state);
3067 let mut fut = pin!(fut);
3068 let sme_fut = test_values.sme_req_stream.into_future();
3069 let mut sme_fut = pin!(sme_fut);
3070
3071 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3073
3074 let request = test_values
3075 .roam_service_request_receiver
3076 .try_recv()
3077 .expect("error receiving roam service request");
3078 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor{ mut roam_trigger_data_receiver, .. } => {
3079 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3081
3082 let rssi_1 = -50;
3084 let snr_1 = 25;
3085 let fidl_signal_report =
3086 fidl_internal::SignalReportIndication { rssi_dbm: rssi_1, snr_db: snr_1 , tx_rate_500kbps: 0};
3087 connect_txn_handle
3088 .send_on_signal_report(&fidl_signal_report)
3089 .expect("failed to send signal report");
3090 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3091
3092 assert_matches!(poll_sme_req(&mut exec, &mut sme_fut), Poll::Pending);
3094
3095 assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3097 assert_matches!(event, TelemetryEvent::OnSignalReport { .. });
3098 });
3099
3100 assert_matches!(roam_trigger_data_receiver.try_recv(), Ok(RoamTriggerData::SignalReportInd(_)));
3102
3103 let status = test_values.status_reader.read_status().expect("failed to read status");
3105 assert_eq!(
3106 status,
3107 Status::Connected {
3108 rssi: rssi_1,
3109 snr: snr_1,
3110 channel: ap_state.tracked.channel.primary
3111 }
3112 );
3113
3114 let rssi_2 = -30;
3116 let snr_2 = 35;
3117 let fidl_signal_report =
3118 fidl_internal::SignalReportIndication { rssi_dbm: rssi_2, snr_db: snr_2 , tx_rate_500kbps: 0};
3119 connect_txn_handle
3120 .send_on_signal_report(&fidl_signal_report)
3121 .expect("failed to send signal report");
3122 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3123
3124 assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3126 assert_matches!(event, TelemetryEvent::OnSignalReport { .. });
3127 });
3128
3129 assert_matches!(roam_trigger_data_receiver.try_recv(), Ok(RoamTriggerData::SignalReportInd(_)));
3131 });
3132 }
3133
3134 #[fuchsia::test]
3135 fn connected_state_on_channel_switched() {
3136 let mut exec = fasync::TestExecutor::new_with_fake_time();
3137 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3138
3139 let mut test_values = test_setup();
3140 let mut telemetry_receiver = test_values.telemetry_receiver;
3141
3142 let connect_selection = generate_connect_selection();
3143 let bss_description =
3144 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3145 let ap_state =
3146 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3147
3148 let (connect_txn_proxy, connect_txn_stream) =
3150 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3151 let options = ConnectedOptions::new(
3152 &mut test_values.common_options,
3153 Box::new(ap_state.clone()),
3154 connect_selection.target.network_has_multiple_bss,
3155 connect_selection.target.network.clone(),
3156 connect_selection.target.credential.clone(),
3157 connect_selection.reason,
3158 connect_txn_proxy.take_event_stream(),
3159 false,
3160 );
3161 let initial_state = connected_state(test_values.common_options, options);
3162
3163 let connect_txn_handle = connect_txn_stream.control_handle();
3164 let fut = run_state_machine(initial_state);
3165 let mut fut = pin!(fut);
3166
3167 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3169
3170 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3172 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3173 assert_eq!(ap_state.tracked.channel.primary, bss_description.primary.number)
3174 });
3175 });
3176
3177 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3179
3180 let channel_switch_info = fidl_internal::ChannelSwitchInfo {
3181 new_primary_channel: fidl_ieee80211::ChannelNumber {
3182 band: fidl_ieee80211::WlanBand::TwoGhz,
3183 number: 10,
3184 },
3185 bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw20,
3186 vht_secondary_80_channel: fidl_ieee80211::ChannelNumber {
3187 band: fidl_ieee80211::WlanBand::TwoGhz,
3188 number: 0,
3189 },
3190 };
3191 connect_txn_handle
3192 .send_on_channel_switched(&channel_switch_info)
3193 .expect("failed to send signal report");
3194 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3195
3196 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3198 assert_matches!(event, TelemetryEvent::OnChannelSwitched { info } => {
3199 assert_eq!(info, channel_switch_info);
3200 });
3201 });
3202
3203 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3205 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3206 assert_eq!(ap_state.tracked.channel.primary, 10)
3207 });
3208 });
3209
3210 let is_sme_reconnecting = false;
3213 let fidl_disconnect_info = generate_disconnect_info(is_sme_reconnecting);
3214 connect_txn_handle
3215 .send_on_disconnect(&fidl_disconnect_info)
3216 .expect("failed to send disconnection event");
3217 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3218
3219 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3221 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3222 assert_eq!(info.unwrap().ap_state.tracked.channel.primary, 10);
3223 });
3224 });
3225 }
3226
3227 #[fuchsia::test]
3228 fn connected_state_on_roam_selection() {
3229 let mut exec = fasync::TestExecutor::new_with_fake_time();
3230 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3231
3232 let mut test_values = test_setup();
3233 let sme_fut = test_values.sme_req_stream.into_future();
3234 let mut sme_fut = pin!(sme_fut);
3235
3236 let connect_selection = generate_connect_selection();
3238 let bss_description =
3239 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3240 let ap_state =
3241 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3242 let (connect_txn_proxy, _connect_txn_stream) =
3243 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3244 let options = ConnectedOptions::new(
3245 &mut test_values.common_options,
3246 Box::new(ap_state.clone()),
3247 connect_selection.target.network_has_multiple_bss,
3248 connect_selection.target.network.clone(),
3249 connect_selection.target.credential.clone(),
3250 connect_selection.reason,
3251 connect_txn_proxy.take_event_stream(),
3252 false,
3253 );
3254 let initial_state = connected_state(test_values.common_options, options);
3255 let fut = run_state_machine(initial_state);
3256 let mut fut = pin!(fut);
3257
3258 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3260
3261 let mut roam_sender;
3263 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3264 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { roam_request_sender, .. } => {
3265 roam_sender = roam_request_sender;
3266 });
3267 });
3268
3269 let roam_candidate = generate_random_scanned_candidate();
3271 roam_sender
3272 .try_send(PolicyRoamRequest { candidate: roam_candidate.clone(), reasons: vec![] })
3273 .unwrap();
3274
3275 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3277
3278 assert_matches!(
3280 poll_sme_req(&mut exec, &mut sme_fut),
3281 Poll::Ready(fidl_sme::ClientSmeRequest::Roam{ req, ..}) => {
3282 assert_eq!(req.bss_description, Sequestered::release(roam_candidate.clone().bss.bss_description));
3283 }
3284 );
3285
3286 assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3288 assert_matches!(event, TelemetryEvent::PolicyRoamAttempt { request, connected_duration } => {
3289 assert_eq!(request.candidate, roam_candidate);
3290 assert_eq!(request.reasons, vec![]);
3291 assert_eq!(connected_duration, zx::Duration::from_minutes(0));
3292
3293 });
3294 });
3295 }
3296
3297 #[fuchsia::test]
3298 fn connected_state_on_roam_result_success() {
3299 let mut exec = fasync::TestExecutor::new_with_fake_time();
3300 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3301
3302 let mut test_values = test_setup();
3303 let mut telemetry_receiver = test_values.telemetry_receiver;
3304
3305 let connect_selection = generate_connect_selection();
3306 let bss_description =
3307 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3308 let ap_state =
3309 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3310
3311 let (connect_txn_proxy, connect_txn_stream) =
3313 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3314 let mut options = ConnectedOptions::new(
3315 &mut test_values.common_options,
3316 Box::new(ap_state.clone()),
3317 connect_selection.target.network_has_multiple_bss,
3318 connect_selection.target.network.clone(),
3319 connect_selection.target.credential.clone(),
3320 connect_selection.reason,
3321 connect_txn_proxy.take_event_stream(),
3322 false,
3323 );
3324 let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3326 options.pending_roam = Some(policy_request.clone().into());
3327 let initial_state = connected_state(test_values.common_options, options);
3328
3329 let connect_txn_handle = connect_txn_stream.control_handle();
3330 let fut = run_state_machine(initial_state);
3331 let mut fut = pin!(fut);
3332
3333 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3335
3336 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3338 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3339 });
3340
3341 let bss_desc = random_fidl_bss_description!();
3343 let roam_result = fidl_sme::RoamResult {
3344 bssid: [1, 1, 1, 1, 1, 1],
3345 status_code: fidl_ieee80211::StatusCode::Success,
3346 original_association_maintained: false,
3347 bss_description: Some(Box::new(bss_desc.clone())),
3348 disconnect_info: None,
3349 is_credential_rejected: false,
3350 };
3351 connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3352 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3353
3354 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3356 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { ap_state, .. } => {
3357 assert_eq!(ap_state.original().bssid.to_array(), bss_desc.bssid);
3358 });
3359 });
3360
3361 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3363 assert_eq!(id, &connect_selection.target.network.clone());
3364 assert_eq!(credential, &connect_selection.target.credential.clone());
3365 assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3366 assert_eq!(bssid, &connect_selection.target.bss.bssid);
3367 assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3368 })
3369 });
3370
3371 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3373 let expected_connect_result = ConnectResultRecord {
3374 id: connect_selection.target.network.clone(),
3375 credential: connect_selection.target.credential.clone(),
3376 bssid: types::Bssid::from(roam_result.bssid),
3377 connect_result: fidl_sme::ConnectResult {
3378 code: roam_result.status_code,
3379 is_credential_rejected: roam_result.is_credential_rejected,
3380 is_reconnect: false,
3381 },
3382 scan_type: types::ScanObservation::Unknown,
3383 };
3384 assert_eq!(data, &expected_connect_result);
3385 });
3386
3387 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3389 assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3390 assert_eq!(result, roam_result);
3391 });
3392 });
3393
3394 assert_matches!(telemetry_receiver.try_recv(), Err(_));
3397
3398 exec.set_fake_time(fasync::MonotonicInstant::after(
3400 PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3401 ));
3402
3403 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3405 }
3406
3407 #[fuchsia::test]
3408 fn connected_state_on_roam_result_failed_original_association_maintained() {
3409 let mut exec = fasync::TestExecutor::new_with_fake_time();
3410 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3411
3412 let mut test_values = test_setup();
3413 let mut telemetry_receiver = test_values.telemetry_receiver;
3414
3415 let connect_selection = generate_connect_selection();
3416 let bss_description =
3417 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3418 let ap_state =
3419 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3420
3421 let (connect_txn_proxy, connect_txn_stream) =
3423 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3424 let mut options = ConnectedOptions::new(
3425 &mut test_values.common_options,
3426 Box::new(ap_state.clone()),
3427 connect_selection.target.network_has_multiple_bss,
3428 connect_selection.target.network.clone(),
3429 connect_selection.target.credential.clone(),
3430 connect_selection.reason,
3431 connect_txn_proxy.take_event_stream(),
3432 false,
3433 );
3434 let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3436 options.pending_roam = Some(policy_request.clone().into());
3437 let initial_state = connected_state(test_values.common_options, options);
3438
3439 let connect_txn_handle = connect_txn_stream.control_handle();
3440 let fut = run_state_machine(initial_state);
3441 let mut fut = pin!(fut);
3442
3443 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3445
3446 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3448 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3449 });
3450
3451 let roam_result = fidl_sme::RoamResult {
3453 bssid: [1, 1, 1, 1, 1, 1],
3454 status_code: fidl_ieee80211::StatusCode::JoinFailure,
3455 original_association_maintained: true,
3456 bss_description: Some(Box::new(bss_description.clone())),
3457 disconnect_info: None,
3458 is_credential_rejected: false,
3459 };
3460 connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3461 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3462
3463 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3465 let expected_connect_result = ConnectResultRecord {
3466 id: connect_selection.target.network.clone(),
3467 credential: connect_selection.target.credential.clone(),
3468 bssid: types::Bssid::from(roam_result.bssid),
3469 connect_result: fidl_sme::ConnectResult {
3470 code: roam_result.status_code,
3471 is_credential_rejected: roam_result.is_credential_rejected,
3472 is_reconnect: false,
3473 },
3474 scan_type: types::ScanObservation::Unknown,
3475 };
3476 assert_eq!(data, &expected_connect_result);
3477 });
3478
3479 assert_matches!(
3481 test_values.defect_receiver.try_recv(),
3482 Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
3483 );
3484
3485 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3487 assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3488 assert_eq!(result, roam_result);
3489 });
3490 });
3491
3492 assert_matches!(telemetry_receiver.try_recv(), Err(_));
3495
3496 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Err(_));
3498
3499 exec.set_fake_time(fasync::MonotonicInstant::after(
3501 PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3502 ));
3503
3504 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3506 }
3507
3508 #[fuchsia::test]
3509 fn connected_state_on_roam_result_failed_and_disconnected() {
3510 let mut exec = fasync::TestExecutor::new_with_fake_time();
3511 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3512
3513 let mut test_values = test_setup();
3514 let mut telemetry_receiver = test_values.telemetry_receiver;
3515 let sme_fut = test_values.sme_req_stream.into_future();
3516 let mut sme_fut = pin!(sme_fut);
3517
3518 let connect_selection = generate_connect_selection();
3519 let bss_description =
3520 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3521 let ap_state =
3522 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3523
3524 let (connect_txn_proxy, connect_txn_stream) =
3526 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3527 let mut options = ConnectedOptions::new(
3528 &mut test_values.common_options,
3529 Box::new(ap_state.clone()),
3530 connect_selection.target.network_has_multiple_bss,
3531 connect_selection.target.network.clone(),
3532 connect_selection.target.credential.clone(),
3533 connect_selection.reason,
3534 connect_txn_proxy.take_event_stream(),
3535 false,
3536 );
3537 let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3539 options.pending_roam = Some(policy_request.clone().into());
3540 let initial_state = connected_state(test_values.common_options, options);
3541
3542 let connect_txn_handle = connect_txn_stream.control_handle();
3543 let fut = run_state_machine(initial_state);
3544 let mut fut = pin!(fut);
3545
3546 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3548
3549 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3551 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3552 });
3553
3554 let disconnect_info = fidl_sme::DisconnectInfo {
3556 is_sme_reconnecting: false,
3557 disconnect_source: fidl_sme::DisconnectSource::Ap(fidl_sme::DisconnectCause {
3558 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DisassociateIndication,
3559 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
3560 }),
3561 };
3562 let roam_result = fidl_sme::RoamResult {
3563 bssid: [1, 1, 1, 1, 1, 1],
3564 status_code: fidl_ieee80211::StatusCode::JoinFailure,
3565 original_association_maintained: false,
3566 bss_description: None,
3567 disconnect_info: Some(Box::new(disconnect_info)),
3568 is_credential_rejected: false,
3569 };
3570 connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3571 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3572
3573 assert_matches!(test_values.saved_networks_manager.get_recorded_connect_reslts().as_slice(), [data] => {
3575 let expected_connect_result = ConnectResultRecord {
3576 id: connect_selection.target.network.clone(),
3577 credential: connect_selection.target.credential.clone(),
3578 bssid: types::Bssid::from(roam_result.bssid),
3579 connect_result: fidl_sme::ConnectResult {
3580 code: roam_result.status_code,
3581 is_credential_rejected: roam_result.is_credential_rejected,
3582 is_reconnect: false,
3583 },
3584 scan_type: types::ScanObservation::Unknown,
3585 };
3586 assert_eq!(data, &expected_connect_result);
3587 });
3588
3589 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3591 assert_eq!(id, &connect_selection.target.network.clone());
3592 assert_eq!(credential, &connect_selection.target.credential.clone());
3593 assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3594 assert_eq!(bssid, &connect_selection.target.bss.bssid);
3595 assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3596 })
3597 });
3598
3599 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3601 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3602 assert_eq!(info.unwrap().disconnect_source, disconnect_info.disconnect_source);
3603 });
3604 });
3605
3606 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3608 assert_matches!(event, TelemetryEvent::PolicyInitiatedRoamResult { result, .. } => {
3609 assert_eq!(result, roam_result);
3610 });
3611 });
3612
3613 assert_matches!(
3615 test_values.defect_receiver.try_recv(),
3616 Ok(Defect::Iface(IfaceFailure::ConnectionFailure { iface_id: 1 }))
3617 );
3618
3619 assert_matches!(
3621 poll_sme_req(&mut exec, &mut sme_fut),
3622 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect { .. })
3623 );
3624
3625 exec.set_fake_time(fasync::MonotonicInstant::after(
3627 PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3628 ));
3629
3630 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3632 }
3633 #[fuchsia::test]
3634 fn connected_state_on_unexpected_policy_roam_result_disconnects() {
3635 let mut exec = fasync::TestExecutor::new_with_fake_time();
3636 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3637
3638 let mut test_values = test_setup();
3639 let mut telemetry_receiver = test_values.telemetry_receiver;
3640 let sme_fut = test_values.sme_req_stream.into_future();
3641 let mut sme_fut = pin!(sme_fut);
3642
3643 let connect_selection = generate_connect_selection();
3644 let bss_description =
3645 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3646 let ap_state =
3647 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3648
3649 let (connect_txn_proxy, connect_txn_stream) =
3651 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3652 let mut options = ConnectedOptions::new(
3653 &mut test_values.common_options,
3654 Box::new(ap_state.clone()),
3655 connect_selection.target.network_has_multiple_bss,
3656 connect_selection.target.network.clone(),
3657 connect_selection.target.credential.clone(),
3658 connect_selection.reason,
3659 connect_txn_proxy.take_event_stream(),
3660 false,
3661 );
3662 let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3664 options.pending_roam = Some(policy_request.into());
3665 let initial_state = connected_state(test_values.common_options, options);
3666
3667 let connect_txn_handle = connect_txn_stream.control_handle();
3668 let fut = run_state_machine(initial_state);
3669 let mut fut = pin!(fut);
3670
3671 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3673
3674 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3676 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3677 });
3678
3679 let bss_desc = random_fidl_bss_description!();
3681 let roam_result = fidl_sme::RoamResult {
3682 bssid: [2, 2, 2, 2, 2, 2],
3683 status_code: fidl_ieee80211::StatusCode::Success,
3684 original_association_maintained: false,
3685 bss_description: Some(Box::new(bss_desc.clone())),
3686 disconnect_info: None,
3687 is_credential_rejected: false,
3688 };
3689 connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3690
3691 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3693
3694 assert_matches!(
3696 poll_sme_req(&mut exec, &mut sme_fut),
3697 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
3698 responder.send().expect("could not send sme response");
3699 }
3700 );
3701
3702 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3704
3705 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3707 assert_eq!(id, &connect_selection.target.network.clone());
3708 assert_eq!(credential, &connect_selection.target.credential.clone());
3709 assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3710 assert_eq!(bssid, &connect_selection.target.bss.bssid);
3711 assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3712 })
3713 });
3714
3715 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3717 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3718 assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3719 });
3720 });
3721
3722 assert_matches!(
3724 test_values.update_receiver.try_recv(),
3725 Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3726 state: fidl_policy::WlanClientState::ConnectionsEnabled,
3727 networks
3728 })) => {
3729 assert_eq!(networks.len(), 1);
3730 assert_eq!(networks[0].id, connect_selection.target.network);
3731 assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3732 assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3733 }
3734 );
3735 }
3736
3737 #[fuchsia::test]
3738 fn connected_state_on_unexpected_policy_roam_result_exits_on_broken_sme() {
3739 let mut exec = fasync::TestExecutor::new_with_fake_time();
3740 exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
3741
3742 let mut test_values = test_setup();
3743 let mut telemetry_receiver = test_values.telemetry_receiver;
3744
3745 let connect_selection = generate_connect_selection();
3746 let bss_description =
3747 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3748 let ap_state =
3749 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3750
3751 let (connect_txn_proxy, connect_txn_stream) =
3753 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3754 let mut options = ConnectedOptions::new(
3755 &mut test_values.common_options,
3756 Box::new(ap_state.clone()),
3757 connect_selection.target.network_has_multiple_bss,
3758 connect_selection.target.network.clone(),
3759 connect_selection.target.credential.clone(),
3760 connect_selection.reason,
3761 connect_txn_proxy.take_event_stream(),
3762 false,
3763 );
3764 let policy_request = generate_policy_roam_request([1, 1, 1, 1, 1, 1].into());
3766 options.pending_roam = Some(policy_request.into());
3767 let initial_state = connected_state(test_values.common_options, options);
3768
3769 let connect_txn_handle = connect_txn_stream.control_handle();
3770 let fut = run_state_machine(initial_state);
3771 let mut fut = pin!(fut);
3772
3773 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3775
3776 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3778 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
3779 });
3780
3781 drop(test_values.sme_req_stream);
3783
3784 let bss_desc = random_fidl_bss_description!();
3786 let roam_result = fidl_sme::RoamResult {
3787 bssid: [2, 2, 2, 2, 2, 2],
3788 status_code: fidl_ieee80211::StatusCode::Success,
3789 original_association_maintained: false,
3790 bss_description: Some(Box::new(bss_desc.clone())),
3791 disconnect_info: None,
3792 is_credential_rejected: false,
3793 };
3794 connect_txn_handle.send_on_roam_result(&roam_result).expect("failed to send roam result");
3795
3796 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3798
3799 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3801 assert_eq!(id, &connect_selection.target.network.clone());
3802 assert_eq!(credential, &connect_selection.target.credential.clone());
3803 assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3804 assert_eq!(bssid, &connect_selection.target.bss.bssid);
3805 assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3806 })
3807 });
3808
3809 assert_matches!(telemetry_receiver.try_recv(), Ok(event) => {
3811 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3812 assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3813 });
3814 });
3815
3816 assert_matches!(
3818 test_values.update_receiver.try_recv(),
3819 Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3820 state: fidl_policy::WlanClientState::ConnectionsEnabled,
3821 networks
3822 })) => {
3823 assert_eq!(networks.len(), 1);
3824 assert_eq!(networks[0].id, connect_selection.target.network);
3825 assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3826 assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3827 }
3828 );
3829 }
3830
3831 #[fuchsia::test]
3832 fn connected_state_pending_roam_expires() {
3833 let mut exec = fasync::TestExecutor::new_with_fake_time();
3834
3835 let mut test_values = test_setup();
3836 let sme_fut = test_values.sme_req_stream.into_future();
3837 let mut sme_fut = pin!(sme_fut);
3838
3839 let connect_selection = generate_connect_selection();
3840 let bss_description =
3841 Sequestered::release(connect_selection.target.bss.bss_description.clone());
3842 let ap_state =
3843 types::ApState::from(BssDescription::try_from(bss_description.clone()).unwrap());
3844
3845 let (connect_txn_proxy, _connect_txn_stream) =
3846 create_proxy_and_stream::<fidl_sme::ConnectTransactionMarker>();
3847 let options = ConnectedOptions::new(
3848 &mut test_values.common_options,
3849 Box::new(ap_state.clone()),
3850 connect_selection.target.network_has_multiple_bss,
3851 connect_selection.target.network.clone(),
3852 connect_selection.target.credential.clone(),
3853 connect_selection.reason,
3854 connect_txn_proxy.take_event_stream(),
3855 false,
3856 );
3857 let initial_state = connected_state(test_values.common_options, options);
3859 let fut = run_state_machine(initial_state);
3860 let mut fut = pin!(fut);
3861
3862 let mut roam_sender;
3864 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
3865 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { roam_request_sender, .. } => {
3866 roam_sender = roam_request_sender;
3867 });
3868 });
3869
3870 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3872
3873 let roam_candidate = generate_random_scanned_candidate();
3875 roam_sender
3876 .try_send(PolicyRoamRequest { candidate: roam_candidate.clone(), reasons: vec![] })
3877 .unwrap();
3878
3879 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3881
3882 assert_matches!(
3884 poll_sme_req(&mut exec, &mut sme_fut),
3885 Poll::Ready(fidl_sme::ClientSmeRequest::Roam{ req, ..}) => {
3886 assert_eq!(req.bss_description, Sequestered::release(roam_candidate.clone().bss.bss_description));
3887 }
3888 );
3889
3890 assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3892 assert_matches!(event, TelemetryEvent::PolicyRoamAttempt { request, .. } => {
3893 assert_eq!(request.candidate,
3894 roam_candidate);
3895 assert_eq!(request.reasons, vec![]);
3896 });
3897 });
3898
3899 exec.set_fake_time(fasync::MonotonicInstant::after(
3901 PENDING_ROAM_TIMEOUT + zx::MonotonicDuration::from_millis(1),
3902 ));
3903
3904 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3906
3907 assert_matches!(
3909 poll_sme_req(&mut exec, &mut sme_fut),
3910 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme }) => {
3911 responder.send().expect("could not send sme response");
3912 }
3913 );
3914
3915 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3917
3918 assert_matches!(test_values.saved_networks_manager.get_recorded_past_connections().as_slice(), [ConnectionRecord {id, credential, data}] => {
3920 assert_eq!(id, &connect_selection.target.network.clone());
3921 assert_eq!(credential, &connect_selection.target.credential.clone());
3922 assert_matches!(data, PastConnectionData {bssid, disconnect_reason, ..} => {
3923 assert_eq!(bssid, &connect_selection.target.bss.bssid);
3924 assert_eq!(disconnect_reason, &types::DisconnectReason::Unknown);
3925 })
3926 });
3927
3928 assert_matches!(test_values.telemetry_receiver.try_recv(), Ok(event) => {
3930 assert_matches!(event, TelemetryEvent::Disconnected { info, .. } => {
3931 assert_eq!(info.unwrap().disconnect_source, fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::Unknown));
3932 });
3933 });
3934
3935 assert_matches!(
3937 test_values.update_receiver.try_recv(),
3938 Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3939 state: fidl_policy::WlanClientState::ConnectionsEnabled,
3940 networks
3941 })) => {
3942 assert_eq!(networks.len(), 1);
3943 assert_eq!(networks[0].id, connect_selection.target.network);
3944 assert_eq!(networks[0].state, fidl_policy::ConnectionState::Disconnected);
3945 assert_eq!(networks[0].status, Some(fidl_policy::DisconnectStatus::ConnectionFailed));
3946 }
3947 );
3948 }
3949
3950 #[fuchsia::test]
3951 fn disconnecting_state_completes_and_exits() {
3952 let mut exec = fasync::TestExecutor::new();
3953 let mut test_values = test_setup();
3954
3955 let (sender, _) = oneshot::channel();
3956 let disconnecting_options = DisconnectingOptions {
3957 disconnect_responder: Some(sender),
3958 previous_network: None,
3959 next_network: None,
3960 reason: types::DisconnectReason::RegulatoryRegionChange,
3961 };
3962 let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
3963 let fut = run_state_machine(initial_state);
3964 let mut fut = pin!(fut);
3965 let sme_fut = test_values.sme_req_stream.into_future();
3966 let mut sme_fut = pin!(sme_fut);
3967
3968 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
3970
3971 assert_matches!(
3973 poll_sme_req(&mut exec, &mut sme_fut),
3974 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::RegulatoryRegionChange }) => {
3975 responder.send().expect("could not send sme response");
3976 }
3977 );
3978
3979 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
3981
3982 assert_matches!(
3984 test_values.update_receiver.try_recv(),
3985 Ok(listener::Message::NotifyListeners(ClientStateUpdate {
3986 state: fidl_policy::WlanClientState::ConnectionsEnabled,
3987 networks
3988 })) => {
3989 assert!(networks.is_empty());
3990 }
3991 );
3992 }
3993
3994 #[fuchsia::test]
3995 fn disconnecting_state_completes_disconnect_to_connecting() {
3996 let mut exec = fasync::TestExecutor::new();
3997 let mut test_values = test_setup();
3998
3999 let previous_connect_selection = generate_connect_selection();
4000 let next_connect_selection = generate_connect_selection();
4001
4002 let bss_description =
4003 Sequestered::release(next_connect_selection.target.bss.bss_description.clone());
4004
4005 let (disconnect_sender, mut disconnect_receiver) = oneshot::channel();
4006 let connecting_options = ConnectingOptions {
4007 connect_selection: next_connect_selection.clone(),
4008 attempt_counter: 0,
4009 };
4010 let disconnecting_options = DisconnectingOptions {
4012 disconnect_responder: Some(disconnect_sender),
4013 previous_network: Some((
4014 previous_connect_selection.target.network.clone(),
4015 fidl_policy::DisconnectStatus::ConnectionStopped,
4016 )),
4017 next_network: Some(connecting_options),
4018 reason: types::DisconnectReason::ProactiveNetworkSwitch,
4019 };
4020 let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4021 let fut = run_state_machine(initial_state);
4022 let mut fut = pin!(fut);
4023 let sme_fut = test_values.sme_req_stream.into_future();
4024 let mut sme_fut = pin!(sme_fut);
4025
4026 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4028
4029 assert_matches!(
4031 poll_sme_req(&mut exec, &mut sme_fut),
4032 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::ProactiveNetworkSwitch }) => {
4033 responder.send().expect("could not send sme response");
4034 }
4035 );
4036
4037 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4039
4040 let client_state_update = ClientStateUpdate {
4042 state: fidl_policy::WlanClientState::ConnectionsEnabled,
4043 networks: vec![ClientNetworkState {
4044 id: previous_connect_selection.target.network.clone(),
4045 state: fidl_policy::ConnectionState::Disconnected,
4046 status: Some(fidl_policy::DisconnectStatus::ConnectionStopped),
4047 }],
4048 };
4049 assert_matches!(
4050 test_values.update_receiver.try_recv(),
4051 Ok(listener::Message::NotifyListeners(updates)) => {
4052 assert_eq!(updates, client_state_update);
4053 });
4054
4055 assert_matches!(exec.run_until_stalled(&mut disconnect_receiver), Poll::Ready(Ok(())));
4056
4057 assert_matches!(
4059 poll_sme_req(&mut exec, &mut sme_fut),
4060 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req, txn, control_handle: _ }) => {
4061 assert_eq!(req.ssid, next_connect_selection.target.network.ssid.clone().to_vec());
4062 assert_eq!(req.deprecated_scan_type, fidl_fuchsia_wlan_common::ScanType::Active);
4063 assert_eq!(req.bss_description, bss_description.clone());
4064 assert_eq!(req.multiple_bss_candidates, next_connect_selection.target.network_has_multiple_bss);
4065 let (_stream, ctrl) = txn.expect("connect txn unused")
4067 .into_stream_and_control_handle();
4068 ctrl
4069 .send_on_connect_result(&fake_successful_connect_result())
4070 .expect("failed to send connection completion");
4071 }
4072 );
4073 }
4074
4075 #[fuchsia::test]
4076 fn disconnecting_state_has_broken_sme() {
4077 let mut exec = fasync::TestExecutor::new();
4078 let test_values = test_setup();
4079
4080 let (sender, mut receiver) = oneshot::channel();
4081 let disconnecting_options = DisconnectingOptions {
4082 disconnect_responder: Some(sender),
4083 previous_network: None,
4084 next_network: None,
4085 reason: types::DisconnectReason::NetworkConfigUpdated,
4086 };
4087 let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4088 let fut = run_state_machine(initial_state);
4089 let mut fut = pin!(fut);
4090
4091 drop(test_values.sme_req_stream);
4093
4094 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4096
4097 assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Err(_)));
4099 }
4100
4101 #[fuchsia::test]
4102 fn serve_loop_handles_startup() {
4103 let mut exec = fasync::TestExecutor::new();
4104 let test_values = test_setup();
4105 let sme_proxy = test_values.common_options.proxy;
4106 let sme_event_stream = sme_proxy.take_event_stream();
4107 let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4108 let sme_fut = test_values.sme_req_stream.into_future();
4109 let mut sme_fut = pin!(sme_fut);
4110
4111 let connect_selection = generate_connect_selection();
4113
4114 let fut = serve(
4115 0,
4116 sme_proxy,
4117 sme_event_stream,
4118 client_req_stream,
4119 test_values.common_options.update_sender,
4120 test_values.common_options.saved_networks_manager,
4121 Some(connect_selection),
4122 test_values.common_options.telemetry_sender,
4123 test_values.common_options.defect_sender,
4124 test_values.common_options.roam_manager,
4125 test_values.common_options.status_publisher,
4126 );
4127 let mut fut = pin!(fut);
4128
4129 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4131 assert_matches!(
4132 poll_sme_req(&mut exec, &mut sme_fut),
4133 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4134 responder.send().expect("could not send sme response");
4135 }
4136 );
4137
4138 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4140 }
4141
4142 #[fuchsia::test]
4143 fn serve_loop_handles_sme_disappearance() {
4144 let mut exec = fasync::TestExecutor::new();
4145 let mut test_values = test_setup();
4146 let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4147
4148 let (sme_proxy, sme_server) = create_proxy::<fidl_sme::ClientSmeMarker>();
4150 let (sme_req_stream, sme_control_handle) = sme_server.into_stream_and_control_handle();
4151
4152 let sme_fut = sme_req_stream.into_future();
4153 let mut sme_fut = pin!(sme_fut);
4154
4155 let sme_event_stream = sme_proxy.take_event_stream();
4156
4157 let connect_selection = generate_connect_selection();
4159
4160 let fut = serve(
4161 0,
4162 SmeForClientStateMachine::new(
4163 sme_proxy,
4164 0,
4165 test_values.common_options.defect_sender.clone(),
4166 ),
4167 sme_event_stream,
4168 client_req_stream,
4169 test_values.common_options.update_sender,
4170 test_values.common_options.saved_networks_manager,
4171 Some(connect_selection),
4172 test_values.common_options.telemetry_sender,
4173 test_values.common_options.defect_sender,
4174 test_values.common_options.roam_manager,
4175 test_values.common_options.status_publisher,
4176 );
4177 let mut fut = pin!(fut);
4178
4179 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4181 assert_matches!(
4182 poll_sme_req(&mut exec, &mut sme_fut),
4183 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4184 responder.send().expect("could not send sme response");
4185 }
4186 );
4187
4188 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4190
4191 sme_control_handle.shutdown_with_epitaph(zx::Status::UNAVAILABLE);
4192
4193 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4195
4196 let mut telemetry_events = Vec::new();
4198 while let Ok(event) = test_values.telemetry_receiver.try_recv() {
4199 telemetry_events.push(event)
4200 }
4201
4202 assert_matches!(
4203 telemetry_events.last(),
4204 Some(&TelemetryEvent::Disconnected { track_subsequent_downtime: false, info: None })
4205 );
4206 }
4207
4208 #[fuchsia::test]
4209 fn serve_loop_handles_disconnect() {
4210 let mut exec = fasync::TestExecutor::new();
4211 let mut test_values = test_setup();
4212 let sme_proxy = test_values.common_options.proxy;
4213 let sme_event_stream = sme_proxy.take_event_stream();
4214 let (client_req_sender, client_req_stream) = mpsc::channel(1);
4215 let sme_fut = test_values.sme_req_stream.into_future();
4216 let mut sme_fut = pin!(sme_fut);
4217
4218 let connect_selection = generate_connect_selection();
4220 let fut = serve(
4221 0,
4222 sme_proxy,
4223 sme_event_stream,
4224 client_req_stream,
4225 test_values.common_options.update_sender,
4226 test_values.common_options.saved_networks_manager,
4227 Some(connect_selection),
4228 test_values.common_options.telemetry_sender,
4229 test_values.common_options.defect_sender,
4230 test_values.common_options.roam_manager,
4231 test_values.common_options.status_publisher,
4232 );
4233 let mut fut = pin!(fut);
4234
4235 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4237 assert_matches!(
4238 poll_sme_req(&mut exec, &mut sme_fut),
4239 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::Startup }) => {
4240 responder.send().expect("could not send sme response");
4241 }
4242 );
4243
4244 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4246
4247 let connect_txn_handle = assert_matches!(
4249 poll_sme_req(&mut exec, &mut sme_fut),
4250 Poll::Ready(fidl_sme::ClientSmeRequest::Connect{ req: _, txn, control_handle: _ }) => {
4251 let (_stream, ctrl) = txn.expect("connect txn unused")
4253 .into_stream_and_control_handle();
4254 ctrl
4255 }
4256 );
4257 connect_txn_handle
4258 .send_on_connect_result(&fake_successful_connect_result())
4259 .expect("failed to send connection completion");
4260 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4261
4262 assert_matches!(test_values.roam_service_request_receiver.try_recv(), Ok(request) => {
4264 assert_matches!(request, RoamServiceRequest::InitializeRoamMonitor { .. });
4265 });
4266
4267 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4269
4270 let mut client = Client::new(client_req_sender);
4272 let (sender, mut receiver) = oneshot::channel();
4273 client
4274 .disconnect(
4275 PolicyDisconnectionMigratedMetricDimensionReason::NetworkConfigUpdated,
4276 sender,
4277 )
4278 .expect("failed to make request");
4279
4280 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4282 assert_matches!(
4283 poll_sme_req(&mut exec, &mut sme_fut),
4284 Poll::Ready(fidl_sme::ClientSmeRequest::Disconnect{ responder, reason: fidl_sme::UserDisconnectReason::NetworkConfigUpdated }) => {
4285 responder.send().expect("could not send sme response");
4286 }
4287 );
4288
4289 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4291
4292 assert_matches!(exec.run_until_stalled(&mut receiver), Poll::Ready(Ok(())));
4294 }
4295
4296 #[fuchsia::test]
4297 fn serve_loop_handles_state_machine_error() {
4298 let mut exec = fasync::TestExecutor::new();
4299 let test_values = test_setup();
4300 let sme_proxy = test_values.common_options.proxy;
4301 let sme_event_stream = sme_proxy.take_event_stream();
4302 let (_client_req_sender, client_req_stream) = mpsc::channel(1);
4303
4304 test_values.common_options.status_publisher.publish_status(Status::Connecting);
4307
4308 let connect_selection = generate_connect_selection();
4310
4311 let fut = serve(
4312 0,
4313 sme_proxy,
4314 sme_event_stream,
4315 client_req_stream,
4316 test_values.common_options.update_sender,
4317 test_values.common_options.saved_networks_manager,
4318 Some(connect_selection),
4319 test_values.common_options.telemetry_sender,
4320 test_values.common_options.defect_sender,
4321 test_values.common_options.roam_manager,
4322 test_values.common_options.status_publisher.clone(),
4323 );
4324 let mut fut = pin!(fut);
4325
4326 drop(test_values.sme_req_stream);
4328
4329 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Ready(()));
4331
4332 let status = test_values.status_reader.read_status().expect("could not get reader");
4334 assert_eq!(status, Status::Disconnected);
4335 }
4336
4337 fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
4338 fidl_sme::ConnectResult {
4339 code: fidl_ieee80211::StatusCode::Success,
4340 is_credential_rejected: false,
4341 is_reconnect: false,
4342 }
4343 }
4344
4345 #[fuchsia::test]
4346 fn disconnecting_sets_status() {
4347 let mut exec = fasync::TestExecutor::new();
4348 let test_values = test_setup();
4349
4350 let (sender, _) = oneshot::channel();
4351 let disconnecting_options = DisconnectingOptions {
4352 disconnect_responder: Some(sender),
4353 previous_network: None,
4354 next_network: None,
4355 reason: types::DisconnectReason::RegulatoryRegionChange,
4356 };
4357
4358 let initial_state = disconnecting_state(test_values.common_options, disconnecting_options);
4360 let fut = run_state_machine(initial_state);
4361 let mut fut = pin!(fut);
4362 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4363
4364 let status = test_values.status_reader.read_status().expect("could not get reader");
4366 assert_eq!(status, Status::Disconnecting);
4367 }
4368
4369 #[fuchsia::test]
4370 fn connecting_sets_status() {
4371 let mut exec = fasync::TestExecutor::new_with_fake_time();
4372 let connection_attempt_time = fasync::MonotonicInstant::from_nanos(0);
4373 exec.set_fake_time(connection_attempt_time);
4374 let test_values = test_setup();
4375
4376 let connect_selection = generate_connect_selection();
4377 let connecting_options =
4378 ConnectingOptions { connect_selection: connect_selection.clone(), attempt_counter: 0 };
4379
4380 let initial_state = connecting_state(test_values.common_options, connecting_options);
4382 let fut = run_state_machine(initial_state);
4383 let mut fut = pin!(fut);
4384 assert_matches!(exec.run_until_stalled(&mut fut), Poll::Pending);
4385
4386 let status = test_values.status_reader.read_status().expect("failed to read status");
4388 assert_matches!(status, Status::Connecting);
4389 }
4390
4391 struct InspectTestValues {
4392 exec: fasync::TestExecutor,
4393 inspector: fuchsia_inspect::Inspector,
4394 _node: fuchsia_inspect::Node,
4395 status_node: fuchsia_inspect_contrib::nodes::BoundedListNode,
4396 }
4397
4398 impl InspectTestValues {
4399 fn new(exec: fasync::TestExecutor) -> Self {
4400 let inspector = fuchsia_inspect::Inspector::default();
4401 let _node = inspector.root().create_child("node");
4402 let status_node =
4403 fuchsia_inspect_contrib::nodes::BoundedListNode::new(_node.clone_weak(), 1);
4404
4405 Self { exec, inspector, _node, status_node }
4406 }
4407
4408 fn log_status(&mut self, status: Status) -> fuchsia_inspect::reader::DiagnosticsHierarchy {
4409 fuchsia_inspect_contrib::inspect_log!(self.status_node, "status" => status);
4410 let read_fut = fuchsia_inspect::reader::read(&self.inspector);
4411 let mut read_fut = pin!(read_fut);
4412 assert_matches!(
4413 self.exec.run_until_stalled(&mut read_fut),
4414 Poll::Ready(Ok(hierarchy)) => hierarchy
4415 )
4416 }
4417 }
4418
4419 #[fuchsia::test]
4420 fn test_disconnecting_status_inspect_log() {
4421 let exec = fasync::TestExecutor::new_with_fake_time();
4422 let mut test_values = InspectTestValues::new(exec);
4423 let hierarchy = test_values.log_status(Status::Disconnecting);
4424 diagnostics_assertions::assert_data_tree!(
4425 @executor test_values.exec,
4426 hierarchy,
4427 root: contains {
4428 node: contains {
4429 "0": contains {
4430 status: "Disconnecting"
4431 }
4432 }
4433 });
4434 }
4435
4436 #[fuchsia::test]
4437 fn test_disconnected_status_inspect_log() {
4438 let exec = fasync::TestExecutor::new_with_fake_time();
4439 let mut test_values = InspectTestValues::new(exec);
4440 let hierarchy = test_values.log_status(Status::Disconnected);
4441 diagnostics_assertions::assert_data_tree!(
4442 @executor test_values.exec,
4443 hierarchy,
4444 root: contains {
4445 node: contains {
4446 "0": contains {
4447 status: "Disconnected"
4448 }
4449 }
4450 });
4451 }
4452
4453 #[fuchsia::test]
4454 fn test_connecting_status_inspect_log() {
4455 let exec = fasync::TestExecutor::new_with_fake_time();
4456 let mut test_values = InspectTestValues::new(exec);
4457 let hierarchy = test_values.log_status(Status::Connecting);
4458 diagnostics_assertions::assert_data_tree!(
4459 @executor test_values.exec,
4460 hierarchy,
4461 root: contains {
4462 node: contains {
4463 "0": contains {
4464 status: "Connecting"
4465 }
4466 }
4467 });
4468 }
4469
4470 #[fuchsia::test]
4471 fn test_connected_status_inspect_log() {
4472 let exec = fasync::TestExecutor::new_with_fake_time();
4473 let mut test_values = InspectTestValues::new(exec);
4474 let hierarchy = test_values.log_status(Status::Connected { channel: 1, rssi: 2, snr: 3 });
4475 diagnostics_assertions::assert_data_tree!(
4476 @executor test_values.exec,
4477 hierarchy,
4478 root: contains {
4479 node: contains {
4480 "0": contains {
4481 status: contains {
4482 Connected: { channel: 1_u64, rssi: 2_i64, snr: 3_i64 }
4483 }
4484 }
4485 }
4486 });
4487 }
4488}