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