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