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