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