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 fidl_sme::ServingApInfo {
625 bssid: ap.bssid.to_array(),
626 ssid: ap.ssid.to_vec(),
627 rssi_dbm: ap.rssi_dbm,
628 snr_db: ap.snr_db,
629 channel: ap.channel.into(),
630 protection: ap.protection.into(),
631 }
632 }
633}
634
635#[derive(Clone, Debug, PartialEq)]
637pub enum ClientSmeStatus {
638 Connected(Box<ServingApInfo>),
639 Connecting(Ssid),
640 Roaming(Bssid),
641 Idle,
642}
643
644impl ClientSmeStatus {
645 pub fn is_connecting(&self) -> bool {
646 matches!(self, ClientSmeStatus::Connecting(_))
647 }
648
649 pub fn is_connected(&self) -> bool {
650 matches!(self, ClientSmeStatus::Connected(_))
651 }
652}
653
654impl From<ClientSmeStatus> for fidl_sme::ClientStatusResponse {
655 fn from(client_sme_status: ClientSmeStatus) -> fidl_sme::ClientStatusResponse {
656 match client_sme_status {
657 ClientSmeStatus::Connected(serving_ap_info) => {
658 fidl_sme::ClientStatusResponse::Connected((*serving_ap_info).into())
659 }
660 ClientSmeStatus::Connecting(ssid) => {
661 fidl_sme::ClientStatusResponse::Connecting(ssid.to_vec())
662 }
663 ClientSmeStatus::Roaming(bssid) => {
664 fidl_sme::ClientStatusResponse::Roaming(bssid.to_array())
665 }
666 ClientSmeStatus::Idle => fidl_sme::ClientStatusResponse::Idle(fidl_sme::Empty {}),
667 }
668 }
669}
670
671impl ClientSme {
672 #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
673 pub fn new(
674 cfg: ClientConfig,
675 info: fidl_mlme::DeviceInfo,
676 inspector: fuchsia_inspect::Inspector,
677 inspect_node: fuchsia_inspect::Node,
678 security_support: fidl_common::SecuritySupport,
679 spectrum_management_support: fidl_common::SpectrumManagementSupport,
680 ) -> (Self, MlmeSink, MlmeStream, timer::EventStream<Event>) {
681 let device_info = Arc::new(info);
682 let (mlme_sink, mlme_stream) = mpsc::unbounded();
683 let (mut timer, time_stream) = timer::create_timer();
684 let inspect = Arc::new(inspect::SmeTree::new(
685 inspector,
686 inspect_node,
687 &device_info,
688 &spectrum_management_support,
689 ));
690 let _ = timer.schedule(event::InspectPulseCheck);
691
692 (
693 ClientSme {
694 cfg,
695 state: Some(ClientState::new(cfg)),
696 scan_sched: <ScanScheduler<
697 Responder<Result<Vec<ScanResult>, fidl_mlme::ScanResultCode>>,
698 >>::new(
699 Arc::clone(&device_info), spectrum_management_support
700 ),
701 wmm_status_responders: vec![],
702 context: Context {
703 mlme_sink: MlmeSink::new(mlme_sink.clone()),
704 device_info,
705 timer,
706 att_id: 0,
707 inspect,
708 security_support,
709 },
710 },
711 MlmeSink::new(mlme_sink),
712 mlme_stream,
713 time_stream,
714 )
715 }
716
717 pub fn on_connect_command(
718 &mut self,
719 req: fidl_sme::ConnectRequest,
720 ) -> ConnectTransactionStream {
721 let (mut connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
722
723 self.state = self.state.take().map(|state| state.cancel_ongoing_connect(&mut self.context));
725
726 let bss_description: BssDescription = match req.bss_description.try_into() {
727 Ok(bss_description) => bss_description,
728 Err(e) => {
729 error!("Failed converting FIDL BssDescription in ConnectRequest: {:?}", e);
730 connect_txn_sink
731 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
732 return connect_txn_stream;
733 }
734 };
735
736 info!("Received ConnectRequest for {}", bss_description);
737
738 if self
739 .cfg
740 .bss_compatibility(
741 &bss_description,
742 &self.context.device_info,
743 &self.context.security_support,
744 )
745 .is_err()
746 {
747 warn!("BSS is incompatible");
748 connect_txn_sink
749 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
750 return connect_txn_stream;
751 }
752
753 let authentication = req.authentication.clone();
754 let protection = match SecurityAuthenticator::try_from(req.authentication)
755 .map_err(From::from)
756 .and_then(|authenticator| {
757 Protection::try_from(SecurityContext {
758 security: &authenticator,
759 device: &self.context.device_info,
760 security_support: &self.context.security_support,
761 config: &self.cfg,
762 bss: &bss_description,
763 })
764 }) {
765 Ok(protection) => protection,
766 Err(error) => {
767 warn!(
768 "{:?}",
769 format!(
770 "Failed to configure protection for network {} ({}): {:?}",
771 bss_description.ssid, bss_description.bssid, error
772 )
773 );
774 connect_txn_sink
775 .send_connect_result(SelectNetworkFailure::IncompatibleConnectRequest.into());
776 return connect_txn_stream;
777 }
778 };
779 let cmd = ConnectCommand {
780 bss: Box::new(bss_description),
781 connect_txn_sink,
782 protection,
783 authentication,
784 };
785
786 self.state = self.state.take().map(|state| state.connect(cmd, &mut self.context));
787 connect_txn_stream
788 }
789
790 pub fn on_roam_command(&mut self, req: fidl_sme::RoamRequest) {
791 if !self.status().is_connected() {
792 error!("SME ignoring roam request because client is not connected");
793 } else {
794 self.state =
795 self.state.take().map(|state| state.roam(&mut self.context, req.bss_description));
796 }
797 }
798
799 pub fn on_disconnect_command(
800 &mut self,
801 policy_disconnect_reason: fidl_sme::UserDisconnectReason,
802 responder: fidl_sme::ClientSmeDisconnectResponder,
803 ) {
804 self.state = self
805 .state
806 .take()
807 .map(|state| state.disconnect(&mut self.context, policy_disconnect_reason, responder));
808 self.context.inspect.update_pulse(self.status());
809 }
810
811 pub fn on_scan_command(
812 &mut self,
813 scan_request: fidl_sme::ScanRequest,
814 ) -> oneshot::Receiver<Result<Vec<wlan_common::scan::ScanResult>, fidl_mlme::ScanResultCode>>
815 {
816 let (responder, receiver) = Responder::new();
817 if self.status().is_connecting() {
818 info!("SME ignoring scan request because a connect is in progress");
819 responder.respond(Err(fidl_mlme::ScanResultCode::ShouldWait));
820 } else {
821 info!(
822 "SME received a scan command, initiating a{} discovery scan",
823 match scan_request {
824 fidl_sme::ScanRequest::Active(_) => "n active",
825 fidl_sme::ScanRequest::Passive(_) => " passive",
826 }
827 );
828 let scan = DiscoveryScan::new(responder, scan_request);
829 let req = self.scan_sched.enqueue_scan_to_discover(scan);
830 self.send_scan_request(req);
831 }
832 receiver
833 }
834
835 pub fn on_start_scheduled_scan_command(
836 &mut self,
837 req: fidl_common::ScheduledScanRequest,
838 ) -> (oneshot::Receiver<Result<(), i32>>, ScheduledScanReceiver) {
839 let (responder, receiver) = Responder::new();
840 let session =
841 self.scan_sched.start_scheduled_scan(req, self.context.mlme_sink.clone(), responder);
842 (receiver, session)
843 }
844
845 pub fn on_get_scheduled_scan_enabled_command(
846 &mut self,
847 ) -> oneshot::Receiver<Result<fidl_mlme::MlmeGetScheduledScanEnabledResponse, i32>> {
848 let (responder, receiver) = Responder::new();
849 self.context.mlme_sink.send(MlmeRequest::GetScheduledScanEnabled(responder));
850 receiver
851 }
852
853 pub fn on_clone_inspect_vmo(&self) -> Option<fidl::Vmo> {
854 self.context.inspect.clone_vmo_data()
855 }
856
857 pub fn status(&self) -> ClientSmeStatus {
858 #[expect(clippy::expect_used)]
860 self.state.as_ref().expect("expected state to be always present").status()
861 }
862
863 pub fn wmm_status(&mut self) -> oneshot::Receiver<fidl_sme::ClientSmeWmmStatusResult> {
864 let (responder, receiver) = Responder::new();
865 self.wmm_status_responders.push(responder);
866 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
867 receiver
868 }
869
870 fn send_scan_request(&mut self, req: Option<fidl_mlme::ScanRequest>) {
871 if let Some(req) = req {
872 self.context.mlme_sink.send(MlmeRequest::Scan(req));
873 }
874 }
875
876 pub fn query_telemetry_support(
877 &mut self,
878 ) -> oneshot::Receiver<Result<fidl_stats::TelemetrySupport, i32>> {
879 let (responder, receiver) = Responder::new();
880 self.context.mlme_sink.send(MlmeRequest::QueryTelemetrySupport(responder));
881 receiver
882 }
883
884 pub fn iface_stats(&mut self) -> oneshot::Receiver<fidl_mlme::GetIfaceStatsResponse> {
885 let (responder, receiver) = Responder::new();
886 self.context.mlme_sink.send(MlmeRequest::GetIfaceStats(responder));
887 receiver
888 }
889
890 pub fn histogram_stats(
891 &mut self,
892 ) -> oneshot::Receiver<fidl_mlme::GetIfaceHistogramStatsResponse> {
893 let (responder, receiver) = Responder::new();
894 self.context.mlme_sink.send(MlmeRequest::GetIfaceHistogramStats(responder));
895 receiver
896 }
897
898 pub fn signal_report(&mut self) -> oneshot::Receiver<Result<fidl_stats::SignalReport, i32>> {
899 let (responder, receiver) = Responder::new();
900 self.context.mlme_sink.send(MlmeRequest::GetSignalReport(responder));
901 receiver
902 }
903
904 pub fn set_mac_address(&mut self, mac_addr: [u8; 6]) -> oneshot::Receiver<Result<(), i32>> {
905 let (responder, receiver) = Responder::new();
906 self.context.mlme_sink.send(MlmeRequest::SetMacAddress(mac_addr, responder));
907 receiver
908 }
909
910 pub fn update_mac_address(&mut self, mac_addr: [u8; 6]) {
911 let mut device_info = (*self.context.device_info).clone();
912 device_info.sta_addr = mac_addr;
913 self.context.device_info = Arc::new(device_info);
914 }
915
916 pub fn device_info(&self) -> Arc<fidl_mlme::DeviceInfo> {
917 Arc::clone(&self.context.device_info)
918 }
919
920 pub fn query_apf_packet_filter_support(
921 &mut self,
922 ) -> oneshot::Receiver<Result<fidl_common::ApfPacketFilterSupport, i32>> {
923 let (responder, receiver) = Responder::new();
924 self.context.mlme_sink.send(MlmeRequest::QueryApfPacketFilterSupport(responder));
925 receiver
926 }
927
928 pub fn install_apf_packet_filter(
929 &mut self,
930 program: Vec<u8>,
931 ) -> oneshot::Receiver<Result<(), i32>> {
932 let (responder, receiver) = Responder::new();
933 self.context.mlme_sink.send(MlmeRequest::InstallApfPacketFilter(
934 fidl_mlme::MlmeInstallApfPacketFilterRequest { program },
935 responder,
936 ));
937 receiver
938 }
939
940 pub fn read_apf_packet_filter_data(
941 &mut self,
942 ) -> oneshot::Receiver<Result<fidl_mlme::MlmeReadApfPacketFilterDataResponse, i32>> {
943 let (responder, receiver) = Responder::new();
944 self.context.mlme_sink.send(MlmeRequest::ReadApfPacketFilterData(responder));
945 receiver
946 }
947
948 pub fn set_apf_packet_filter_enabled(
949 &mut self,
950 enabled: bool,
951 ) -> oneshot::Receiver<Result<(), i32>> {
952 let (responder, receiver) = Responder::new();
953 self.context.mlme_sink.send(MlmeRequest::SetApfPacketFilterEnabled(
954 fidl_mlme::MlmeSetApfPacketFilterEnabledRequest { enabled },
955 responder,
956 ));
957 receiver
958 }
959
960 pub fn get_apf_packet_filter_enabled(
961 &mut self,
962 ) -> oneshot::Receiver<Result<fidl_mlme::MlmeGetApfPacketFilterEnabledResponse, i32>> {
963 let (responder, receiver) = Responder::new();
964 self.context.mlme_sink.send(MlmeRequest::GetApfPacketFilterEnabled(responder));
965 receiver
966 }
967}
968
969impl super::Station for ClientSme {
970 type Event = Event;
971
972 fn on_mlme_event(&mut self, event: fidl_mlme::MlmeEvent) {
973 match event {
974 fidl_mlme::MlmeEvent::OnScanResult { result } => {
975 self.scan_sched
976 .on_mlme_scan_result(result)
977 .unwrap_or_else(|e| error!("scan result error: {:?}", e));
978 }
979 fidl_mlme::MlmeEvent::OnScanEnd { end } => {
980 match self.scan_sched.on_mlme_scan_end(end, &self.context.inspect) {
981 Err(e) => error!("scan end error: {:?}", e),
982 Ok((scan_end, next_request)) => {
983 self.send_scan_request(next_request);
986
987 match scan_end.result_code {
988 fidl_mlme::ScanResultCode::Success => {
989 let scan_result_list: Vec<ScanResult> = scan_end
990 .bss_description_list
991 .into_iter()
992 .map(|bss_description| {
993 self.cfg.create_scan_result(
994 zx::MonotonicInstant::from_nanos(0),
996 bss_description,
997 &self.context.device_info,
998 &self.context.security_support,
999 )
1000 })
1001 .collect();
1002 for responder in scan_end.tokens {
1003 responder.respond(Ok(scan_result_list.clone()));
1004 }
1005 }
1006 result_code => {
1007 let count = scan_end.bss_description_list.len();
1008 if count > 0 {
1009 warn!("Incomplete scan with {} pending results.", count);
1010 }
1011 for responder in scan_end.tokens {
1012 responder.respond(Err(result_code));
1013 }
1014 }
1015 }
1016 }
1017 }
1018 }
1019 fidl_mlme::MlmeEvent::OnScheduledScanMatchesAvailable { txn_id } => {
1020 self.scan_sched.on_scheduled_scan_matches_available(
1021 txn_id,
1022 &self.context.inspect,
1023 &self.cfg,
1024 &self.context.device_info,
1025 &self.context.security_support,
1026 );
1027 }
1028 fidl_mlme::MlmeEvent::OnScheduledScanStoppedByFirmware { txn_id } => {
1029 self.scan_sched.on_scheduled_scan_stopped_by_firmware(txn_id);
1030 }
1031 fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp } => {
1032 for responder in self.wmm_status_responders.drain(..) {
1033 let result = if status == zx::sys::ZX_OK { Ok(resp) } else { Err(status) };
1034 responder.respond(result);
1035 }
1036 let event = fidl_mlme::MlmeEvent::OnWmmStatusResp { status, resp };
1037 self.state =
1038 self.state.take().map(|state| state.on_mlme_event(event, &mut self.context));
1039 }
1040 other => {
1041 self.state =
1042 self.state.take().map(|state| state.on_mlme_event(other, &mut self.context));
1043 }
1044 };
1045
1046 self.context.inspect.update_pulse(self.status());
1047 }
1048
1049 fn on_timeout(&mut self, timed_event: timer::Event<Event>) {
1050 self.state = self.state.take().map(|state| match timed_event.event {
1051 event @ Event::RsnaCompletionTimeout(..)
1052 | event @ Event::RsnaResponseTimeout(..)
1053 | event @ Event::RsnaRetransmissionTimeout(..)
1054 | event @ Event::SaeTimeout(..)
1055 | event @ Event::DeauthenticateTimeout(..) => {
1056 state.handle_timeout(event, &mut self.context)
1057 }
1058 Event::InspectPulseCheck(..) => {
1059 self.context.mlme_sink.send(MlmeRequest::WmmStatusReq);
1060 let _ = self.context.timer.schedule(event::InspectPulseCheck);
1061 state
1062 }
1063 });
1064
1065 self.context.inspect.update_pulse(self.status());
1068 }
1069}
1070
1071fn report_connect_finished(connect_txn_sink: &mut ConnectTransactionSink, result: ConnectResult) {
1072 connect_txn_sink.send_connect_result(result);
1073}
1074
1075fn report_roam_finished(connect_txn_sink: &mut ConnectTransactionSink, result: RoamResult) {
1076 connect_txn_sink.send_roam_result(result);
1077}
1078
1079#[cfg(test)]
1080mod tests {
1081 use super::*;
1082 use crate::Config as SmeConfig;
1083 use assert_matches::assert_matches;
1084 use fidl_fuchsia_wlan_common as fidl_common;
1085 use fidl_fuchsia_wlan_mlme as fidl_mlme;
1086 use fuchsia_inspect as finspect;
1087 use ieee80211::MacAddr;
1088 use std::collections::HashSet;
1089 use std::sync::LazyLock;
1090 use test_case::test_case;
1091 use wlan_common::{
1092 channel::{Cbw, Channel},
1093 fake_bss_description, fake_fidl_bss_description,
1094 ie::{IeType, fake_ht_cap_bytes, fake_vht_cap_bytes},
1095 security::{wep::WEP40_KEY_BYTES, wpa::credential::PSK_SIZE_BYTES},
1096 test_utils::{
1097 fake_features::{
1098 fake_security_support, fake_security_support_empty,
1099 fake_spectrum_management_support_empty,
1100 },
1101 fake_stas::{FakeProtectionCfg, IesOverrides},
1102 },
1103 };
1104
1105 use super::test_utils::{create_on_wmm_status_resp, fake_wmm_param, fake_wmm_status_resp};
1106
1107 use crate::{Station, test_utils};
1108
1109 static CLIENT_ADDR: LazyLock<MacAddr> =
1110 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
1111
1112 fn authentication_open() -> fidl_internal::Authentication {
1113 fidl_internal::Authentication { protocol: fidl_internal::Protocol::Open, credentials: None }
1114 }
1115
1116 fn authentication_wep40() -> fidl_internal::Authentication {
1117 fidl_internal::Authentication {
1118 protocol: fidl_internal::Protocol::Wep,
1119 credentials: Some(Box::new(fidl_internal::Credentials::Wep(
1120 fidl_internal::WepCredentials { key: [1; WEP40_KEY_BYTES].into() },
1121 ))),
1122 }
1123 }
1124
1125 fn authentication_wpa1_passphrase() -> fidl_internal::Authentication {
1126 fidl_internal::Authentication {
1127 protocol: fidl_internal::Protocol::Wpa1,
1128 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
1129 fidl_internal::WpaCredentials::Passphrase(b"password".as_slice().into()),
1130 ))),
1131 }
1132 }
1133
1134 fn authentication_wpa2_personal_psk() -> fidl_internal::Authentication {
1135 fidl_internal::Authentication {
1136 protocol: fidl_internal::Protocol::Wpa2Personal,
1137 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
1138 fidl_internal::WpaCredentials::Psk([1; PSK_SIZE_BYTES]),
1139 ))),
1140 }
1141 }
1142
1143 fn authentication_wpa2_personal_passphrase() -> fidl_internal::Authentication {
1144 fidl_internal::Authentication {
1145 protocol: fidl_internal::Protocol::Wpa2Personal,
1146 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
1147 fidl_internal::WpaCredentials::Passphrase(b"password".as_slice().into()),
1148 ))),
1149 }
1150 }
1151
1152 fn authentication_wpa3_personal_passphrase() -> fidl_internal::Authentication {
1153 fidl_internal::Authentication {
1154 protocol: fidl_internal::Protocol::Wpa3Personal,
1155 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
1156 fidl_internal::WpaCredentials::Passphrase(b"password".as_slice().into()),
1157 ))),
1158 }
1159 }
1160
1161 fn report_fake_scan_result(
1162 sme: &mut ClientSme,
1163 timestamp_nanos: i64,
1164 bss: fidl_ieee80211::BssDescription,
1165 ) {
1166 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
1167 result: fidl_mlme::ScanResult { txn_id: 1, timestamp_nanos, bss },
1168 });
1169 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
1170 end: fidl_mlme::ScanEnd { txn_id: 1, code: fidl_mlme::ScanResultCode::Success },
1171 });
1172 }
1173
1174 #[test_case(FakeProtectionCfg::Open)]
1175 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1176 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1177 #[test_case(FakeProtectionCfg::Wpa2)]
1178 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1179 fn default_client_protection_is_bss_compatible(protection: FakeProtectionCfg) {
1180 let cfg = ClientConfig::default();
1181 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1182 assert!(
1183 cfg.bss_compatibility(
1184 &fake_bss_description!(protection => protection),
1185 &fake_device_info,
1186 &fake_security_support_empty()
1187 )
1188 .is_ok(),
1189 );
1190 }
1191
1192 #[test_case(FakeProtectionCfg::Wpa1)]
1193 #[test_case(FakeProtectionCfg::Wpa3)]
1194 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1195 #[test_case(FakeProtectionCfg::Eap)]
1196 fn default_client_protection_is_bss_incompatible(protection: FakeProtectionCfg) {
1197 let cfg = ClientConfig::default();
1198 let fake_device_info = test_utils::fake_device_info([1u8; 6].into());
1199 assert!(
1200 cfg.bss_compatibility(
1201 &fake_bss_description!(protection => protection),
1202 &fake_device_info,
1203 &fake_security_support_empty()
1204 )
1205 .is_err(),
1206 );
1207 }
1208
1209 #[test_case(FakeProtectionCfg::Open)]
1210 #[test_case(FakeProtectionCfg::OpenOweTransition)]
1211 #[test_case(FakeProtectionCfg::Wpa1Wpa2TkipOnly)]
1212 #[test_case(FakeProtectionCfg::Wpa2TkipOnly)]
1213 #[test_case(FakeProtectionCfg::Wpa2)]
1214 #[test_case(FakeProtectionCfg::Wpa2Wpa3)]
1215 fn compatible_default_client_protection_security_protocol_intersection_is_non_empty(
1216 protection: FakeProtectionCfg,
1217 ) {
1218 let cfg = ClientConfig::default();
1219 assert!(
1220 !cfg.security_protocol_intersection(
1221 &fake_bss_description!(protection => protection),
1222 &fake_security_support_empty()
1223 )
1224 .is_empty()
1225 );
1226 }
1227
1228 #[test_case(FakeProtectionCfg::Owe)]
1229 #[test_case(FakeProtectionCfg::Wpa1)]
1230 #[test_case(FakeProtectionCfg::Wpa3)]
1231 #[test_case(FakeProtectionCfg::Wpa3Transition)]
1232 #[test_case(FakeProtectionCfg::Eap)]
1233 fn incompatible_default_client_protection_security_protocol_intersection_is_empty(
1234 protection: FakeProtectionCfg,
1235 ) {
1236 let cfg = ClientConfig::default();
1237 assert!(
1238 cfg.security_protocol_intersection(
1239 &fake_bss_description!(protection => protection),
1240 &fake_security_support_empty()
1241 )
1242 .is_empty(),
1243 );
1244 }
1245
1246 #[test_case(FakeProtectionCfg::Wpa1, [SecurityDescriptor::WPA1])]
1247 #[test_case(FakeProtectionCfg::Wpa3, [SecurityDescriptor::WPA3_PERSONAL])]
1248 #[test_case(FakeProtectionCfg::Wpa3Transition, [SecurityDescriptor::WPA3_PERSONAL])]
1249 #[test_case(FakeProtectionCfg::Eap, [])]
1251 fn default_client_protection_security_protocols_by_mac_role_eq(
1252 protection: FakeProtectionCfg,
1253 expected: impl IntoIterator<Item = SecurityDescriptor>,
1254 ) {
1255 let cfg = ClientConfig::default();
1256 let security_protocols: HashSet<_> = cfg
1257 .security_protocols_by_mac_role(&fake_bss_description!(protection => protection))
1258 .collect();
1259 assert_eq!(
1262 security_protocols,
1263 HashSet::from_iter(
1264 [
1265 (SecurityDescriptor::OPEN, MacRole::Client),
1266 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1267 ]
1268 .into_iter()
1269 .chain(expected.into_iter().map(|protocol| (protocol, MacRole::Ap)))
1270 ),
1271 );
1272 }
1273
1274 #[test]
1275 fn configured_client_bss_owe_compatible() {
1276 let cfg = ClientConfig::from_config(Config::default(), false, true);
1278 let mut security_support = fake_security_support_empty();
1279 security_support.mfp.as_mut().unwrap().supported = Some(true);
1280 security_support.owe.as_mut().unwrap().supported = Some(true);
1281 assert!(
1282 !cfg.security_protocol_intersection(&fake_bss_description!(Owe), &security_support)
1283 .is_empty()
1284 );
1285 }
1286
1287 #[test]
1288 fn configured_client_bss_wep_compatible() {
1289 let cfg = ClientConfig::from_config(Config::default().with_wep(), false, false);
1291 assert!(
1292 !cfg.security_protocol_intersection(
1293 &fake_bss_description!(Wep),
1294 &fake_security_support_empty()
1295 )
1296 .is_empty()
1297 );
1298 }
1299
1300 #[test]
1301 fn configured_client_bss_wpa1_compatible() {
1302 let cfg = ClientConfig::from_config(Config::default().with_wpa1(), false, false);
1304 assert!(
1305 !cfg.security_protocol_intersection(
1306 &fake_bss_description!(Wpa1),
1307 &fake_security_support_empty()
1308 )
1309 .is_empty()
1310 );
1311 }
1312
1313 #[test]
1314 fn configured_client_bss_wpa3_compatible() {
1315 let cfg = ClientConfig::from_config(Config::default(), true, false);
1317 let mut security_support = fake_security_support_empty();
1318 security_support.mfp.as_mut().unwrap().supported = Some(true);
1319 assert!(
1320 !cfg.security_protocol_intersection(&fake_bss_description!(Wpa3), &security_support)
1321 .is_empty()
1322 );
1323 assert!(
1324 !cfg.security_protocol_intersection(
1325 &fake_bss_description!(Wpa3Transition),
1326 &security_support,
1327 )
1328 .is_empty()
1329 );
1330 }
1331
1332 #[test]
1333 fn verify_rates_compatibility() {
1334 let cfg = ClientConfig::default();
1336 let device_info = test_utils::fake_device_info([1u8; 6].into());
1337 assert!(
1338 cfg.has_compatible_channel_and_data_rates(&fake_bss_description!(Open), &device_info)
1339 );
1340
1341 let bss = fake_bss_description!(Open, rates: vec![0x8C, 0xFF]);
1343 assert!(cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1344
1345 let bss = fake_bss_description!(Open, rates: vec![0x81]);
1347 assert!(!cfg.has_compatible_channel_and_data_rates(&bss, &device_info));
1348 }
1349
1350 #[test]
1351 fn convert_scan_result() {
1352 let cfg = ClientConfig::default();
1353 let bss_description = fake_bss_description!(Wpa2,
1354 ssid: Ssid::empty(),
1355 bssid: [0u8; 6],
1356 rssi_dbm: -30,
1357 snr_db: 0,
1358 channel: Channel::new(1, Cbw::Cbw20),
1359 ies_overrides: IesOverrides::new()
1360 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1361 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1362 );
1363 let device_info = test_utils::fake_device_info([1u8; 6].into());
1364 let timestamp = zx::MonotonicInstant::get();
1365 let scan_result = cfg.create_scan_result(
1366 timestamp,
1367 bss_description.clone(),
1368 &device_info,
1369 &fake_security_support(),
1370 );
1371
1372 assert_eq!(
1373 scan_result,
1374 ScanResult {
1375 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1376 timestamp,
1377 bss_description,
1378 }
1379 );
1380
1381 let wmm_param = *ie::parse_wmm_param(&fake_wmm_param().bytes[..])
1382 .expect("expect WMM param to be parseable");
1383 let bss_description = fake_bss_description!(Wpa2,
1384 ssid: Ssid::empty(),
1385 bssid: [0u8; 6],
1386 rssi_dbm: -30,
1387 snr_db: 0,
1388 channel: Channel::new(1, Cbw::Cbw20),
1389 wmm_param: Some(wmm_param),
1390 ies_overrides: IesOverrides::new()
1391 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1392 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1393 );
1394 let timestamp = zx::MonotonicInstant::get();
1395 let scan_result = cfg.create_scan_result(
1396 timestamp,
1397 bss_description.clone(),
1398 &device_info,
1399 &fake_security_support(),
1400 );
1401
1402 assert_eq!(
1403 scan_result,
1404 ScanResult {
1405 compatibility: Compatible::expect_ok([SecurityDescriptor::WPA2_PERSONAL]),
1406 timestamp,
1407 bss_description,
1408 }
1409 );
1410
1411 let bss_description = fake_bss_description!(Wep,
1412 ssid: Ssid::empty(),
1413 bssid: [0u8; 6],
1414 rssi_dbm: -30,
1415 snr_db: 0,
1416 channel: Channel::new(1, Cbw::Cbw20),
1417 ies_overrides: IesOverrides::new()
1418 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1419 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1420 );
1421 let timestamp = zx::MonotonicInstant::get();
1422 let scan_result = cfg.create_scan_result(
1423 timestamp,
1424 bss_description.clone(),
1425 &device_info,
1426 &fake_security_support(),
1427 );
1428 assert_eq!(
1429 scan_result,
1430 ScanResult {
1431 compatibility: Incompatible::expect_err(
1432 "incompatible channel, PHY data rates, or security protocols",
1433 Some([
1434 (SecurityDescriptor::WEP, MacRole::Ap),
1435 (SecurityDescriptor::OPEN, MacRole::Client),
1436 (SecurityDescriptor::WPA2_PERSONAL, MacRole::Client),
1437 ])
1438 ),
1439 timestamp,
1440 bss_description,
1441 },
1442 );
1443
1444 let cfg = ClientConfig::from_config(Config::default().with_wep(), false, false);
1445 let bss_description = fake_bss_description!(Wep,
1446 ssid: Ssid::empty(),
1447 bssid: [0u8; 6],
1448 rssi_dbm: -30,
1449 snr_db: 0,
1450 channel: Channel::new(1, Cbw::Cbw20),
1451 ies_overrides: IesOverrides::new()
1452 .set(IeType::HT_CAPABILITIES, fake_ht_cap_bytes().to_vec())
1453 .set(IeType::VHT_CAPABILITIES, fake_vht_cap_bytes().to_vec()),
1454 );
1455 let timestamp = zx::MonotonicInstant::get();
1456 let scan_result = cfg.create_scan_result(
1457 timestamp,
1458 bss_description.clone(),
1459 &device_info,
1460 &fake_security_support(),
1461 );
1462 assert_eq!(
1463 scan_result,
1464 ScanResult {
1465 compatibility: Compatible::expect_ok([SecurityDescriptor::WEP]),
1466 timestamp,
1467 bss_description,
1468 }
1469 );
1470 }
1471
1472 #[test_case(EstablishRsnaFailureReason::RsnaResponseTimeout(
1473 wlan_rsn::Error::LikelyWrongCredential
1474 ))]
1475 #[test_case(EstablishRsnaFailureReason::RsnaCompletionTimeout(
1476 wlan_rsn::Error::LikelyWrongCredential
1477 ))]
1478 fn test_connect_detection_of_rejected_wpa1_or_wpa2_credentials(
1479 reason: EstablishRsnaFailureReason,
1480 ) {
1481 let failure = ConnectFailure::EstablishRsnaFailure(EstablishRsnaFailure {
1482 auth_method: Some(auth::MethodName::Psk),
1483 reason,
1484 });
1485 assert!(failure.likely_due_to_credential_rejected());
1486 }
1487
1488 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1489 #[test_case(fake_bss_description!(Wpa1), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1490 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1491 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1492 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1493 #[test_case(fake_bss_description!(Wpa2), EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::LikelyWrongCredential))]
1494 fn test_roam_detection_of_rejected_wpa1_or_wpa2_credentials(
1495 selected_bss: BssDescription,
1496 failure_reason: EstablishRsnaFailureReason,
1497 ) {
1498 let disconnect_info = fidl_sme::DisconnectInfo {
1499 is_sme_reconnecting: false,
1500 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1501 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1502 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1503 }),
1504 };
1505 let failure = RoamFailure {
1506 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1507 failure_type: RoamFailureType::EstablishRsnaFailure,
1508 selected_bssid: selected_bss.bssid,
1509 disconnect_info,
1510 auth_method: Some(auth::MethodName::Psk),
1511 establish_rsna_failure_reason: Some(failure_reason),
1512 selected_bss: Some(selected_bss),
1513 };
1514 assert!(failure.likely_due_to_credential_rejected());
1515 }
1516
1517 #[test]
1518 fn test_connect_detection_of_rejected_wpa3_credentials() {
1519 let bss = fake_bss_description!(Wpa3);
1520 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1521 bss_protection: bss.protection(),
1522 code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1523 });
1524
1525 assert!(failure.likely_due_to_credential_rejected());
1526 }
1527
1528 #[test]
1529 fn test_roam_detection_of_rejected_wpa3_credentials() {
1530 let selected_bss = fake_bss_description!(Wpa3);
1531 let disconnect_info = fidl_sme::DisconnectInfo {
1532 is_sme_reconnecting: false,
1533 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1534 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1535 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1536 }),
1537 };
1538 let failure = RoamFailure {
1539 status_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1540 failure_type: RoamFailureType::ReassociationFailure,
1541 selected_bssid: selected_bss.bssid,
1542 disconnect_info,
1543 auth_method: Some(auth::MethodName::Sae),
1544 establish_rsna_failure_reason: None,
1545 selected_bss: Some(selected_bss),
1546 };
1547 assert!(failure.likely_due_to_credential_rejected());
1548 }
1549
1550 #[test]
1551 fn test_connect_detection_of_rejected_wep_credentials() {
1552 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1553 bss_protection: BssProtection::Wep,
1554 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1555 });
1556 assert!(failure.likely_due_to_credential_rejected());
1557 }
1558
1559 #[test]
1560 fn test_roam_detection_of_rejected_wep_credentials() {
1561 let selected_bss = fake_bss_description!(Wep);
1562 let disconnect_info = fidl_sme::DisconnectInfo {
1563 is_sme_reconnecting: false,
1564 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1565 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1566 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1567 }),
1568 };
1569 let failure = RoamFailure {
1570 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1571 failure_type: RoamFailureType::ReassociationFailure,
1572 selected_bssid: selected_bss.bssid,
1573 disconnect_info,
1574 auth_method: Some(auth::MethodName::Psk),
1575 establish_rsna_failure_reason: None,
1576 selected_bss: Some(selected_bss),
1577 };
1578 assert!(failure.likely_due_to_credential_rejected());
1579 }
1580
1581 #[test]
1582 fn test_connect_no_detection_of_rejected_wpa1_or_wpa2_credentials() {
1583 let failure = ConnectFailure::ScanFailure(fidl_mlme::ScanResultCode::InternalError);
1584 assert!(!failure.likely_due_to_credential_rejected());
1585
1586 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1587 bss_protection: BssProtection::Wpa2Personal,
1588 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1589 });
1590 assert!(!failure.likely_due_to_credential_rejected());
1591 }
1592
1593 #[test_case(fake_bss_description!(Wpa1))]
1594 #[test_case(fake_bss_description!(Wpa1Wpa2TkipOnly))]
1595 #[test_case(fake_bss_description!(Wpa2))]
1596 fn test_roam_no_detection_of_rejected_wpa1_or_wpa2_credentials(selected_bss: BssDescription) {
1597 let disconnect_info = fidl_sme::DisconnectInfo {
1598 is_sme_reconnecting: false,
1599 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1600 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1601 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1602 }),
1603 };
1604 let failure = RoamFailure {
1605 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1606 failure_type: RoamFailureType::EstablishRsnaFailure,
1607 selected_bssid: selected_bss.bssid,
1608 disconnect_info,
1609 auth_method: Some(auth::MethodName::Psk),
1610 establish_rsna_failure_reason: Some(EstablishRsnaFailureReason::StartSupplicantFailed),
1611 selected_bss: Some(selected_bss),
1612 };
1613 assert!(!failure.likely_due_to_credential_rejected());
1614 }
1615
1616 #[test]
1617 fn test_connect_no_detection_of_rejected_wpa3_credentials() {
1618 let bss = fake_bss_description!(Wpa3);
1619 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1620 bss_protection: bss.protection(),
1621 code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1622 });
1623
1624 assert!(!failure.likely_due_to_credential_rejected());
1625 }
1626
1627 #[test]
1628 fn test_roam_no_detection_of_rejected_wpa3_credentials() {
1629 let selected_bss = fake_bss_description!(Wpa3);
1630 let disconnect_info = fidl_sme::DisconnectInfo {
1631 is_sme_reconnecting: false,
1632 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1633 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1634 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1635 }),
1636 };
1637 let failure = RoamFailure {
1638 status_code: fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported,
1639 failure_type: RoamFailureType::ReassociationFailure,
1640 selected_bssid: selected_bss.bssid,
1641 disconnect_info,
1642 auth_method: Some(auth::MethodName::Sae),
1643 establish_rsna_failure_reason: None,
1644 selected_bss: Some(selected_bss),
1645 };
1646 assert!(!failure.likely_due_to_credential_rejected());
1647 }
1648
1649 #[test]
1650 fn test_connect_no_detection_of_rejected_wep_credentials() {
1651 let failure = ConnectFailure::AssociationFailure(AssociationFailure {
1652 bss_protection: BssProtection::Wep,
1653 code: fidl_ieee80211::StatusCode::InvalidParameters,
1654 });
1655 assert!(!failure.likely_due_to_credential_rejected());
1656 }
1657
1658 #[test]
1659 fn test_roam_no_detection_of_rejected_wep_credentials() {
1660 let selected_bss = fake_bss_description!(Wep);
1661 let disconnect_info = fidl_sme::DisconnectInfo {
1662 is_sme_reconnecting: false,
1663 disconnect_source: fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
1664 mlme_event_name: fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1665 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
1666 }),
1667 };
1668 let failure = RoamFailure {
1669 status_code: fidl_ieee80211::StatusCode::StatusInvalidElement,
1670 failure_type: RoamFailureType::ReassociationFailure,
1671 selected_bssid: selected_bss.bssid,
1672 disconnect_info,
1673 auth_method: Some(auth::MethodName::Psk),
1674 establish_rsna_failure_reason: None,
1675 selected_bss: Some(selected_bss),
1676 };
1677 assert!(!failure.likely_due_to_credential_rejected());
1678 }
1679
1680 #[test_case(fake_bss_description!(Open), authentication_open() => matches Ok(Protection::Open))]
1681 #[test_case(fake_bss_description!(Open), authentication_wpa2_personal_passphrase() => matches Err(_))]
1682 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_passphrase() => matches Ok(Protection::Rsna(_)))]
1683 #[test_case(fake_bss_description!(Wpa2), authentication_wpa2_personal_psk() => matches Ok(Protection::Rsna(_)))]
1684 #[test_case(fake_bss_description!(Wpa2), authentication_open() => matches Err(_))]
1685 fn test_protection_from_authentication(
1686 bss: BssDescription,
1687 authentication: fidl_internal::Authentication,
1688 ) -> Result<Protection, anyhow::Error> {
1689 let device = test_utils::fake_device_info(*CLIENT_ADDR);
1690 let security_support = fake_security_support();
1691 let config = Default::default();
1692
1693 let authenticator = SecurityAuthenticator::try_from(authentication).unwrap();
1695 Protection::try_from(SecurityContext {
1696 security: &authenticator,
1697 device: &device,
1698 security_support: &security_support,
1699 config: &config,
1700 bss: &bss,
1701 })
1702 }
1703
1704 #[fuchsia::test(allow_stalls = false)]
1705 async fn status_connecting() {
1706 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
1707 assert_eq!(ClientSmeStatus::Idle, sme.status());
1708
1709 let bss_description =
1711 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1712 let _recv = sme.on_connect_command(connect_req(
1713 Ssid::try_from("foo").unwrap(),
1714 bss_description,
1715 authentication_open(),
1716 ));
1717 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1718
1719 let ssid = assert_matches!(sme.state.as_ref().unwrap().status(), ClientSmeStatus::Connecting(ssid) => ssid);
1722 assert_eq!(Ssid::try_from("foo").unwrap(), ssid);
1723 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1724
1725 let bss_description =
1727 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap());
1728 let _recv2 = sme.on_connect_command(connect_req(
1729 Ssid::try_from("bar").unwrap(),
1730 bss_description,
1731 authentication_open(),
1732 ));
1733 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bar").unwrap()), sme.status());
1734 }
1735
1736 #[test]
1737 fn connecting_to_wep_network_supported() {
1738 let _executor = fuchsia_async::TestExecutor::new();
1739 let inspector = finspect::Inspector::default();
1740 let sme_root_node = inspector.root().create_child("sme");
1741 let (mut sme, _mlme_sink, mut mlme_stream, _time_stream) = ClientSme::new(
1742 ClientConfig::from_config(SmeConfig::default().with_wep(), false, false),
1743 test_utils::fake_device_info(*CLIENT_ADDR),
1744 inspector,
1745 sme_root_node,
1746 fake_security_support(),
1747 fake_spectrum_management_support_empty(),
1748 );
1749 assert_eq!(ClientSmeStatus::Idle, sme.status());
1750
1751 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1753 let req =
1754 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1755 let _recv = sme.on_connect_command(req);
1756 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1757
1758 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1759 }
1760
1761 #[fuchsia::test(allow_stalls = false)]
1762 async fn test_scheduled_scan_session_events() {
1763 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1764
1765 let req = fidl_common::ScheduledScanRequest { ..Default::default() };
1766
1767 let (receiver, mut session_event_stream) = sme.on_start_scheduled_scan_command(req.clone());
1768
1769 assert_matches!(
1770 mlme_stream.try_next(),
1771 Ok(Some(MlmeRequest::StartScheduledScan(fidl_mlme::MlmeStartScheduledScanRequest { txn_id: id, req: _ }, responder))) => {
1772 assert_eq!(id, 1);
1773 responder.respond(Ok(()));
1774 }
1775 );
1776
1777 let result = receiver.await.expect("receiver failed");
1778 assert!(result.is_ok());
1779
1780 let bss = fake_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1781 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
1782 result: fidl_mlme::ScanResult { txn_id: 1, timestamp_nanos: 1000, bss: bss.into() },
1783 });
1784
1785 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScheduledScanMatchesAvailable { txn_id: 1 });
1786
1787 assert_matches!(
1788 session_event_stream.try_next(),
1789 Ok(Some(scan_results)) => {
1790 let results = wlan_common::scan::read_vmo(scan_results).unwrap();
1791 assert_eq!(results.len(), 1);
1792 let parsed_bss = wlan_common::bss::BssDescription::try_from(results[0].bss_description.clone()).unwrap();
1793 assert_eq!(parsed_bss.ssid, Ssid::try_from("foo").unwrap());
1794 }
1795 );
1796
1797 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScheduledScanStoppedByFirmware { txn_id: 1 });
1798
1799 assert_matches!(session_event_stream.try_next(), Ok(None));
1800 }
1801
1802 #[fuchsia::test(allow_stalls = false)]
1803 async fn test_concurrent_scheduled_scan_sessions() {
1804 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1805 let req = fidl_common::ScheduledScanRequest { ..Default::default() };
1806
1807 let (receiver1, mut session_event_stream1) =
1809 sme.on_start_scheduled_scan_command(req.clone());
1810
1811 assert_matches!(
1812 mlme_stream.try_next(),
1813 Ok(Some(MlmeRequest::StartScheduledScan(fidl_mlme::MlmeStartScheduledScanRequest { txn_id: id, req: _ }, responder))) => {
1814 assert_eq!(id, 1);
1815 assert_eq!(session_event_stream1.txn_id, 1);
1816 responder.respond(Ok(()));
1817 }
1818 );
1819 let _ = receiver1.await.unwrap();
1820
1821 let (receiver2, mut session_event_stream2) =
1823 sme.on_start_scheduled_scan_command(req.clone());
1824
1825 assert_matches!(
1826 mlme_stream.try_next(),
1827 Ok(Some(MlmeRequest::StartScheduledScan(fidl_mlme::MlmeStartScheduledScanRequest { txn_id: id, req: _ }, responder))) => {
1828 assert_eq!(id, 2);
1829 assert_eq!(session_event_stream2.txn_id, 2);
1830 responder.respond(Ok(()));
1831 }
1832 );
1833 let _ = receiver2.await.unwrap();
1834
1835 let bss1 = fake_bss_description!(Open, ssid: Ssid::try_from("session1").unwrap());
1837 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
1838 result: fidl_mlme::ScanResult { txn_id: 1, timestamp_nanos: 1000, bss: bss1.into() },
1839 });
1840 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScheduledScanMatchesAvailable { txn_id: 1 });
1841
1842 assert_matches!(
1844 session_event_stream1.try_next(),
1845 Ok(Some(scan_results)) => {
1846 let results = wlan_common::scan::read_vmo(scan_results).unwrap();
1847 assert_eq!(results.len(), 1);
1848 let parsed_bss = wlan_common::bss::BssDescription::try_from(results[0].bss_description.clone()).unwrap();
1849 assert_eq!(parsed_bss.ssid, Ssid::try_from("session1").unwrap());
1850 }
1851 );
1852
1853 assert_matches!(session_event_stream2.try_next(), Err(_));
1855
1856 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScheduledScanStoppedByFirmware { txn_id: 2 });
1858
1859 assert_matches!(session_event_stream2.try_next(), Ok(None));
1860
1861 assert!(sme.scan_sched.scheduled_scan_receivers.contains_key(&1));
1863 }
1864
1865 #[fuchsia::test(allow_stalls = false)]
1866 async fn connecting_to_wep_network_unsupported() {
1867 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1868 assert_eq!(ClientSmeStatus::Idle, sme.status());
1869
1870 let bss_description = fake_fidl_bss_description!(Wep, ssid: Ssid::try_from("foo").unwrap());
1872 let req =
1873 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_wep40());
1874 let mut _connect_fut = sme.on_connect_command(req);
1875 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1876 }
1877
1878 #[fuchsia::test(allow_stalls = false)]
1879 async fn connecting_password_supplied_for_protected_network() {
1880 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1881 assert_eq!(ClientSmeStatus::Idle, sme.status());
1882
1883 let bss_description =
1885 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1886 let req = connect_req(
1887 Ssid::try_from("foo").unwrap(),
1888 bss_description,
1889 authentication_wpa2_personal_passphrase(),
1890 );
1891 let _recv = sme.on_connect_command(req);
1892 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("foo").unwrap()), sme.status());
1893
1894 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1895 }
1896
1897 #[fuchsia::test(allow_stalls = false)]
1898 async fn connecting_psk_supplied_for_protected_network() {
1899 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1900 assert_eq!(ClientSmeStatus::Idle, sme.status());
1901
1902 let bss_description =
1904 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("IEEE").unwrap());
1905 let req = connect_req(
1906 Ssid::try_from("IEEE").unwrap(),
1907 bss_description,
1908 authentication_wpa2_personal_psk(),
1909 );
1910 let _recv = sme.on_connect_command(req);
1911 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("IEEE").unwrap()), sme.status());
1912
1913 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
1914 }
1915
1916 #[fuchsia::test(allow_stalls = false)]
1917 async fn connecting_password_supplied_for_unprotected_network() {
1918 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1919 assert_eq!(ClientSmeStatus::Idle, sme.status());
1920
1921 let bss_description =
1922 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1923 let req = connect_req(
1924 Ssid::try_from("foo").unwrap(),
1925 bss_description,
1926 authentication_wpa2_personal_passphrase(),
1927 );
1928 let mut connect_txn_stream = sme.on_connect_command(req);
1929 assert_eq!(ClientSmeStatus::Idle, sme.status());
1930
1931 assert_matches!(
1933 connect_txn_stream.try_next(),
1934 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1935 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1936 }
1937 );
1938 }
1939
1940 #[fuchsia::test(allow_stalls = false)]
1941 async fn connecting_psk_supplied_for_unprotected_network() {
1942 let (mut sme, mut _mlme_stream, _time_stream) = create_sme().await;
1943 assert_eq!(ClientSmeStatus::Idle, sme.status());
1944
1945 let bss_description =
1946 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
1947 let req = connect_req(
1948 Ssid::try_from("foo").unwrap(),
1949 bss_description,
1950 authentication_wpa2_personal_psk(),
1951 );
1952 let mut connect_txn_stream = sme.on_connect_command(req);
1953 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1954
1955 assert_matches!(
1957 connect_txn_stream.try_next(),
1958 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1959 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1960 }
1961 );
1962 }
1963
1964 #[fuchsia::test(allow_stalls = false)]
1965 async fn connecting_no_password_supplied_for_protected_network() {
1966 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1967 assert_eq!(ClientSmeStatus::Idle, sme.status());
1968
1969 let bss_description =
1970 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
1971 let req =
1972 connect_req(Ssid::try_from("foo").unwrap(), bss_description, authentication_open());
1973 let mut connect_txn_stream = sme.on_connect_command(req);
1974 assert_eq!(ClientSmeStatus::Idle, sme.state.as_ref().unwrap().status());
1975
1976 assert_no_connect(&mut mlme_stream);
1978
1979 assert_matches!(
1981 connect_txn_stream.try_next(),
1982 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
1983 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
1984 }
1985 );
1986 }
1987
1988 #[fuchsia::test(allow_stalls = false)]
1989 async fn connecting_bypass_join_scan_open() {
1990 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
1991 assert_eq!(ClientSmeStatus::Idle, sme.status());
1992
1993 let bss_description =
1994 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bssname").unwrap());
1995 let req =
1996 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
1997 let mut connect_txn_stream = sme.on_connect_command(req);
1998
1999 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
2000 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
2001 assert_matches!(connect_txn_stream.try_next(), Err(_));
2003 }
2004
2005 #[fuchsia::test(allow_stalls = false)]
2006 async fn connecting_bypass_join_scan_protected() {
2007 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2008 assert_eq!(ClientSmeStatus::Idle, sme.status());
2009
2010 let bss_description =
2011 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
2012 let req = connect_req(
2013 Ssid::try_from("bssname").unwrap(),
2014 bss_description,
2015 authentication_wpa2_personal_passphrase(),
2016 );
2017 let mut connect_txn_stream = sme.on_connect_command(req);
2018
2019 assert_eq!(ClientSmeStatus::Connecting(Ssid::try_from("bssname").unwrap()), sme.status());
2020 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(..))));
2021 assert_matches!(connect_txn_stream.try_next(), Err(_));
2023 }
2024
2025 #[fuchsia::test(allow_stalls = false)]
2026 async fn connecting_bypass_join_scan_mismatched_credential() {
2027 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2028 assert_eq!(ClientSmeStatus::Idle, sme.status());
2029
2030 let bss_description =
2031 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("bssname").unwrap());
2032 let req =
2033 connect_req(Ssid::try_from("bssname").unwrap(), bss_description, authentication_open());
2034 let mut connect_txn_stream = sme.on_connect_command(req);
2035
2036 assert_eq!(ClientSmeStatus::Idle, sme.status());
2037 assert_no_connect(&mut mlme_stream);
2038
2039 assert_matches!(
2041 connect_txn_stream.try_next(),
2042 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2043 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2044 }
2045 );
2046 }
2047
2048 #[fuchsia::test(allow_stalls = false)]
2049 async fn connecting_bypass_join_scan_unsupported_bss() {
2050 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2051 assert_eq!(ClientSmeStatus::Idle, sme.status());
2052
2053 let bss_description =
2054 fake_fidl_bss_description!(Wpa3Enterprise, ssid: Ssid::try_from("bssname").unwrap());
2055 let req = connect_req(
2056 Ssid::try_from("bssname").unwrap(),
2057 bss_description,
2058 authentication_wpa3_personal_passphrase(),
2059 );
2060 let mut connect_txn_stream = sme.on_connect_command(req);
2061
2062 assert_eq!(ClientSmeStatus::Idle, sme.status());
2063 assert_no_connect(&mut mlme_stream);
2064
2065 assert_matches!(
2067 connect_txn_stream.try_next(),
2068 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2069 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2070 }
2071 );
2072 }
2073
2074 #[fuchsia::test(allow_stalls = false)]
2075 async fn connecting_right_credential_type_no_privacy() {
2076 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
2077
2078 let bss_description = fake_fidl_bss_description!(
2079 Wpa2,
2080 ssid: Ssid::try_from("foo").unwrap(),
2081 );
2082 let bss_description = fidl_ieee80211::BssDescription {
2085 capability_info: wlan_common::mac::CapabilityInfo(bss_description.capability_info)
2086 .with_privacy(false)
2087 .0,
2088 ..bss_description
2089 };
2090 let mut connect_txn_stream = sme.on_connect_command(connect_req(
2091 Ssid::try_from("foo").unwrap(),
2092 bss_description,
2093 authentication_wpa2_personal_passphrase(),
2094 ));
2095
2096 assert_matches!(
2097 connect_txn_stream.try_next(),
2098 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2099 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2100 }
2101 );
2102 }
2103
2104 #[fuchsia::test(allow_stalls = false)]
2105 async fn connecting_mismatched_security_protocol() {
2106 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
2107
2108 let bss_description =
2109 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
2110 let mut connect_txn_stream = sme.on_connect_command(connect_req(
2111 Ssid::try_from("wpa2").unwrap(),
2112 bss_description,
2113 authentication_wep40(),
2114 ));
2115 assert_matches!(
2116 connect_txn_stream.try_next(),
2117 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2118 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2119 }
2120 );
2121
2122 let bss_description =
2123 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
2124 let mut connect_txn_stream = sme.on_connect_command(connect_req(
2125 Ssid::try_from("wpa2").unwrap(),
2126 bss_description,
2127 authentication_wpa1_passphrase(),
2128 ));
2129 assert_matches!(
2130 connect_txn_stream.try_next(),
2131 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2132 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2133 }
2134 );
2135
2136 let bss_description =
2137 fake_fidl_bss_description!(Wpa3, ssid: Ssid::try_from("wpa3").unwrap());
2138 let mut connect_txn_stream = sme.on_connect_command(connect_req(
2139 Ssid::try_from("wpa3").unwrap(),
2140 bss_description,
2141 authentication_wpa2_personal_passphrase(),
2142 ));
2143 assert_matches!(
2144 connect_txn_stream.try_next(),
2145 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2146 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2147 }
2148 );
2149 }
2150
2151 #[fuchsia::test(allow_stalls = false, logging = false)]
2153 async fn connecting_right_credential_type_but_short_password() {
2154 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
2155
2156 let bss_description =
2157 fake_fidl_bss_description!(Wpa2, ssid: Ssid::try_from("foo").unwrap());
2158 let mut connect_txn_stream = sme.on_connect_command(connect_req(
2159 Ssid::try_from("foo").unwrap(),
2160 bss_description.clone(),
2161 fidl_internal::Authentication {
2162 protocol: fidl_internal::Protocol::Wpa2Personal,
2163 credentials: Some(Box::new(fidl_internal::Credentials::Wpa(
2164 fidl_internal::WpaCredentials::Passphrase(b"nope".as_slice().into()),
2165 ))),
2166 },
2167 ));
2168 report_fake_scan_result(
2169 &mut sme,
2170 zx::MonotonicInstant::get().into_nanos(),
2171 bss_description,
2172 );
2173
2174 assert_matches!(
2175 connect_txn_stream.try_next(),
2176 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2177 assert_eq!(result, SelectNetworkFailure::IncompatibleConnectRequest.into());
2178 }
2179 );
2180 }
2181
2182 #[fuchsia::test(allow_stalls = false, logging = false)]
2184 async fn new_connect_attempt_cancels_pending_connect() {
2185 let (mut sme, _mlme_stream, _time_stream) = create_sme().await;
2186
2187 let bss_description =
2188 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2189 let req = connect_req(
2190 Ssid::try_from("foo").unwrap(),
2191 bss_description.clone(),
2192 authentication_open(),
2193 );
2194 let mut connect_txn_stream1 = sme.on_connect_command(req);
2195
2196 let req2 = connect_req(
2197 Ssid::try_from("foo").unwrap(),
2198 bss_description.clone(),
2199 authentication_open(),
2200 );
2201 let mut connect_txn_stream2 = sme.on_connect_command(req2);
2202
2203 assert_matches!(
2205 connect_txn_stream1.try_next(),
2206 Ok(Some(ConnectTransactionEvent::OnConnectResult {
2207 result: ConnectResult::Canceled,
2208 is_reconnect: false
2209 }))
2210 );
2211
2212 report_fake_scan_result(
2215 &mut sme,
2216 zx::MonotonicInstant::get().into_nanos(),
2217 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap()),
2218 );
2219
2220 let req3 = connect_req(
2221 Ssid::try_from("foo").unwrap(),
2222 bss_description.clone(),
2223 authentication_open(),
2224 );
2225 let mut _connect_fut3 = sme.on_connect_command(req3);
2226
2227 assert_matches!(
2229 connect_txn_stream2.try_next(),
2230 Ok(Some(ConnectTransactionEvent::OnConnectResult {
2231 result: ConnectResult::Canceled,
2232 is_reconnect: false
2233 }))
2234 );
2235 }
2236
2237 #[fuchsia::test(allow_stalls = false)]
2238 async fn test_simple_scan_error() {
2239 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2240 let mut recv =
2241 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {
2242 channels: vec![],
2243 }));
2244
2245 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2246 end: fidl_mlme::ScanEnd {
2247 txn_id: 1,
2248 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2249 },
2250 });
2251
2252 assert_eq!(
2253 recv.try_recv(),
2254 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2255 );
2256 }
2257
2258 #[fuchsia::test(allow_stalls = false)]
2259 async fn test_scan_error_after_some_results_returned() {
2260 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2261 let mut recv =
2262 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {
2263 channels: vec![],
2264 }));
2265
2266 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2267 bss.bssid = [3; 6];
2268 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2269 result: fidl_mlme::ScanResult {
2270 txn_id: 1,
2271 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2272 bss,
2273 },
2274 });
2275 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2276 bss.bssid = [4; 6];
2277 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
2278 result: fidl_mlme::ScanResult {
2279 txn_id: 1,
2280 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
2281 bss,
2282 },
2283 });
2284
2285 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
2286 end: fidl_mlme::ScanEnd {
2287 txn_id: 1,
2288 code: fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware,
2289 },
2290 });
2291
2292 assert_eq!(
2294 recv.try_recv(),
2295 Ok(Some(Err(fidl_mlme::ScanResultCode::CanceledByDriverOrFirmware)))
2296 );
2297 }
2298
2299 #[fuchsia::test(allow_stalls = false)]
2300 async fn test_scan_is_rejected_while_connecting() {
2301 let (mut sme, _mlme_strem, _time_stream) = create_sme().await;
2302
2303 let bss_description =
2305 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap());
2306 let _recv = sme.on_connect_command(connect_req(
2307 Ssid::try_from("foo").unwrap(),
2308 bss_description,
2309 authentication_open(),
2310 ));
2311 assert_matches!(sme.status(), ClientSmeStatus::Connecting(_));
2312
2313 let mut recv =
2315 sme.on_scan_command(fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {
2316 channels: vec![],
2317 }));
2318 assert_eq!(recv.try_recv(), Ok(Some(Err(fidl_mlme::ScanResultCode::ShouldWait))));
2319 }
2320
2321 #[fuchsia::test(allow_stalls = false)]
2322 async fn test_wmm_status_success() {
2323 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2324 let mut receiver = sme.wmm_status();
2325
2326 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2327
2328 let resp = fake_wmm_status_resp();
2329 #[allow(
2330 clippy::redundant_field_names,
2331 reason = "mass allow for https://fxbug.dev/381896734"
2332 )]
2333 sme.on_mlme_event(fidl_mlme::MlmeEvent::OnWmmStatusResp {
2334 status: zx::sys::ZX_OK,
2335 resp: resp,
2336 });
2337
2338 assert_eq!(receiver.try_recv(), Ok(Some(Ok(resp))));
2339 }
2340
2341 #[fuchsia::test(allow_stalls = false)]
2342 async fn test_wmm_status_failed() {
2343 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2344 let mut receiver = sme.wmm_status();
2345
2346 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::WmmStatusReq)));
2347 sme.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_ERR_IO));
2348 assert_eq!(receiver.try_recv(), Ok(Some(Err(zx::sys::ZX_ERR_IO))));
2349 }
2350
2351 #[fuchsia::test(allow_stalls = false)]
2352 async fn test_query_apf_packet_filter_support() {
2353 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2354 let mut _receiver = sme.query_apf_packet_filter_support();
2355 assert_matches!(
2356 mlme_stream.try_next(),
2357 Ok(Some(MlmeRequest::QueryApfPacketFilterSupport(..)))
2358 );
2359 }
2360
2361 #[fuchsia::test(allow_stalls = false)]
2362 async fn test_install_apf_packet_filter() {
2363 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2364 let program = vec![1, 2, 3];
2365 let mut _receiver = sme.install_apf_packet_filter(program.clone());
2366 let req = assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::InstallApfPacketFilter(req, ..))) => req);
2367 assert_eq!(req.program, program);
2368 }
2369
2370 #[fuchsia::test(allow_stalls = false)]
2371 async fn test_read_apf_packet_filter_data() {
2372 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2373 let mut _receiver = sme.read_apf_packet_filter_data();
2374 assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::ReadApfPacketFilterData(..))));
2375 }
2376
2377 #[fuchsia::test(allow_stalls = false)]
2378 async fn test_set_apf_packet_filter_enabled() {
2379 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2380 let mut _receiver = sme.set_apf_packet_filter_enabled(true);
2381 let req = assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetApfPacketFilterEnabled(req, ..))) => req);
2382 assert!(req.enabled);
2383 }
2384
2385 #[fuchsia::test(allow_stalls = false)]
2386 async fn test_get_apf_packet_filter_enabled() {
2387 let (mut sme, mut mlme_stream, _time_stream) = create_sme().await;
2388 let mut _receiver = sme.get_apf_packet_filter_enabled();
2389 assert_matches!(
2390 mlme_stream.try_next(),
2391 Ok(Some(MlmeRequest::GetApfPacketFilterEnabled(..)))
2392 );
2393 }
2394
2395 fn assert_no_connect(mlme_stream: &mut mpsc::UnboundedReceiver<MlmeRequest>) {
2396 loop {
2397 match mlme_stream.try_next() {
2398 Ok(event) => match event {
2399 Some(MlmeRequest::Connect(..)) => {
2400 panic!("unexpected connect request sent to MLME")
2401 }
2402 None => break,
2403 _ => (),
2404 },
2405 Err(e) => {
2406 assert_eq!(e.to_string(), "receiver channel is empty");
2407 break;
2408 }
2409 }
2410 }
2411 }
2412
2413 fn connect_req(
2414 ssid: Ssid,
2415 bss_description: fidl_ieee80211::BssDescription,
2416 authentication: fidl_internal::Authentication,
2417 ) -> fidl_sme::ConnectRequest {
2418 fidl_sme::ConnectRequest {
2419 ssid: ssid.to_vec(),
2420 bss_description,
2421 multiple_bss_candidates: true,
2422 authentication,
2423 deprecated_scan_type: fidl_common::ScanType::Passive,
2424 }
2425 }
2426
2427 async fn create_sme() -> (ClientSme, MlmeStream, timer::EventStream<Event>) {
2434 let inspector = finspect::Inspector::default();
2435 let sme_root_node = inspector.root().create_child("sme");
2436 let (client_sme, _mlme_sink, mlme_stream, time_stream) = ClientSme::new(
2437 ClientConfig::default(),
2438 test_utils::fake_device_info(*CLIENT_ADDR),
2439 inspector,
2440 sme_root_node,
2441 fake_security_support(),
2442 fake_spectrum_management_support_empty(),
2443 );
2444 (client_sme, mlme_stream, time_stream)
2445 }
2446}