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