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#[derive(Debug, PartialEq)]
298pub enum RoamResult {
299 Success(Box<BssDescription>),
300 Failed(Box<RoamFailure>),
301}
302
303impl<T: Into<RoamFailure>> From<T> for RoamResult {
304 fn from(failure: T) -> Self {
305 RoamResult::Failed(Box::new(failure.into()))
306 }
307}
308
309#[derive(Debug)]
310pub struct ConnectTransactionSink {
311 sink: UnboundedSink<ConnectTransactionEvent>,
312 is_reconnecting: bool,
313}
314
315impl ConnectTransactionSink {
316 pub fn new_unbounded() -> (Self, ConnectTransactionStream) {
317 let (sender, receiver) = mpsc::unbounded();
318 let sink =
319 ConnectTransactionSink { sink: UnboundedSink::new(sender), is_reconnecting: false };
320 (sink, receiver)
321 }
322
323 pub fn is_reconnecting(&self) -> bool {
324 self.is_reconnecting
325 }
326
327 pub fn send_connect_result(&mut self, result: ConnectResult) {
328 let event =
329 ConnectTransactionEvent::OnConnectResult { result, is_reconnect: self.is_reconnecting };
330 self.send(event);
331 }
332
333 pub fn send_roam_result(&mut self, result: RoamResult) {
334 let event = ConnectTransactionEvent::OnRoamResult { result };
335 self.send(event);
336 }
337
338 pub fn send(&mut self, event: ConnectTransactionEvent) {
339 if let ConnectTransactionEvent::OnDisconnect { info } = &event {
340 self.is_reconnecting = info.is_sme_reconnecting;
341 };
342 self.sink.send(event);
343 }
344}
345
346pub type ConnectTransactionStream = mpsc::UnboundedReceiver<ConnectTransactionEvent>;
347
348#[derive(Debug, PartialEq)]
349pub enum ConnectTransactionEvent {
350 OnConnectResult { result: ConnectResult, is_reconnect: bool },
351 OnRoamResult { result: RoamResult },
352 OnDisconnect { info: fidl_sme::DisconnectInfo },
353 OnSignalReport { ind: fidl_internal::SignalReportIndication },
354 OnChannelSwitched { info: fidl_internal::ChannelSwitchInfo },
355}
356
357#[derive(Debug, PartialEq)]
358pub enum ConnectFailure {
359 SelectNetworkFailure(SelectNetworkFailure),
360 ScanFailure(fidl_mlme::ScanResultCode),
363 JoinFailure(fidl_ieee80211::StatusCode),
366 AuthenticationFailure(fidl_ieee80211::StatusCode),
367 AssociationFailure(AssociationFailure),
368 EstablishRsnaFailure(EstablishRsnaFailure),
369}
370
371impl ConnectFailure {
372 #[allow(clippy::collapsible_match, reason = "mass allow for https://fxbug.dev/381896734")]
374 #[allow(
375 clippy::match_like_matches_macro,
376 reason = "mass allow for https://fxbug.dev/381896734"
377 )]
378 pub fn is_timeout(&self) -> bool {
379 match self {
382 ConnectFailure::AuthenticationFailure(failure) => match failure {
383 fidl_ieee80211::StatusCode::RejectedSequenceTimeout => true,
384 _ => false,
385 },
386 ConnectFailure::EstablishRsnaFailure(failure) => match failure {
387 EstablishRsnaFailure {
388 reason: EstablishRsnaFailureReason::RsnaResponseTimeout(_),
389 ..
390 }
391 | EstablishRsnaFailure {
392 reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(_),
393 ..
394 } => true,
395 _ => false,
396 },
397 _ => false,
398 }
399 }
400
401 pub fn likely_due_to_credential_rejected(&self) -> bool {
407 match self {
408 ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
426 auth_method: Some(auth::MethodName::Psk),
427 reason:
428 EstablishRsnaFailureReason::RsnaResponseTimeout(
429 wlan_rsn::Error::LikelyWrongCredential,
430 ),
431 })
432 | ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
433 auth_method: Some(auth::MethodName::Psk),
434 reason:
435 EstablishRsnaFailureReason::RsnaCompletionTimeout(
436 wlan_rsn::Error::LikelyWrongCredential,
437 ),
438 }) => true,
439
440 ConnectFailure::AssociationFailure(AssociationFailure {
449 bss_protection: BssProtection::Wep,
450 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
451 }) => true,
452
453 ConnectFailure::AssociationFailure(AssociationFailure {
457 bss_protection: BssProtection::Wpa3Personal,
458 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
459 })
460 | ConnectFailure::AssociationFailure(AssociationFailure {
461 bss_protection: BssProtection::Wpa2Wpa3Personal,
462 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
463 }) => true,
464 _ => false,
465 }
466 }
467
468 pub fn status_code(&self) -> fidl_ieee80211::StatusCode {
469 match self {
470 ConnectFailure::JoinFailure(code)
471 | ConnectFailure::AuthenticationFailure(code)
472 | ConnectFailure::AssociationFailure(AssociationFailure { code, .. }) => *code,
473 ConnectFailure::EstablishRsnaFailure(..) => {
474 fidl_ieee80211::StatusCode::EstablishRsnaFailure
475 }
476 ConnectFailure::ScanFailure(fidl_mlme::ScanResultCode::ShouldWait) => {
478 fidl_ieee80211::StatusCode::Canceled
479 }
480 ConnectFailure::SelectNetworkFailure(..) | ConnectFailure::ScanFailure(..) => {
481 fidl_ieee80211::StatusCode::RefusedReasonUnspecified
482 }
483 }
484 }
485}
486
487#[derive(Debug, PartialEq)]
488pub enum RoamFailureType {
489 SelectNetworkFailure,
490 RoamStartMalformedFailure,
491 RoamResultMalformedFailure,
492 RoamRequestMalformedFailure,
493 RoamConfirmationMalformedFailure,
494 ReassociationFailure,
495 EstablishRsnaFailure,
496}
497
498#[derive(Debug, PartialEq)]
499pub struct RoamFailure {
500 failure_type: RoamFailureType,
501 pub selected_bssid: Bssid,
502 pub status_code: fidl_ieee80211::StatusCode,
503 pub disconnect_info: fidl_sme::DisconnectInfo,
504 auth_method: Option<auth::MethodName>,
505 pub selected_bss: Option<BssDescription>,
506 establish_rsna_failure_reason: Option<EstablishRsnaFailureReason>,
507}
508
509impl RoamFailure {
510 #[allow(
513 clippy::match_like_matches_macro,
514 reason = "mass allow for https://fxbug.dev/381896734"
515 )]
516 pub fn likely_due_to_credential_rejected(&self) -> bool {
517 match self.failure_type {
518 RoamFailureType::EstablishRsnaFailure => match self.auth_method {
520 Some(auth::MethodName::Psk) => match self.establish_rsna_failure_reason {
521 Some(EstablishRsnaFailureReason::RsnaResponseTimeout(
522 wlan_rsn::Error::LikelyWrongCredential,
523 ))
524 | Some(EstablishRsnaFailureReason::RsnaCompletionTimeout(
525 wlan_rsn::Error::LikelyWrongCredential,
526 )) => true,
527 _ => false,
528 },
529 _ => false,
530 },
531 RoamFailureType::ReassociationFailure => {
532 match &self.selected_bss {
533 Some(selected_bss) => match selected_bss.protection() {
534 BssProtection::Wep => match self.status_code {
536 fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported => true,
537 _ => false,
538 },
539 BssProtection::Wpa3Personal
541 | BssProtection::Wpa2Wpa3Personal => match self.status_code {
542 fidl_ieee80211::StatusCode::RejectedSequenceTimeout => true,
543 _ => false,
544 },
545 _ => false,
546 },
547 None => false,
550 }
551 }
552 _ => false,
553 }
554 }
555}
556
557#[derive(Debug, PartialEq)]
558pub enum SelectNetworkFailure {
559 NoScanResultWithSsid,
560 IncompatibleConnectRequest,
561 InternalProtectionError,
562}
563
564impl From<SelectNetworkFailure> for ConnectFailure {
565 fn from(failure: SelectNetworkFailure) -> Self {
566 ConnectFailure::SelectNetworkFailure(failure)
567 }
568}
569
570#[derive(Debug, PartialEq)]
571pub struct AssociationFailure {
572 pub bss_protection: BssProtection,
573 pub code: fidl_ieee80211::StatusCode,
574}
575
576impl From<AssociationFailure> for ConnectFailure {
577 fn from(failure: AssociationFailure) -> Self {
578 ConnectFailure::AssociationFailure(failure)
579 }
580}
581
582#[derive(Debug, PartialEq)]
583pub struct EstablishRsnaFailure {
584 pub auth_method: Option<auth::MethodName>,
585 pub reason: EstablishRsnaFailureReason,
586}
587
588#[derive(Debug, PartialEq)]
589pub enum EstablishRsnaFailureReason {
590 StartSupplicantFailed,
591 RsnaResponseTimeout(wlan_rsn::Error),
592 RsnaCompletionTimeout(wlan_rsn::Error),
593 InternalError,
594}
595
596impl From<EstablishRsnaFailure> for ConnectFailure {
597 fn from(failure: EstablishRsnaFailure) -> Self {
598 ConnectFailure::EstablishRsnaFailure(failure)
599 }
600}
601
602#[derive(Clone, Debug, PartialEq)]
605pub struct ServingApInfo {
606 pub bssid: Bssid,
607 pub ssid: Ssid,
608 pub rssi_dbm: i8,
609 pub snr_db: i8,
610 pub signal_report_time: zx::MonotonicInstant,
611 pub channel: wlan_common::channel::Channel,
612 pub protection: BssProtection,
613 pub ht_cap: Option<fidl_ieee80211::HtCapabilities>,
614 pub vht_cap: Option<fidl_ieee80211::VhtCapabilities>,
615 pub probe_resp_wsc: Option<wsc::ProbeRespWsc>,
616 pub wmm_param: Option<ie::WmmParam>,
617}
618
619impl From<ServingApInfo> for fidl_sme::ServingApInfo {
620 fn from(ap: ServingApInfo) -> fidl_sme::ServingApInfo {
621 fidl_sme::ServingApInfo {
622 bssid: ap.bssid.to_array(),
623 ssid: ap.ssid.to_vec(),
624 rssi_dbm: ap.rssi_dbm,
625 snr_db: ap.snr_db,
626 channel: ap.channel.into(),
627 protection: ap.protection.into(),
628 }
629 }
630}
631
632#[derive(Clone, Debug, PartialEq)]
634pub enum ClientSmeStatus {
635 Connected(Box<ServingApInfo>),
636 Connecting(Ssid),
637 Roaming(Bssid),
638 Idle,
639}
640
641impl ClientSmeStatus {
642 pub fn is_connecting(&self) -> bool {
643 matches!(self, ClientSmeStatus::Connecting(_))
644 }
645
646 pub fn is_connected(&self) -> bool {
647 matches!(self, ClientSmeStatus::Connected(_))
648 }
649}
650
651impl From<ClientSmeStatus> for fidl_sme::ClientStatusResponse {
652 fn from(client_sme_status: ClientSmeStatus) -> fidl_sme::ClientStatusResponse {
653 match client_sme_status {
654 ClientSmeStatus::Connected(serving_ap_info) => {
655 fidl_sme::ClientStatusResponse::Connected((*serving_ap_info).into())
656 }
657 ClientSmeStatus::Connecting(ssid) => {
658 fidl_sme::ClientStatusResponse::Connecting(ssid.to_vec())
659 }
660 ClientSmeStatus::Roaming(bssid) => {
661 fidl_sme::ClientStatusResponse::Roaming(bssid.to_array())
662 }
663 ClientSmeStatus::Idle => fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {}),
664 }
665 }
666}
667
668impl ClientSme {
669 #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
670 pub fn new(
671 cfg: ClientConfig,
672 info: fidl_mlme::DeviceInfo,
673 inspector: fuchsia_inspect::Inspector,
674 inspect_node: fuchsia_inspect::Node,
675 security_support: fidl_common::SecuritySupport,
676 spectrum_management_support: fidl_common::SpectrumManagementSupport,
677 ) -> (Self, MlmeSink, MlmeStream, timer::EventStream<Event>) {
678 let device_info = Arc::new(info);
679 let (mlme_sink, mlme_stream) = mpsc::unbounded();
680 let (mut timer, time_stream) = timer::create_timer();
681 let inspect = Arc::new(inspect::SmeTree::new(
682 inspector,
683 inspect_node,
684 &device_info,
685 &spectrum_management_support,
686 ));
687 let _ = timer.schedule(event::InspectPulseCheck);
688
689 (
690 ClientSme {
691 cfg,
692 state: Some(ClientState::new(cfg)),
693 scan_sched: <ScanScheduler<
694 Responder<Result<Vec<ScanResult>, fidl_mlme::ScanResultCode>>,
695 >>::new(
696 Arc::clone(&device_info), spectrum_management_support
697 ),
698 wmm_status_responders: vec![],
699 context: Context {
700 mlme_sink: MlmeSink::new(mlme_sink.clone()),
701 device_info,
702 timer,
703 att_id: 0,
704 inspect,
705 security_support,
706 },
707 },
708 MlmeSink::new(mlme_sink),
709 mlme_stream,
710 time_stream,
711 )
712 }
713
714 pub fn on_connect_command(
715 &mut self,
716 req: fidl_sme::ConnectRequest,
717 ) -> ConnectTransactionStream {
718 let (mut connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
719
720 self.state = self.state.take().map(|state| state.cancel_ongoing_connect(&mut self.context));
722
723 let bss_description: BssDescription = match req.bss_description.try_into() {
724 Ok(bss_description) => bss_description,
725 Err(e) => {
726 error!("Failed converting FIDL BssDescription in ConnectRequest: {:?}", e);
727 connect_txn_sink
728 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
729 return connect_txn_stream;
730 }
731 };
732
733 info!("Received ConnectRequest for {}", bss_description);
734
735 if self
736 .cfg
737 .bss_compatibility(
738 &bss_description,
739 &self.context.device_info,
740 &self.context.security_support,
741 )
742 .is_err()
743 {
744 warn!("BSS is incompatible");
745 connect_txn_sink
746 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
747 return connect_txn_stream;
748 }
749
750 let authentication = req.authentication.clone();
751 let protection = match SecurityAuthenticator::try_from(req.authentication)
752 .map_err(From::from)
753 .and_then(|authenticator| {
754 Protection::try_from(SecurityContext {
755 security: &authenticator,
756 device: &self.context.device_info,
757 security_support: &self.context.security_support,
758 config: &self.cfg,
759 bss: &bss_description,
760 })
761 }) {
762 Ok(protection) => protection,
763 Err(error) => {
764 warn!(
765 "{:?}",
766 format!(
767 "Failed to configure protection for network {} ({}): {:?}",
768 bss_description.ssid, bss_description.bssid, error
769 )
770 );
771 connect_txn_sink
772 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
773 return connect_txn_stream;
774 }
775 };
776 let cmd = ConnectCommand {
777 bss: Box::new(bss_description),
778 connect_txn_sink,
779 protection,
780 authentication,
781 };
782
783 self.state = self.state.take().map(|state| state.connect(cmd, &mut self.context));
784 connect_txn_stream
785 }
786
787 pub fn on_roam_command(&mut self, req: fidl_sme::RoamRequest) {
788 if !self.status().is_connected() {
789 error!("SME ignoring roam request because client is not connected");
790 } else {
791 self.state =
792 self.state.take().map(|state| state.roam(&mut self.context, req.bss_description));
793 }
794 }
795
796 pub fn on_disconnect_command(
797 &mut self,
798 policy_disconnect_reason: fidl_sme::UserDisconnectReason,
799 responder: fidl_sme::ClientSmeDisconnectResponder,
800 ) {
801 self.state = self
802 .state
803 .take()
804 .map(|state| state.disconnect(&mut self.context, policy_disconnect_reason, responder));
805 self.context.inspect.update_pulse(self.status());
806 }
807
808 pub fn on_scan_command(
809 &mut self,
810 scan_request: fidl_sme::ScanRequest,
811 ) -> oneshot::Receiver<Result<Vec<wlan_common::scan::ScanResult>, fidl_mlme::ScanResultCode>>
812 {
813 let (responder, receiver) = Responder::new();
814 if self.status().is_connecting() {
815 info!("SME ignoring scan request because a connect is in progress");
816 responder.respond(Err(fidl_mlme::ScanResultCode::ShouldWait));
817 } else {
818 info!(
819 "SME received a scan command, initiating a{} discovery scan",
820 match scan_request {
821 fidl_sme::ScanRequest::Active(_) => "n active",
822 fidl_sme::ScanRequest::Passive(_) => " passive",
823 }
824 );
825 let scan = DiscoveryScan::new(responder, scan_request);
826 let req = self.scan_sched.enqueue_scan_to_discover(scan);
827 self.send_scan_request(req);
828 }
829 receiver
830 }
831
832 pub fn on_clone_inspect_vmo(&self) -> Option<fidl::Vmo> {
833 self.context.inspect.clone_vmo_data()
834 }
835
836 pub fn status(&self) -> ClientSmeStatus {
837 #[expect(clippy::expect_used)]
839 self.state.as_ref().expect("expected state to be always present").status()
840 }
841
842 pub fn wmm_status(&mut self) -> oneshot::Receiver<fidl_sme::ClientSmeWmmStatusResult> {
843 let (responder, receiver) = Responder::new();
844 self.wmm_status_responders.push(responder);
845 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
846 receiver
847 }
848
849 fn send_scan_request(&mut self, req: Option<fidl_mlme::ScanRequest>) {
850 if let Some(req) = req {
851 self.context.mlme_sink.send(MlmeRequest::Scan(req));
852 }
853 }
854
855 pub fn query_telemetry_support(
856 &mut self,
857 ) -> oneshot::Receiver<Result<fidl_stats::TelemetrySupport, i32>> {
858 let (responder, receiver) = Responder::new();
859 self.context.mlme_sink.send(MlmeRequest::QueryTelemetrySupport(responder));
860 receiver
861 }
862
863 pub fn iface_stats(&mut self) -> oneshot::Receiver<fidl_mlme::GetIfaceStatsResponse> {
864 let (responder, receiver) = Responder::new();
865 self.context.mlme_sink.send(MlmeRequest::GetIfaceStats(responder));
866 receiver
867 }
868
869 pub fn histogram_stats(
870 &mut self,
871 ) -> oneshot::Receiver<fidl_mlme::GetIfaceHistogramStatsResponse> {
872 let (responder, receiver) = Responder::new();
873 self.context.mlme_sink.send(MlmeRequest::GetIfaceHistogramStats(responder));
874 receiver
875 }
876
877 pub fn signal_report(&mut self) -> oneshot::Receiver<Result<fidl_stats::SignalReport, i32>> {
878 let (responder, receiver) = Responder::new();
879 self.context.mlme_sink.send(MlmeRequest::GetSignalReport(responder));
880 receiver
881 }
882
883 pub fn set_mac_address(&mut self, mac_addr: [u8; 6]) -> oneshot::Receiver<Result<(), i32>> {
884 let (responder, receiver) = Responder::new();
885 self.context.mlme_sink.send(MlmeRequest::SetMacAddress(mac_addr, responder));
886 receiver
887 }
888
889 pub fn query_apf_packet_filter_support(
890 &mut self,
891 ) -> oneshot::Receiver<Result<fidl_common::ApfPacketFilterSupport, i32>> {
892 let (responder, receiver) = Responder::new();
893 self.context.mlme_sink.send(MlmeRequest::QueryApfPacketFilterSupport(responder));
894 receiver
895 }
896
897 pub fn install_apf_packet_filter(
898 &mut self,
899 program: Vec<u8>,
900 ) -> oneshot::Receiver<Result<(), i32>> {
901 let (responder, receiver) = Responder::new();
902 self.context.mlme_sink.send(MlmeRequest::InstallApfPacketFilter(
903 fidl_mlme::MlmeInstallApfPacketFilterRequest { program },
904 responder,
905 ));
906 receiver
907 }
908
909 pub fn read_apf_packet_filter_data(
910 &mut self,
911 ) -> oneshot::Receiver<Result<fidl_mlme::MlmeReadApfPacketFilterDataResponse, i32>> {
912 let (responder, receiver) = Responder::new();
913 self.context.mlme_sink.send(MlmeRequest::ReadApfPacketFilterData(responder));
914 receiver
915 }
916
917 pub fn set_apf_packet_filter_enabled(
918 &mut self,
919 enabled: bool,
920 ) -> oneshot::Receiver<Result<(), i32>> {
921 let (responder, receiver) = Responder::new();
922 self.context.mlme_sink.send(MlmeRequest::SetApfPacketFilterEnabled(
923 fidl_mlme::MlmeSetApfPacketFilterEnabledRequest { enabled },
924 responder,
925 ));
926 receiver
927 }
928
929 pub fn get_apf_packet_filter_enabled(
930 &mut self,
931 ) -> oneshot::Receiver<Result<fidl_mlme::MlmeGetApfPacketFilterEnabledResponse, i32>> {
932 let (responder, receiver) = Responder::new();
933 self.context.mlme_sink.send(MlmeRequest::GetApfPacketFilterEnabled(responder));
934 receiver
935 }
936}
937
938impl super::Station for ClientSme {
939 type Event = Event;
940
941 fn on_mlme_event(&mut self, event: fidl_mlme::MlmeEvent) {
942 match event {
943 fidl_mlme::MlmeEvent::OnScanResult { result } => self
944 .scan_sched
945 .on_mlme_scan_result(result)
946 .unwrap_or_else(|e| error!("scan result error: {:?}", e)),
947 fidl_mlme::MlmeEvent::OnScanEnd { end } => {
948 match self.scan_sched.on_mlme_scan_end(end, &self.context.inspect) {
949 Err(e) => error!("scan end error: {:?}", e),
950 Ok((scan_end, next_request)) => {
951 self.send_scan_request(next_request);
954
955 match scan_end.result_code {
956 fidl_mlme::ScanResultCode::Success => {
957 let scan_result_list: Vec<ScanResult> = scan_end
958 .bss_description_list
959 .into_iter()
960 .map(|bss_description| {
961 self.cfg.create_scan_result(
962 zx::MonotonicInstant::from_nanos(0),
964 bss_description,
965 &self.context.device_info,
966 &self.context.security_support,
967 )
968 })
969 .collect();
970 for responder in scan_end.tokens {
971 responder.respond(Ok(scan_result_list.clone()));
972 }
973 }
974 result_code => {
975 let count = scan_end.bss_description_list.len();
976 if count > 0 {
977 warn!("Incomplete scan with {} pending results.", count);
978 }
979 for responder in scan_end.tokens {
980 responder.respond(Err(result_code));
981 }
982 }
983 }
984 }
985 }
986 }
987 fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp } => {
988 for responder in self.wmm_status_responders.drain(..) {
989 let result = if status == zx::sys::ZX_OK { Ok(resp) } else { Err(status) };
990 responder.respond(result);
991 }
992 let event = fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp };
993 self.state =
994 self.state.take().map(|state| state.on_mlme_event(event, &mut self.context));
995 }
996 other => {
997 self.state =
998 self.state.take().map(|state| state.on_mlme_event(other, &mut self.context));
999 }
1000 };
1001
1002 self.context.inspect.update_pulse(self.status());
1003 }
1004
1005 fn on_timeout(&mut self, timed_event: timer::Event<Event>) {
1006 self.state = self.state.take().map(|state| match timed_event.event {
1007 event @ Event::RsnaCompletionTimeout(..)
1008 | event @ Event::RsnaResponseTimeout(..)
1009 | event @ Event::RsnaRetransmissionTimeout(..)
1010 | event @ Event::SaeTimeout(..)
1011 | event @ Event::DeauthenticateTimeout(..) => {
1012 state.handle_timeout(event, &mut self.context)
1013 }
1014 Event::InspectPulseCheck(..) => {
1015 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
1016 let _ = self.context.timer.schedule(event::InspectPulseCheck);
1017 state
1018 }
1019 });
1020
1021 self.context.inspect.update_pulse(self.status());
1024 }
1025}
1026
1027fn report_connect_finished(connect_txn_sink: &mut ConnectTransactionSink, result: ConnectResult) {
1028 connect_txn_sink.send_connect_result(result);
1029}
1030
1031fn report_roam_finished(connect_txn_sink: &mut ConnectTransactionSink, result: RoamResult) {
1032 connect_txn_sink.send_roam_result(result);
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037 use super::*;
1038 use crate::Config as SmeConfig;
1039 use assert_matches::assert_matches;
1040 use ieee80211::MacAddr;
1041 use std::collections::HashSet;
1042 use std::sync::LazyLock;
1043 use test_case::test_case;
1044 use wlan_common::{
1045 channel::{Cbw, Channel},
1046 fake_bss_description, fake_fidl_bss_description,
1047 ie::{IeType, fake_ht_cap_bytes, fake_vht_cap_bytes},
1048 security::{wep::WEP40_KEY_BYTES, wpa::credential::PSK_SIZE_BYTES},
1049 test_utils::{
1050 fake_features::{
1051 fake_security_support, fake_security_support_empty,
1052 fake_spectrum_management_support_empty,
1053 },
1054 fake_stas::{FakeProtectionCfg, IesOverrides},
1055 },
1056 };
1057 use {
1058 fidl_fuchsia_wlan_common as fidl_common,
1059 fidl_fuchsia_wlan_common_security as fidl_security, fidl_fuchsia_wlan_mlme as fidl_mlme,
1060 fuchsia_inspect as finspect,
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_security::Authentication {
1071 fidl_security::Authentication { protocol: fidl_security::Protocol::Open, credentials: None }
1072 }
1073
1074 fn authentication_wep40() -> fidl_security::Authentication {
1075 fidl_security::Authentication {
1076 protocol: fidl_security::Protocol::Wep,
1077 credentials: Some(Box::new(fidl_security::Credentials::Wep(
1078 fidl_security::WepCredentials { key: [1; WEP40_KEY_BYTES].into() },
1079 ))),
1080 }
1081 }
1082
1083 fn authentication_wpa1_passphrase() -> fidl_security::Authentication {
1084 fidl_security::Authentication {
1085 protocol: fidl_security::Protocol::Wpa1,
1086 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1087 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1088 ))),
1089 }
1090 }
1091
1092 fn authentication_wpa2_personal_psk() -> fidl_security::Authentication {
1093 fidl_security::Authentication {
1094 protocol: fidl_security::Protocol::Wpa2Personal,
1095 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1096 fidl_security::WpaCredentials::Psk([1; PSK_SIZE_BYTES]),
1097 ))),
1098 }
1099 }
1100
1101 fn authentication_wpa2_personal_passphrase() -> fidl_security::Authentication {
1102 fidl_security::Authentication {
1103 protocol: fidl_security::Protocol::Wpa2Personal,
1104 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1105 fidl_security::WpaCredentials::Passphrase(b"password".as_slice().into()),
1106 ))),
1107 }
1108 }
1109
1110 fn authentication_wpa3_personal_passphrase() -> fidl_security::Authentication {
1111 fidl_security::Authentication {
1112 protocol: fidl_security::Protocol::Wpa3Personal,
1113 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
1114 fidl_security::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_common::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_security::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_common::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_security::Authentication {
2016 protocol: fidl_security::Protocol::Wpa2Personal,
2017 credentials: Some(Box::new(fidl_security::Credentials::Wpa(
2018 fidl_security::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
2097 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2098 end: fidl_mlme::ScanEnd {
2099 txn_id: 1,
2100 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2101 },
2102 });
2103
2104 assert_eq!(
2105 recv.try_recv(),
2106 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2107 );
2108 }
2109
2110 #[fuchsia::test(allow_stalls = false)]
2111 async fn test_scan_error_after_some_results_returned() {
2112 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2113 let mut recv =
2114 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2115
2116 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2117 bss.bssid = [3; 6];
2118 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2119 result: fidl_mlme::ScanResult {
2120 txn_id: 1,
2121 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2122 bss,
2123 },
2124 });
2125 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2126 bss.bssid = [4; 6];
2127 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2128 result: fidl_mlme::ScanResult {
2129 txn_id: 1,
2130 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2131 bss,
2132 },
2133 });
2134
2135 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2136 end: fidl_mlme::ScanEnd {
2137 txn_id: 1,
2138 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2139 },
2140 });
2141
2142 assert_eq!(
2144 recv.try_recv(),
2145 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2146 );
2147 }
2148
2149 #[fuchsia::test(allow_stalls = false)]
2150 async fn test_scan_is_rejected_while_connecting() {
2151 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2152
2153 let bss_description =
2155 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2156 let _recv = sme.on_connect_command(connect_req(
2157 Ssid::try_from("foo").unwrap(),
2158 bss_description,
2159 authentication_open(),
2160 ));
2161 assert_matches!(sme.status(), ClientSmeStatus::Connecting(_));
2162
2163 let mut recv =
2165 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}));
2166 assert_eq!(recv.try_recv(), Ok(Some(Err(fidl_mlme::ScanResultCode::ShouldWait))));
2167 }
2168
2169 #[fuchsia::test(allow_stalls = false)]
2170 async fn test_wmm_status_success() {
2171 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2172 let mut receiver = sme.wmm_status();
2173
2174 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2175
2176 let resp = fake_wmm_status_resp();
2177 #[allow(
2178 clippy::redundant_field_names,
2179 reason = "mass allow for https://fxbug.dev/381896734"
2180 )]
2181 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnWmmStatusResp {
2182 status: zx::sys::ZX_OK,
2183 resp: resp,
2184 });
2185
2186 assert_eq!(receiver.try_recv(), Ok(Some(Ok(resp))));
2187 }
2188
2189 #[fuchsia::test(allow_stalls = false)]
2190 async fn test_wmm_status_failed() {
2191 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2192 let mut receiver = sme.wmm_status();
2193
2194 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2195 sme.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_ERR_IO));
2196 assert_eq!(receiver.try_recv(), Ok(Some(Err(zx::sys::ZX_ERR_IO))));
2197 }
2198
2199 #[fuchsia::test(allow_stalls = false)]
2200 async fn test_query_apf_packet_filter_support() {
2201 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2202 let mut _receiver = sme.query_apf_packet_filter_support();
2203 assert_matches!(
2204 mlme_stream.try_next(),
2205 Ok(Some(MlmeRequest::QueryApfPacketFilterSupport(..)))
2206 );
2207 }
2208
2209 #[fuchsia::test(allow_stalls = false)]
2210 async fn test_install_apf_packet_filter() {
2211 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2212 let program = vec![1, 2, 3];
2213 let mut _receiver = sme.install_apf_packet_filter(program.clone());
2214 let req = assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::InstallApfPacketFilter(req, ..))) => req);
2215 assert_eq!(req.program, program);
2216 }
2217
2218 #[fuchsia::test(allow_stalls = false)]
2219 async fn test_read_apf_packet_filter_data() {
2220 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2221 let mut _receiver = sme.read_apf_packet_filter_data();
2222 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::ReadApfPacketFilterData(..))));
2223 }
2224
2225 #[fuchsia::test(allow_stalls = false)]
2226 async fn test_set_apf_packet_filter_enabled() {
2227 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2228 let mut _receiver = sme.set_apf_packet_filter_enabled(true);
2229 let req = assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetApfPacketFilterEnabled(req, ..))) => req);
2230 assert!(req.enabled);
2231 }
2232
2233 #[fuchsia::test(allow_stalls = false)]
2234 async fn test_get_apf_packet_filter_enabled() {
2235 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2236 let mut _receiver = sme.get_apf_packet_filter_enabled();
2237 assert_matches!(
2238 mlme_stream.try_next(),
2239 Ok(Some(MlmeRequest::GetApfPacketFilterEnabled(..)))
2240 );
2241 }
2242
2243 fn assert_no_connect(mlme_stream: &mut mpsc::UnboundedReceiver<MlmeRequest>) {
2244 loop {
2245 match mlme_stream.try_next() {
2246 Ok(event) => match event {
2247 Some(MlmeRequest::Connect(..)) => {
2248 panic!("unexpected connect request sent to MLME")
2249 }
2250 None => break,
2251 _ => (),
2252 },
2253 Err(e) => {
2254 assert_eq!(e.to_string(), "receiver channel is empty");
2255 break;
2256 }
2257 }
2258 }
2259 }
2260
2261 fn connect_req(
2262 ssid: Ssid,
2263 bss_description: fidl_common::BssDescription,
2264 authentication: fidl_security::Authentication,
2265 ) -> fidl_sme::ConnectRequest {
2266 fidl_sme::ConnectRequest {
2267 ssid: ssid.to_vec(),
2268 bss_description,
2269 multiple_bss_candidates: true,
2270 authentication,
2271 deprecated_scan_type: fidl_common::ScanType::Passive,
2272 }
2273 }
2274
2275 async fn create_sme() -> (ClientSme, MlmeStream, timer::EventStream<Event>) {
2282 let inspector = finspect::Inspector::default();
2283 let sme_root_node = inspector.root().create_child("sme");
2284 let (client_sme, _mlme_sink, mlme_stream, time_stream) = ClientSme::new(
2285 ClientConfig::default(),
2286 test_utils::fake_device_info(*CLIENT_ADDR),
2287 inspector,
2288 sme_root_node,
2289 fake_security_support(),
2290 fake_spectrum_management_support_empty(),
2291 );
2292 (client_sme, mlme_stream, time_stream)
2293 }
2294}