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