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