1mod event;
6mod inspect;
7mod protection;
8mod rsn;
9mod scan;
10mod state;
11
12mod wpa;
13
14#[cfg(test)]
15pub mod test_utils;
16
17use self::event::Event;
18use self::protection::{Protection, SecurityContext};
19use self::scan::{DiscoveryScan, ScanScheduler};
20use self::state::{ClientState, ConnectCommand};
21use crate::responder::Responder;
22use crate::{Config, MlmeRequest, MlmeSink, MlmeStream};
23use fuchsia_inspect_auto_persist::{self as auto_persist, AutoPersist};
24use futures::channel::{mpsc, oneshot};
25use ieee80211::{Bssid, MacAddrBytes, Ssid};
26use log::{error, info, warn};
27use std::sync::Arc;
28use wlan_common::bss::{BssDescription, Protection as BssProtection};
29use wlan_common::capabilities::derive_join_capabilities;
30use wlan_common::ie::rsn::rsne;
31use wlan_common::ie::{self, wsc};
32use wlan_common::mac::MacRole;
33use wlan_common::scan::{Compatibility, Compatible, Incompatible, ScanResult};
34use wlan_common::security::{SecurityAuthenticator, SecurityDescriptor};
35use wlan_common::sink::UnboundedSink;
36use wlan_common::timer;
37use wlan_rsn::auth;
38use {
39 fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
40 fidl_fuchsia_wlan_internal as fidl_internal, fidl_fuchsia_wlan_mlme as fidl_mlme,
41 fidl_fuchsia_wlan_sme as fidl_sme, fidl_fuchsia_wlan_stats as fidl_stats,
42};
43
44mod internal {
49 use crate::client::event::Event;
50 use crate::client::{inspect, ConnectionAttemptId};
51 use crate::MlmeSink;
52 use std::sync::Arc;
53 use wlan_common::timer::Timer;
54 use {fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_mlme as fidl_mlme};
55
56 pub struct Context {
57 pub device_info: Arc<fidl_mlme::DeviceInfo>,
58 pub mlme_sink: MlmeSink,
59 pub(crate) timer: Timer<Event>,
60 pub att_id: ConnectionAttemptId,
61 pub(crate) inspect: Arc<inspect::SmeTree>,
62 pub security_support: fidl_common::SecuritySupport,
63 }
64}
65
66use self::internal::*;
67
68pub type ConnectionAttemptId = u64;
72
73pub type ScanTxnId = u64;
74
75#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
76pub struct ClientConfig {
77 cfg: Config,
78 pub wpa3_supported: bool,
79}
80
81impl ClientConfig {
82 pub fn from_config(cfg: Config, wpa3_supported: bool) -> Self {
83 Self { cfg, wpa3_supported }
84 }
85
86 pub fn create_scan_result(
88 &self,
89 timestamp: zx::MonotonicInstant,
90 bss_description: BssDescription,
91 device_info: &fidl_mlme::DeviceInfo,
92 security_support: &fidl_common::SecuritySupport,
93 ) -> ScanResult {
94 ScanResult {
95 compatibility: self.bss_compatibility(&bss_description, device_info, security_support),
96 timestamp,
97 bss_description,
98 }
99 }
100
101 pub fn bss_compatibility(
106 &self,
107 bss: &BssDescription,
108 device_info: &fidl_mlme::DeviceInfo,
109 security_support: &fidl_common::SecuritySupport,
110 ) -> Compatibility {
111 self.has_compatible_channel_and_data_rates(bss, device_info)
114 .then(|| {
115 Compatible::try_from_features(
116 self.security_protocol_intersection(bss, security_support),
117 )
118 })
119 .flatten()
120 .ok_or_else(|| {
121 Incompatible::try_from_features(
122 "incompatible channel, PHY data rates, or security protocols",
123 Some(self.security_protocols_by_mac_role(bss)),
124 )
125 .unwrap_or_else(|| {
126 Incompatible::from_description("incompatible channel or PHY data rates")
127 })
128 })
129 }
130
131 fn security_protocol_intersection(
136 &self,
137 bss: &BssDescription,
138 security_support: &fidl_common::SecuritySupport,
139 ) -> Vec<SecurityDescriptor> {
140 let has_privacy = wlan_common::mac::CapabilityInfo(bss.capability_info).privacy();
143 let has_wep_support = || self.cfg.wep_supported;
144 let has_wpa1_support = || self.cfg.wpa1_supported;
145 let has_wpa2_support = || {
146 has_privacy
150 && bss.rsne().is_some_and(|rsne| {
151 rsne::from_bytes(rsne)
152 .is_ok_and(|(_, a_rsne)| a_rsne.is_wpa2_rsn_compatible(security_support))
153 })
154 };
155 let has_wpa3_support = || {
156 self.wpa3_supported
157 && has_privacy
158 && bss.rsne().is_some_and(|rsne| {
159 rsne::from_bytes(rsne)
160 .is_ok_and(|(_, a_rsne)| a_rsne.is_wpa3_rsn_compatible(security_support))
161 })
162 };
163
164 match bss.protection() {
169 BssProtection::Open => vec![SecurityDescriptor::OPEN],
170 BssProtection::Wep if has_wep_support() => vec![SecurityDescriptor::WEP],
171 BssProtection::Wep => vec![],
172 BssProtection::Wpa1 if has_wpa1_support() => vec![SecurityDescriptor::WPA1],
173 BssProtection::Wpa1 => vec![],
174 BssProtection::Wpa1Wpa2PersonalTkipOnly | BssProtection::Wpa1Wpa2Personal => {
175 has_wpa2_support()
176 .then_some(SecurityDescriptor::WPA2_PERSONAL)
177 .into_iter()
178 .chain(has_wpa1_support().then_some(SecurityDescriptor::WPA1))
179 .collect()
180 }
181 BssProtection::Wpa2PersonalTkipOnly | BssProtection::Wpa2Personal
182 if has_wpa2_support() =>
183 {
184 vec![SecurityDescriptor::WPA2_PERSONAL]
185 }
186 BssProtection::Wpa2PersonalTkipOnly | BssProtection::Wpa2Personal => vec![],
187 BssProtection::Wpa2Wpa3Personal => match (has_wpa3_support(), has_wpa2_support()) {
188 (true, true) => {
189 vec![SecurityDescriptor::WPA3_PERSONAL, SecurityDescriptor::WPA2_PERSONAL]
190 }
191 (true, false) => vec![SecurityDescriptor::WPA3_PERSONAL],
192 (false, true) => vec![SecurityDescriptor::WPA2_PERSONAL],
193 (false, false) => vec![],
194 },
195 BssProtection::Wpa3Personal if has_wpa3_support() => {
196 vec![SecurityDescriptor::WPA3_PERSONAL]
197 }
198 BssProtection::Wpa3Personal => vec![],
199 BssProtection::Wpa2Enterprise | BssProtection::Wpa3Enterprise => vec![],
201 BssProtection::Unknown => vec![],
202 }
203 }
204
205 fn security_protocols_by_mac_role(
206 &self,
207 bss: &BssDescription,
208 ) -> impl Iterator<Item = (SecurityDescriptor, MacRole)> {
209 let has_privacy = wlan_common::mac::CapabilityInfo(bss.capability_info).privacy();
210 let has_wep_support = || self.cfg.wep_supported;
211 let has_wpa1_support = || self.cfg.wpa1_supported;
212 let has_wpa2_support = || {
213 has_privacy
217 };
218 let has_wpa3_support = || self.wpa3_supported && has_privacy;
219 let client_security_protocols = Some(SecurityDescriptor::OPEN)
220 .into_iter()
221 .chain(has_wep_support().then_some(SecurityDescriptor::WEP))
222 .chain(has_wpa1_support().then_some(SecurityDescriptor::WPA1))
223 .chain(has_wpa2_support().then_some(SecurityDescriptor::WPA2_PERSONAL))
224 .chain(has_wpa3_support().then_some(SecurityDescriptor::WPA3_PERSONAL))
225 .map(|descriptor| (descriptor, MacRole::Client));
226
227 let bss_security_protocols = match bss.protection() {
228 BssProtection::Open => &[SecurityDescriptor::OPEN][..],
229 BssProtection::Wep => &[SecurityDescriptor::WEP][..],
230 BssProtection::Wpa1 => &[SecurityDescriptor::WPA1][..],
231 BssProtection::Wpa1Wpa2PersonalTkipOnly | BssProtection::Wpa1Wpa2Personal => {
232 &[SecurityDescriptor::WPA1, SecurityDescriptor::WPA2_PERSONAL][..]
233 }
234 BssProtection::Wpa2PersonalTkipOnly | BssProtection::Wpa2Personal => {
235 &[SecurityDescriptor::WPA2_PERSONAL][..]
236 }
237 BssProtection::Wpa2Wpa3Personal => {
238 &[SecurityDescriptor::WPA3_PERSONAL, SecurityDescriptor::WPA2_PERSONAL][..]
239 }
240 BssProtection::Wpa3Personal => &[SecurityDescriptor::WPA3_PERSONAL][..],
241 BssProtection::Wpa2Enterprise | BssProtection::Wpa3Enterprise => &[],
243 BssProtection::Unknown => &[],
244 }
245 .iter()
246 .cloned()
247 .map(|descriptor| (descriptor, MacRole::Ap));
248
249 client_security_protocols.chain(bss_security_protocols)
250 }
251
252 fn has_compatible_channel_and_data_rates(
253 &self,
254 bss: &BssDescription,
255 device_info: &fidl_mlme::DeviceInfo,
256 ) -> bool {
257 derive_join_capabilities(bss.channel, bss.rates(), device_info).is_ok()
258 }
259}
260
261pub struct ClientSme {
262 cfg: ClientConfig,
263 state: Option<ClientState>,
264 scan_sched: ScanScheduler<Responder<Result<Vec<ScanResult>, fidl_mlme::ScanResultCode>>>,
265 wmm_status_responders: Vec<Responder<fidl_sme::ClientSmeWmmStatusResult>>,
266 auto_persist_last_pulse: AutoPersist<()>,
267 context: Context,
268}
269
270#[derive(Debug, PartialEq)]
271pub enum ConnectResult {
272 Success,
273 Canceled,
274 Failed(ConnectFailure),
275}
276
277impl<T: Into<ConnectFailure>> From<T> for ConnectResult {
278 fn from(failure: T) -> Self {
279 ConnectResult::Failed(failure.into())
280 }
281}
282
283#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq)]
285pub enum RoamResult {
286 Success(Box<BssDescription>),
287 Failed(RoamFailure),
288}
289
290impl<T: Into<RoamFailure>> From<T> for RoamResult {
291 fn from(failure: T) -> Self {
292 RoamResult::Failed(failure.into())
293 }
294}
295
296#[derive(Debug)]
297pub struct ConnectTransactionSink {
298 sink: UnboundedSink<ConnectTransactionEvent>,
299 is_reconnecting: bool,
300}
301
302impl ConnectTransactionSink {
303 pub fn new_unbounded() -> (Self, ConnectTransactionStream) {
304 let (sender, receiver) = mpsc::unbounded();
305 let sink =
306 ConnectTransactionSink { sink: UnboundedSink::new(sender), is_reconnecting: false };
307 (sink, receiver)
308 }
309
310 pub fn is_reconnecting(&self) -> bool {
311 self.is_reconnecting
312 }
313
314 pub fn send_connect_result(&mut self, result: ConnectResult) {
315 let event =
316 ConnectTransactionEvent::OnConnectResult { result, is_reconnect: self.is_reconnecting };
317 self.send(event);
318 }
319
320 pub fn send_roam_result(&mut self, result: RoamResult) {
321 let event = ConnectTransactionEvent::OnRoamResult { result };
322 self.send(event);
323 }
324
325 pub fn send(&mut self, event: ConnectTransactionEvent) {
326 if let ConnectTransactionEvent::OnDisconnect { info } = &event {
327 self.is_reconnecting = info.is_sme_reconnecting;
328 };
329 self.sink.send(event);
330 }
331}
332
333pub type ConnectTransactionStream = mpsc::UnboundedReceiver<ConnectTransactionEvent>;
334
335#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq)]
337pub enum ConnectTransactionEvent {
338 OnConnectResult { result: ConnectResult, is_reconnect: bool },
339 OnRoamResult { result: RoamResult },
340 OnDisconnect { info: fidl_sme::DisconnectInfo },
341 OnSignalReport { ind: fidl_internal::SignalReportIndication },
342 OnChannelSwitched { info: fidl_internal::ChannelSwitchInfo },
343}
344
345#[derive(Debug, PartialEq)]
346pub enum ConnectFailure {
347 SelectNetworkFailure(SelectNetworkFailure),
348 ScanFailure(fidl_mlme::ScanResultCode),
351 JoinFailure(fidl_ieee80211::StatusCode),
354 AuthenticationFailure(fidl_ieee80211::StatusCode),
355 AssociationFailure(AssociationFailure),
356 EstablishRsnaFailure(EstablishRsnaFailure),
357}
358
359impl ConnectFailure {
360 #[allow(clippy::collapsible_match, reason = "mass allow for https://fxbug.dev/381896734")]
362 #[allow(
363 clippy::match_like_matches_macro,
364 reason = "mass allow for https://fxbug.dev/381896734"
365 )]
366 pub fn is_timeout(&self) -> bool {
367 match self {
370 ConnectFailure::AuthenticationFailure(failure) => match failure {
371 fidl_ieee80211::StatusCode::RejectedSequenceTimeout => true,
372 _ => false,
373 },
374 ConnectFailure::EstablishRsnaFailure(failure) => match failure {
375 EstablishRsnaFailure {
376 reason: EstablishRsnaFailureReason::RsnaResponseTimeout(_),
377 ..
378 }
379 | EstablishRsnaFailure {
380 reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(_),
381 ..
382 } => true,
383 _ => false,
384 },
385 _ => false,
386 }
387 }
388
389 pub fn likely_due_to_credential_rejected(&self) -> bool {
395 match self {
396 ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
414 auth_method: Some(auth::MethodName::Psk),
415 reason:
416 EstablishRsnaFailureReason::RsnaResponseTimeout(
417 wlan_rsn::Error::LikelyWrongCredential,
418 ),
419 })
420 | ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
421 auth_method: Some(auth::MethodName::Psk),
422 reason:
423 EstablishRsnaFailureReason::RsnaCompletionTimeout(
424 wlan_rsn::Error::LikelyWrongCredential,
425 ),
426 }) => true,
427
428 ConnectFailure::AssociationFailure(AssociationFailure {
437 bss_protection: BssProtection::Wep,
438 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
439 }) => true,
440
441 ConnectFailure::AssociationFailure(AssociationFailure {
445 bss_protection: BssProtection::Wpa3Personal,
446 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
447 })
448 | ConnectFailure::AssociationFailure(AssociationFailure {
449 bss_protection: BssProtection::Wpa2Wpa3Personal,
450 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
451 }) => true,
452 _ => false,
453 }
454 }
455
456 pub fn status_code(&self) -> fidl_ieee80211::StatusCode {
457 match self {
458 ConnectFailure::JoinFailure(code)
459 | ConnectFailure::AuthenticationFailure(code)
460 | ConnectFailure::AssociationFailure(AssociationFailure { code, .. }) => *code,
461 ConnectFailure::EstablishRsnaFailure(..) => {
462 fidl_ieee80211::StatusCode::EstablishRsnaFailure
463 }
464 ConnectFailure::ScanFailure(fidl_mlme::ScanResultCode::ShouldWait) => {
466 fidl_ieee80211::StatusCode::Canceled
467 }
468 ConnectFailure::SelectNetworkFailure(..) | ConnectFailure::ScanFailure(..) => {
469 fidl_ieee80211::StatusCode::RefusedReasonUnspecified
470 }
471 }
472 }
473}
474
475#[derive(Debug, PartialEq)]
476pub enum RoamFailureType {
477 SelectNetworkFailure,
478 RoamStartMalformedFailure,
479 RoamResultMalformedFailure,
480 RoamRequestMalformedFailure,
481 RoamConfirmationMalformedFailure,
482 ReassociationFailure,
483 EstablishRsnaFailure,
484}
485
486#[derive(Debug, PartialEq)]
487pub struct RoamFailure {
488 failure_type: RoamFailureType,
489 pub selected_bssid: Bssid,
490 pub status_code: fidl_ieee80211::StatusCode,
491 pub disconnect_info: fidl_sme::DisconnectInfo,
492 auth_method: Option<auth::MethodName>,
493 pub selected_bss: Option<BssDescription>,
494 establish_rsna_failure_reason: Option<EstablishRsnaFailureReason>,
495}
496
497impl RoamFailure {
498 #[allow(
501 clippy::match_like_matches_macro,
502 reason = "mass allow for https://fxbug.dev/381896734"
503 )]
504 pub fn likely_due_to_credential_rejected(&self) -> bool {
505 match self.failure_type {
506 RoamFailureType::EstablishRsnaFailure => match self.auth_method {
508 Some(auth::MethodName::Psk) => match self.establish_rsna_failure_reason {
509 Some(EstablishRsnaFailureReason::RsnaResponseTimeout(
510 wlan_rsn::Error::LikelyWrongCredential,
511 ))
512 | Some(EstablishRsnaFailureReason::RsnaCompletionTimeout(
513 wlan_rsn::Error::LikelyWrongCredential,
514 )) => true,
515 _ => false,
516 },
517 _ => false,
518 },
519 RoamFailureType::ReassociationFailure => {
520 match &self.selected_bss {
521 Some(selected_bss) => match selected_bss.protection() {
522 BssProtection::Wep => match self.status_code {
524 fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported => true,
525 _ => false,
526 },
527 BssProtection::Wpa3Personal
529 | BssProtection::Wpa2Wpa3Personal => match self.status_code {
530 fidl_ieee80211::StatusCode::RejectedSequenceTimeout => true,
531 _ => false,
532 },
533 _ => false,
534 },
535 None => false,
538 }
539 }
540 _ => false,
541 }
542 }
543}
544
545#[derive(Debug, PartialEq)]
546pub enum SelectNetworkFailure {
547 NoScanResultWithSsid,
548 IncompatibleConnectRequest,
549 InternalProtectionError,
550}
551
552impl From<SelectNetworkFailure> for ConnectFailure {
553 fn from(failure: SelectNetworkFailure) -> Self {
554 ConnectFailure::SelectNetworkFailure(failure)
555 }
556}
557
558#[derive(Debug, PartialEq)]
559pub struct AssociationFailure {
560 pub bss_protection: BssProtection,
561 pub code: fidl_ieee80211::StatusCode,
562}
563
564impl From<AssociationFailure> for ConnectFailure {
565 fn from(failure: AssociationFailure) -> Self {
566 ConnectFailure::AssociationFailure(failure)
567 }
568}
569
570#[derive(Debug, PartialEq)]
571pub struct EstablishRsnaFailure {
572 pub auth_method: Option<auth::MethodName>,
573 pub reason: EstablishRsnaFailureReason,
574}
575
576#[derive(Debug, PartialEq)]
577pub enum EstablishRsnaFailureReason {
578 StartSupplicantFailed,
579 RsnaResponseTimeout(wlan_rsn::Error),
580 RsnaCompletionTimeout(wlan_rsn::Error),
581 InternalError,
582}
583
584impl From<EstablishRsnaFailure> for ConnectFailure {
585 fn from(failure: EstablishRsnaFailure) -> Self {
586 ConnectFailure::EstablishRsnaFailure(failure)
587 }
588}
589
590#[derive(Clone, Debug, PartialEq)]
593pub struct ServingApInfo {
594 pub bssid: Bssid,
595 pub ssid: Ssid,
596 pub rssi_dbm: i8,
597 pub snr_db: i8,
598 pub signal_report_time: zx::MonotonicInstant,
599 pub channel: wlan_common::channel::Channel,
600 pub protection: BssProtection,
601 pub ht_cap: Option<fidl_ieee80211::HtCapabilities>,
602 pub vht_cap: Option<fidl_ieee80211::VhtCapabilities>,
603 pub probe_resp_wsc: Option<wsc::ProbeRespWsc>,
604 pub wmm_param: Option<ie::WmmParam>,
605}
606
607impl From<ServingApInfo> for fidl_sme::ServingApInfo {
608 fn from(ap: ServingApInfo) -> fidl_sme::ServingApInfo {
609 fidl_sme::ServingApInfo {
610 bssid: ap.bssid.to_array(),
611 ssid: ap.ssid.to_vec(),
612 rssi_dbm: ap.rssi_dbm,
613 snr_db: ap.snr_db,
614 channel: ap.channel.into(),
615 protection: ap.protection.into(),
616 }
617 }
618}
619
620#[allow(clippy::large_enum_variant)]
622#[derive(Clone, Debug, PartialEq)]
623pub enum ClientSmeStatus {
624 Connected(ServingApInfo),
625 Connecting(Ssid),
626 Roaming(Bssid),
627 Idle,
628}
629
630impl ClientSmeStatus {
631 pub fn is_connecting(&self) -> bool {
632 matches!(self, ClientSmeStatus::Connecting(_))
633 }
634
635 pub fn is_connected(&self) -> bool {
636 matches!(self, ClientSmeStatus::Connected(_))
637 }
638}
639
640impl From<ClientSmeStatus> for fidl_sme::ClientStatusResponse {
641 fn from(client_sme_status: ClientSmeStatus) -> fidl_sme::ClientStatusResponse {
642 match client_sme_status {
643 ClientSmeStatus::Connected(serving_ap_info) => {
644 fidl_sme::ClientStatusResponse::Connected(serving_ap_info.into())
645 }
646 ClientSmeStatus::Connecting(ssid) => {
647 fidl_sme::ClientStatusResponse::Connecting(ssid.to_vec())
648 }
649 ClientSmeStatus::Roaming(bssid) => {
650 fidl_sme::ClientStatusResponse::Roaming(bssid.to_array())
651 }
652 ClientSmeStatus::Idle => fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {}),
653 }
654 }
655}
656
657impl ClientSme {
658 #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
659 pub fn new(
660 cfg: ClientConfig,
661 info: fidl_mlme::DeviceInfo,
662 inspector: fuchsia_inspect::Inspector,
663 inspect_node: fuchsia_inspect::Node,
664 persistence_req_sender: auto_persist::PersistenceReqSender,
665 security_support: fidl_common::SecuritySupport,
666 spectrum_management_support: fidl_common::SpectrumManagementSupport,
667 ) -> (Self, MlmeSink, MlmeStream, timer::EventStream<Event>) {
668 let device_info = Arc::new(info);
669 let (mlme_sink, mlme_stream) = mpsc::unbounded();
670 let (mut timer, time_stream) = timer::create_timer();
671 let inspect = Arc::new(inspect::SmeTree::new(
672 inspector,
673 inspect_node,
674 &device_info,
675 &spectrum_management_support,
676 ));
677 let _ = timer.schedule(event::InspectPulseCheck);
678 let _ = timer.schedule(event::InspectPulsePersist);
679 let mut auto_persist_last_pulse =
680 AutoPersist::new((), "wlanstack-last-pulse", persistence_req_sender);
681 {
682 let _guard = auto_persist_last_pulse.get_mut();
684 }
685
686 (
687 ClientSme {
688 cfg,
689 state: Some(ClientState::new(cfg)),
690 scan_sched: <ScanScheduler<
691 Responder<Result<Vec<ScanResult>, fidl_mlme::ScanResultCode>>,
692 >>::new(
693 Arc::clone(&device_info), spectrum_management_support
694 ),
695 wmm_status_responders: vec![],
696 auto_persist_last_pulse,
697 context: Context {
698 mlme_sink: MlmeSink::new(mlme_sink.clone()),
699 device_info,
700 timer,
701 att_id: 0,
702 inspect,
703 security_support,
704 },
705 },
706 MlmeSink::new(mlme_sink),
707 mlme_stream,
708 time_stream,
709 )
710 }
711
712 pub fn on_connect_command(
713 &mut self,
714 req: fidl_sme::ConnectRequest,
715 ) -> ConnectTransactionStream {
716 let (mut connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
717
718 self.state = self.state.take().map(|state| state.cancel_ongoing_connect(&mut self.context));
720
721 let bss_description: BssDescription = match req.bss_description.try_into() {
722 Ok(bss_description) => bss_description,
723 Err(e) => {
724 error!("Failed converting FIDL BssDescription in ConnectRequest: {:?}", e);
725 connect_txn_sink
726 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
727 return connect_txn_stream;
728 }
729 };
730
731 info!("Received ConnectRequest for {}", bss_description);
732
733 if self
734 .cfg
735 .bss_compatibility(
736 &bss_description,
737 &self.context.device_info,
738 &self.context.security_support,
739 )
740 .is_err()
741 {
742 warn!("BSS is incompatible");
743 connect_txn_sink
744 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
745 return connect_txn_stream;
746 }
747
748 let authentication = req.authentication.clone();
749 let protection = match SecurityAuthenticator::try_from(req.authentication)
750 .map_err(From::from)
751 .and_then(|authenticator| {
752 Protection::try_from(SecurityContext {
753 security: &authenticator,
754 device: &self.context.device_info,
755 security_support: &self.context.security_support,
756 config: &self.cfg,
757 bss: &bss_description,
758 })
759 }) {
760 Ok(protection) => protection,
761 Err(error) => {
762 warn!(
763 "{:?}",
764 format!(
765 "Failed to configure protection for network {} ({}): {:?}",
766 bss_description.ssid, bss_description.bssid, error
767 )
768 );
769 connect_txn_sink
770 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
771 return connect_txn_stream;
772 }
773 };
774 let cmd = ConnectCommand {
775 bss: Box::new(bss_description),
776 connect_txn_sink,
777 protection,
778 authentication,
779 };
780
781 self.state = self.state.take().map(|state| state.connect(cmd, &mut self.context));
782 connect_txn_stream
783 }
784
785 pub fn on_roam_command(&mut self, req: fidl_sme::RoamRequest) {
786 if !self.status().is_connected() {
787 error!("SME ignoring roam request because client is not connected");
788 } else {
789 self.state =
790 self.state.take().map(|state| state.roam(&mut self.context, req.bss_description));
791 }
792 }
793
794 pub fn on_disconnect_command(
795 &mut self,
796 policy_disconnect_reason: fidl_sme::UserDisconnectReason,
797 responder: fidl_sme::ClientSmeDisconnectResponder,
798 ) {
799 self.state = self
800 .state
801 .take()
802 .map(|state| state.disconnect(&mut self.context, policy_disconnect_reason, responder));
803 self.context.inspect.update_pulse(self.status());
804 }
805
806 pub fn on_scan_command(
807 &mut self,
808 scan_request: fidl_sme::ScanRequest,
809 ) -> oneshot::Receiver<Result<Vec<wlan_common::scan::ScanResult>, fidl_mlme::ScanResultCode>>
810 {
811 let (responder, receiver) = Responder::new();
812 if self.status().is_connecting() {
813 info!("SME ignoring scan request because a connect is in progress");
814 responder.respond(Err(fidl_mlme::ScanResultCode::ShouldWait));
815 } else {
816 info!(
817 "SME received a scan command, initiating a{} discovery scan",
818 match scan_request {
819 fidl_sme::ScanRequest::Active(_) => "n active",
820 fidl_sme::ScanRequest::Passive(_) => " passive",
821 }
822 );
823 let scan = DiscoveryScan::new(responder, scan_request);
824 let req = self.scan_sched.enqueue_scan_to_discover(scan);
825 self.send_scan_request(req);
826 }
827 receiver
828 }
829
830 pub fn on_clone_inspect_vmo(&self) -> Option<fidl::Vmo> {
831 self.context.inspect.clone_vmo_data()
832 }
833
834 pub fn status(&self) -> ClientSmeStatus {
835 #[expect(clippy::expect_used)]
837 self.state.as_ref().expect("expected state to be always present").status()
838 }
839
840 pub fn wmm_status(&mut self) -> oneshot::Receiver<fidl_sme::ClientSmeWmmStatusResult> {
841 let (responder, receiver) = Responder::new();
842 self.wmm_status_responders.push(responder);
843 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
844 receiver
845 }
846
847 fn send_scan_request(&mut self, req: Option<fidl_mlme::ScanRequest>) {
848 if let Some(req) = req {
849 self.context.mlme_sink.send(MlmeRequest::Scan(req));
850 }
851 }
852
853 pub fn query_telemetry_support(
854 &mut self,
855 ) -> oneshot::Receiver<Result<fidl_stats::TelemetrySupport, i32>> {
856 let (responder, receiver) = Responder::new();
857 self.context.mlme_sink.send(MlmeRequest::QueryTelemetrySupport(responder));
858 receiver
859 }
860
861 pub fn iface_stats(&mut self) -> oneshot::Receiver<fidl_mlme::GetIfaceStatsResponse> {
862 let (responder, receiver) = Responder::new();
863 self.context.mlme_sink.send(MlmeRequest::GetIfaceStats(responder));
864 receiver
865 }
866
867 pub fn histogram_stats(
868 &mut self,
869 ) -> oneshot::Receiver<fidl_mlme::GetIfaceHistogramStatsResponse> {
870 let (responder, receiver) = Responder::new();
871 self.context.mlme_sink.send(MlmeRequest::GetIfaceHistogramStats(responder));
872 receiver
873 }
874}
875
876impl super::Station for ClientSme {
877 type Event = Event;
878
879 fn on_mlme_event(&mut self, event: fidl_mlme::MlmeEvent) {
880 match event {
881 fidl_mlme::MlmeEvent::OnScanResult { result } => self
882 .scan_sched
883 .on_mlme_scan_result(result)
884 .unwrap_or_else(|e| error!("scan result error: {:?}", e)),
885 fidl_mlme::MlmeEvent::OnScanEnd { end } => {
886 match self.scan_sched.on_mlme_scan_end(end, &self.context.inspect) {
887 Err(e) => error!("scan end error: {:?}", e),
888 Ok((scan_end, next_request)) => {
889 self.send_scan_request(next_request);
892
893 match scan_end.result_code {
894 fidl_mlme::ScanResultCode::Success => {
895 let scan_result_list: Vec<ScanResult> = scan_end
896 .bss_description_list
897 .into_iter()
898 .map(|bss_description| {
899 self.cfg.create_scan_result(
900 zx::MonotonicInstant::from_nanos(0),
902 bss_description,
903 &self.context.device_info,
904 &self.context.security_support,
905 )
906 })
907 .collect();
908 for responder in scan_end.tokens {
909 responder.respond(Ok(scan_result_list.clone()));
910 }
911 }
912 result_code => {
913 let count = scan_end.bss_description_list.len();
914 if count > 0 {
915 warn!("Incomplete scan with {} pending results.", count);
916 }
917 for responder in scan_end.tokens {
918 responder.respond(Err(result_code));
919 }
920 }
921 }
922 }
923 }
924 }
925 fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp } => {
926 for responder in self.wmm_status_responders.drain(..) {
927 let result = if status == zx::sys::ZX_OK { Ok(resp) } else { Err(status) };
928 responder.respond(result);
929 }
930 let event = fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp };
931 self.state =
932 self.state.take().map(|state| state.on_mlme_event(event, &mut self.context));
933 }
934 other => {
935 self.state =
936 self.state.take().map(|state| state.on_mlme_event(other, &mut self.context));
937 }
938 };
939
940 self.context.inspect.update_pulse(self.status());
941 }
942
943 fn on_timeout(&mut self, timed_event: timer::Event<Event>) {
944 self.state = self.state.take().map(|state| match timed_event.event {
945 event @ Event::RsnaCompletionTimeout(..)
946 | event @ Event::RsnaResponseTimeout(..)
947 | event @ Event::RsnaRetransmissionTimeout(..)
948 | event @ Event::SaeTimeout(..)
949 | event @ Event::DeauthenticateTimeout(..) => {
950 state.handle_timeout(event, &mut self.context)
951 }
952 Event::InspectPulseCheck(..) => {
953 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
954 let _ = self.context.timer.schedule(event::InspectPulseCheck);
955 state
956 }
957 Event::InspectPulsePersist(..) => {
958 let _guard = self.auto_persist_last_pulse.get_mut();
963 let _ = self.context.timer.schedule(event::InspectPulsePersist);
964 state
965 }
966 });
967
968 self.context.inspect.update_pulse(self.status());
971 }
972}
973
974fn report_connect_finished(connect_txn_sink: &mut ConnectTransactionSink, result: ConnectResult) {
975 connect_txn_sink.send_connect_result(result);
976}
977
978fn report_roam_finished(connect_txn_sink: &mut ConnectTransactionSink, result: RoamResult) {
979 connect_txn_sink.send_roam_result(result);
980}
981
982#[cfg(test)]
983mod tests {
984 use super::*;
985 use crate::Config as SmeConfig;
986 use ieee80211::MacAddr;
987 use lazy_static::lazy_static;
988 use std::collections::HashSet;
989 use test_case::test_case;
990 use wlan_common::{
991 assert_variant,
992 channel::{Cbw, Channel},
993 fake_bss_description, fake_fidl_bss_description,
994 ie::{fake_ht_cap_bytes, fake_vht_cap_bytes, IeType},
995 security::{wep::WEP40_KEY_BYTES, wpa::credential::PSK_SIZE_BYTES},
996 test_utils::{
997 fake_features::{
998 fake_security_support, fake_security_support_empty,
999 fake_spectrum_management_support_empty,
1000 },
1001 fake_stas::{FakeProtectionCfg, IesOverrides},
1002 },
1003 };
1004 use {
1005 fidl_fuchsia_wlan_common as fidl_common,
1006 fidl_fuchsia_wlan_common_security as fidl_security, fidl_fuchsia_wlan_mlme as fidl_mlme,
1007 fuchsia_inspect as finspect,
1008 };
1009
1010 use super::test_utils::{create_on_wmm_status_resp, fake_wmm_param, fake_wmm_status_resp};
1011
1012 use crate::{test_utils, Station};
1013
1014 lazy_static! {
1015 static ref CLIENT_ADDR: MacAddr = [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into();
1016 }
1017
1018 fn authentication_open() -> fidl_security::Authentication {
1019 fidl_security::Authentication { protocol: fidl_security::Protocol::Open, credentials: None }
1020 }
1021
1022 fn authentication_wep40() -> fidl_security::Authentication {
1023 fidl_security::Authentication {
1024 protocol: fidl_security::Protocol::Wep,
1025 credentials: Some(Box::new(fidl_security::Credentials::Wep(
1026 fidl_security::WepCredentials { key: [1; WEP40_KEY_BYTES].into() },
1027 ))),
1028 }
1029 }
1030
1031 fn authentication_wpa1_passphrase() -> fidl_security::Authentication {
1032 fidl_security::Authentication {
1033 protocol: fidl_security::Protocol::Wpa1,
1034 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1035 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1036 ))),
1037 }
1038 }
1039
1040 fn authentication_wpa2_personal_psk() -> fidl_security::Authentication {
1041 fidl_security::Authentication {
1042 protocol: fidl_security::Protocol::Wpa2Personal,
1043 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1044 fidl_security::WpaCredentials::Psk([1; PSK_SIZE_BYTES]),
1045 ))),
1046 }
1047 }
1048
1049 fn authentication_wpa2_personal_passphrase() -> fidl_security::Authentication {
1050 fidl_security::Authentication {
1051 protocol: fidl_security::Protocol::Wpa2Personal,
1052 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1053 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1054 ))),
1055 }
1056 }
1057
1058 fn authentication_wpa3_personal_passphrase() -> fidl_security::Authentication {
1059 fidl_security::Authentication {
1060 protocol: fidl_security::Protocol::Wpa3Personal,
1061 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1062 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1063 ))),
1064 }
1065 }
1066
1067 fn report_fake_scan_result(
1068 sme: &mut ClientSme,
1069 timestamp_nanos: i64,
1070 bss: fidl_common::BssDescription,
1071 ) {
1072 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
1073 result: fidl_mlme::ScanResult { txn_id: 1, timestamp_nanos, bss },
1074 });
1075 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
1076 end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCode::Success },
1077 });
1078 }
1079
1080 #[test_case(FakeProtectionCfg::Open)]
1081 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1082 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1083 #[test_case(FakeProtectionCfg::Wpa2)]
1084 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1085 fn default_client_protection_is_bss_compatible(protection: FakeProtectionCfg) {
1086 let cfg = ClientConfig::default();
1087 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1088 assert!(cfg
1089 .bss_compatibility(
1090 &fake_bss_description!(protection => protection),
1091 &fake_device_info,
1092 &fake_security_support_empty()
1093 )
1094 .is_ok(),);
1095 }
1096
1097 #[test_case(FakeProtectionCfg::Wpa1)]
1098 #[test_case(FakeProtectionCfg::Wpa3)]
1099 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1100 #[test_case(FakeProtectionCfg::Eap)]
1101 fn default_client_protection_is_bss_incompatible(protection: FakeProtectionCfg) {
1102 let cfg = ClientConfig::default();
1103 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1104 assert!(cfg
1105 .bss_compatibility(
1106 &fake_bss_description!(protection => protection),
1107 &fake_device_info,
1108 &fake_security_support_empty()
1109 )
1110 .is_err(),);
1111 }
1112
1113 #[test_case(FakeProtectionCfg::Open)]
1114 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1115 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1116 #[test_case(FakeProtectionCfg::Wpa2)]
1117 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1118 fn compatible_default_client_protection_security_protocol_intersection_is_non_empty(
1119 protection: FakeProtectionCfg,
1120 ) {
1121 let cfg = ClientConfig::default();
1122 assert!(!cfg
1123 .security_protocol_intersection(
1124 &fake_bss_description!(protection => protection),
1125 &fake_security_support_empty()
1126 )
1127 .is_empty());
1128 }
1129
1130 #[test_case(FakeProtectionCfg::Wpa1)]
1131 #[test_case(FakeProtectionCfg::Wpa3)]
1132 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1133 #[test_case(FakeProtectionCfg::Eap)]
1134 fn incompatible_default_client_protection_security_protocol_intersection_is_empty(
1135 protection: FakeProtectionCfg,
1136 ) {
1137 let cfg = ClientConfig::default();
1138 assert!(cfg
1139 .security_protocol_intersection(
1140 &fake_bss_description!(protection => protection),
1141 &fake_security_support_empty()
1142 )
1143 .is_empty(),);
1144 }
1145
1146 #[test_case(FakeProtectionCfg::Wpa1, [SecurityDescriptor::WPA1])]
1147 #[test_case(FakeProtectionCfg::Wpa3, [SecurityDescriptor::WPA3_PERSONAL])]
1148 #[test_case(FakeProtectionCfg::Wpa3Transition, [SecurityDescriptor::WPA3_PERSONAL])]
1149 #[test_case(FakeProtectionCfg::Eap, [])]
1151 fn default_client_protection_security_protocols_by_mac_role_eq(
1152 protection: FakeProtectionCfg,
1153 expected: impl IntoIterator<Item = SecurityDescriptor>,
1154 ) {
1155 let cfg = ClientConfig::default();
1156 let security_protocols: HashSet<_> = cfg
1157 .security_protocols_by_mac_role(&fake_bss_description!(protection => protection))
1158 .collect();
1159 assert_eq!(
1162 security_protocols,
1163 HashSet::from_iter(
1164 [
1165 (SecurityDescriptor::OPEN, MacRole::Client),
1166 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1167 ]
1168 .into_iter()
1169 .chain(expected.into_iter().map(|protocol| (protocol, MacRole::Ap)))
1170 ),
1171 );
1172 }
1173
1174 #[test]
1175 fn configured_client_bss_wep_compatible() {
1176 let cfg = ClientConfig::from_config(Config::default().with_wep(), false);
1178 assert!(!cfg
1179 .security_protocol_intersection(
1180 &fake_bss_description!(Wep),
1181 &fake_security_support_empty()
1182 )
1183 .is_empty());
1184 }
1185
1186 #[test]
1187 fn configured_client_bss_wpa1_compatible() {
1188 let cfg = ClientConfig::from_config(Config::default().with_wpa1(), false);
1190 assert!(!cfg
1191 .security_protocol_intersection(
1192 &fake_bss_description!(Wpa1),
1193 &fake_security_support_empty()
1194 )
1195 .is_empty());
1196 }
1197
1198 #[test]
1199 fn configured_client_bss_wpa3_compatible() {
1200 let cfg = ClientConfig::from_config(Config::default(), true);
1202 let mut security_support = fake_security_support_empty();
1203 security_support.mfp.supported = true;
1204 assert!(!cfg
1205 .security_protocol_intersection(&fake_bss_description!(Wpa3), &security_support)
1206 .is_empty());
1207 assert!(!cfg
1208 .security_protocol_intersection(
1209 &fake_bss_description!(Wpa3Transition),
1210 &security_support,
1211 )
1212 .is_empty());
1213 }
1214
1215 #[test]
1216 fn verify_rates_compatibility() {
1217 let cfg = ClientConfig::default();
1219 let device_info = test_utils::fake_device_info([1u8; 6].into());
1220 assert!(
1221 cfg.has_compatible_channel_and_data_rates(&fake_bss_description!(Open), &device_info)
1222 );
1223
1224 let bss = fake_bss_description!(Open, rates: vec![0x8C, 0xFF]);
1226 assert!(cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1227
1228 let bss = fake_bss_description!(Open, rates: vec![0x81]);
1230 assert!(!cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1231 }
1232
1233 #[test]
1234 fn convert_scan_result() {
1235 let cfg = ClientConfig::default();
1236 let bss_description = fake_bss_description!(Wpa2,
1237 ssid: Ssid::empty(),
1238 bssid: [0u8; 6],
1239 rssi_dbm: -30,
1240 snr_db: 0,
1241 channel: Channel::new(1, Cbw::Cbw20),
1242 ies_overrides: IesOverrides::new()
1243 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1244 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1245 );
1246 let device_info = test_utils::fake_device_info([1u8; 6].into());
1247 let timestamp = zx::MonotonicInstant::get();
1248 let scan_result = cfg.create_scan_result(
1249 timestamp,
1250 bss_description.clone(),
1251 &device_info,
1252 &fake_security_support(),
1253 );
1254
1255 assert_eq!(
1256 scan_result,
1257 ScanResult {
1258 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1259 timestamp,
1260 bss_description,
1261 }
1262 );
1263
1264 let wmm_param = *ie::parse_wmm_param(&fake_wmm_param().bytes[..])
1265 .expect("expect WMM param to be parseable");
1266 let bss_description = fake_bss_description!(Wpa2,
1267 ssid: Ssid::empty(),
1268 bssid: [0u8; 6],
1269 rssi_dbm: -30,
1270 snr_db: 0,
1271 channel: Channel::new(1, Cbw::Cbw20),
1272 wmm_param: Some(wmm_param),
1273 ies_overrides: IesOverrides::new()
1274 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1275 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1276 );
1277 let timestamp = zx::MonotonicInstant::get();
1278 let scan_result = cfg.create_scan_result(
1279 timestamp,
1280 bss_description.clone(),
1281 &device_info,
1282 &fake_security_support(),
1283 );
1284
1285 assert_eq!(
1286 scan_result,
1287 ScanResult {
1288 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1289 timestamp,
1290 bss_description,
1291 }
1292 );
1293
1294 let bss_description = fake_bss_description!(Wep,
1295 ssid: Ssid::empty(),
1296 bssid: [0u8; 6],
1297 rssi_dbm: -30,
1298 snr_db: 0,
1299 channel: Channel::new(1, Cbw::Cbw20),
1300 ies_overrides: IesOverrides::new()
1301 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1302 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1303 );
1304 let timestamp = zx::MonotonicInstant::get();
1305 let scan_result = cfg.create_scan_result(
1306 timestamp,
1307 bss_description.clone(),
1308 &device_info,
1309 &fake_security_support(),
1310 );
1311 assert_eq!(
1312 scan_result,
1313 ScanResult {
1314 compatibility: Incompatible::expect_err(
1315 "incompatible channel, PHY data rates, or security protocols",
1316 Some([
1317 (SecurityDescriptor::WEP, MacRole::Ap),
1318 (SecurityDescriptor::OPEN, MacRole::Client),
1319 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1320 ])
1321 ),
1322 timestamp,
1323 bss_description,
1324 },
1325 );
1326
1327 let cfg = ClientConfig::from_config(Config::default().with_wep(), false);
1328 let bss_description = fake_bss_description!(Wep,
1329 ssid: Ssid::empty(),
1330 bssid: [0u8; 6],
1331 rssi_dbm: -30,
1332 snr_db: 0,
1333 channel: Channel::new(1, Cbw::Cbw20),
1334 ies_overrides: IesOverrides::new()
1335 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1336 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1337 );
1338 let timestamp = zx::MonotonicInstant::get();
1339 let scan_result = cfg.create_scan_result(
1340 timestamp,
1341 bss_description.clone(),
1342 &device_info,
1343 &fake_security_support(),
1344 );
1345 assert_eq!(
1346 scan_result,
1347 ScanResult {
1348 compatibility: Compatible::expect_ok([SecurityDescriptor::WEP]),
1349 timestamp,
1350 bss_description,
1351 }
1352 );
1353 }
1354
1355 #[test_case(EstablishRsnaFailureReason::RsnaResponseTimeout(
1356 wlan_rsn::Error::LikelyWrongCredential
1357 ))]
1358 #[test_case(EstablishRsnaFailureReason::RsnaCompletionTimeout(
1359 wlan_rsn::Error::LikelyWrongCredential
1360 ))]
1361 fn test_connect_detection_of_rejected_wpa1_or_wpa2_credentials(
1362 reason: EstablishRsnaFailureReason,
1363 ) {
1364 let failure = ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
1365 auth_method: Some(auth::MethodName::Psk),
1366 reason,
1367 });
1368 assert!(failure.likely_due_to_credential_rejected());
1369 }
1370
1371 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1372 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1373 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1374 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1375 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1376 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1377 fn test_roam_detection_of_rejected_wpa1_or_wpa2_credentials(
1378 selected_bss: BssDescription,
1379 failure_reason: EstablishRsnaFailureReason,
1380 ) {
1381 let disconnect_info = fidl_sme::DisconnectInfo {
1382 is_sme_reconnecting: false,
1383 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1384 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1385 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1386 }),
1387 };
1388 let failure = RoamFailure {
1389 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1390 failure_type: RoamFailureType::EstablishRsnaFailure,
1391 selected_bssid: selected_bss.bssid,
1392 disconnect_info,
1393 auth_method: Some(auth::MethodName::Psk),
1394 establish_rsna_failure_reason: Some(failure_reason),
1395 selected_bss: Some(selected_bss),
1396 };
1397 assert!(failure.likely_due_to_credential_rejected());
1398 }
1399
1400 #[test]
1401 fn test_connect_detection_of_rejected_wpa3_credentials() {
1402 let bss = fake_bss_description!(Wpa3);
1403 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1404 bss_protection: bss.protection(),
1405 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1406 });
1407
1408 assert!(failure.likely_due_to_credential_rejected());
1409 }
1410
1411 #[test]
1412 fn test_roam_detection_of_rejected_wpa3_credentials() {
1413 let selected_bss = fake_bss_description!(Wpa3);
1414 let disconnect_info = fidl_sme::DisconnectInfo {
1415 is_sme_reconnecting: false,
1416 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1417 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1418 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1419 }),
1420 };
1421 let failure = RoamFailure {
1422 status_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1423 failure_type: RoamFailureType::ReassociationFailure,
1424 selected_bssid: selected_bss.bssid,
1425 disconnect_info,
1426 auth_method: Some(auth::MethodName::Sae),
1427 establish_rsna_failure_reason: None,
1428 selected_bss: Some(selected_bss),
1429 };
1430 assert!(failure.likely_due_to_credential_rejected());
1431 }
1432
1433 #[test]
1434 fn test_connect_detection_of_rejected_wep_credentials() {
1435 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1436 bss_protection: BssProtection::Wep,
1437 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1438 });
1439 assert!(failure.likely_due_to_credential_rejected());
1440 }
1441
1442 #[test]
1443 fn test_roam_detection_of_rejected_wep_credentials() {
1444 let selected_bss = fake_bss_description!(Wep);
1445 let disconnect_info = fidl_sme::DisconnectInfo {
1446 is_sme_reconnecting: false,
1447 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1448 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1449 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1450 }),
1451 };
1452 let failure = RoamFailure {
1453 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1454 failure_type: RoamFailureType::ReassociationFailure,
1455 selected_bssid: selected_bss.bssid,
1456 disconnect_info,
1457 auth_method: Some(auth::MethodName::Psk),
1458 establish_rsna_failure_reason: None,
1459 selected_bss: Some(selected_bss),
1460 };
1461 assert!(failure.likely_due_to_credential_rejected());
1462 }
1463
1464 #[test]
1465 fn test_connect_no_detection_of_rejected_wpa1_or_wpa2_credentials() {
1466 let failure = ConnectFailure::ScanFailure(fidl_mlme::ScanResultCode::InternalError);
1467 assert!(!failure.likely_due_to_credential_rejected());
1468
1469 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1470 bss_protection: BssProtection::Wpa2Personal,
1471 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1472 });
1473 assert!(!failure.likely_due_to_credential_rejected());
1474 }
1475
1476 #[test_case(fake_bss_description!(Wpa1))]
1477 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly))]
1478 #[test_case(fake_bss_description!(Wpa2))]
1479 fn test_roam_no_detection_of_rejected_wpa1_or_wpa2_credentials(selected_bss: BssDescription) {
1480 let disconnect_info = fidl_sme::DisconnectInfo {
1481 is_sme_reconnecting: false,
1482 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1483 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1484 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1485 }),
1486 };
1487 let failure = RoamFailure {
1488 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1489 failure_type: RoamFailureType::EstablishRsnaFailure,
1490 selected_bssid: selected_bss.bssid,
1491 disconnect_info,
1492 auth_method: Some(auth::MethodName::Psk),
1493 establish_rsna_failure_reason: Some(EstablishRsnaFailureReason::StartSupplicantFailed),
1494 selected_bss: Some(selected_bss),
1495 };
1496 assert!(!failure.likely_due_to_credential_rejected());
1497 }
1498
1499 #[test]
1500 fn test_connect_no_detection_of_rejected_wpa3_credentials() {
1501 let bss = fake_bss_description!(Wpa3);
1502 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1503 bss_protection: bss.protection(),
1504 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1505 });
1506
1507 assert!(!failure.likely_due_to_credential_rejected());
1508 }
1509
1510 #[test]
1511 fn test_roam_no_detection_of_rejected_wpa3_credentials() {
1512 let selected_bss = fake_bss_description!(Wpa3);
1513 let disconnect_info = fidl_sme::DisconnectInfo {
1514 is_sme_reconnecting: false,
1515 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1516 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1517 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1518 }),
1519 };
1520 let failure = RoamFailure {
1521 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1522 failure_type: RoamFailureType::ReassociationFailure,
1523 selected_bssid: selected_bss.bssid,
1524 disconnect_info,
1525 auth_method: Some(auth::MethodName::Sae),
1526 establish_rsna_failure_reason: None,
1527 selected_bss: Some(selected_bss),
1528 };
1529 assert!(!failure.likely_due_to_credential_rejected());
1530 }
1531
1532 #[test]
1533 fn test_connect_no_detection_of_rejected_wep_credentials() {
1534 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1535 bss_protection: BssProtection::Wep,
1536 code: fidl_ieee80211::StatusCode::InvalidParameters,
1537 });
1538 assert!(!failure.likely_due_to_credential_rejected());
1539 }
1540
1541 #[test]
1542 fn test_roam_no_detection_of_rejected_wep_credentials() {
1543 let selected_bss = fake_bss_description!(Wep);
1544 let disconnect_info = fidl_sme::DisconnectInfo {
1545 is_sme_reconnecting: false,
1546 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1547 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1548 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1549 }),
1550 };
1551 let failure = RoamFailure {
1552 status_code: fidl_ieee80211::StatusCode::StatusInvalidElement,
1553 failure_type: RoamFailureType::ReassociationFailure,
1554 selected_bssid: selected_bss.bssid,
1555 disconnect_info,
1556 auth_method: Some(auth::MethodName::Psk),
1557 establish_rsna_failure_reason: None,
1558 selected_bss: Some(selected_bss),
1559 };
1560 assert!(!failure.likely_due_to_credential_rejected());
1561 }
1562
1563 #[test_case(fake_bss_description!(Open), authentication_open() => matches Ok(Protection::Open))]
1564 #[test_case(fake_bss_description!(Open), authentication_wpa2_personal_passphrase() => matches Err(_))]
1565 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_passphrase() => matches Ok(Protection::Rsna(_)))]
1566 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_psk() => matches Ok(Protection::Rsna(_)))]
1567 #[test_case(fake_bss_description!(Wpa2), authentication_open() => matches Err(_))]
1568 fn test_protection_from_authentication(
1569 bss: BssDescription,
1570 authentication: fidl_security::Authentication,
1571 ) -> Result<Protection, anyhow::Error> {
1572 let device = test_utils::fake_device_info(*CLIENT_ADDR);
1573 let security_support = fake_security_support();
1574 let config = Default::default();
1575
1576 let authenticator = SecurityAuthenticator::try_from(authentication).unwrap();
1578 Protection::try_from(SecurityContext {
1579 security: &authenticator,
1580 device: &device,
1581 security_support: &security_support,
1582 config: &config,
1583 bss: &bss,
1584 })
1585 }
1586
1587 #[fuchsia::test(allow_stalls = false)]
1588 async fn status_connecting() {
1589 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1590 assert_eq!(ClientSmeStatus::Idle, sme.status());
1591
1592 let bss_description =
1594 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1595 let _recv = sme.on_connect_command(connect_req(
1596 Ssid::try_from("foo").unwrap(),
1597 bss_description,
1598 authentication_open(),
1599 ));
1600 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1601
1602 let ssid = assert_variant!(sme.state.as_ref().unwrap().status(), ClientSmeStatus::Connecting(ssid) => ssid);
1605 assert_eq!(Ssid::try_from("foo").unwrap(), ssid);
1606 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1607
1608 let bss_description =
1610 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap());
1611 let _recv2 = sme.on_connect_command(connect_req(
1612 Ssid::try_from("bar").unwrap(),
1613 bss_description,
1614 authentication_open(),
1615 ));
1616 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bar").unwrap()), sme.status());
1617 }
1618
1619 #[test]
1620 fn connecting_to_wep_network_supported() {
1621 let _executor = fuchsia_async::TestExecutor::new();
1622 let inspector = finspect::Inspector::default();
1623 let sme_root_node = inspector.root().create_child("sme");
1624 let (persistence_req_sender, _persistence_receiver) =
1625 test_utils::create_inspect_persistence_channel();
1626 let (mut sme, _mlme_sink, mut mlme_stream, _time_stream) = ClientSme::new(
1627 ClientConfig::from_config(SmeConfig::default().with_wep(), false),
1628 test_utils::fake_device_info(*CLIENT_ADDR),
1629 inspector,
1630 sme_root_node,
1631 persistence_req_sender,
1632 fake_security_support(),
1633 fake_spectrum_management_support_empty(),
1634 );
1635 assert_eq!(ClientSmeStatus::Idle, sme.status());
1636
1637 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1639 let req =
1640 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1641 let _recv = sme.on_connect_command(req);
1642 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1643
1644 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1645 }
1646
1647 #[fuchsia::test(allow_stalls = false)]
1648 async fn connecting_to_wep_network_unsupported() {
1649 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1650 assert_eq!(ClientSmeStatus::Idle, sme.status());
1651
1652 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1654 let req =
1655 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1656 let mut _connect_fut = sme.on_connect_command(req);
1657 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1658 }
1659
1660 #[fuchsia::test(allow_stalls = false)]
1661 async fn connecting_password_supplied_for_protected_network() {
1662 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1663 assert_eq!(ClientSmeStatus::Idle, sme.status());
1664
1665 let bss_description =
1667 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1668 let req = connect_req(
1669 Ssid::try_from("foo").unwrap(),
1670 bss_description,
1671 authentication_wpa2_personal_passphrase(),
1672 );
1673 let _recv = sme.on_connect_command(req);
1674 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1675
1676 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1677 }
1678
1679 #[fuchsia::test(allow_stalls = false)]
1680 async fn connecting_psk_supplied_for_protected_network() {
1681 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1682 assert_eq!(ClientSmeStatus::Idle, sme.status());
1683
1684 let bss_description =
1686 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("IEEE").unwrap());
1687 let req = connect_req(
1688 Ssid::try_from("IEEE").unwrap(),
1689 bss_description,
1690 authentication_wpa2_personal_psk(),
1691 );
1692 let _recv = sme.on_connect_command(req);
1693 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("IEEE").unwrap()), sme.status());
1694
1695 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1696 }
1697
1698 #[fuchsia::test(allow_stalls = false)]
1699 async fn connecting_password_supplied_for_unprotected_network() {
1700 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1701 assert_eq!(ClientSmeStatus::Idle, sme.status());
1702
1703 let bss_description =
1704 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1705 let req = connect_req(
1706 Ssid::try_from("foo").unwrap(),
1707 bss_description,
1708 authentication_wpa2_personal_passphrase(),
1709 );
1710 let mut connect_txn_stream = sme.on_connect_command(req);
1711 assert_eq!(ClientSmeStatus::Idle, sme.status());
1712
1713 assert_variant!(
1715 connect_txn_stream.try_next(),
1716 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1717 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1718 }
1719 );
1720 }
1721
1722 #[fuchsia::test(allow_stalls = false)]
1723 async fn connecting_psk_supplied_for_unprotected_network() {
1724 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1725 assert_eq!(ClientSmeStatus::Idle, sme.status());
1726
1727 let bss_description =
1728 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1729 let req = connect_req(
1730 Ssid::try_from("foo").unwrap(),
1731 bss_description,
1732 authentication_wpa2_personal_psk(),
1733 );
1734 let mut connect_txn_stream = sme.on_connect_command(req);
1735 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1736
1737 assert_variant!(
1739 connect_txn_stream.try_next(),
1740 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1741 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1742 }
1743 );
1744 }
1745
1746 #[fuchsia::test(allow_stalls = false)]
1747 async fn connecting_no_password_supplied_for_protected_network() {
1748 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1749 assert_eq!(ClientSmeStatus::Idle, sme.status());
1750
1751 let bss_description =
1752 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1753 let req =
1754 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_open());
1755 let mut connect_txn_stream = sme.on_connect_command(req);
1756 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1757
1758 assert_no_connect(&mut mlme_stream);
1760
1761 assert_variant!(
1763 connect_txn_stream.try_next(),
1764 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1765 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1766 }
1767 );
1768 }
1769
1770 #[fuchsia::test(allow_stalls = false)]
1771 async fn connecting_bypass_join_scan_open() {
1772 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1773 assert_eq!(ClientSmeStatus::Idle, sme.status());
1774
1775 let bss_description =
1776 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bssname").unwrap());
1777 let req =
1778 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
1779 let mut connect_txn_stream = sme.on_connect_command(req);
1780
1781 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
1782 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1783 assert_variant!(connect_txn_stream.try_next(), Err(_));
1785 }
1786
1787 #[fuchsia::test(allow_stalls = false)]
1788 async fn connecting_bypass_join_scan_protected() {
1789 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1790 assert_eq!(ClientSmeStatus::Idle, sme.status());
1791
1792 let bss_description =
1793 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
1794 let req = connect_req(
1795 Ssid::try_from("bssname").unwrap(),
1796 bss_description,
1797 authentication_wpa2_personal_passphrase(),
1798 );
1799 let mut connect_txn_stream = sme.on_connect_command(req);
1800
1801 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
1802 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1803 assert_variant!(connect_txn_stream.try_next(), Err(_));
1805 }
1806
1807 #[fuchsia::test(allow_stalls = false)]
1808 async fn connecting_bypass_join_scan_mismatched_credential() {
1809 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1810 assert_eq!(ClientSmeStatus::Idle, sme.status());
1811
1812 let bss_description =
1813 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
1814 let req =
1815 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
1816 let mut connect_txn_stream = sme.on_connect_command(req);
1817
1818 assert_eq!(ClientSmeStatus::Idle, sme.status());
1819 assert_no_connect(&mut mlme_stream);
1820
1821 assert_variant!(
1823 connect_txn_stream.try_next(),
1824 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1825 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1826 }
1827 );
1828 }
1829
1830 #[fuchsia::test(allow_stalls = false)]
1831 async fn connecting_bypass_join_scan_unsupported_bss() {
1832 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1833 assert_eq!(ClientSmeStatus::Idle, sme.status());
1834
1835 let bss_description =
1836 fake_fidl_bss_description!(Wpa3Enterprise, ssid: Ssid::try_from("bssname").unwrap());
1837 let req = connect_req(
1838 Ssid::try_from("bssname").unwrap(),
1839 bss_description,
1840 authentication_wpa3_personal_passphrase(),
1841 );
1842 let mut connect_txn_stream = sme.on_connect_command(req);
1843
1844 assert_eq!(ClientSmeStatus::Idle, sme.status());
1845 assert_no_connect(&mut mlme_stream);
1846
1847 assert_variant!(
1849 connect_txn_stream.try_next(),
1850 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1851 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1852 }
1853 );
1854 }
1855
1856 #[fuchsia::test(allow_stalls = false)]
1857 async fn connecting_right_credential_type_no_privacy() {
1858 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1859
1860 let bss_description = fake_fidl_bss_description!(
1861 Wpa2,
1862 ssid: Ssid::try_from("foo").unwrap(),
1863 );
1864 let bss_description = fidl_common::BssDescription {
1867 capability_info: wlan_common::mac::CapabilityInfo(bss_description.capability_info)
1868 .with_privacy(false)
1869 .0,
1870 ..bss_description
1871 };
1872 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1873 Ssid::try_from("foo").unwrap(),
1874 bss_description,
1875 authentication_wpa2_personal_passphrase(),
1876 ));
1877
1878 assert_variant!(
1879 connect_txn_stream.try_next(),
1880 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1881 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1882 }
1883 );
1884 }
1885
1886 #[fuchsia::test(allow_stalls = false)]
1887 async fn connecting_mismatched_security_protocol() {
1888 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1889
1890 let bss_description =
1891 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
1892 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1893 Ssid::try_from("wpa2").unwrap(),
1894 bss_description,
1895 authentication_wep40(),
1896 ));
1897 assert_variant!(
1898 connect_txn_stream.try_next(),
1899 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1900 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1901 }
1902 );
1903
1904 let bss_description =
1905 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
1906 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1907 Ssid::try_from("wpa2").unwrap(),
1908 bss_description,
1909 authentication_wpa1_passphrase(),
1910 ));
1911 assert_variant!(
1912 connect_txn_stream.try_next(),
1913 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1914 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1915 }
1916 );
1917
1918 let bss_description =
1919 fake_fidl_bss_description!(Wpa3, ssid: Ssid::try_from("wpa3").unwrap());
1920 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1921 Ssid::try_from("wpa3").unwrap(),
1922 bss_description,
1923 authentication_wpa2_personal_passphrase(),
1924 ));
1925 assert_variant!(
1926 connect_txn_stream.try_next(),
1927 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1928 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1929 }
1930 );
1931 }
1932
1933 #[fuchsia::test(allow_stalls = false, logging = false)]
1935 async fn connecting_right_credential_type_but_short_password() {
1936 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1937
1938 let bss_description =
1939 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1940 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1941 Ssid::try_from("foo").unwrap(),
1942 bss_description.clone(),
1943 fidl_security::Authentication {
1944 protocol: fidl_security::Protocol::Wpa2Personal,
1945 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1946 fidl_security::WpaCredentials::Passphrase(b"nope".as_slice().into()),
1947 ))),
1948 },
1949 ));
1950 report_fake_scan_result(
1951 &mut sme,
1952 zx::MonotonicInstant::get().into_nanos(),
1953 bss_description,
1954 );
1955
1956 assert_variant!(
1957 connect_txn_stream.try_next(),
1958 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1959 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1960 }
1961 );
1962 }
1963
1964 #[fuchsia::test(allow_stalls = false, logging = false)]
1966 async fn new_connect_attempt_cancels_pending_connect() {
1967 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1968
1969 let bss_description =
1970 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1971 let req = connect_req(
1972 Ssid::try_from("foo").unwrap(),
1973 bss_description.clone(),
1974 authentication_open(),
1975 );
1976 let mut connect_txn_stream1 = sme.on_connect_command(req);
1977
1978 let req2 = connect_req(
1979 Ssid::try_from("foo").unwrap(),
1980 bss_description.clone(),
1981 authentication_open(),
1982 );
1983 let mut connect_txn_stream2 = sme.on_connect_command(req2);
1984
1985 assert_variant!(
1987 connect_txn_stream1.try_next(),
1988 Ok(Some(ConnectTransactionEvent::OnConnectResult {
1989 result: ConnectResult::Canceled,
1990 is_reconnect: false
1991 }))
1992 );
1993
1994 report_fake_scan_result(
1997 &mut sme,
1998 zx::MonotonicInstant::get().into_nanos(),
1999 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()),
2000 );
2001
2002 let req3 = connect_req(
2003 Ssid::try_from("foo").unwrap(),
2004 bss_description.clone(),
2005 authentication_open(),
2006 );
2007 let mut _connect_fut3 = sme.on_connect_command(req3);
2008
2009 assert_variant!(
2011 connect_txn_stream2.try_next(),
2012 Ok(Some(ConnectTransactionEvent::OnConnectResult {
2013 result: ConnectResult::Canceled,
2014 is_reconnect: false
2015 }))
2016 );
2017 }
2018
2019 #[fuchsia::test(allow_stalls = false)]
2020 async fn test_simple_scan_error() {
2021 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2022 let mut recv =
2023 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2024
2025 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2026 end: fidl_mlme::ScanEnd {
2027 txn_id: 1,
2028 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2029 },
2030 });
2031
2032 assert_eq!(
2033 recv.try_recv(),
2034 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2035 );
2036 }
2037
2038 #[fuchsia::test(allow_stalls = false)]
2039 async fn test_scan_error_after_some_results_returned() {
2040 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2041 let mut recv =
2042 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2043
2044 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2045 bss.bssid = [3; 6];
2046 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2047 result: fidl_mlme::ScanResult {
2048 txn_id: 1,
2049 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2050 bss,
2051 },
2052 });
2053 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2054 bss.bssid = [4; 6];
2055 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2056 result: fidl_mlme::ScanResult {
2057 txn_id: 1,
2058 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2059 bss,
2060 },
2061 });
2062
2063 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2064 end: fidl_mlme::ScanEnd {
2065 txn_id: 1,
2066 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2067 },
2068 });
2069
2070 assert_eq!(
2072 recv.try_recv(),
2073 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2074 );
2075 }
2076
2077 #[fuchsia::test(allow_stalls = false)]
2078 async fn test_scan_is_rejected_while_connecting() {
2079 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2080
2081 let bss_description =
2083 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2084 let _recv = sme.on_connect_command(connect_req(
2085 Ssid::try_from("foo").unwrap(),
2086 bss_description,
2087 authentication_open(),
2088 ));
2089 assert_variant!(sme.status(), ClientSmeStatus::Connecting(_));
2090
2091 let mut recv =
2093 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2094 assert_eq!(recv.try_recv(), Ok(Some(Err(fidl_mlme::ScanResultCode::ShouldWait))));
2095 }
2096
2097 #[fuchsia::test(allow_stalls = false)]
2098 async fn test_wmm_status_success() {
2099 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2100 let mut receiver = sme.wmm_status();
2101
2102 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2103
2104 let resp = fake_wmm_status_resp();
2105 #[allow(
2106 clippy::redundant_field_names,
2107 reason = "mass allow for https://fxbug.dev/381896734"
2108 )]
2109 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnWmmStatusResp {
2110 status: zx::sys::ZX_OK,
2111 resp: resp,
2112 });
2113
2114 assert_eq!(receiver.try_recv(), Ok(Some(Ok(resp))));
2115 }
2116
2117 #[fuchsia::test(allow_stalls = false)]
2118 async fn test_wmm_status_failed() {
2119 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2120 let mut receiver = sme.wmm_status();
2121
2122 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2123 sme.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_ERR_IO));
2124 assert_eq!(receiver.try_recv(), Ok(Some(Err(zx::sys::ZX_ERR_IO))));
2125 }
2126
2127 #[test]
2128 fn test_inspect_pulse_persist() {
2129 let _executor = fuchsia_async::TestExecutor::new();
2130 let inspector = finspect::Inspector::default();
2131 let sme_root_node = inspector.root().create_child("sme");
2132 let (persistence_req_sender, mut persistence_receiver) =
2133 test_utils::create_inspect_persistence_channel();
2134 let (mut sme, _mlme_sink, _mlme_stream, mut time_stream) = ClientSme::new(
2135 ClientConfig::from_config(SmeConfig::default().with_wep(), false),
2136 test_utils::fake_device_info(*CLIENT_ADDR),
2137 inspector,
2138 sme_root_node,
2139 persistence_req_sender,
2140 fake_security_support(),
2141 fake_spectrum_management_support_empty(),
2142 );
2143 assert_eq!(ClientSmeStatus::Idle, sme.status());
2144
2145 assert_variant!(persistence_receiver.try_next(), Ok(Some(tag)) => {
2147 assert_eq!(&tag, "wlanstack-last-pulse");
2148 });
2149
2150 let mut persist_event = None;
2151 while let Ok(Some((_timeout, timed_event, _handle))) = time_stream.try_next() {
2152 if let Event::InspectPulsePersist(..) = timed_event.event {
2153 persist_event = Some(timed_event);
2154 break;
2155 }
2156 }
2157 assert!(persist_event.is_some());
2158 sme.on_timeout(persist_event.unwrap());
2159
2160 assert_variant!(persistence_receiver.try_next(), Ok(Some(tag)) => {
2162 assert_eq!(&tag, "wlanstack-last-pulse");
2163 });
2164 }
2165
2166 fn assert_no_connect(mlme_stream: &mut mpsc::UnboundedReceiver<MlmeRequest>) {
2167 loop {
2168 match mlme_stream.try_next() {
2169 Ok(event) => match event {
2170 Some(MlmeRequest::Connect(..)) => {
2171 panic!("unexpected connect request sent to MLME")
2172 }
2173 None => break,
2174 _ => (),
2175 },
2176 Err(e) => {
2177 assert_eq!(e.to_string(), "receiver channel is empty");
2178 break;
2179 }
2180 }
2181 }
2182 }
2183
2184 fn connect_req(
2185 ssid: Ssid,
2186 bss_description: fidl_common::BssDescription,
2187 authentication: fidl_security::Authentication,
2188 ) -> fidl_sme::ConnectRequest {
2189 fidl_sme::ConnectRequest {
2190 ssid: ssid.to_vec(),
2191 bss_description,
2192 multiple_bss_candidates: true,
2193 authentication,
2194 deprecated_scan_type: fidl_common::ScanType::Passive,
2195 }
2196 }
2197
2198 async fn create_sme() -> (ClientSme, MlmeStream, timer::EventStream<Event>) {
2205 let inspector = finspect::Inspector::default();
2206 let sme_root_node = inspector.root().create_child("sme");
2207 let (persistence_req_sender, _persistence_receiver) =
2208 test_utils::create_inspect_persistence_channel();
2209 let (client_sme, _mlme_sink, mlme_stream, time_stream) = ClientSme::new(
2210 ClientConfig::default(),
2211 test_utils::fake_device_info(*CLIENT_ADDR),
2212 inspector,
2213 sme_root_node,
2214 persistence_req_sender,
2215 fake_security_support(),
2216 fake_spectrum_management_support_empty(),
2217 );
2218 (client_sme, mlme_stream, time_stream)
2219 }
2220}