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