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