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::MlmeSink;
50 use crate::client::event::Event;
51 use crate::client::{ConnectionAttemptId, inspect};
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 pub fn signal_report(&mut self) -> oneshot::Receiver<Result<fidl_stats::SignalReport, i32>> {
876 let (responder, receiver) = Responder::new();
877 self.context.mlme_sink.send(MlmeRequest::GetSignalReport(responder));
878 receiver
879 }
880
881 pub fn set_mac_address(&mut self, mac_addr: [u8; 6]) -> oneshot::Receiver<Result<(), i32>> {
882 let (responder, receiver) = Responder::new();
883 self.context.mlme_sink.send(MlmeRequest::SetMacAddress(mac_addr, responder));
884 receiver
885 }
886}
887
888impl super::Station for ClientSme {
889 type Event = Event;
890
891 fn on_mlme_event(&mut self, event: fidl_mlme::MlmeEvent) {
892 match event {
893 fidl_mlme::MlmeEvent::OnScanResult { result } => self
894 .scan_sched
895 .on_mlme_scan_result(result)
896 .unwrap_or_else(|e| error!("scan result error: {:?}", e)),
897 fidl_mlme::MlmeEvent::OnScanEnd { end } => {
898 match self.scan_sched.on_mlme_scan_end(end, &self.context.inspect) {
899 Err(e) => error!("scan end error: {:?}", e),
900 Ok((scan_end, next_request)) => {
901 self.send_scan_request(next_request);
904
905 match scan_end.result_code {
906 fidl_mlme::ScanResultCode::Success => {
907 let scan_result_list: Vec<ScanResult> = scan_end
908 .bss_description_list
909 .into_iter()
910 .map(|bss_description| {
911 self.cfg.create_scan_result(
912 zx::MonotonicInstant::from_nanos(0),
914 bss_description,
915 &self.context.device_info,
916 &self.context.security_support,
917 )
918 })
919 .collect();
920 for responder in scan_end.tokens {
921 responder.respond(Ok(scan_result_list.clone()));
922 }
923 }
924 result_code => {
925 let count = scan_end.bss_description_list.len();
926 if count > 0 {
927 warn!("Incomplete scan with {} pending results.", count);
928 }
929 for responder in scan_end.tokens {
930 responder.respond(Err(result_code));
931 }
932 }
933 }
934 }
935 }
936 }
937 fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp } => {
938 for responder in self.wmm_status_responders.drain(..) {
939 let result = if status == zx::sys::ZX_OK { Ok(resp) } else { Err(status) };
940 responder.respond(result);
941 }
942 let event = fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp };
943 self.state =
944 self.state.take().map(|state| state.on_mlme_event(event, &mut self.context));
945 }
946 other => {
947 self.state =
948 self.state.take().map(|state| state.on_mlme_event(other, &mut self.context));
949 }
950 };
951
952 self.context.inspect.update_pulse(self.status());
953 }
954
955 fn on_timeout(&mut self, timed_event: timer::Event<Event>) {
956 self.state = self.state.take().map(|state| match timed_event.event {
957 event @ Event::RsnaCompletionTimeout(..)
958 | event @ Event::RsnaResponseTimeout(..)
959 | event @ Event::RsnaRetransmissionTimeout(..)
960 | event @ Event::SaeTimeout(..)
961 | event @ Event::DeauthenticateTimeout(..) => {
962 state.handle_timeout(event, &mut self.context)
963 }
964 Event::InspectPulseCheck(..) => {
965 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
966 let _ = self.context.timer.schedule(event::InspectPulseCheck);
967 state
968 }
969 Event::InspectPulsePersist(..) => {
970 let _guard = self.auto_persist_last_pulse.get_mut();
975 let _ = self.context.timer.schedule(event::InspectPulsePersist);
976 state
977 }
978 });
979
980 self.context.inspect.update_pulse(self.status());
983 }
984}
985
986fn report_connect_finished(connect_txn_sink: &mut ConnectTransactionSink, result: ConnectResult) {
987 connect_txn_sink.send_connect_result(result);
988}
989
990fn report_roam_finished(connect_txn_sink: &mut ConnectTransactionSink, result: RoamResult) {
991 connect_txn_sink.send_roam_result(result);
992}
993
994#[cfg(test)]
995mod tests {
996 use super::*;
997 use crate::Config as SmeConfig;
998 use assert_matches::assert_matches;
999 use ieee80211::MacAddr;
1000 use std::collections::HashSet;
1001 use std::sync::LazyLock;
1002 use test_case::test_case;
1003 use wlan_common::{
1004 channel::{Cbw, Channel},
1005 fake_bss_description, fake_fidl_bss_description,
1006 ie::{IeType, fake_ht_cap_bytes, fake_vht_cap_bytes},
1007 security::{wep::WEP40_KEY_BYTES, wpa::credential::PSK_SIZE_BYTES},
1008 test_utils::{
1009 fake_features::{
1010 fake_security_support, fake_security_support_empty,
1011 fake_spectrum_management_support_empty,
1012 },
1013 fake_stas::{FakeProtectionCfg, IesOverrides},
1014 },
1015 };
1016 use {
1017 fidl_fuchsia_wlan_common as fidl_common,
1018 fidl_fuchsia_wlan_common_security as fidl_security, fidl_fuchsia_wlan_mlme as fidl_mlme,
1019 fuchsia_inspect as finspect,
1020 };
1021
1022 use super::test_utils::{create_on_wmm_status_resp, fake_wmm_param, fake_wmm_status_resp};
1023
1024 use crate::{Station, test_utils};
1025
1026 static CLIENT_ADDR: LazyLock<MacAddr> =
1027 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
1028
1029 fn authentication_open() -> fidl_security::Authentication {
1030 fidl_security::Authentication { protocol: fidl_security::Protocol::Open, credentials: None }
1031 }
1032
1033 fn authentication_wep40() -> fidl_security::Authentication {
1034 fidl_security::Authentication {
1035 protocol: fidl_security::Protocol::Wep,
1036 credentials: Some(Box::new(fidl_security::Credentials::Wep(
1037 fidl_security::WepCredentials { key: [1; WEP40_KEY_BYTES].into() },
1038 ))),
1039 }
1040 }
1041
1042 fn authentication_wpa1_passphrase() -> fidl_security::Authentication {
1043 fidl_security::Authentication {
1044 protocol: fidl_security::Protocol::Wpa1,
1045 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1046 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1047 ))),
1048 }
1049 }
1050
1051 fn authentication_wpa2_personal_psk() -> fidl_security::Authentication {
1052 fidl_security::Authentication {
1053 protocol: fidl_security::Protocol::Wpa2Personal,
1054 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1055 fidl_security::WpaCredentials::Psk([1; PSK_SIZE_BYTES]),
1056 ))),
1057 }
1058 }
1059
1060 fn authentication_wpa2_personal_passphrase() -> fidl_security::Authentication {
1061 fidl_security::Authentication {
1062 protocol: fidl_security::Protocol::Wpa2Personal,
1063 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1064 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1065 ))),
1066 }
1067 }
1068
1069 fn authentication_wpa3_personal_passphrase() -> fidl_security::Authentication {
1070 fidl_security::Authentication {
1071 protocol: fidl_security::Protocol::Wpa3Personal,
1072 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1073 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1074 ))),
1075 }
1076 }
1077
1078 fn report_fake_scan_result(
1079 sme: &mut ClientSme,
1080 timestamp_nanos: i64,
1081 bss: fidl_common::BssDescription,
1082 ) {
1083 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
1084 result: fidl_mlme::ScanResult { txn_id: 1, timestamp_nanos, bss },
1085 });
1086 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
1087 end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCode::Success },
1088 });
1089 }
1090
1091 #[test_case(FakeProtectionCfg::Open)]
1092 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1093 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1094 #[test_case(FakeProtectionCfg::Wpa2)]
1095 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1096 fn default_client_protection_is_bss_compatible(protection: FakeProtectionCfg) {
1097 let cfg = ClientConfig::default();
1098 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1099 assert!(
1100 cfg.bss_compatibility(
1101 &fake_bss_description!(protection => protection),
1102 &fake_device_info,
1103 &fake_security_support_empty()
1104 )
1105 .is_ok(),
1106 );
1107 }
1108
1109 #[test_case(FakeProtectionCfg::Wpa1)]
1110 #[test_case(FakeProtectionCfg::Wpa3)]
1111 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1112 #[test_case(FakeProtectionCfg::Eap)]
1113 fn default_client_protection_is_bss_incompatible(protection: FakeProtectionCfg) {
1114 let cfg = ClientConfig::default();
1115 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1116 assert!(
1117 cfg.bss_compatibility(
1118 &fake_bss_description!(protection => protection),
1119 &fake_device_info,
1120 &fake_security_support_empty()
1121 )
1122 .is_err(),
1123 );
1124 }
1125
1126 #[test_case(FakeProtectionCfg::Open)]
1127 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1128 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1129 #[test_case(FakeProtectionCfg::Wpa2)]
1130 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1131 fn compatible_default_client_protection_security_protocol_intersection_is_non_empty(
1132 protection: FakeProtectionCfg,
1133 ) {
1134 let cfg = ClientConfig::default();
1135 assert!(
1136 !cfg.security_protocol_intersection(
1137 &fake_bss_description!(protection => protection),
1138 &fake_security_support_empty()
1139 )
1140 .is_empty()
1141 );
1142 }
1143
1144 #[test_case(FakeProtectionCfg::Wpa1)]
1145 #[test_case(FakeProtectionCfg::Wpa3)]
1146 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1147 #[test_case(FakeProtectionCfg::Eap)]
1148 fn incompatible_default_client_protection_security_protocol_intersection_is_empty(
1149 protection: FakeProtectionCfg,
1150 ) {
1151 let cfg = ClientConfig::default();
1152 assert!(
1153 cfg.security_protocol_intersection(
1154 &fake_bss_description!(protection => protection),
1155 &fake_security_support_empty()
1156 )
1157 .is_empty(),
1158 );
1159 }
1160
1161 #[test_case(FakeProtectionCfg::Wpa1, [SecurityDescriptor::WPA1])]
1162 #[test_case(FakeProtectionCfg::Wpa3, [SecurityDescriptor::WPA3_PERSONAL])]
1163 #[test_case(FakeProtectionCfg::Wpa3Transition, [SecurityDescriptor::WPA3_PERSONAL])]
1164 #[test_case(FakeProtectionCfg::Eap, [])]
1166 fn default_client_protection_security_protocols_by_mac_role_eq(
1167 protection: FakeProtectionCfg,
1168 expected: impl IntoIterator<Item = SecurityDescriptor>,
1169 ) {
1170 let cfg = ClientConfig::default();
1171 let security_protocols: HashSet<_> = cfg
1172 .security_protocols_by_mac_role(&fake_bss_description!(protection => protection))
1173 .collect();
1174 assert_eq!(
1177 security_protocols,
1178 HashSet::from_iter(
1179 [
1180 (SecurityDescriptor::OPEN, MacRole::Client),
1181 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1182 ]
1183 .into_iter()
1184 .chain(expected.into_iter().map(|protocol| (protocol, MacRole::Ap)))
1185 ),
1186 );
1187 }
1188
1189 #[test]
1190 fn configured_client_bss_wep_compatible() {
1191 let cfg = ClientConfig::from_config(Config::default().with_wep(), false);
1193 assert!(
1194 !cfg.security_protocol_intersection(
1195 &fake_bss_description!(Wep),
1196 &fake_security_support_empty()
1197 )
1198 .is_empty()
1199 );
1200 }
1201
1202 #[test]
1203 fn configured_client_bss_wpa1_compatible() {
1204 let cfg = ClientConfig::from_config(Config::default().with_wpa1(), false);
1206 assert!(
1207 !cfg.security_protocol_intersection(
1208 &fake_bss_description!(Wpa1),
1209 &fake_security_support_empty()
1210 )
1211 .is_empty()
1212 );
1213 }
1214
1215 #[test]
1216 fn configured_client_bss_wpa3_compatible() {
1217 let cfg = ClientConfig::from_config(Config::default(), true);
1219 let mut security_support = fake_security_support_empty();
1220 security_support.mfp.supported = true;
1221 assert!(
1222 !cfg.security_protocol_intersection(&fake_bss_description!(Wpa3), &security_support)
1223 .is_empty()
1224 );
1225 assert!(
1226 !cfg.security_protocol_intersection(
1227 &fake_bss_description!(Wpa3Transition),
1228 &security_support,
1229 )
1230 .is_empty()
1231 );
1232 }
1233
1234 #[test]
1235 fn verify_rates_compatibility() {
1236 let cfg = ClientConfig::default();
1238 let device_info = test_utils::fake_device_info([1u8; 6].into());
1239 assert!(
1240 cfg.has_compatible_channel_and_data_rates(&fake_bss_description!(Open), &device_info)
1241 );
1242
1243 let bss = fake_bss_description!(Open, rates: vec![0x8C, 0xFF]);
1245 assert!(cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1246
1247 let bss = fake_bss_description!(Open, rates: vec![0x81]);
1249 assert!(!cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1250 }
1251
1252 #[test]
1253 fn convert_scan_result() {
1254 let cfg = ClientConfig::default();
1255 let bss_description = fake_bss_description!(Wpa2,
1256 ssid: Ssid::empty(),
1257 bssid: [0u8; 6],
1258 rssi_dbm: -30,
1259 snr_db: 0,
1260 channel: Channel::new(1, Cbw::Cbw20),
1261 ies_overrides: IesOverrides::new()
1262 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1263 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1264 );
1265 let device_info = test_utils::fake_device_info([1u8; 6].into());
1266 let timestamp = zx::MonotonicInstant::get();
1267 let scan_result = cfg.create_scan_result(
1268 timestamp,
1269 bss_description.clone(),
1270 &device_info,
1271 &fake_security_support(),
1272 );
1273
1274 assert_eq!(
1275 scan_result,
1276 ScanResult {
1277 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1278 timestamp,
1279 bss_description,
1280 }
1281 );
1282
1283 let wmm_param = *ie::parse_wmm_param(&fake_wmm_param().bytes[..])
1284 .expect("expect WMM param to be parseable");
1285 let bss_description = fake_bss_description!(Wpa2,
1286 ssid: Ssid::empty(),
1287 bssid: [0u8; 6],
1288 rssi_dbm: -30,
1289 snr_db: 0,
1290 channel: Channel::new(1, Cbw::Cbw20),
1291 wmm_param: Some(wmm_param),
1292 ies_overrides: IesOverrides::new()
1293 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1294 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1295 );
1296 let timestamp = zx::MonotonicInstant::get();
1297 let scan_result = cfg.create_scan_result(
1298 timestamp,
1299 bss_description.clone(),
1300 &device_info,
1301 &fake_security_support(),
1302 );
1303
1304 assert_eq!(
1305 scan_result,
1306 ScanResult {
1307 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1308 timestamp,
1309 bss_description,
1310 }
1311 );
1312
1313 let bss_description = fake_bss_description!(Wep,
1314 ssid: Ssid::empty(),
1315 bssid: [0u8; 6],
1316 rssi_dbm: -30,
1317 snr_db: 0,
1318 channel: Channel::new(1, Cbw::Cbw20),
1319 ies_overrides: IesOverrides::new()
1320 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1321 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1322 );
1323 let timestamp = zx::MonotonicInstant::get();
1324 let scan_result = cfg.create_scan_result(
1325 timestamp,
1326 bss_description.clone(),
1327 &device_info,
1328 &fake_security_support(),
1329 );
1330 assert_eq!(
1331 scan_result,
1332 ScanResult {
1333 compatibility: Incompatible::expect_err(
1334 "incompatible channel, PHY data rates, or security protocols",
1335 Some([
1336 (SecurityDescriptor::WEP, MacRole::Ap),
1337 (SecurityDescriptor::OPEN, MacRole::Client),
1338 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1339 ])
1340 ),
1341 timestamp,
1342 bss_description,
1343 },
1344 );
1345
1346 let cfg = ClientConfig::from_config(Config::default().with_wep(), false);
1347 let bss_description = fake_bss_description!(Wep,
1348 ssid: Ssid::empty(),
1349 bssid: [0u8; 6],
1350 rssi_dbm: -30,
1351 snr_db: 0,
1352 channel: Channel::new(1, Cbw::Cbw20),
1353 ies_overrides: IesOverrides::new()
1354 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1355 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1356 );
1357 let timestamp = zx::MonotonicInstant::get();
1358 let scan_result = cfg.create_scan_result(
1359 timestamp,
1360 bss_description.clone(),
1361 &device_info,
1362 &fake_security_support(),
1363 );
1364 assert_eq!(
1365 scan_result,
1366 ScanResult {
1367 compatibility: Compatible::expect_ok([SecurityDescriptor::WEP]),
1368 timestamp,
1369 bss_description,
1370 }
1371 );
1372 }
1373
1374 #[test_case(EstablishRsnaFailureReason::RsnaResponseTimeout(
1375 wlan_rsn::Error::LikelyWrongCredential
1376 ))]
1377 #[test_case(EstablishRsnaFailureReason::RsnaCompletionTimeout(
1378 wlan_rsn::Error::LikelyWrongCredential
1379 ))]
1380 fn test_connect_detection_of_rejected_wpa1_or_wpa2_credentials(
1381 reason: EstablishRsnaFailureReason,
1382 ) {
1383 let failure = ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
1384 auth_method: Some(auth::MethodName::Psk),
1385 reason,
1386 });
1387 assert!(failure.likely_due_to_credential_rejected());
1388 }
1389
1390 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1391 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1392 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1393 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1394 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1395 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1396 fn test_roam_detection_of_rejected_wpa1_or_wpa2_credentials(
1397 selected_bss: BssDescription,
1398 failure_reason: EstablishRsnaFailureReason,
1399 ) {
1400 let disconnect_info = fidl_sme::DisconnectInfo {
1401 is_sme_reconnecting: false,
1402 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1403 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1404 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1405 }),
1406 };
1407 let failure = RoamFailure {
1408 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1409 failure_type: RoamFailureType::EstablishRsnaFailure,
1410 selected_bssid: selected_bss.bssid,
1411 disconnect_info,
1412 auth_method: Some(auth::MethodName::Psk),
1413 establish_rsna_failure_reason: Some(failure_reason),
1414 selected_bss: Some(selected_bss),
1415 };
1416 assert!(failure.likely_due_to_credential_rejected());
1417 }
1418
1419 #[test]
1420 fn test_connect_detection_of_rejected_wpa3_credentials() {
1421 let bss = fake_bss_description!(Wpa3);
1422 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1423 bss_protection: bss.protection(),
1424 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1425 });
1426
1427 assert!(failure.likely_due_to_credential_rejected());
1428 }
1429
1430 #[test]
1431 fn test_roam_detection_of_rejected_wpa3_credentials() {
1432 let selected_bss = fake_bss_description!(Wpa3);
1433 let disconnect_info = fidl_sme::DisconnectInfo {
1434 is_sme_reconnecting: false,
1435 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1436 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1437 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1438 }),
1439 };
1440 let failure = RoamFailure {
1441 status_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1442 failure_type: RoamFailureType::ReassociationFailure,
1443 selected_bssid: selected_bss.bssid,
1444 disconnect_info,
1445 auth_method: Some(auth::MethodName::Sae),
1446 establish_rsna_failure_reason: None,
1447 selected_bss: Some(selected_bss),
1448 };
1449 assert!(failure.likely_due_to_credential_rejected());
1450 }
1451
1452 #[test]
1453 fn test_connect_detection_of_rejected_wep_credentials() {
1454 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1455 bss_protection: BssProtection::Wep,
1456 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1457 });
1458 assert!(failure.likely_due_to_credential_rejected());
1459 }
1460
1461 #[test]
1462 fn test_roam_detection_of_rejected_wep_credentials() {
1463 let selected_bss = fake_bss_description!(Wep);
1464 let disconnect_info = fidl_sme::DisconnectInfo {
1465 is_sme_reconnecting: false,
1466 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1467 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1468 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1469 }),
1470 };
1471 let failure = RoamFailure {
1472 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1473 failure_type: RoamFailureType::ReassociationFailure,
1474 selected_bssid: selected_bss.bssid,
1475 disconnect_info,
1476 auth_method: Some(auth::MethodName::Psk),
1477 establish_rsna_failure_reason: None,
1478 selected_bss: Some(selected_bss),
1479 };
1480 assert!(failure.likely_due_to_credential_rejected());
1481 }
1482
1483 #[test]
1484 fn test_connect_no_detection_of_rejected_wpa1_or_wpa2_credentials() {
1485 let failure = ConnectFailure::ScanFailure(fidl_mlme::ScanResultCode::InternalError);
1486 assert!(!failure.likely_due_to_credential_rejected());
1487
1488 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1489 bss_protection: BssProtection::Wpa2Personal,
1490 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1491 });
1492 assert!(!failure.likely_due_to_credential_rejected());
1493 }
1494
1495 #[test_case(fake_bss_description!(Wpa1))]
1496 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly))]
1497 #[test_case(fake_bss_description!(Wpa2))]
1498 fn test_roam_no_detection_of_rejected_wpa1_or_wpa2_credentials(selected_bss: BssDescription) {
1499 let disconnect_info = fidl_sme::DisconnectInfo {
1500 is_sme_reconnecting: false,
1501 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1502 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1503 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1504 }),
1505 };
1506 let failure = RoamFailure {
1507 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1508 failure_type: RoamFailureType::EstablishRsnaFailure,
1509 selected_bssid: selected_bss.bssid,
1510 disconnect_info,
1511 auth_method: Some(auth::MethodName::Psk),
1512 establish_rsna_failure_reason: Some(EstablishRsnaFailureReason::StartSupplicantFailed),
1513 selected_bss: Some(selected_bss),
1514 };
1515 assert!(!failure.likely_due_to_credential_rejected());
1516 }
1517
1518 #[test]
1519 fn test_connect_no_detection_of_rejected_wpa3_credentials() {
1520 let bss = fake_bss_description!(Wpa3);
1521 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1522 bss_protection: bss.protection(),
1523 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1524 });
1525
1526 assert!(!failure.likely_due_to_credential_rejected());
1527 }
1528
1529 #[test]
1530 fn test_roam_no_detection_of_rejected_wpa3_credentials() {
1531 let selected_bss = fake_bss_description!(Wpa3);
1532 let disconnect_info = fidl_sme::DisconnectInfo {
1533 is_sme_reconnecting: false,
1534 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1535 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1536 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1537 }),
1538 };
1539 let failure = RoamFailure {
1540 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1541 failure_type: RoamFailureType::ReassociationFailure,
1542 selected_bssid: selected_bss.bssid,
1543 disconnect_info,
1544 auth_method: Some(auth::MethodName::Sae),
1545 establish_rsna_failure_reason: None,
1546 selected_bss: Some(selected_bss),
1547 };
1548 assert!(!failure.likely_due_to_credential_rejected());
1549 }
1550
1551 #[test]
1552 fn test_connect_no_detection_of_rejected_wep_credentials() {
1553 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1554 bss_protection: BssProtection::Wep,
1555 code: fidl_ieee80211::StatusCode::InvalidParameters,
1556 });
1557 assert!(!failure.likely_due_to_credential_rejected());
1558 }
1559
1560 #[test]
1561 fn test_roam_no_detection_of_rejected_wep_credentials() {
1562 let selected_bss = fake_bss_description!(Wep);
1563 let disconnect_info = fidl_sme::DisconnectInfo {
1564 is_sme_reconnecting: false,
1565 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1566 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1567 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1568 }),
1569 };
1570 let failure = RoamFailure {
1571 status_code: fidl_ieee80211::StatusCode::StatusInvalidElement,
1572 failure_type: RoamFailureType::ReassociationFailure,
1573 selected_bssid: selected_bss.bssid,
1574 disconnect_info,
1575 auth_method: Some(auth::MethodName::Psk),
1576 establish_rsna_failure_reason: None,
1577 selected_bss: Some(selected_bss),
1578 };
1579 assert!(!failure.likely_due_to_credential_rejected());
1580 }
1581
1582 #[test_case(fake_bss_description!(Open), authentication_open() => matches Ok(Protection::Open))]
1583 #[test_case(fake_bss_description!(Open), authentication_wpa2_personal_passphrase() => matches Err(_))]
1584 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_passphrase() => matches Ok(Protection::Rsna(_)))]
1585 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_psk() => matches Ok(Protection::Rsna(_)))]
1586 #[test_case(fake_bss_description!(Wpa2), authentication_open() => matches Err(_))]
1587 fn test_protection_from_authentication(
1588 bss: BssDescription,
1589 authentication: fidl_security::Authentication,
1590 ) -> Result<Protection, anyhow::Error> {
1591 let device = test_utils::fake_device_info(*CLIENT_ADDR);
1592 let security_support = fake_security_support();
1593 let config = Default::default();
1594
1595 let authenticator = SecurityAuthenticator::try_from(authentication).unwrap();
1597 Protection::try_from(SecurityContext {
1598 security: &authenticator,
1599 device: &device,
1600 security_support: &security_support,
1601 config: &config,
1602 bss: &bss,
1603 })
1604 }
1605
1606 #[fuchsia::test(allow_stalls = false)]
1607 async fn status_connecting() {
1608 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1609 assert_eq!(ClientSmeStatus::Idle, sme.status());
1610
1611 let bss_description =
1613 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1614 let _recv = sme.on_connect_command(connect_req(
1615 Ssid::try_from("foo").unwrap(),
1616 bss_description,
1617 authentication_open(),
1618 ));
1619 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1620
1621 let ssid = assert_matches!(sme.state.as_ref().unwrap().status(), ClientSmeStatus::Connecting(ssid) => ssid);
1624 assert_eq!(Ssid::try_from("foo").unwrap(), ssid);
1625 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1626
1627 let bss_description =
1629 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap());
1630 let _recv2 = sme.on_connect_command(connect_req(
1631 Ssid::try_from("bar").unwrap(),
1632 bss_description,
1633 authentication_open(),
1634 ));
1635 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bar").unwrap()), sme.status());
1636 }
1637
1638 #[test]
1639 fn connecting_to_wep_network_supported() {
1640 let _executor = fuchsia_async::TestExecutor::new();
1641 let inspector = finspect::Inspector::default();
1642 let sme_root_node = inspector.root().create_child("sme");
1643 let (persistence_req_sender, _persistence_receiver) =
1644 test_utils::create_inspect_persistence_channel();
1645 let (mut sme, _mlme_sink, mut mlme_stream, _time_stream) = ClientSme::new(
1646 ClientConfig::from_config(SmeConfig::default().with_wep(), false),
1647 test_utils::fake_device_info(*CLIENT_ADDR),
1648 inspector,
1649 sme_root_node,
1650 persistence_req_sender,
1651 fake_security_support(),
1652 fake_spectrum_management_support_empty(),
1653 );
1654 assert_eq!(ClientSmeStatus::Idle, sme.status());
1655
1656 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1658 let req =
1659 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1660 let _recv = sme.on_connect_command(req);
1661 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1662
1663 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1664 }
1665
1666 #[fuchsia::test(allow_stalls = false)]
1667 async fn connecting_to_wep_network_unsupported() {
1668 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1669 assert_eq!(ClientSmeStatus::Idle, sme.status());
1670
1671 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1673 let req =
1674 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1675 let mut _connect_fut = sme.on_connect_command(req);
1676 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1677 }
1678
1679 #[fuchsia::test(allow_stalls = false)]
1680 async fn connecting_password_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("foo").unwrap());
1687 let req = connect_req(
1688 Ssid::try_from("foo").unwrap(),
1689 bss_description,
1690 authentication_wpa2_personal_passphrase(),
1691 );
1692 let _recv = sme.on_connect_command(req);
1693 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1694
1695 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1696 }
1697
1698 #[fuchsia::test(allow_stalls = false)]
1699 async fn connecting_psk_supplied_for_protected_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 =
1705 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("IEEE").unwrap());
1706 let req = connect_req(
1707 Ssid::try_from("IEEE").unwrap(),
1708 bss_description,
1709 authentication_wpa2_personal_psk(),
1710 );
1711 let _recv = sme.on_connect_command(req);
1712 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("IEEE").unwrap()), sme.status());
1713
1714 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1715 }
1716
1717 #[fuchsia::test(allow_stalls = false)]
1718 async fn connecting_password_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_passphrase(),
1728 );
1729 let mut connect_txn_stream = sme.on_connect_command(req);
1730 assert_eq!(ClientSmeStatus::Idle, sme.status());
1731
1732 assert_matches!(
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_psk_supplied_for_unprotected_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!(Open, ssid: Ssid::try_from("foo").unwrap());
1748 let req = connect_req(
1749 Ssid::try_from("foo").unwrap(),
1750 bss_description,
1751 authentication_wpa2_personal_psk(),
1752 );
1753 let mut connect_txn_stream = sme.on_connect_command(req);
1754 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1755
1756 assert_matches!(
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_no_password_supplied_for_protected_network() {
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!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1772 let req =
1773 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_open());
1774 let mut connect_txn_stream = sme.on_connect_command(req);
1775 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1776
1777 assert_no_connect(&mut mlme_stream);
1779
1780 assert_matches!(
1782 connect_txn_stream.try_next(),
1783 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1784 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1785 }
1786 );
1787 }
1788
1789 #[fuchsia::test(allow_stalls = false)]
1790 async fn connecting_bypass_join_scan_open() {
1791 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1792 assert_eq!(ClientSmeStatus::Idle, sme.status());
1793
1794 let bss_description =
1795 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bssname").unwrap());
1796 let req =
1797 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
1798 let mut connect_txn_stream = sme.on_connect_command(req);
1799
1800 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
1801 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1802 assert_matches!(connect_txn_stream.try_next(), Err(_));
1804 }
1805
1806 #[fuchsia::test(allow_stalls = false)]
1807 async fn connecting_bypass_join_scan_protected() {
1808 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1809 assert_eq!(ClientSmeStatus::Idle, sme.status());
1810
1811 let bss_description =
1812 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
1813 let req = connect_req(
1814 Ssid::try_from("bssname").unwrap(),
1815 bss_description,
1816 authentication_wpa2_personal_passphrase(),
1817 );
1818 let mut connect_txn_stream = sme.on_connect_command(req);
1819
1820 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
1821 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1822 assert_matches!(connect_txn_stream.try_next(), Err(_));
1824 }
1825
1826 #[fuchsia::test(allow_stalls = false)]
1827 async fn connecting_bypass_join_scan_mismatched_credential() {
1828 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1829 assert_eq!(ClientSmeStatus::Idle, sme.status());
1830
1831 let bss_description =
1832 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
1833 let req =
1834 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
1835 let mut connect_txn_stream = sme.on_connect_command(req);
1836
1837 assert_eq!(ClientSmeStatus::Idle, sme.status());
1838 assert_no_connect(&mut mlme_stream);
1839
1840 assert_matches!(
1842 connect_txn_stream.try_next(),
1843 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1844 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1845 }
1846 );
1847 }
1848
1849 #[fuchsia::test(allow_stalls = false)]
1850 async fn connecting_bypass_join_scan_unsupported_bss() {
1851 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1852 assert_eq!(ClientSmeStatus::Idle, sme.status());
1853
1854 let bss_description =
1855 fake_fidl_bss_description!(Wpa3Enterprise, ssid: Ssid::try_from("bssname").unwrap());
1856 let req = connect_req(
1857 Ssid::try_from("bssname").unwrap(),
1858 bss_description,
1859 authentication_wpa3_personal_passphrase(),
1860 );
1861 let mut connect_txn_stream = sme.on_connect_command(req);
1862
1863 assert_eq!(ClientSmeStatus::Idle, sme.status());
1864 assert_no_connect(&mut mlme_stream);
1865
1866 assert_matches!(
1868 connect_txn_stream.try_next(),
1869 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1870 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1871 }
1872 );
1873 }
1874
1875 #[fuchsia::test(allow_stalls = false)]
1876 async fn connecting_right_credential_type_no_privacy() {
1877 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1878
1879 let bss_description = fake_fidl_bss_description!(
1880 Wpa2,
1881 ssid: Ssid::try_from("foo").unwrap(),
1882 );
1883 let bss_description = fidl_common::BssDescription {
1886 capability_info: wlan_common::mac::CapabilityInfo(bss_description.capability_info)
1887 .with_privacy(false)
1888 .0,
1889 ..bss_description
1890 };
1891 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1892 Ssid::try_from("foo").unwrap(),
1893 bss_description,
1894 authentication_wpa2_personal_passphrase(),
1895 ));
1896
1897 assert_matches!(
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
1905 #[fuchsia::test(allow_stalls = false)]
1906 async fn connecting_mismatched_security_protocol() {
1907 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1908
1909 let bss_description =
1910 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
1911 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1912 Ssid::try_from("wpa2").unwrap(),
1913 bss_description,
1914 authentication_wep40(),
1915 ));
1916 assert_matches!(
1917 connect_txn_stream.try_next(),
1918 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1919 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1920 }
1921 );
1922
1923 let bss_description =
1924 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
1925 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1926 Ssid::try_from("wpa2").unwrap(),
1927 bss_description,
1928 authentication_wpa1_passphrase(),
1929 ));
1930 assert_matches!(
1931 connect_txn_stream.try_next(),
1932 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1933 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1934 }
1935 );
1936
1937 let bss_description =
1938 fake_fidl_bss_description!(Wpa3, ssid: Ssid::try_from("wpa3").unwrap());
1939 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1940 Ssid::try_from("wpa3").unwrap(),
1941 bss_description,
1942 authentication_wpa2_personal_passphrase(),
1943 ));
1944 assert_matches!(
1945 connect_txn_stream.try_next(),
1946 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1947 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1948 }
1949 );
1950 }
1951
1952 #[fuchsia::test(allow_stalls = false, logging = false)]
1954 async fn connecting_right_credential_type_but_short_password() {
1955 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1956
1957 let bss_description =
1958 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1959 let mut connect_txn_stream = sme.on_connect_command(connect_req(
1960 Ssid::try_from("foo").unwrap(),
1961 bss_description.clone(),
1962 fidl_security::Authentication {
1963 protocol: fidl_security::Protocol::Wpa2Personal,
1964 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1965 fidl_security::WpaCredentials::Passphrase(b"nope".as_slice().into()),
1966 ))),
1967 },
1968 ));
1969 report_fake_scan_result(
1970 &mut sme,
1971 zx::MonotonicInstant::get().into_nanos(),
1972 bss_description,
1973 );
1974
1975 assert_matches!(
1976 connect_txn_stream.try_next(),
1977 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1978 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1979 }
1980 );
1981 }
1982
1983 #[fuchsia::test(allow_stalls = false, logging = false)]
1985 async fn new_connect_attempt_cancels_pending_connect() {
1986 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1987
1988 let bss_description =
1989 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1990 let req = connect_req(
1991 Ssid::try_from("foo").unwrap(),
1992 bss_description.clone(),
1993 authentication_open(),
1994 );
1995 let mut connect_txn_stream1 = sme.on_connect_command(req);
1996
1997 let req2 = connect_req(
1998 Ssid::try_from("foo").unwrap(),
1999 bss_description.clone(),
2000 authentication_open(),
2001 );
2002 let mut connect_txn_stream2 = sme.on_connect_command(req2);
2003
2004 assert_matches!(
2006 connect_txn_stream1.try_next(),
2007 Ok(Some(ConnectTransactionEvent::OnConnectResult {
2008 result: ConnectResult::Canceled,
2009 is_reconnect: false
2010 }))
2011 );
2012
2013 report_fake_scan_result(
2016 &mut sme,
2017 zx::MonotonicInstant::get().into_nanos(),
2018 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()),
2019 );
2020
2021 let req3 = connect_req(
2022 Ssid::try_from("foo").unwrap(),
2023 bss_description.clone(),
2024 authentication_open(),
2025 );
2026 let mut _connect_fut3 = sme.on_connect_command(req3);
2027
2028 assert_matches!(
2030 connect_txn_stream2.try_next(),
2031 Ok(Some(ConnectTransactionEvent::OnConnectResult {
2032 result: ConnectResult::Canceled,
2033 is_reconnect: false
2034 }))
2035 );
2036 }
2037
2038 #[fuchsia::test(allow_stalls = false)]
2039 async fn test_simple_scan_error() {
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 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2045 end: fidl_mlme::ScanEnd {
2046 txn_id: 1,
2047 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2048 },
2049 });
2050
2051 assert_eq!(
2052 recv.try_recv(),
2053 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2054 );
2055 }
2056
2057 #[fuchsia::test(allow_stalls = false)]
2058 async fn test_scan_error_after_some_results_returned() {
2059 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2060 let mut recv =
2061 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2062
2063 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2064 bss.bssid = [3; 6];
2065 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2066 result: fidl_mlme::ScanResult {
2067 txn_id: 1,
2068 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2069 bss,
2070 },
2071 });
2072 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2073 bss.bssid = [4; 6];
2074 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2075 result: fidl_mlme::ScanResult {
2076 txn_id: 1,
2077 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2078 bss,
2079 },
2080 });
2081
2082 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2083 end: fidl_mlme::ScanEnd {
2084 txn_id: 1,
2085 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2086 },
2087 });
2088
2089 assert_eq!(
2091 recv.try_recv(),
2092 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2093 );
2094 }
2095
2096 #[fuchsia::test(allow_stalls = false)]
2097 async fn test_scan_is_rejected_while_connecting() {
2098 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2099
2100 let bss_description =
2102 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2103 let _recv = sme.on_connect_command(connect_req(
2104 Ssid::try_from("foo").unwrap(),
2105 bss_description,
2106 authentication_open(),
2107 ));
2108 assert_matches!(sme.status(), ClientSmeStatus::Connecting(_));
2109
2110 let mut recv =
2112 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2113 assert_eq!(recv.try_recv(), Ok(Some(Err(fidl_mlme::ScanResultCode::ShouldWait))));
2114 }
2115
2116 #[fuchsia::test(allow_stalls = false)]
2117 async fn test_wmm_status_success() {
2118 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2119 let mut receiver = sme.wmm_status();
2120
2121 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2122
2123 let resp = fake_wmm_status_resp();
2124 #[allow(
2125 clippy::redundant_field_names,
2126 reason = "mass allow for https://fxbug.dev/381896734"
2127 )]
2128 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnWmmStatusResp {
2129 status: zx::sys::ZX_OK,
2130 resp: resp,
2131 });
2132
2133 assert_eq!(receiver.try_recv(), Ok(Some(Ok(resp))));
2134 }
2135
2136 #[fuchsia::test(allow_stalls = false)]
2137 async fn test_wmm_status_failed() {
2138 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2139 let mut receiver = sme.wmm_status();
2140
2141 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2142 sme.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_ERR_IO));
2143 assert_eq!(receiver.try_recv(), Ok(Some(Err(zx::sys::ZX_ERR_IO))));
2144 }
2145
2146 #[test]
2147 fn test_inspect_pulse_persist() {
2148 let _executor = fuchsia_async::TestExecutor::new();
2149 let inspector = finspect::Inspector::default();
2150 let sme_root_node = inspector.root().create_child("sme");
2151 let (persistence_req_sender, mut persistence_receiver) =
2152 test_utils::create_inspect_persistence_channel();
2153 let (mut sme, _mlme_sink, _mlme_stream, mut time_stream) = ClientSme::new(
2154 ClientConfig::from_config(SmeConfig::default().with_wep(), false),
2155 test_utils::fake_device_info(*CLIENT_ADDR),
2156 inspector,
2157 sme_root_node,
2158 persistence_req_sender,
2159 fake_security_support(),
2160 fake_spectrum_management_support_empty(),
2161 );
2162 assert_eq!(ClientSmeStatus::Idle, sme.status());
2163
2164 assert_matches!(persistence_receiver.try_next(), Ok(Some(tag)) => {
2166 assert_eq!(&tag, "wlanstack-last-pulse");
2167 });
2168
2169 let mut persist_event = None;
2170 while let Ok(Some((_timeout, timed_event, _handle))) = time_stream.try_next() {
2171 if let Event::InspectPulsePersist(..) = timed_event.event {
2172 persist_event = Some(timed_event);
2173 break;
2174 }
2175 }
2176 assert!(persist_event.is_some());
2177 sme.on_timeout(persist_event.unwrap());
2178
2179 assert_matches!(persistence_receiver.try_next(), Ok(Some(tag)) => {
2181 assert_eq!(&tag, "wlanstack-last-pulse");
2182 });
2183 }
2184
2185 fn assert_no_connect(mlme_stream: &mut mpsc::UnboundedReceiver<MlmeRequest>) {
2186 loop {
2187 match mlme_stream.try_next() {
2188 Ok(event) => match event {
2189 Some(MlmeRequest::Connect(..)) => {
2190 panic!("unexpected connect request sent to MLME")
2191 }
2192 None => break,
2193 _ => (),
2194 },
2195 Err(e) => {
2196 assert_eq!(e.to_string(), "receiver channel is empty");
2197 break;
2198 }
2199 }
2200 }
2201 }
2202
2203 fn connect_req(
2204 ssid: Ssid,
2205 bss_description: fidl_common::BssDescription,
2206 authentication: fidl_security::Authentication,
2207 ) -> fidl_sme::ConnectRequest {
2208 fidl_sme::ConnectRequest {
2209 ssid: ssid.to_vec(),
2210 bss_description,
2211 multiple_bss_candidates: true,
2212 authentication,
2213 deprecated_scan_type: fidl_common::ScanType::Passive,
2214 }
2215 }
2216
2217 async fn create_sme() -> (ClientSme, MlmeStream, timer::EventStream<Event>) {
2224 let inspector = finspect::Inspector::default();
2225 let sme_root_node = inspector.root().create_child("sme");
2226 let (persistence_req_sender, _persistence_receiver) =
2227 test_utils::create_inspect_persistence_channel();
2228 let (client_sme, _mlme_sink, mlme_stream, time_stream) = ClientSme::new(
2229 ClientConfig::default(),
2230 test_utils::fake_device_info(*CLIENT_ADDR),
2231 inspector,
2232 sme_root_node,
2233 persistence_req_sender,
2234 fake_security_support(),
2235 fake_spectrum_management_support_empty(),
2236 );
2237 (client_sme, mlme_stream, time_stream)
2238 }
2239}