1mod link_state;
6
7use crate::client::event::{self, Event};
8use crate::client::internal::Context;
9use crate::client::protection::{build_protection_ie, Protection, ProtectionIe, SecurityContext};
10use crate::client::{
11 report_connect_finished, report_roam_finished, AssociationFailure, ClientConfig,
12 ClientSmeStatus, ConnectResult, ConnectTransactionEvent, ConnectTransactionSink,
13 EstablishRsnaFailure, EstablishRsnaFailureReason, RoamFailure, RoamFailureType, RoamResult,
14 ServingApInfo,
15};
16use crate::{mlme_event_name, MlmeRequest, MlmeSink};
17use anyhow::bail;
18use fidl_fuchsia_wlan_common_security::Authentication;
19use fidl_fuchsia_wlan_mlme::{self as fidl_mlme, MlmeEvent};
20use fuchsia_inspect_contrib::inspect_log;
21use fuchsia_inspect_contrib::log::InspectBytes;
22use ieee80211::{Bssid, MacAddr, MacAddrBytes, Ssid};
23use link_state::LinkState;
24use log::{error, info, warn};
25use wlan_common::bss::BssDescription;
26use wlan_common::ie::rsn::cipher;
27use wlan_common::ie::rsn::suite_selector::OUI;
28use wlan_common::ie::{self};
29use wlan_common::security::wep::WepKey;
30use wlan_common::security::SecurityAuthenticator;
31use wlan_common::timer::EventHandle;
32use wlan_rsn::auth;
33use wlan_rsn::rsna::{AuthRejectedReason, AuthStatus, SecAssocUpdate, UpdateSink};
34use wlan_statemachine::*;
35use {
36 fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
37 fidl_fuchsia_wlan_internal as fidl_internal, fidl_fuchsia_wlan_sme as fidl_sme,
38};
39
40const DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT: u32 = 60; const MAX_REASSOCIATIONS_WITHOUT_LINK_UP: u32 = 5;
48
49const IDLE_STATE: &str = "IdleState";
50const DISCONNECTING_STATE: &str = "DisconnectingState";
51const CONNECTING_STATE: &str = "ConnectingState";
52const ROAMING_STATE: &str = "RoamingState";
53const RSNA_STATE: &str = "EstablishingRsnaState";
54const LINK_UP_STATE: &str = "LinkUpState";
55
56#[derive(Debug)]
57pub struct ConnectCommand {
58 pub bss: Box<BssDescription>,
59 pub connect_txn_sink: ConnectTransactionSink,
60 pub protection: Protection,
61 pub authentication: Authentication,
62}
63
64#[derive(Debug)]
65pub struct Idle {
66 cfg: ClientConfig,
67}
68
69#[derive(Debug)]
70pub struct Connecting {
71 cfg: ClientConfig,
72 cmd: ConnectCommand,
73 protection_ie: Option<ProtectionIe>,
74 reassociation_loop_count: u32,
75}
76
77#[derive(Debug)]
80enum RoamInitiator {
81 RoamStartInd,
82 RoamRequest,
83}
84
85impl From<RoamInitiator> for fidl_sme::DisconnectMlmeEventName {
86 fn from(roam_initiator: RoamInitiator) -> fidl_sme::DisconnectMlmeEventName {
87 match roam_initiator {
88 RoamInitiator::RoamStartInd => fidl_sme::DisconnectMlmeEventName::RoamStartIndication,
89 RoamInitiator::RoamRequest => fidl_sme::DisconnectMlmeEventName::RoamRequest,
90 }
91 }
92}
93
94#[derive(Debug)]
95pub struct Associated {
96 cfg: ClientConfig,
97 connect_txn_sink: ConnectTransactionSink,
98 latest_ap_state: Box<BssDescription>,
99 auth_method: Option<auth::MethodName>,
100 last_signal_report_time: zx::MonotonicInstant,
101 link_state: LinkState,
102 protection_ie: Option<ProtectionIe>,
103 wmm_param: Option<ie::WmmParam>,
105 last_channel_switch_time: Option<zx::MonotonicInstant>,
106 reassociation_loop_count: u32,
107 authentication: Authentication,
108 roam_in_progress: Option<RoamInitiator>,
110}
111
112#[derive(Debug)]
113pub struct Roaming {
114 cfg: ClientConfig,
115 cmd: ConnectCommand,
116 auth_method: Option<auth::MethodName>,
117 protection_ie: Option<ProtectionIe>,
118}
119
120#[derive(Debug)]
121pub struct Disconnecting {
122 cfg: ClientConfig,
123 action: PostDisconnectAction,
124 _timeout: Option<EventHandle>,
125}
126
127statemachine!(
128 #[derive(Debug)]
129 pub enum ClientState,
130 () => Idle,
131 Idle => Connecting,
132 Connecting => [Associated, Disconnecting, Idle],
133 Associated => [Connecting, Roaming, Disconnecting, Idle],
135 Roaming => [Associated, Disconnecting, Idle],
136 Disconnecting => [Connecting, Idle],
139);
140
141#[allow(clippy::large_enum_variant)] enum PostDisconnectAction {
150 ReportConnectFinished { sink: ConnectTransactionSink, result: ConnectResult },
151 RespondDisconnect { responder: fidl_sme::ClientSmeDisconnectResponder },
152 BeginConnect { cmd: ConnectCommand },
153 ReportRoamFinished { sink: ConnectTransactionSink, result: RoamResult },
154 None,
155}
156
157enum AfterDisconnectState {
158 Idle(Idle),
159 Connecting(Connecting),
160}
161
162impl std::fmt::Debug for PostDisconnectAction {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
164 f.write_str("PostDisconnectAction::")?;
165 match self {
166 PostDisconnectAction::RespondDisconnect { .. } => f.write_str("RespondDisconnect"),
167 PostDisconnectAction::BeginConnect { .. } => f.write_str("BeginConnect"),
168 PostDisconnectAction::ReportConnectFinished { .. } => {
169 f.write_str("ReportConnectFinished")
170 }
171 PostDisconnectAction::ReportRoamFinished { .. } => f.write_str("ReportRoamFinished"),
172 PostDisconnectAction::None => f.write_str("None"),
173 }?;
174 Ok(())
175 }
176}
177
178pub enum StateChangeContext {
180 Disconnect { msg: String, disconnect_source: fidl_sme::DisconnectSource },
181 Connect { msg: String, bssid: Bssid, ssid: Ssid },
182 Roam { msg: String, bssid: Bssid },
183 Msg(String),
184}
185
186trait StateChangeContextExt {
187 fn set_msg(&mut self, msg: String);
188}
189
190impl StateChangeContextExt for Option<StateChangeContext> {
191 fn set_msg(&mut self, msg: String) {
192 match self {
193 Some(ctx) => match ctx {
194 StateChangeContext::Disconnect { msg: ref mut inner, .. } => *inner = msg,
195 StateChangeContext::Connect { msg: ref mut inner, .. } => *inner = msg,
196 StateChangeContext::Roam { msg: ref mut inner, .. } => *inner = msg,
197 StateChangeContext::Msg(inner) => *inner = msg,
198 },
199 None => {
200 *self = Some(StateChangeContext::Msg(msg));
201 }
202 }
203 }
204}
205
206impl Idle {
207 fn on_connect(
208 self,
209 cmd: ConnectCommand,
210 state_change_ctx: &mut Option<StateChangeContext>,
211 context: &mut Context,
212 ) -> Result<Connecting, Idle> {
213 let protection_ie = match build_protection_ie(&cmd.protection) {
215 Ok(ie) => ie,
216 Err(e) => {
217 let msg = format!("Failed to build protection IEs: {}", e);
218 error!("{}", msg);
219 let _ = state_change_ctx.replace(StateChangeContext::Connect {
220 msg,
221 bssid: cmd.bss.bssid,
222 ssid: cmd.bss.ssid,
223 });
224 return Err(self);
225 }
226 };
227 let (auth_type, sae_password, wep_key) = match &cmd.protection {
228 Protection::Rsna(rsna) => match rsna.supplicant.get_auth_cfg() {
229 auth::Config::Sae { .. } => (fidl_mlme::AuthenticationTypes::Sae, vec![], None),
230 auth::Config::DriverSae { password } => {
231 (fidl_mlme::AuthenticationTypes::Sae, password.clone(), None)
232 }
233 auth::Config::ComputedPsk(_) => {
234 (fidl_mlme::AuthenticationTypes::OpenSystem, vec![], None)
235 }
236 },
237 Protection::Wep(ref key) => {
238 let wep_key = build_wep_set_key_descriptor(cmd.bss.bssid, key);
239 inspect_log!(context.inspect.rsn_events.lock(), {
240 derived_key: "WEP",
241 cipher: format!("{:?}", cipher::Cipher::new_dot11(wep_key.cipher_suite_type.into_primitive() as u8)),
242 key_index: wep_key.key_id,
243 });
244 (fidl_mlme::AuthenticationTypes::SharedKey, vec![], Some(wep_key))
245 }
246 _ => (fidl_mlme::AuthenticationTypes::OpenSystem, vec![], None),
247 };
248 let security_ie = match protection_ie.as_ref() {
249 Some(ProtectionIe::Rsne(v)) => v.to_vec(),
250 Some(ProtectionIe::VendorIes(v)) => v.to_vec(),
251 None => vec![],
252 };
253 context.mlme_sink.send(MlmeRequest::Connect(fidl_mlme::ConnectRequest {
254 selected_bss: (*cmd.bss).clone().into(),
255 connect_failure_timeout: DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT,
256 auth_type,
257 sae_password,
258 wep_key: wep_key.map(Box::new),
259 security_ie,
260 }));
261 context.att_id += 1;
262
263 let msg = connect_cmd_inspect_summary(&cmd);
264 let _ = state_change_ctx.replace(StateChangeContext::Connect {
265 msg,
266 bssid: cmd.bss.bssid,
267 ssid: cmd.bss.ssid.clone(),
268 });
269
270 Ok(Connecting { cfg: self.cfg, cmd, protection_ie, reassociation_loop_count: 0 })
271 }
272
273 fn on_disconnect_complete(
274 self,
275 context: &mut Context,
276 action: PostDisconnectAction,
277 state_change_ctx: &mut Option<StateChangeContext>,
278 ) -> AfterDisconnectState {
279 match action {
280 PostDisconnectAction::RespondDisconnect { responder } => {
281 if let Err(e) = responder.send() {
282 error!("Failed to send disconnect response: {}", e);
283 }
284 AfterDisconnectState::Idle(self)
285 }
286 PostDisconnectAction::BeginConnect { cmd } => {
287 match self.on_connect(cmd, state_change_ctx, context) {
288 Ok(connecting) => AfterDisconnectState::Connecting(connecting),
289 Err(idle) => AfterDisconnectState::Idle(idle),
290 }
291 }
292 PostDisconnectAction::ReportConnectFinished { mut sink, result } => {
293 sink.send_connect_result(result);
294 AfterDisconnectState::Idle(self)
295 }
296 PostDisconnectAction::ReportRoamFinished { mut sink, result } => {
297 sink.send_roam_result(result);
298 AfterDisconnectState::Idle(self)
299 }
300 PostDisconnectAction::None => AfterDisconnectState::Idle(self),
301 }
302 }
303}
304
305fn parse_wmm_from_ies(ies: &[u8]) -> Option<ie::WmmParam> {
306 let mut wmm_param = None;
307 for (id, body) in ie::Reader::new(ies) {
308 if id == ie::Id::VENDOR_SPECIFIC {
309 if let Ok(ie::VendorIe::WmmParam(wmm_param_body)) = ie::parse_vendor_ie(body) {
310 match ie::parse_wmm_param(wmm_param_body) {
311 Ok(param) => wmm_param = Some(*param),
312 Err(e) => {
313 warn!(
314 "Fail parsing IEs for WMM param. Bytes: {:?}. Error: {}",
315 wmm_param_body, e
316 );
317 }
318 }
319 }
320 }
321 }
322 wmm_param
323}
324
325impl Connecting {
326 #[allow(clippy::result_large_err)] fn on_connect_conf(
328 mut self,
329 conf: fidl_mlme::ConnectConfirm,
330 state_change_ctx: &mut Option<StateChangeContext>,
331 context: &mut Context,
332 ) -> Result<Associated, Disconnecting> {
333 let auth_method = self.cmd.protection.rsn_auth_method();
334 let wmm_param = parse_wmm_from_ies(&conf.association_ies);
335
336 let link_state = match conf.result_code {
337 fidl_ieee80211::StatusCode::Success => {
338 match LinkState::new(self.cmd.protection, context) {
340 Ok(link_state) => link_state,
341 Err(failure_reason) => {
342 let msg = "Connect terminated; failed to initialize LinkState".to_string();
343 error!("{}", msg);
344 state_change_ctx.set_msg(msg);
345 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
346 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
347 return Err(Disconnecting {
348 cfg: self.cfg,
349 action: PostDisconnectAction::ReportConnectFinished {
350 sink: self.cmd.connect_txn_sink,
351 result: EstablishRsnaFailure {
352 auth_method,
353 reason: failure_reason,
354 }
355 .into(),
356 },
357 _timeout: Some(timeout),
358 });
359 }
360 }
361 }
362 other => {
363 let msg = format!("Connect request failed: {:?}", other);
364 warn!("{}", msg);
365 state_change_ctx.set_msg(msg);
366 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
367 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
368 return Err(Disconnecting {
369 cfg: self.cfg,
370 action: PostDisconnectAction::ReportConnectFinished {
371 sink: self.cmd.connect_txn_sink,
372 result: ConnectResult::Failed(
373 AssociationFailure {
374 bss_protection: self.cmd.bss.protection(),
375 code: other,
376 }
377 .into(),
378 ),
379 },
380 _timeout: Some(timeout),
381 });
382 }
383 };
384 state_change_ctx.set_msg("Connect succeeded".to_string());
385
386 if let LinkState::LinkUp(_) = link_state {
387 report_connect_finished(&mut self.cmd.connect_txn_sink, ConnectResult::Success);
388 self.reassociation_loop_count = 0;
389 }
390
391 Ok(Associated {
392 cfg: self.cfg,
393 connect_txn_sink: self.cmd.connect_txn_sink,
394 auth_method,
395 last_signal_report_time: now(),
396 latest_ap_state: self.cmd.bss,
397 link_state,
398 protection_ie: self.protection_ie,
399 wmm_param,
400 last_channel_switch_time: None,
401 reassociation_loop_count: self.reassociation_loop_count,
402 authentication: self.cmd.authentication,
403 roam_in_progress: None,
404 })
405 }
406
407 fn on_deauthenticate_ind(
408 mut self,
409 ind: fidl_mlme::DeauthenticateIndication,
410 state_change_ctx: &mut Option<StateChangeContext>,
411 ) -> Idle {
412 let msg = format!("Association request failed due to spurious deauthentication; reason code: {:?}, locally_initiated: {:?}",
413 ind.reason_code, ind.locally_initiated);
414 warn!("{}", msg);
415 report_connect_finished(
418 &mut self.cmd.connect_txn_sink,
419 ConnectResult::Failed(
420 AssociationFailure {
421 bss_protection: self.cmd.bss.protection(),
422 code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
423 }
424 .into(),
425 ),
426 );
427 state_change_ctx.set_msg(msg);
428 Idle { cfg: self.cfg }
429 }
430
431 fn on_disassociate_ind(
432 self,
433 ind: fidl_mlme::DisassociateIndication,
434 state_change_ctx: &mut Option<StateChangeContext>,
435 context: &mut Context,
436 ) -> Disconnecting {
437 let msg = format!("Association request failed due to spurious disassociation; reason code: {:?}, locally_initiated: {:?}",
438 ind.reason_code, ind.locally_initiated);
439 warn!("{}", msg);
440 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
441 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
442 state_change_ctx.set_msg(msg);
443 Disconnecting {
444 cfg: self.cfg,
445 action: PostDisconnectAction::ReportConnectFinished {
446 sink: self.cmd.connect_txn_sink,
447 result: ConnectResult::Failed(
448 AssociationFailure {
449 bss_protection: self.cmd.bss.protection(),
450 code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
451 }
452 .into(),
453 ),
454 },
455 _timeout: Some(timeout),
456 }
457 }
458
459 fn on_sae_handshake_ind(
462 &mut self,
463 ind: fidl_mlme::SaeHandshakeIndication,
464 context: &mut Context,
465 ) -> Result<(), anyhow::Error> {
466 process_sae_handshake_ind(&mut self.cmd.protection, ind, context)
467 }
468
469 fn on_sae_frame_rx(
470 &mut self,
471 frame: fidl_mlme::SaeFrame,
472 context: &mut Context,
473 ) -> Result<(), anyhow::Error> {
474 process_sae_frame_rx(&mut self.cmd.protection, frame, context)
475 }
476
477 #[allow(clippy::result_large_err)] fn handle_timeout(
479 mut self,
480 event: Event,
481 state_change_ctx: &mut Option<StateChangeContext>,
482 context: &mut Context,
483 ) -> Result<Self, Disconnecting> {
484 match process_sae_timeout(&mut self.cmd.protection, self.cmd.bss.bssid, event, context) {
485 Ok(()) => Ok(self),
486 Err(e) => {
487 let msg = format!("failed to handle SAE timeout: {:?}", e);
490 error!("{}", msg);
491 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
492 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
493 state_change_ctx.set_msg(msg);
494 Err(Disconnecting {
495 cfg: self.cfg,
496 action: PostDisconnectAction::ReportConnectFinished {
497 sink: self.cmd.connect_txn_sink,
498 result: ConnectResult::Failed(
499 AssociationFailure {
500 bss_protection: self.cmd.bss.protection(),
501 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
502 }
503 .into(),
504 ),
505 },
506 _timeout: Some(timeout),
507 })
508 }
509 }
510 }
511
512 fn disconnect(mut self, context: &mut Context, action: PostDisconnectAction) -> Disconnecting {
513 report_connect_finished(&mut self.cmd.connect_txn_sink, ConnectResult::Canceled);
514 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
515 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
516 Disconnecting { cfg: self.cfg, action, _timeout: Some(timeout) }
517 }
518}
519
520impl Associated {
521 #[allow(clippy::result_large_err)] fn on_disassociate_ind(
523 mut self,
524 ind: fidl_mlme::DisassociateIndication,
525 state_change_ctx: &mut Option<StateChangeContext>,
526 context: &mut Context,
527 ) -> Result<Connecting, Disconnecting> {
528 let (mut protection, connected_duration) = self.link_state.disconnect();
529
530 let disconnect_reason = fidl_sme::DisconnectCause {
531 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DisassociateIndication,
532 reason_code: ind.reason_code,
533 };
534 let disconnect_source = if ind.locally_initiated {
535 fidl_sme::DisconnectSource::Mlme(disconnect_reason)
536 } else {
537 fidl_sme::DisconnectSource::Ap(disconnect_reason)
538 };
539
540 if self.reassociation_loop_count >= MAX_REASSOCIATIONS_WITHOUT_LINK_UP {
541 let fidl_disconnect_info =
543 fidl_sme::DisconnectInfo { is_sme_reconnecting: false, disconnect_source };
544 self.connect_txn_sink
545 .send(ConnectTransactionEvent::OnDisconnect { info: fidl_disconnect_info });
546 let msg = format!(
547 "received DisassociateInd msg; reason code {:?}. Too many retries, disconnecting.",
548 ind.reason_code
549 );
550 let _ =
551 state_change_ctx.replace(StateChangeContext::Disconnect { msg, disconnect_source });
552 send_deauthenticate_request(&self.latest_ap_state.bssid, &context.mlme_sink);
553 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
554 Err(Disconnecting {
555 cfg: self.cfg,
556 action: PostDisconnectAction::None,
557 _timeout: Some(timeout),
558 })
559 } else {
560 if connected_duration.is_some() {
561 let fidl_disconnect_info =
563 fidl_sme::DisconnectInfo { is_sme_reconnecting: true, disconnect_source };
564 self.connect_txn_sink
565 .send(ConnectTransactionEvent::OnDisconnect { info: fidl_disconnect_info });
566 }
567
568 let msg = format!("received DisassociateInd msg; reason code {:?}", ind.reason_code);
569 let _ = state_change_ctx.replace(match connected_duration {
570 Some(_) => StateChangeContext::Disconnect { msg, disconnect_source },
571 None => StateChangeContext::Msg(msg),
572 });
573
574 if let Protection::Rsna(rsna) = &mut protection {
576 rsna.supplicant.reset();
578 }
579
580 context.att_id += 1;
581 let cmd = ConnectCommand {
582 bss: self.latest_ap_state,
583 connect_txn_sink: self.connect_txn_sink,
584 protection,
585 authentication: self.authentication,
586 };
587 let req = fidl_mlme::ReconnectRequest { peer_sta_address: cmd.bss.bssid.to_array() };
588 context.mlme_sink.send(MlmeRequest::Reconnect(req));
589 Ok(Connecting {
590 cfg: self.cfg,
591 cmd,
592 protection_ie: self.protection_ie,
593 reassociation_loop_count: self.reassociation_loop_count + 1,
594 })
595 }
596 }
597
598 fn on_deauthenticate_ind(
599 mut self,
600 ind: fidl_mlme::DeauthenticateIndication,
601 state_change_ctx: &mut Option<StateChangeContext>,
602 ) -> Idle {
603 let (_, connected_duration) = self.link_state.disconnect();
604
605 let disconnect_reason = fidl_sme::DisconnectCause {
606 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
607 reason_code: ind.reason_code,
608 };
609 let disconnect_source = if ind.locally_initiated {
610 fidl_sme::DisconnectSource::Mlme(disconnect_reason)
611 } else {
612 fidl_sme::DisconnectSource::Ap(disconnect_reason)
613 };
614 let disconnect_info =
615 fidl_sme::DisconnectInfo { is_sme_reconnecting: false, disconnect_source };
616
617 match connected_duration {
618 Some(_duration) => {
619 self.connect_txn_sink
620 .send(ConnectTransactionEvent::OnDisconnect { info: disconnect_info });
621 }
622 None => match self.roam_in_progress {
623 Some(_) => {
624 report_roam_finished(
625 &mut self.connect_txn_sink,
626 RoamFailure {
627 failure_type: RoamFailureType::EstablishRsnaFailure,
628 selected_bss: Some(*self.latest_ap_state.clone()),
629 disconnect_info,
630 selected_bssid: self.latest_ap_state.bssid,
631 auth_method: self.auth_method,
632 status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
633 establish_rsna_failure_reason: Some(
634 EstablishRsnaFailureReason::InternalError,
635 ),
636 }
637 .into(),
638 );
639 }
640 _ => {
641 report_connect_finished(
642 &mut self.connect_txn_sink,
643 EstablishRsnaFailure {
644 auth_method: self.auth_method,
645 reason: EstablishRsnaFailureReason::InternalError,
646 }
647 .into(),
648 );
649 }
650 },
651 }
652
653 let _ = state_change_ctx.replace(StateChangeContext::Disconnect {
654 msg: format!("received DeauthenticateInd msg; reason code {:?}", ind.reason_code),
655 disconnect_source,
656 });
657 Idle { cfg: self.cfg }
658 }
659
660 #[allow(clippy::result_large_err)] fn process_link_state_update<U, H>(
662 mut self,
663 update: U,
664 update_handler: H,
665 context: &mut Context,
666 state_change_ctx: &mut Option<StateChangeContext>,
667 ) -> Result<Self, Disconnecting>
668 where
669 H: Fn(
670 LinkState,
671 U,
672 &BssDescription,
673 &mut Option<StateChangeContext>,
674 &mut Context,
675 ) -> Result<LinkState, EstablishRsnaFailureReason>,
676 {
677 let was_establishing_rsna = match &self.link_state {
678 LinkState::EstablishingRsna(_) => true,
679 LinkState::Init(_) | LinkState::LinkUp(_) => false,
680 };
681
682 let link_state = match update_handler(
683 self.link_state,
684 update,
685 &self.latest_ap_state,
686 state_change_ctx,
687 context,
688 ) {
689 Ok(link_state) => link_state,
690 Err(failure_reason) => {
691 send_deauthenticate_request(&self.latest_ap_state.bssid, &context.mlme_sink);
692
693 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
694 match self.roam_in_progress {
695 Some(roam_initiator) => {
696 report_roam_finished(
697 &mut self.connect_txn_sink,
698 RoamFailure {
699 failure_type: RoamFailureType::EstablishRsnaFailure,
700 selected_bssid: self.latest_ap_state.bssid,
701 status_code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
702 disconnect_info: make_roam_disconnect_info(
703 roam_initiator.into(),
704 None,
705 ),
706 auth_method: self.auth_method,
707 selected_bss: Some(*self.latest_ap_state),
708 establish_rsna_failure_reason: Some(failure_reason),
709 }
710 .into(),
711 );
712 }
713 _ => {
714 report_connect_finished(
715 &mut self.connect_txn_sink,
716 EstablishRsnaFailure {
717 auth_method: self.auth_method,
718 reason: failure_reason,
719 }
720 .into(),
721 );
722 }
723 }
724 return Err(Disconnecting {
725 cfg: self.cfg,
726 action: PostDisconnectAction::None,
727 _timeout: Some(timeout),
728 });
729 }
730 };
731
732 if let LinkState::LinkUp(_) = link_state {
733 if was_establishing_rsna {
734 match self.roam_in_progress {
735 Some(_roam_initiator) => {
736 report_roam_finished(
737 &mut self.connect_txn_sink,
738 RoamResult::Success(Box::new(*self.latest_ap_state.clone())),
739 );
740 self.roam_in_progress = None;
741 }
742 _ => {
743 report_connect_finished(&mut self.connect_txn_sink, ConnectResult::Success);
744 }
745 }
746 self.reassociation_loop_count = 0;
747 }
748 }
749
750 Ok(Self { link_state, ..self })
751 }
752
753 #[allow(clippy::result_large_err)] fn on_eapol_ind(
755 self,
756 ind: fidl_mlme::EapolIndication,
757 state_change_ctx: &mut Option<StateChangeContext>,
758 context: &mut Context,
759 ) -> Result<Self, Disconnecting> {
760 if !self.latest_ap_state.needs_eapol_exchange() {
762 return Ok(self);
763 }
764
765 if &ind.src_addr != self.latest_ap_state.bssid.as_array() {
767 let eapol_pdu = &ind.data[..];
768 inspect_log!(context.inspect.rsn_events.lock(), {
769 rx_eapol_frame: InspectBytes(&eapol_pdu),
770 foreign_bssid: MacAddr::from(ind.src_addr).to_string(),
771 current_bssid: self.latest_ap_state.bssid.to_string(),
772 status: "rejected (foreign BSS)",
773 });
774 return Ok(self);
775 }
776
777 self.process_link_state_update(ind, LinkState::on_eapol_ind, context, state_change_ctx)
778 }
779
780 #[allow(clippy::result_large_err)] fn on_eapol_conf(
782 self,
783 resp: fidl_mlme::EapolConfirm,
784 state_change_ctx: &mut Option<StateChangeContext>,
785 context: &mut Context,
786 ) -> Result<Self, Disconnecting> {
787 self.process_link_state_update(resp, LinkState::on_eapol_conf, context, state_change_ctx)
788 }
789
790 #[allow(clippy::result_large_err)] fn on_set_keys_conf(
792 self,
793 conf: fidl_mlme::SetKeysConfirm,
794 state_change_ctx: &mut Option<StateChangeContext>,
795 context: &mut Context,
796 ) -> Result<Self, Disconnecting> {
797 self.process_link_state_update(conf, LinkState::on_set_keys_conf, context, state_change_ctx)
798 }
799
800 fn on_channel_switched(&mut self, info: fidl_internal::ChannelSwitchInfo) {
801 self.connect_txn_sink.send(ConnectTransactionEvent::OnChannelSwitched { info });
802 self.latest_ap_state.channel.primary = info.new_channel;
803 self.last_channel_switch_time = Some(now());
804 }
805
806 fn on_wmm_status_resp(
807 &mut self,
808 status: zx::sys::zx_status_t,
809 resp: fidl_internal::WmmStatusResponse,
810 ) {
811 if status == zx::sys::ZX_OK {
812 let wmm_param = self.wmm_param.get_or_insert_default();
813 let mut wmm_info = wmm_param.wmm_info.ap_wmm_info();
814 wmm_info.set_uapsd(resp.apsd);
815 wmm_param.wmm_info.0 = wmm_info.0;
816 update_wmm_ac_param(&mut wmm_param.ac_be_params, &resp.ac_be_params);
817 update_wmm_ac_param(&mut wmm_param.ac_bk_params, &resp.ac_bk_params);
818 update_wmm_ac_param(&mut wmm_param.ac_vo_params, &resp.ac_vo_params);
819 update_wmm_ac_param(&mut wmm_param.ac_vi_params, &resp.ac_vi_params);
820 }
821 }
822
823 #[allow(clippy::result_large_err)] fn handle_timeout(
825 mut self,
826 event: Event,
827 state_change_ctx: &mut Option<StateChangeContext>,
828 context: &mut Context,
829 ) -> Result<Self, Disconnecting> {
830 match self.link_state.handle_timeout(event, state_change_ctx, context) {
831 Ok(link_state) => Ok(Associated { link_state, ..self }),
832 Err(failure_reason) => {
833 send_deauthenticate_request(&self.latest_ap_state.bssid, &context.mlme_sink);
834 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
835 match self.roam_in_progress {
836 Some(roam_initiator) => {
837 report_roam_finished(
838 &mut self.connect_txn_sink,
839 RoamFailure {
840 failure_type: RoamFailureType::EstablishRsnaFailure,
841 selected_bssid: self.latest_ap_state.bssid,
842 status_code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
843 disconnect_info: make_roam_disconnect_info(
844 roam_initiator.into(),
845 Some(fidl_ieee80211::ReasonCode::Timeout),
846 ),
847 auth_method: self.auth_method,
848 selected_bss: Some(*self.latest_ap_state),
849 establish_rsna_failure_reason: Some(failure_reason),
850 }
851 .into(),
852 );
853 }
854 _ => {
855 report_connect_finished(
856 &mut self.connect_txn_sink,
857 EstablishRsnaFailure {
858 auth_method: self.auth_method,
859 reason: failure_reason,
860 }
861 .into(),
862 );
863 }
864 }
865
866 Err(Disconnecting {
867 cfg: self.cfg,
868 action: PostDisconnectAction::None,
869 _timeout: Some(timeout),
870 })
871 }
872 }
873 }
874
875 fn disconnect(self, context: &mut Context, action: PostDisconnectAction) -> Disconnecting {
876 send_deauthenticate_request(&self.latest_ap_state.bssid, &context.mlme_sink);
877 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
878 Disconnecting { cfg: self.cfg, action, _timeout: Some(timeout) }
879 }
880}
881
882#[allow(clippy::large_enum_variant)] enum AfterRoamFailureState {
885 Idle(Idle),
886 Disconnecting(Disconnecting),
887}
888
889impl Roaming {
890 #[allow(clippy::result_large_err)] fn on_disassociate_ind(
896 self,
897 ind: fidl_mlme::DisassociateIndication,
898 state_change_ctx: &mut Option<StateChangeContext>,
899 context: &mut Context,
900 ) -> Result<Roaming, Disconnecting> {
901 let peer_sta_address: Bssid = ind.peer_sta_address.into();
902 if peer_sta_address != self.cmd.bss.bssid {
903 return Ok(self);
904 }
905
906 let disconnect_info = make_roam_disconnect_info(
907 fidl_sme::DisconnectMlmeEventName::DisassociateIndication,
908 Some(ind.reason_code),
909 );
910
911 let failure = RoamFailure {
912 failure_type: RoamFailureType::ReassociationFailure,
913 selected_bss: Some(*self.cmd.bss.clone()),
914 disconnect_info,
915 selected_bssid: self.cmd.bss.bssid,
916 auth_method: self.auth_method,
917 status_code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
918 establish_rsna_failure_reason: None,
919 };
920 let msg = format!("Roam failed due to disassociation, reason_code: {:?}", ind.reason_code);
921 Err(Self::to_disconnecting(
922 msg,
923 failure,
924 self.cfg,
925 self.cmd.connect_txn_sink,
926 state_change_ctx,
927 context,
928 ))
929 }
930
931 fn on_deauthenticate_ind(
934 self,
935 ind: fidl_mlme::DeauthenticateIndication,
936 state_change_ctx: &mut Option<StateChangeContext>,
937 ) -> Result<Roaming, Idle> {
938 let peer_sta_address: Bssid = ind.peer_sta_address.into();
939 if peer_sta_address != self.cmd.bss.bssid {
940 return Ok(self);
941 }
942
943 let disconnect_info = make_roam_disconnect_info(
944 fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
945 Some(ind.reason_code),
946 );
947
948 let failure = RoamFailure {
949 failure_type: RoamFailureType::ReassociationFailure,
950 selected_bss: Some(*self.cmd.bss.clone()),
951 disconnect_info,
952 selected_bssid: self.cmd.bss.bssid,
953 auth_method: self.auth_method,
954 status_code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
955 establish_rsna_failure_reason: None,
956 };
957 let msg =
958 format!("Roam failed due to deauthentication, reason_code: {:?}", ind.reason_code);
959 Err(self.to_idle(msg, failure, state_change_ctx))
960 }
961
962 fn on_sae_handshake_ind(
963 &mut self,
964 ind: fidl_mlme::SaeHandshakeIndication,
965 context: &mut Context,
966 ) -> Result<(), anyhow::Error> {
967 process_sae_handshake_ind(&mut self.cmd.protection, ind, context)
968 }
969
970 fn on_sae_frame_rx(
971 &mut self,
972 frame: fidl_mlme::SaeFrame,
973 context: &mut Context,
974 ) -> Result<(), anyhow::Error> {
975 process_sae_frame_rx(&mut self.cmd.protection, frame, context)
976 }
977
978 fn handle_timeout(
979 mut self,
980 event: Event,
981 state_change_ctx: &mut Option<StateChangeContext>,
982 context: &mut Context,
983 ) -> Result<Self, Idle> {
984 match process_sae_timeout(&mut self.cmd.protection, self.cmd.bss.bssid, event, context) {
985 Ok(()) => Ok(self),
986 Err(e) => {
987 let msg = format!("failed to handle SAE timeout: {:?}", e);
990 let disconnect_info = make_roam_disconnect_info(
991 fidl_sme::DisconnectMlmeEventName::SaeHandshakeResponse,
992 None,
993 );
994 let failure = RoamFailure {
997 failure_type: RoamFailureType::ReassociationFailure,
998 selected_bss: Some(*self.cmd.bss.clone()),
999 disconnect_info,
1000 selected_bssid: self.cmd.bss.bssid,
1001 auth_method: self.auth_method,
1002 status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1003 establish_rsna_failure_reason: None,
1004 };
1005
1006 send_deauthenticate_request(&self.cmd.bss.bssid, &context.mlme_sink);
1009
1010 Err(self.to_idle(msg, failure, state_change_ctx))
1011 }
1012 }
1013 }
1014
1015 fn to_disconnecting(
1016 msg: String,
1018 failure: RoamFailure,
1019 cfg: ClientConfig,
1020 sink: ConnectTransactionSink,
1021 state_change_ctx: &mut Option<StateChangeContext>,
1022 context: &mut Context,
1023 ) -> Disconnecting {
1024 warn!("{}", msg);
1025 _ = state_change_ctx.replace(StateChangeContext::Disconnect {
1026 msg,
1027 disconnect_source: failure.disconnect_info.disconnect_source,
1028 });
1029
1030 send_deauthenticate_request(&failure.selected_bssid, &context.mlme_sink);
1031 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
1032
1033 Disconnecting {
1034 cfg,
1035 action: PostDisconnectAction::ReportRoamFinished { sink, result: failure.into() },
1036 _timeout: Some(timeout),
1037 }
1038 }
1039
1040 #[allow(clippy::wrong_self_convention, reason = "mass allow for https://fxbug.dev/381896734")]
1041 fn to_idle(
1042 mut self,
1043 msg: String,
1044 failure: RoamFailure,
1045 state_change_ctx: &mut Option<StateChangeContext>,
1046 ) -> Idle {
1047 warn!("{}", msg);
1048 _ = state_change_ctx.replace(StateChangeContext::Disconnect {
1049 msg,
1050 disconnect_source: failure.disconnect_info.disconnect_source,
1051 });
1052 report_roam_finished(&mut self.cmd.connect_txn_sink, failure.into());
1053 Idle { cfg: self.cfg }
1054 }
1055}
1056
1057impl Disconnecting {
1058 fn handle_deauthenticate_conf(
1059 self,
1060 _conf: fidl_mlme::DeauthenticateConfirm,
1061 state_change_ctx: &mut Option<StateChangeContext>,
1062 context: &mut Context,
1063 ) -> AfterDisconnectState {
1064 Idle { cfg: self.cfg }.on_disconnect_complete(context, self.action, state_change_ctx)
1065 }
1066
1067 #[allow(clippy::result_large_err)] fn handle_timeout(
1069 self,
1070 event: Event,
1071 state_change_ctx: &mut Option<StateChangeContext>,
1072 context: &mut Context,
1073 ) -> Result<Self, AfterDisconnectState> {
1074 if let Event::DeauthenticateTimeout(_) = event {
1075 let msg = "Completing disconnect without confirm due to disconnect timeout".to_string();
1076 error!("{}", msg);
1077 state_change_ctx.set_msg(msg);
1078 return Err(Idle { cfg: self.cfg }.on_disconnect_complete(
1079 context,
1080 self.action,
1081 state_change_ctx,
1082 ));
1083 }
1084 Ok(self)
1085 }
1086
1087 fn disconnect(self, action: PostDisconnectAction) -> Disconnecting {
1088 match self.action {
1092 PostDisconnectAction::RespondDisconnect { responder } => {
1093 if let Err(e) = responder.send() {
1094 error!("Failed to send disconnect response: {}", e);
1095 }
1096 }
1097 PostDisconnectAction::ReportConnectFinished { mut sink, result } => {
1098 report_connect_finished(&mut sink, result);
1099 }
1100 PostDisconnectAction::ReportRoamFinished { mut sink, result } => {
1101 report_roam_finished(&mut sink, result);
1102 }
1103 PostDisconnectAction::BeginConnect { mut cmd } => {
1104 report_connect_finished(&mut cmd.connect_txn_sink, ConnectResult::Canceled);
1105 }
1106 PostDisconnectAction::None => (),
1107 }
1108 Disconnecting { action, ..self }
1109 }
1110}
1111
1112impl ClientState {
1113 pub fn new(cfg: ClientConfig) -> Self {
1114 Self::from(State::new(Idle { cfg }))
1115 }
1116
1117 fn state_name(&self) -> &'static str {
1118 match self {
1119 Self::Idle(_) => IDLE_STATE,
1120 Self::Connecting(_) => CONNECTING_STATE,
1121 Self::Associated(state) => match state.link_state {
1122 LinkState::EstablishingRsna(_) => RSNA_STATE,
1123 LinkState::LinkUp(_) => LINK_UP_STATE,
1124 #[expect(clippy::unreachable)]
1127 _ => unreachable!(),
1128 },
1129 Self::Roaming(_) => ROAMING_STATE,
1130 Self::Disconnecting(_) => DISCONNECTING_STATE,
1131 }
1132 }
1133
1134 pub fn on_mlme_event(self, event: MlmeEvent, context: &mut Context) -> Self {
1135 let start_state = self.state_name();
1136 let mut state_change_ctx: Option<StateChangeContext> = None;
1137
1138 let new_state = match self {
1139 Self::Idle(_) => {
1140 match event {
1141 MlmeEvent::OnWmmStatusResp { .. } => (),
1142 MlmeEvent::DeauthenticateConf { resp } => {
1143 warn!(
1144 "Unexpected MLME message while Idle: {:?} for BSSID {:?}",
1145 mlme_event_name(&event),
1146 resp.peer_sta_address
1147 );
1148 }
1149 _ => warn!("Unexpected MLME message while Idle: {:?}", mlme_event_name(&event)),
1150 }
1151 self
1152 }
1153 Self::Connecting(state) => match event {
1154 MlmeEvent::ConnectConf { resp } => {
1155 let (transition, connecting) = state.release_data();
1156 match connecting.on_connect_conf(resp, &mut state_change_ctx, context) {
1157 Ok(associated) => transition.to(associated).into(),
1158 Err(disconnecting) => transition.to(disconnecting).into(),
1159 }
1160 }
1161 MlmeEvent::DeauthenticateInd { ind } => {
1162 let (transition, connecting) = state.release_data();
1163 let idle = connecting.on_deauthenticate_ind(ind, &mut state_change_ctx);
1164 transition.to(idle).into()
1165 }
1166 MlmeEvent::DisassociateInd { ind } => {
1167 let (transition, connecting) = state.release_data();
1168 let disconnecting =
1169 connecting.on_disassociate_ind(ind, &mut state_change_ctx, context);
1170 transition.to(disconnecting).into()
1171 }
1172 MlmeEvent::OnSaeHandshakeInd { ind } => {
1173 let (transition, mut connecting) = state.release_data();
1174 if let Err(e) = connecting.on_sae_handshake_ind(ind, context) {
1175 error!("Failed to process SaeHandshakeInd: {:?}", e);
1176 }
1177 transition.to(connecting).into()
1178 }
1179 MlmeEvent::OnSaeFrameRx { frame } => {
1180 let (transition, mut connecting) = state.release_data();
1181 if let Err(e) = connecting.on_sae_frame_rx(frame, context) {
1182 error!("Failed to process SaeFrameRx: {:?}", e);
1183 }
1184 transition.to(connecting).into()
1185 }
1186 _ => state.into(),
1187 },
1188 Self::Associated(mut state) => match event {
1189 MlmeEvent::DisassociateInd { ind } => {
1190 let (transition, associated) = state.release_data();
1191 match associated.on_disassociate_ind(ind, &mut state_change_ctx, context) {
1192 Ok(connecting) => transition.to(connecting).into(),
1193 Err(disconnecting) => transition.to(disconnecting).into(),
1194 }
1195 }
1196 MlmeEvent::DeauthenticateInd { ind } => {
1197 let (transition, associated) = state.release_data();
1198 let idle = associated.on_deauthenticate_ind(ind, &mut state_change_ctx);
1199 transition.to(idle).into()
1200 }
1201 MlmeEvent::SignalReport { ind } => {
1202 if matches!(state.link_state, LinkState::LinkUp(_)) {
1203 state
1204 .connect_txn_sink
1205 .send(ConnectTransactionEvent::OnSignalReport { ind });
1206 }
1207 state.latest_ap_state.rssi_dbm = ind.rssi_dbm;
1208 state.latest_ap_state.snr_db = ind.snr_db;
1209 state.last_signal_report_time = now();
1210 state.into()
1211 }
1212 MlmeEvent::EapolInd { ind } => {
1213 let (transition, associated) = state.release_data();
1214 match associated.on_eapol_ind(ind, &mut state_change_ctx, context) {
1215 Ok(associated) => transition.to(associated).into(),
1216 Err(disconnecting) => transition.to(disconnecting).into(),
1217 }
1218 }
1219 MlmeEvent::EapolConf { resp } => {
1220 let (transition, associated) = state.release_data();
1221 match associated.on_eapol_conf(resp, &mut state_change_ctx, context) {
1222 Ok(associated) => transition.to(associated).into(),
1223 Err(disconnecting) => transition.to(disconnecting).into(),
1224 }
1225 }
1226 MlmeEvent::SetKeysConf { conf } => {
1227 let (transition, associated) = state.release_data();
1228 match associated.on_set_keys_conf(conf, &mut state_change_ctx, context) {
1229 Ok(associated) => transition.to(associated).into(),
1230 Err(disconnecting) => transition.to(disconnecting).into(),
1231 }
1232 }
1233 MlmeEvent::OnChannelSwitched { info } => {
1234 state.on_channel_switched(info);
1235 state.into()
1236 }
1237 MlmeEvent::OnWmmStatusResp { status, resp } => {
1238 state.on_wmm_status_resp(status, resp);
1239 state.into()
1240 }
1241 MlmeEvent::RoamStartInd { ind } => {
1242 _ = state_change_ctx.replace(StateChangeContext::Msg(
1243 "Fullmac-initiated roam initiated".to_owned(),
1244 ));
1245 let (transition, associated) = state.release_data();
1246 match roam_internal(
1247 associated,
1248 context,
1249 ind.selected_bssid.into(),
1250 ind.selected_bss,
1251 &mut state_change_ctx,
1252 RoamInitiator::RoamStartInd,
1253 ) {
1254 Ok(roaming) => transition.to(roaming).into(),
1255 Err(disconnecting) => transition.to(disconnecting).into(),
1256 }
1257 }
1258 _ => state.into(),
1259 },
1260 Self::Roaming(state) => match event {
1261 MlmeEvent::OnSaeHandshakeInd { ind } => {
1262 let (transition, mut roaming) = state.release_data();
1263 if let Err(e) = roaming.on_sae_handshake_ind(ind, context) {
1264 error!("Failed to process SaeHandshakeInd: {:?}", e);
1265 }
1266 transition.to(roaming).into()
1267 }
1268 MlmeEvent::OnSaeFrameRx { frame } => {
1269 let (transition, mut roaming) = state.release_data();
1270 if let Err(e) = roaming.on_sae_frame_rx(frame, context) {
1271 error!("Failed to process SaeFrameRx: {:?}", e);
1272 }
1273 transition.to(roaming).into()
1274 }
1275 MlmeEvent::DisassociateInd { ind } => {
1276 let (transition, roaming) = state.release_data();
1277 match roaming.on_disassociate_ind(ind, &mut state_change_ctx, context) {
1278 Ok(roaming) => transition.to(roaming).into(),
1279 Err(disconnecting) => transition.to(disconnecting).into(),
1280 }
1281 }
1282 MlmeEvent::DeauthenticateInd { ind } => {
1283 let (transition, roaming) = state.release_data();
1284 match roaming.on_deauthenticate_ind(ind, &mut state_change_ctx) {
1285 Ok(roaming) => transition.to(roaming).into(),
1286 Err(idle) => transition.to(idle).into(),
1287 }
1288 }
1289 MlmeEvent::RoamConf { conf } => {
1290 let (transition, roaming) = state.release_data();
1291
1292 match roam_handle_result(
1293 roaming,
1294 RoamResultFields {
1295 selected_bssid: conf.selected_bssid.into(),
1296 status_code: conf.status_code,
1297 original_association_maintained: conf.original_association_maintained,
1298 target_bss_authenticated: conf.target_bss_authenticated,
1299 association_ies: conf.association_ies,
1300 },
1301 context,
1302 &mut state_change_ctx,
1303 RoamInitiator::RoamRequest,
1304 ) {
1305 Ok(associated) => transition.to(associated).into(),
1306 Err(after_roam_failure_state) => match after_roam_failure_state {
1307 AfterRoamFailureState::Disconnecting(disconnecting) => {
1308 transition.to(disconnecting).into()
1309 }
1310 AfterRoamFailureState::Idle(idle) => transition.to(idle).into(),
1311 },
1312 }
1313 }
1314 MlmeEvent::RoamResultInd { ind } => {
1315 let (transition, roaming) = state.release_data();
1316
1317 match roam_handle_result(
1318 roaming,
1319 RoamResultFields {
1320 selected_bssid: ind.selected_bssid.into(),
1321 status_code: ind.status_code,
1322 original_association_maintained: ind.original_association_maintained,
1323 target_bss_authenticated: ind.target_bss_authenticated,
1324 association_ies: ind.association_ies,
1325 },
1326 context,
1327 &mut state_change_ctx,
1328 RoamInitiator::RoamStartInd,
1329 ) {
1330 Ok(associated) => transition.to(associated).into(),
1331 Err(after_roam_failure_state) => match after_roam_failure_state {
1332 AfterRoamFailureState::Disconnecting(disconnecting) => {
1333 transition.to(disconnecting).into()
1334 }
1335 AfterRoamFailureState::Idle(idle) => transition.to(idle).into(),
1336 },
1337 }
1338 }
1339 _ => state.into(),
1340 },
1341 Self::Disconnecting(state) => match event {
1342 MlmeEvent::DeauthenticateConf { resp } => {
1343 let (transition, disconnecting) = state.release_data();
1344 match disconnecting.handle_deauthenticate_conf(
1345 resp,
1346 &mut state_change_ctx,
1347 context,
1348 ) {
1349 AfterDisconnectState::Idle(idle) => transition.to(idle).into(),
1350 AfterDisconnectState::Connecting(connecting) => {
1351 transition.to(connecting).into()
1352 }
1353 }
1354 }
1355 _ => state.into(),
1356 },
1357 };
1358
1359 log_state_change(start_state, &new_state, state_change_ctx, context);
1360 new_state
1361 }
1362
1363 pub fn handle_timeout(self, event: Event, context: &mut Context) -> Self {
1364 let start_state = self.state_name();
1365 let mut state_change_ctx: Option<StateChangeContext> = None;
1366
1367 let new_state = match self {
1368 Self::Connecting(state) => {
1369 let (transition, connecting) = state.release_data();
1370 match connecting.handle_timeout(event, &mut state_change_ctx, context) {
1371 Ok(connecting) => transition.to(connecting).into(),
1372 Err(disconnecting) => transition.to(disconnecting).into(),
1373 }
1374 }
1375 Self::Associated(state) => {
1376 let (transition, associated) = state.release_data();
1377 match associated.handle_timeout(event, &mut state_change_ctx, context) {
1378 Ok(associated) => transition.to(associated).into(),
1379 Err(disconnecting) => transition.to(disconnecting).into(),
1380 }
1381 }
1382 Self::Roaming(state) => {
1383 let (transition, roaming) = state.release_data();
1384 match roaming.handle_timeout(event, &mut state_change_ctx, context) {
1385 Ok(roaming) => transition.to(roaming).into(),
1386 Err(idle) => transition.to(idle).into(),
1387 }
1388 }
1389 Self::Disconnecting(state) => {
1390 let (transition, disconnecting) = state.release_data();
1391 match disconnecting.handle_timeout(event, &mut state_change_ctx, context) {
1392 Ok(disconnecting) => transition.to(disconnecting).into(),
1393 Err(after_disconnect) => match after_disconnect {
1394 AfterDisconnectState::Idle(idle) => transition.to(idle).into(),
1395 AfterDisconnectState::Connecting(connecting) => {
1396 transition.to(connecting).into()
1397 }
1398 },
1399 }
1400 }
1401 _ => self,
1402 };
1403
1404 log_state_change(start_state, &new_state, state_change_ctx, context);
1405 new_state
1406 }
1407
1408 pub fn connect(self, cmd: ConnectCommand, context: &mut Context) -> Self {
1409 let start_state = self.state_name();
1410 let mut state_change_ctx: Option<StateChangeContext> = None;
1411
1412 let new_state = self.disconnect_internal(
1413 context,
1414 PostDisconnectAction::BeginConnect { cmd },
1415 &mut state_change_ctx,
1416 );
1417
1418 log_state_change(start_state, &new_state, state_change_ctx, context);
1419 new_state
1420 }
1421
1422 pub fn roam(self, context: &mut Context, selected_bss: fidl_common::BssDescription) -> Self {
1423 let start_state = self.state_name();
1424 let mut state_change_ctx =
1425 Some(StateChangeContext::Msg("Policy-initiated roam attempt in progress".to_owned()));
1426
1427 let new_state = match self {
1428 ClientState::Associated(state) => {
1429 let (transition, state) = state.release_data();
1430 match roam_internal(
1431 state,
1432 context,
1433 selected_bss.bssid.into(),
1434 selected_bss,
1435 &mut state_change_ctx,
1436 RoamInitiator::RoamRequest,
1437 ) {
1438 Ok(roaming) => transition.to(roaming).into(),
1439 Err(disconnecting) => transition.to(disconnecting).into(),
1440 }
1441 }
1442 ClientState::Connecting(state) => {
1447 warn!("Requested roam could not be attempted, client is connecting");
1448 state.into()
1449 }
1450 ClientState::Roaming(state) => {
1451 error!("Overlapping roam request ignored, client is already roaming");
1452 state.into()
1453 }
1454 ClientState::Disconnecting(state) => {
1455 warn!("Requested roam could not be attempted, client is disconnecting");
1456 state.into()
1457 }
1458 ClientState::Idle(state) => {
1459 warn!("Requested roam could not be attempted, client is idle");
1460 state.into()
1461 }
1462 };
1463
1464 log_state_change(start_state, &new_state, state_change_ctx, context);
1465 new_state
1466 }
1467
1468 pub fn disconnect(
1469 mut self,
1470 context: &mut Context,
1471 user_disconnect_reason: fidl_sme::UserDisconnectReason,
1472 responder: fidl_sme::ClientSmeDisconnectResponder,
1473 ) -> Self {
1474 let mut disconnected_from_link_up = false;
1475 let disconnect_source = fidl_sme::DisconnectSource::User(user_disconnect_reason);
1476 if let Self::Associated(state) = &mut self {
1477 if let LinkState::LinkUp(_link_up) = &state.link_state {
1478 disconnected_from_link_up = true;
1479 let fidl_disconnect_info =
1480 fidl_sme::DisconnectInfo { is_sme_reconnecting: false, disconnect_source };
1481 state
1482 .connect_txn_sink
1483 .send(ConnectTransactionEvent::OnDisconnect { info: fidl_disconnect_info });
1484 }
1485 }
1486
1487 let start_state = self.state_name();
1488
1489 let new_state = self.disconnect_internal(
1490 context,
1491 PostDisconnectAction::RespondDisconnect { responder },
1492 &mut None,
1493 );
1494
1495 let msg =
1496 format!("received disconnect command from user; reason {:?}", user_disconnect_reason);
1497 let state_change_ctx = Some(if disconnected_from_link_up {
1498 StateChangeContext::Disconnect { msg, disconnect_source }
1499 } else {
1500 StateChangeContext::Msg(msg)
1501 });
1502 log_state_change(start_state, &new_state, state_change_ctx, context);
1503 new_state
1504 }
1505
1506 fn disconnect_internal(
1507 self,
1508 context: &mut Context,
1509 action: PostDisconnectAction,
1510 state_change_ctx: &mut Option<StateChangeContext>,
1511 ) -> Self {
1512 match self {
1513 Self::Idle(state) => {
1514 let (transition, state) = state.release_data();
1515 match state.on_disconnect_complete(context, action, state_change_ctx) {
1516 AfterDisconnectState::Idle(idle) => transition.to(idle).into(),
1517 AfterDisconnectState::Connecting(connecting) => {
1518 transition.to(connecting).into()
1519 }
1520 }
1521 }
1522 Self::Connecting(state) => {
1523 let (transition, state) = state.release_data();
1524 transition.to(state.disconnect(context, action)).into()
1525 }
1526 Self::Associated(state) => {
1527 let (transition, state) = state.release_data();
1528 transition.to(state.disconnect(context, action)).into()
1529 }
1530 Self::Roaming(state) => {
1531 let (transition, state) = state.release_data();
1532 transition.to(Idle { cfg: state.cfg }).into()
1533 }
1534 Self::Disconnecting(state) => {
1535 let (transition, state) = state.release_data();
1536 transition.to(state.disconnect(action)).into()
1537 }
1538 }
1539 }
1540
1541 pub fn cancel_ongoing_connect(self, context: &mut Context) -> Self {
1543 if self.in_transition_state() {
1549 let mut state_change_ctx = None;
1550 self.disconnect_internal(context, PostDisconnectAction::None, &mut state_change_ctx)
1551 } else {
1552 self
1553 }
1554 }
1555
1556 #[allow(
1557 clippy::match_like_matches_macro,
1558 reason = "mass allow for https://fxbug.dev/381896734"
1559 )]
1560 fn in_transition_state(&self) -> bool {
1561 match self {
1562 Self::Idle(_) => false,
1563 Self::Associated(state) => match state.link_state {
1564 LinkState::LinkUp { .. } => false,
1565 _ => true,
1566 },
1567 Self::Roaming(_) => false,
1568 _ => true,
1569 }
1570 }
1571
1572 pub fn status(&self) -> ClientSmeStatus {
1573 match self {
1574 Self::Idle(_) => ClientSmeStatus::Idle,
1575 Self::Connecting(connecting) => {
1576 ClientSmeStatus::Connecting(connecting.cmd.bss.ssid.clone())
1577 }
1578 Self::Associated(associated) => match associated.link_state {
1579 LinkState::EstablishingRsna { .. } => {
1580 ClientSmeStatus::Connecting(associated.latest_ap_state.ssid.clone())
1581 }
1582 LinkState::LinkUp { .. } => {
1583 let latest_ap_state = &associated.latest_ap_state;
1584 ClientSmeStatus::Connected(ServingApInfo {
1585 bssid: latest_ap_state.bssid,
1586 ssid: latest_ap_state.ssid.clone(),
1587 rssi_dbm: latest_ap_state.rssi_dbm,
1588 snr_db: latest_ap_state.snr_db,
1589 signal_report_time: associated.last_signal_report_time,
1590 channel: latest_ap_state.channel,
1591 protection: latest_ap_state.protection(),
1592 ht_cap: latest_ap_state.raw_ht_cap(),
1593 vht_cap: latest_ap_state.raw_vht_cap(),
1594 probe_resp_wsc: latest_ap_state.probe_resp_wsc(),
1595 wmm_param: associated.wmm_param,
1596 })
1597 }
1598 #[expect(clippy::unreachable)]
1601 _ => unreachable!(),
1602 },
1603 Self::Roaming(roaming) => ClientSmeStatus::Roaming(roaming.cmd.bss.bssid),
1604 Self::Disconnecting(disconnecting) => match &disconnecting.action {
1605 PostDisconnectAction::BeginConnect { cmd } => {
1606 ClientSmeStatus::Connecting(cmd.bss.ssid.clone())
1607 }
1608 _ => ClientSmeStatus::Idle,
1609 },
1610 }
1611 }
1612}
1613
1614#[allow(clippy::result_large_err)] fn roam_internal(
1616 state: Associated,
1617 context: &mut Context,
1618 selected_bssid: Bssid,
1619 selected_bss: fidl_common::BssDescription,
1620 state_change_ctx: &mut Option<StateChangeContext>,
1621 roam_initiator: RoamInitiator,
1622) -> Result<Roaming, Disconnecting> {
1623 let (mut orig_bss_protection, _connected_duration) = state.link_state.disconnect();
1625 if let Protection::Rsna(rsna) = &mut orig_bss_protection {
1626 rsna.supplicant.reset();
1628 }
1629
1630 let (mlme_event_name, deauth_addr) = match roam_initiator {
1633 RoamInitiator::RoamStartInd => {
1635 (fidl_sme::DisconnectMlmeEventName::RoamStartIndication, &selected_bssid)
1636 }
1637 RoamInitiator::RoamRequest => {
1639 (fidl_sme::DisconnectMlmeEventName::RoamRequest, &state.latest_ap_state.bssid)
1640 }
1641 };
1642
1643 let selected_bss = match BssDescription::try_from(selected_bss) {
1644 Ok(selected_bss) => selected_bss,
1645 Err(error) => {
1646 error!("Roam cannot proceed due to missing/malformed BSS description: {:?}", error);
1647
1648 send_deauthenticate_request(deauth_addr, &context.mlme_sink);
1649 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
1650
1651 let disconnect_info = make_roam_disconnect_info(mlme_event_name, None);
1652
1653 let failure_type = match roam_initiator {
1654 RoamInitiator::RoamStartInd => RoamFailureType::RoamStartMalformedFailure,
1655 RoamInitiator::RoamRequest => RoamFailureType::RoamRequestMalformedFailure,
1656 };
1657
1658 return Err(Disconnecting {
1659 cfg: state.cfg,
1660 action: PostDisconnectAction::ReportRoamFinished {
1661 sink: state.connect_txn_sink,
1662 result: RoamFailure {
1663 failure_type,
1664 selected_bss: None,
1665 disconnect_info,
1666 selected_bssid,
1667 auth_method: state.auth_method,
1668 status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1669 establish_rsna_failure_reason: None,
1670 }
1671 .into(),
1672 },
1673 _timeout: Some(timeout),
1674 });
1675 }
1676 };
1677
1678 let authentication = state.authentication.clone();
1679 let selected_bss_protection = match SecurityAuthenticator::try_from(authentication)
1680 .map_err(From::from)
1681 .and_then(|authenticator| {
1682 Protection::try_from(SecurityContext {
1683 security: &authenticator,
1684 device: &context.device_info,
1685 security_support: &context.security_support,
1686 config: &state.cfg,
1687 bss: &selected_bss.clone(),
1688 })
1689 }) {
1690 Ok(protection) => protection,
1691 Err(error) => {
1692 error!("Failed to configure protection for selected BSS during roam: {:?}", error);
1693
1694 send_deauthenticate_request(deauth_addr, &context.mlme_sink);
1695 let timeout = context.timer.schedule(event::DeauthenticateTimeout);
1696
1697 let disconnect_info = make_roam_disconnect_info(mlme_event_name, None);
1698
1699 return Err(Disconnecting {
1700 cfg: state.cfg,
1701 action: PostDisconnectAction::ReportRoamFinished {
1702 sink: state.connect_txn_sink,
1703 result: RoamFailure {
1704 status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1705 failure_type: RoamFailureType::SelectNetworkFailure,
1706 selected_bssid: selected_bss.bssid,
1707 disconnect_info,
1708 auth_method: state.auth_method,
1709 selected_bss: Some(selected_bss),
1710 establish_rsna_failure_reason: None,
1711 }
1712 .into(),
1713 },
1714 _timeout: Some(timeout),
1715 });
1716 }
1717 };
1718
1719 if matches!(roam_initiator, RoamInitiator::RoamRequest) {
1721 let roam_req = fidl_mlme::RoamRequest { selected_bss: selected_bss.clone().into() };
1722 context.mlme_sink.send(MlmeRequest::Roam(roam_req));
1723 }
1724
1725 _ = state_change_ctx.replace(StateChangeContext::Roam {
1726 msg: "Roam attempt in progress".to_owned(),
1727 bssid: selected_bssid,
1728 });
1729 Ok(Roaming {
1730 cfg: state.cfg,
1731 cmd: ConnectCommand {
1732 bss: Box::new(selected_bss),
1733 connect_txn_sink: state.connect_txn_sink,
1734 protection: selected_bss_protection,
1735 authentication: state.authentication,
1736 },
1737 auth_method: state.auth_method,
1738 protection_ie: state.protection_ie,
1740 })
1741}
1742
1743struct RoamResultFields {
1744 selected_bssid: Bssid,
1745 status_code: fidl_ieee80211::StatusCode,
1746 original_association_maintained: bool,
1747 target_bss_authenticated: bool,
1748 association_ies: Vec<u8>,
1749}
1750
1751#[allow(clippy::result_large_err)] fn roam_handle_result(
1755 mut state: Roaming,
1756 result_fields: RoamResultFields,
1757 context: &mut Context,
1758 state_change_ctx: &mut Option<StateChangeContext>,
1759 roam_initiator: RoamInitiator,
1760) -> Result<Associated, AfterRoamFailureState> {
1761 if result_fields.original_association_maintained {
1762 warn!("Roam result claims that device is still associated with original BSS, but Fast BSS Transition is currently unsupported");
1763 }
1764
1765 let mlme_event_name = match roam_initiator {
1767 RoamInitiator::RoamStartInd => fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
1768 RoamInitiator::RoamRequest => fidl_sme::DisconnectMlmeEventName::RoamConfirmation,
1769 };
1770
1771 if result_fields.selected_bssid != state.cmd.bss.bssid {
1772 let disconnect_info = make_roam_disconnect_info(mlme_event_name, None);
1773 let failure_type = match roam_initiator {
1774 RoamInitiator::RoamStartInd => RoamFailureType::RoamResultMalformedFailure,
1775 RoamInitiator::RoamRequest => RoamFailureType::RoamConfirmationMalformedFailure,
1776 };
1777 let failure = RoamFailure {
1778 failure_type,
1779 selected_bss: None,
1780 disconnect_info,
1781 selected_bssid: state.cmd.bss.bssid,
1782 auth_method: state.auth_method,
1783 status_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1784 establish_rsna_failure_reason: None,
1785 };
1786 return Err(AfterRoamFailureState::Disconnecting(Roaming::to_disconnecting(
1787 "Roam failed; unexpected BSSID in result".to_owned(),
1788 failure,
1789 state.cfg,
1790 state.cmd.connect_txn_sink,
1791 state_change_ctx,
1792 context,
1793 )));
1794 }
1795
1796 #[allow(clippy::clone_on_copy, reason = "mass allow for https://fxbug.dev/381896734")]
1797 match result_fields.status_code {
1798 fidl_ieee80211::StatusCode::Success => {
1799 let wmm_param = parse_wmm_from_ies(&result_fields.association_ies);
1800
1801 let link_state = match LinkState::new(state.cmd.protection, context) {
1803 Ok(link_state) => link_state,
1804 Err(failure_reason) => {
1805 let disconnect_info = make_roam_disconnect_info(mlme_event_name, None);
1806 let failure = RoamFailure {
1807 failure_type: RoamFailureType::EstablishRsnaFailure,
1808 selected_bss: Some(*state.cmd.bss.clone()),
1809 disconnect_info,
1810 selected_bssid: state.cmd.bss.bssid,
1811 auth_method: state.auth_method,
1812 status_code: fidl_ieee80211::StatusCode::EstablishRsnaFailure,
1813 establish_rsna_failure_reason: Some(failure_reason),
1814 };
1815
1816 if result_fields.target_bss_authenticated {
1817 return Err(AfterRoamFailureState::Disconnecting(
1818 Roaming::to_disconnecting(
1819 "Roam failed; SME failed to initialize LinkState".to_owned(),
1820 failure,
1821 state.cfg,
1822 state.cmd.connect_txn_sink,
1823 state_change_ctx,
1824 context,
1825 ),
1826 ));
1827 } else {
1828 report_roam_finished(&mut state.cmd.connect_txn_sink, failure.into());
1829 return Err(AfterRoamFailureState::Idle(Idle { cfg: state.cfg }));
1830 }
1831 }
1832 };
1833
1834 let ssid = state.cmd.bss.ssid.clone();
1835 _ = state_change_ctx.replace(StateChangeContext::Connect {
1836 msg: "Fullmac-initiated roam succeeded".to_owned(),
1837 bssid: state.cmd.bss.bssid.clone(),
1838 ssid,
1839 });
1840
1841 let roam_in_progress = match link_state {
1842 LinkState::LinkUp(_) => {
1843 report_roam_finished(
1844 &mut state.cmd.connect_txn_sink,
1845 RoamResult::Success(Box::new(*state.cmd.bss.clone())),
1846 );
1847 None
1848 }
1849 _ => Some(roam_initiator),
1850 };
1851
1852 Ok(Associated {
1853 cfg: state.cfg,
1854 connect_txn_sink: state.cmd.connect_txn_sink,
1855 latest_ap_state: state.cmd.bss,
1856 auth_method: state.auth_method,
1857 last_signal_report_time: now(),
1858 link_state,
1859 protection_ie: state.protection_ie,
1860 wmm_param,
1862 last_channel_switch_time: None,
1863 reassociation_loop_count: 0,
1864 authentication: state.cmd.authentication,
1865 roam_in_progress,
1866 })
1867 }
1868 _ => {
1870 let msg = format!("Roam failed, status_code {:?}", result_fields.status_code);
1871 error!("{}", msg);
1872 let disconnect_info = make_roam_disconnect_info(mlme_event_name, None);
1873 let failure = RoamFailure {
1874 failure_type: RoamFailureType::ReassociationFailure,
1875 selected_bss: Some(*state.cmd.bss.clone()),
1876 disconnect_info,
1877 selected_bssid: state.cmd.bss.bssid,
1878 auth_method: state.auth_method,
1879 status_code: result_fields.status_code,
1880 establish_rsna_failure_reason: None,
1881 };
1882
1883 if result_fields.target_bss_authenticated {
1884 Err(AfterRoamFailureState::Disconnecting(Roaming::to_disconnecting(
1885 msg,
1886 failure,
1887 state.cfg,
1888 state.cmd.connect_txn_sink,
1889 state_change_ctx,
1890 context,
1891 )))
1892 } else {
1893 Err(AfterRoamFailureState::Idle(state.to_idle(msg, failure, state_change_ctx)))
1894 }
1895 }
1896 }
1897}
1898
1899fn update_wmm_ac_param(ac_params: &mut ie::WmmAcParams, update: &fidl_internal::WmmAcParams) {
1900 ac_params.aci_aifsn.set_aifsn(update.aifsn);
1901 ac_params.aci_aifsn.set_acm(update.acm);
1902 ac_params.ecw_min_max.set_ecw_min(update.ecw_min);
1903 ac_params.ecw_min_max.set_ecw_max(update.ecw_max);
1904 ac_params.txop_limit = update.txop_limit;
1905}
1906
1907fn process_sae_updates(updates: UpdateSink, peer_sta_address: MacAddr, context: &mut Context) {
1908 for update in updates {
1909 match update {
1910 SecAssocUpdate::TxSaeFrame(frame) => {
1911 context.mlme_sink.send(MlmeRequest::SaeFrameTx(frame));
1912 }
1913 SecAssocUpdate::SaeAuthStatus(status) => context.mlme_sink.send(
1914 MlmeRequest::SaeHandshakeResp(fidl_mlme::SaeHandshakeResponse {
1915 peer_sta_address: peer_sta_address.to_array(),
1916 status_code: match status {
1917 AuthStatus::Success => fidl_ieee80211::StatusCode::Success,
1918 AuthStatus::Rejected(reason) => match reason {
1919 AuthRejectedReason::TooManyRetries => {
1920 fidl_ieee80211::StatusCode::RejectedSequenceTimeout
1921 }
1922 AuthRejectedReason::PmksaExpired | AuthRejectedReason::AuthFailed => {
1923 fidl_ieee80211::StatusCode::RefusedReasonUnspecified
1924 }
1925 },
1926 AuthStatus::InternalError => {
1927 fidl_ieee80211::StatusCode::RefusedReasonUnspecified
1928 }
1929 },
1930 }),
1931 ),
1932
1933 SecAssocUpdate::ScheduleSaeTimeout(id) => {
1934 context.timer.schedule(event::SaeTimeout(id)).drop_without_cancel();
1939 }
1940 _ => (),
1941 }
1942 }
1943}
1944
1945fn process_sae_handshake_ind(
1946 protection: &mut Protection,
1947 ind: fidl_mlme::SaeHandshakeIndication,
1948 context: &mut Context,
1949) -> Result<(), anyhow::Error> {
1950 let supplicant = match protection {
1951 Protection::Rsna(rsna) => &mut rsna.supplicant,
1952 _ => bail!("Unexpected SAE handshake indication"),
1953 };
1954
1955 let mut updates = UpdateSink::default();
1956 supplicant.on_sae_handshake_ind(&mut updates)?;
1957 process_sae_updates(updates, MacAddr::from(ind.peer_sta_address), context);
1958 Ok(())
1959}
1960
1961fn process_sae_frame_rx(
1962 protection: &mut Protection,
1963 frame: fidl_mlme::SaeFrame,
1964 context: &mut Context,
1965) -> Result<(), anyhow::Error> {
1966 let peer_sta_address = MacAddr::from(frame.peer_sta_address);
1967 let supplicant = match protection {
1968 Protection::Rsna(rsna) => &mut rsna.supplicant,
1969 _ => bail!("Unexpected SAE frame received"),
1970 };
1971
1972 let mut updates = UpdateSink::default();
1973 supplicant.on_sae_frame_rx(&mut updates, frame)?;
1974 process_sae_updates(updates, peer_sta_address, context);
1975 Ok(())
1976}
1977
1978#[allow(clippy::single_match, reason = "mass allow for https://fxbug.dev/381896734")]
1979fn process_sae_timeout(
1980 protection: &mut Protection,
1981 bssid: Bssid,
1982 event: Event,
1983 context: &mut Context,
1984) -> Result<(), anyhow::Error> {
1985 match event {
1986 Event::SaeTimeout(timer) => {
1987 let supplicant = match protection {
1988 Protection::Rsna(rsna) => &mut rsna.supplicant,
1989 _ => return Ok(()),
1991 };
1992
1993 let mut updates = UpdateSink::default();
1994 supplicant.on_sae_timeout(&mut updates, timer.0)?;
1995 process_sae_updates(updates, MacAddr::from(bssid), context);
1996 }
1997 _ => (),
1998 }
1999 Ok(())
2000}
2001
2002fn log_state_change(
2003 start_state: &str,
2004 new_state: &ClientState,
2005 state_change_ctx: Option<StateChangeContext>,
2006 context: &mut Context,
2007) {
2008 if start_state == new_state.state_name() && state_change_ctx.is_none() {
2009 return;
2010 }
2011
2012 match state_change_ctx {
2013 Some(inner) => match inner {
2014 StateChangeContext::Disconnect { msg, disconnect_source } => {
2015 if start_state != IDLE_STATE {
2018 info!(
2019 "{} => {}, ctx: `{}`, disconnect_source: {:?}",
2020 start_state,
2021 new_state.state_name(),
2022 msg,
2023 disconnect_source,
2024 );
2025 }
2026
2027 inspect_log!(context.inspect.state_events.lock(), {
2028 from: start_state,
2029 to: new_state.state_name(),
2030 ctx: msg,
2031 });
2032 }
2033 StateChangeContext::Connect { msg, bssid, ssid } => {
2034 inspect_log!(context.inspect.state_events.lock(), {
2035 from: start_state,
2036 to: new_state.state_name(),
2037 ctx: msg,
2038 bssid: bssid.to_string(),
2039 ssid: ssid.to_string(),
2040 });
2041 }
2042 StateChangeContext::Roam { msg, bssid } => {
2043 inspect_log!(context.inspect.state_events.lock(), {
2044 from: start_state,
2045 to: new_state.state_name(),
2046 ctx: msg,
2047 bssid: bssid.to_string(),
2048 });
2049 }
2050 StateChangeContext::Msg(msg) => {
2051 inspect_log!(context.inspect.state_events.lock(), {
2052 from: start_state,
2053 to: new_state.state_name(),
2054 ctx: msg,
2055 });
2056 }
2057 },
2058 None => {
2059 inspect_log!(context.inspect.state_events.lock(), {
2060 from: start_state,
2061 to: new_state.state_name(),
2062 });
2063 }
2064 }
2065}
2066
2067fn build_wep_set_key_descriptor(bssid: Bssid, key: &WepKey) -> fidl_mlme::SetKeyDescriptor {
2068 let cipher_suite = match key {
2069 WepKey::Wep40(_) => cipher::WEP_40,
2070 WepKey::Wep104(_) => cipher::WEP_104,
2071 };
2072 fidl_mlme::SetKeyDescriptor {
2073 key_type: fidl_mlme::KeyType::Pairwise,
2074 key: key.clone().into(),
2075 key_id: 0,
2076 address: bssid.to_array(),
2077 cipher_suite_oui: OUI.into(),
2078 cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
2079 cipher_suite.into(),
2080 ),
2081 rsc: 0,
2082 }
2083}
2084
2085fn connect_cmd_inspect_summary(cmd: &ConnectCommand) -> String {
2089 let bss = &cmd.bss;
2090 format!(
2091 "ConnectCmd {{ \
2092 capability_info: {capability_info:?}, rates: {rates:?}, \
2093 protected: {protected:?}, channel: {channel}, \
2094 rssi: {rssi:?}, ht_cap: {ht_cap:?}, ht_op: {ht_op:?}, \
2095 vht_cap: {vht_cap:?}, vht_op: {vht_op:?} }}",
2096 capability_info = bss.capability_info,
2097 rates = bss.rates(),
2098 protected = bss.rsne().is_some(),
2099 channel = bss.channel,
2100 rssi = bss.rssi_dbm,
2101 ht_cap = bss.ht_cap().is_some(),
2102 ht_op = bss.ht_op().is_some(),
2103 vht_cap = bss.vht_cap().is_some(),
2104 vht_op = bss.vht_op().is_some()
2105 )
2106}
2107
2108fn send_deauthenticate_request(bssid: &Bssid, mlme_sink: &MlmeSink) {
2109 mlme_sink.send(MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
2110 peer_sta_address: bssid.to_array(),
2111 reason_code: fidl_ieee80211::ReasonCode::StaLeaving,
2112 }));
2113}
2114
2115fn make_roam_disconnect_info(
2117 mlme_event_name: fidl_sme::DisconnectMlmeEventName,
2118 reason_code: Option<fidl_ieee80211::ReasonCode>,
2119) -> fidl_sme::DisconnectInfo {
2120 let reason_code = match reason_code {
2121 Some(reason_code) => reason_code,
2122 None => fidl_ieee80211::ReasonCode::UnspecifiedReason,
2123 };
2124 let disconnect_reason = fidl_sme::DisconnectCause { mlme_event_name, reason_code };
2125 let disconnect_source = fidl_sme::DisconnectSource::Mlme(disconnect_reason);
2126 fidl_sme::DisconnectInfo { is_sme_reconnecting: false, disconnect_source }
2127}
2128
2129fn now() -> zx::MonotonicInstant {
2130 zx::MonotonicInstant::get()
2131}
2132
2133#[cfg(test)]
2134mod tests {
2135 use super::*;
2136 use anyhow::format_err;
2137 use diagnostics_assertions::{
2138 assert_data_tree, AnyBytesProperty, AnyNumericProperty, AnyStringProperty,
2139 };
2140 use fidl_fuchsia_wlan_common_security::{Credentials, Protocol};
2141 use fuchsia_async::DurationExt;
2142 use fuchsia_inspect::Inspector;
2143 use futures::channel::mpsc;
2144 use futures::{Stream, StreamExt};
2145 use link_state::{EstablishingRsna, LinkUp};
2146 use std::sync::Arc;
2147 use std::task::Poll;
2148 use wlan_common::bss::Protection as BssProtection;
2149 use wlan_common::channel::{Cbw, Channel};
2150 use wlan_common::ie::fake_ies::{fake_probe_resp_wsc_ie_bytes, get_vendor_ie_bytes_for_wsc_ie};
2151 use wlan_common::ie::rsn::rsne::Rsne;
2152 use wlan_common::test_utils::fake_features::{
2153 fake_security_support, fake_spectrum_management_support_empty,
2154 };
2155 use wlan_common::test_utils::fake_stas::IesOverrides;
2156 use wlan_common::{assert_variant, fake_bss_description, timer};
2157 use wlan_rsn::key::exchange::Key;
2158 use wlan_rsn::rsna::SecAssocStatus;
2159 use wlan_rsn::NegotiatedProtection;
2160 use {
2161 fidl_fuchsia_wlan_common as fidl_common,
2162 fidl_fuchsia_wlan_common_security as fidl_security, fidl_internal,
2163 };
2164
2165 use crate::client::event::RsnaCompletionTimeout;
2166 use crate::client::rsn::Rsna;
2167 use crate::client::test_utils::{
2168 create_connect_conf, create_on_wmm_status_resp, expect_stream_empty, fake_wmm_param,
2169 mock_psk_supplicant, MockSupplicant, MockSupplicantController,
2170 };
2171 use crate::client::{inspect, ConnectTransactionStream, RoamFailureType};
2172 use crate::test_utils::{self, make_wpa1_ie};
2173 use crate::MlmeStream;
2174
2175 #[test]
2176 fn connect_happy_path_unprotected() {
2177 let mut h = TestHelper::new();
2178 let state = idle_state();
2179 let (command, mut connect_txn_stream) = connect_command_one();
2180 let bss = (*command.bss).clone();
2181
2182 let state = state.connect(command, &mut h.context);
2184
2185 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(req))) => {
2187 assert_eq!(req, fidl_mlme::ConnectRequest {
2188 selected_bss: bss.clone().into(),
2189 connect_failure_timeout: DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT,
2190 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
2191 sae_password: vec![],
2192 wep_key: None,
2193 security_ie: vec![],
2194 });
2195 });
2196
2197 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2199 let _state = state.on_mlme_event(connect_conf, &mut h.context);
2200
2201 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2203 assert_eq!(result, ConnectResult::Success);
2204 });
2205
2206 assert_data_tree!(h.inspector, root: contains {
2207 usme: contains {
2208 state_events: {
2209 "0": {
2210 "@time": AnyNumericProperty,
2211 ctx: AnyStringProperty,
2212 from: IDLE_STATE,
2213 to: CONNECTING_STATE,
2214 bssid: bss.bssid.to_string(),
2215 ssid: bss.ssid.to_string(),
2216 },
2217 "1": {
2218 "@time": AnyNumericProperty,
2219 ctx: AnyStringProperty,
2220 from: CONNECTING_STATE,
2221 to: LINK_UP_STATE,
2222 },
2223 },
2224 },
2225 });
2226 }
2227
2228 #[test]
2229 fn connect_happy_path_protected() {
2230 let mut h = TestHelper::new();
2231 let (supplicant, suppl_mock) = mock_psk_supplicant();
2232
2233 let state = idle_state();
2234 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2235 let bss = (*command.bss).clone();
2236
2237 let state = state.connect(command, &mut h.context);
2239
2240 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(req))) => {
2242 assert_eq!(req, fidl_mlme::ConnectRequest {
2243 selected_bss: bss.clone().into(),
2244 connect_failure_timeout: DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT,
2245 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
2246 sae_password: vec![],
2247 wep_key: None,
2248 security_ie: vec![
2249 0x30, 18, 1, 0, 0x00, 0x0F, 0xAC, 4, 1, 0, 0x00, 0x0F, 0xAC, 4, 1, 0, 0x00, 0x0F, 0xAC, 2, ],
2255 });
2256 });
2257
2258 suppl_mock
2260 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
2261 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2262 let state = state.on_mlme_event(connect_conf, &mut h.context);
2263 assert!(suppl_mock.is_supplicant_started());
2264
2265 let update = SecAssocUpdate::TxEapolKeyFrame {
2267 frame: test_utils::eapol_key_frame(),
2268 expect_response: true,
2269 };
2270 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2271
2272 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
2273
2274 let ptk = SecAssocUpdate::Key(Key::Ptk(test_utils::ptk()));
2276 let gtk = SecAssocUpdate::Key(Key::Gtk(test_utils::gtk()));
2277 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![ptk, gtk]);
2278
2279 expect_set_ptk(&mut h.mlme_stream, bss.bssid);
2280 expect_set_gtk(&mut h.mlme_stream);
2281
2282 let state = on_set_keys_conf(state, &mut h, vec![0, 2]);
2283
2284 let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
2286 let _state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2287
2288 expect_set_ctrl_port(&mut h.mlme_stream, bss.bssid, fidl_mlme::ControlledPortState::Open);
2289 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2290 assert_eq!(result, ConnectResult::Success);
2291 });
2292
2293 assert_data_tree!(h.inspector, root: contains {
2294 usme: contains {
2295 state_events: {
2296 "0": {
2297 "@time": AnyNumericProperty,
2298 ctx: AnyStringProperty,
2299 from: IDLE_STATE,
2300 to: CONNECTING_STATE,
2301 bssid: bss.bssid.to_string(),
2302 ssid: bss.ssid.to_string(),
2303 },
2304 "1": {
2305 "@time": AnyNumericProperty,
2306 ctx: AnyStringProperty,
2307 from: CONNECTING_STATE,
2308 to: RSNA_STATE,
2309 },
2310 "2": {
2311 "@time": AnyNumericProperty,
2312 ctx: AnyStringProperty,
2313 from: RSNA_STATE,
2314 to: LINK_UP_STATE,
2315 },
2316 },
2317 },
2318 });
2319 }
2320
2321 #[test]
2322 fn connect_happy_path_wpa1() {
2323 let mut h = TestHelper::new();
2324 let (supplicant, suppl_mock) = mock_psk_supplicant();
2325
2326 let state = idle_state();
2327 let (command, mut connect_txn_stream) = connect_command_wpa1(supplicant);
2328 let bss = (*command.bss).clone();
2329
2330 let state = state.connect(command, &mut h.context);
2332
2333 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(req))) => {
2335 assert_eq!(req, fidl_mlme::ConnectRequest {
2336 selected_bss: bss.clone().into(),
2337 connect_failure_timeout: DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT,
2338 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
2339 sae_password: vec![],
2340 wep_key: None,
2341 security_ie: vec![
2342 0xdd, 0x16, 0x00, 0x50, 0xf2, 0x01, 0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, ],
2349 });
2350 });
2351
2352 suppl_mock
2354 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
2355 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2356 let state = state.on_mlme_event(connect_conf, &mut h.context);
2357 assert!(suppl_mock.is_supplicant_started());
2358
2359 let update = SecAssocUpdate::TxEapolKeyFrame {
2361 frame: test_utils::eapol_key_frame(),
2362 expect_response: false,
2363 };
2364 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2365
2366 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
2367
2368 let ptk = SecAssocUpdate::Key(Key::Ptk(test_utils::wpa1_ptk()));
2370 let gtk = SecAssocUpdate::Key(Key::Gtk(test_utils::wpa1_gtk()));
2371 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![ptk, gtk]);
2372
2373 expect_set_wpa1_ptk(&mut h.mlme_stream, bss.bssid);
2374 expect_set_wpa1_gtk(&mut h.mlme_stream);
2375
2376 let state = on_set_keys_conf(state, &mut h, vec![0, 2]);
2377
2378 let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
2380 let _state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2381
2382 expect_set_ctrl_port(&mut h.mlme_stream, bss.bssid, fidl_mlme::ControlledPortState::Open);
2383 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2384 assert_eq!(result, ConnectResult::Success);
2385 });
2386
2387 assert_data_tree!(h.inspector, root: contains {
2388 usme: contains {
2389 state_events: {
2390 "0": {
2391 "@time": AnyNumericProperty,
2392 ctx: AnyStringProperty,
2393 from: IDLE_STATE,
2394 to: CONNECTING_STATE,
2395 bssid: bss.bssid.to_string(),
2396 ssid: bss.ssid.to_string(),
2397 },
2398 "1": {
2399 "@time": AnyNumericProperty,
2400 ctx: AnyStringProperty,
2401 from: CONNECTING_STATE,
2402 to: RSNA_STATE,
2403 },
2404 "2": {
2405 "@time": AnyNumericProperty,
2406 ctx: AnyStringProperty,
2407 from: RSNA_STATE,
2408 to: LINK_UP_STATE,
2409 },
2410 },
2411 },
2412 });
2413 }
2414
2415 #[test]
2416 fn connect_happy_path_wep() {
2417 let mut h = TestHelper::new();
2418
2419 let state = idle_state();
2420 let (command, mut connect_txn_stream) = connect_command_wep();
2421 let bss = (*command.bss).clone();
2422
2423 let state = state.connect(command, &mut h.context);
2425
2426 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(req))) => {
2428 assert_eq!(req, fidl_mlme::ConnectRequest {
2429 selected_bss: bss.clone().into(),
2430 connect_failure_timeout: DEFAULT_JOIN_AUTH_ASSOC_FAILURE_TIMEOUT,
2431 auth_type: fidl_mlme::AuthenticationTypes::SharedKey,
2432 sae_password: vec![],
2433 wep_key: Some(Box::new(fidl_mlme::SetKeyDescriptor {
2434 key_type: fidl_mlme::KeyType::Pairwise,
2435 key: vec![3; 5],
2436 key_id: 0,
2437 address: bss.bssid.to_array(),
2438 cipher_suite_oui: OUI.into(),
2439 cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(1),
2440 rsc: 0,
2441 })),
2442 security_ie: vec![],
2443 });
2444 });
2445
2446 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2448 let _state = state.on_mlme_event(connect_conf, &mut h.context);
2449
2450 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2452 assert_eq!(result, ConnectResult::Success);
2453 });
2454
2455 assert_data_tree!(h.inspector, root: contains {
2456 usme: contains {
2457 state_events: {
2458 "0": {
2459 "@time": AnyNumericProperty,
2460 ctx: AnyStringProperty,
2461 from: IDLE_STATE,
2462 to: CONNECTING_STATE,
2463 bssid: bss.bssid.to_string(),
2464 ssid: bss.ssid.to_string(),
2465 },
2466 "1": {
2467 "@time": AnyNumericProperty,
2468 ctx: AnyStringProperty,
2469 from: CONNECTING_STATE,
2470 to: LINK_UP_STATE,
2471 },
2472 },
2473 },
2474 });
2475 }
2476
2477 #[test]
2478 fn connect_happy_path_wmm() {
2479 let mut h = TestHelper::new();
2480 let state = idle_state();
2481 let (command, mut connect_txn_stream) = connect_command_one();
2482 let bss = (*command.bss).clone();
2483
2484 let state = state.connect(command, &mut h.context);
2486
2487 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(_req))));
2489
2490 let connect_conf = fidl_mlme::MlmeEvent::ConnectConf {
2492 resp: fidl_mlme::ConnectConfirm {
2493 peer_sta_address: bss.bssid.to_array(),
2494 result_code: fidl_ieee80211::StatusCode::Success,
2495 association_id: 42,
2496 association_ies: vec![
2497 0xdd, 0x18, 0x00, 0x50, 0xf2, 0x02, 0x01, 0x01, 0x80, 0x00, 0x03, 0xa4, 0x00, 0x00, 0x27, 0xa4, 0x00, 0x00, 0x42, 0x43, 0x5e, 0x00, 0x62, 0x32, 0x2f, 0x00, ],
2506 },
2507 };
2508 let _state = state.on_mlme_event(connect_conf, &mut h.context);
2509
2510 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2512 assert_eq!(result, ConnectResult::Success);
2513 });
2514
2515 assert_data_tree!(h.inspector, root: contains {
2516 usme: contains {
2517 state_events: {
2518 "0": {
2519 "@time": AnyNumericProperty,
2520 ctx: AnyStringProperty,
2521 from: IDLE_STATE,
2522 to: CONNECTING_STATE,
2523 bssid: bss.bssid.to_string(),
2524 ssid: bss.ssid.to_string(),
2525 },
2526 "1": {
2527 "@time": AnyNumericProperty,
2528 ctx: AnyStringProperty,
2529 from: CONNECTING_STATE,
2530 to: LINK_UP_STATE,
2531 },
2532 },
2533 },
2534 });
2535 }
2536
2537 #[test]
2538 fn set_keys_failure() {
2539 let mut h = TestHelper::new();
2540 let (supplicant, suppl_mock) = mock_psk_supplicant();
2541
2542 let state = idle_state();
2543 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2544 let bss = (*command.bss).clone();
2545
2546 let state = state.connect(command, &mut h.context);
2548
2549 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(_req))));
2551
2552 suppl_mock
2554 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
2555 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2556 let state = state.on_mlme_event(connect_conf, &mut h.context);
2557 assert!(suppl_mock.is_supplicant_started());
2558
2559 let update = SecAssocUpdate::TxEapolKeyFrame {
2561 frame: test_utils::eapol_key_frame(),
2562 expect_response: false,
2563 };
2564 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2565
2566 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
2567
2568 let ptk = SecAssocUpdate::Key(Key::Ptk(test_utils::ptk()));
2570 let gtk = SecAssocUpdate::Key(Key::Gtk(test_utils::gtk()));
2571 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![ptk, gtk]);
2572
2573 expect_set_ptk(&mut h.mlme_stream, bss.bssid);
2574 expect_set_gtk(&mut h.mlme_stream);
2575
2576 let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
2578 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2579
2580 assert!(connect_txn_stream.try_next().is_err());
2582
2583 let state = state.on_mlme_event(
2585 MlmeEvent::SetKeysConf {
2586 conf: fidl_mlme::SetKeysConfirm {
2587 results: vec![
2588 fidl_mlme::SetKeyResult { key_id: 0, status: zx::Status::OK.into_raw() },
2589 fidl_mlme::SetKeyResult {
2590 key_id: 2,
2591 status: zx::Status::INTERNAL.into_raw(),
2592 },
2593 ],
2594 },
2595 },
2596 &mut h.context,
2597 );
2598
2599 assert_variant!(connect_txn_stream.try_next(),
2600 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2601 assert_variant!(result, ConnectResult::Failed(_))
2602 });
2603
2604 let state = exchange_deauth(state, &mut h);
2605 assert_idle(state);
2606
2607 assert_data_tree!(h.inspector, root: contains {
2608 usme: contains {
2609 state_events: {
2610 "0": {
2611 "@time": AnyNumericProperty,
2612 ctx: AnyStringProperty,
2613 from: IDLE_STATE,
2614 to: CONNECTING_STATE,
2615 bssid: bss.bssid.to_string(),
2616 ssid: bss.ssid.to_string(),
2617 },
2618 "1": {
2619 "@time": AnyNumericProperty,
2620 ctx: AnyStringProperty,
2621 from: CONNECTING_STATE,
2622 to: RSNA_STATE,
2623 },
2624 "2": {
2625 "@time": AnyNumericProperty,
2626 ctx: AnyStringProperty,
2627 from: RSNA_STATE,
2628 to: DISCONNECTING_STATE,
2629 },
2630 "3": {
2631 "@time": AnyNumericProperty,
2632 from: DISCONNECTING_STATE,
2633 to: IDLE_STATE,
2634 },
2635 },
2636 },
2637 });
2638 }
2639
2640 #[test]
2641 fn deauth_while_connecting() {
2642 let mut h = TestHelper::new();
2643 let (cmd_one, mut connect_txn_stream1) = connect_command_one();
2644 let bss = cmd_one.bss.clone();
2645 let bss_protection = bss.protection();
2646 let state = connecting_state(cmd_one);
2647 let deauth_ind = MlmeEvent::DeauthenticateInd {
2648 ind: fidl_mlme::DeauthenticateIndication {
2649 peer_sta_address: [7, 7, 7, 7, 7, 7],
2650 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
2651 locally_initiated: false,
2652 },
2653 };
2654 let state = state.on_mlme_event(deauth_ind, &mut h.context);
2655 assert_variant!(connect_txn_stream1.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2656 assert_eq!(result, AssociationFailure {
2657 bss_protection,
2658 code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
2659 }
2660 .into());
2661 });
2662 assert_idle(state);
2663
2664 assert_data_tree!(h.inspector, root: contains {
2665 usme: contains {
2666 state_events: {
2667 "0": {
2668 "@time": AnyNumericProperty,
2669 ctx: AnyStringProperty,
2670 from: CONNECTING_STATE,
2671 to: IDLE_STATE,
2672 },
2673 },
2674 },
2675 });
2676 }
2677
2678 #[test]
2679 fn disassoc_while_connecting() {
2680 let mut h = TestHelper::new();
2681 let (cmd_one, mut connect_txn_stream1) = connect_command_one();
2682 let bss = cmd_one.bss.clone();
2683 let bss_protection = bss.protection();
2684 let state = connecting_state(cmd_one);
2685 let disassoc_ind = MlmeEvent::DisassociateInd {
2686 ind: fidl_mlme::DisassociateIndication {
2687 peer_sta_address: [7, 7, 7, 7, 7, 7],
2688 reason_code: fidl_ieee80211::ReasonCode::PeerkeyMismatch,
2689 locally_initiated: false,
2690 },
2691 };
2692 let state = state.on_mlme_event(disassoc_ind, &mut h.context);
2693 let state = exchange_deauth(state, &mut h);
2694 assert_variant!(connect_txn_stream1.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2695 assert_eq!(result, AssociationFailure {
2696 bss_protection,
2697 code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
2698 }
2699 .into());
2700 });
2701 assert_idle(state);
2702
2703 assert_data_tree!(h.inspector, root: contains {
2704 usme: contains {
2705 state_events: {
2706 "0": {
2707 "@time": AnyNumericProperty,
2708 ctx: AnyStringProperty,
2709 from: CONNECTING_STATE,
2710 to: DISCONNECTING_STATE,
2711 },
2712 "1": {
2713 "@time": AnyNumericProperty,
2714 from: DISCONNECTING_STATE,
2715 to: IDLE_STATE,
2716 },
2717 },
2718 },
2719 });
2720 }
2721
2722 #[test]
2723 fn supplicant_fails_to_start_while_connecting() {
2724 let mut h = TestHelper::new();
2725 let (supplicant, suppl_mock) = mock_psk_supplicant();
2726 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2727 let bss = command.bss.clone();
2728 let state = connecting_state(command);
2729
2730 suppl_mock.set_start_failure(format_err!("failed to start supplicant"));
2731
2732 let assoc_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2734 let state = state.on_mlme_event(assoc_conf, &mut h.context);
2735
2736 let state = exchange_deauth(state, &mut h);
2737 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2738 assert_eq!(result, EstablishRsnaFailure {
2739 auth_method: Some(auth::MethodName::Psk),
2740 reason: EstablishRsnaFailureReason::StartSupplicantFailed,
2741 }
2742 .into());
2743 });
2744 assert_idle(state);
2745
2746 assert_data_tree!(h.inspector, root: contains {
2747 usme: contains {
2748 state_events: {
2749 "0": {
2750 "@time": AnyNumericProperty,
2751 ctx: AnyStringProperty,
2752 from: CONNECTING_STATE,
2753 to: DISCONNECTING_STATE,
2754 },
2755 "1": {
2756 "@time": AnyNumericProperty,
2757 from: DISCONNECTING_STATE,
2758 to: IDLE_STATE,
2759 },
2760 },
2761 },
2762 });
2763 }
2764
2765 #[test]
2766 fn bad_eapol_frame_while_establishing_rsna() {
2767 let mut h = TestHelper::new();
2768 let (supplicant, suppl_mock) = mock_psk_supplicant();
2769 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2770 let bss = command.bss.clone();
2771 let state = establishing_rsna_state(command);
2772
2773 let update = SecAssocUpdate::Status(SecAssocStatus::EssSaEstablished);
2775 suppl_mock.set_on_eapol_frame_updates(vec![update]);
2776
2777 let eapol_ind = create_eapol_ind(bss.bssid, vec![1, 2, 3, 4]);
2779 let s = state.on_mlme_event(eapol_ind, &mut h.context);
2780
2781 assert_variant!(connect_txn_stream.try_next(), Err(_));
2783 assert_variant!(s, ClientState::Associated(state) => {
2784 assert_variant!(&state.link_state, LinkState::EstablishingRsna { .. })});
2785
2786 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
2787
2788 assert_data_tree!(h.inspector, root: contains {
2789 usme: contains {
2790 state_events: {},
2791 rsn_events: {
2792 "0": {
2793 "@time": AnyNumericProperty,
2794 rx_eapol_frame: AnyBytesProperty,
2795 status: AnyStringProperty,
2796 }
2797 },
2798 },
2799 });
2800 }
2801
2802 #[test]
2803 fn supplicant_fails_to_process_eapol_while_establishing_rsna() {
2804 let mut h = TestHelper::new();
2805 let (supplicant, suppl_mock) = mock_psk_supplicant();
2806 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2807 let bss = command.bss.clone();
2808 let state = establishing_rsna_state(command);
2809
2810 suppl_mock.set_on_eapol_frame_failure(format_err!("supplicant::on_eapol_frame fails"));
2811
2812 let eapol_ind = create_eapol_ind(bss.bssid, test_utils::eapol_key_frame().into());
2814 let s = state.on_mlme_event(eapol_ind, &mut h.context);
2815
2816 assert_variant!(connect_txn_stream.try_next(), Err(_));
2818 assert_variant!(s, ClientState::Associated(state) => {
2819 assert_variant!(&state.link_state, LinkState::EstablishingRsna { .. })});
2820
2821 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
2822
2823 assert_data_tree!(h.inspector, root: contains {
2824 usme: contains {
2825 state_events: {},
2826 rsn_events: {
2827 "0": {
2828 "@time": AnyNumericProperty,
2829 rx_eapol_frame: AnyBytesProperty,
2830 status: AnyStringProperty,
2831 }
2832 },
2833 },
2834 });
2835 }
2836
2837 #[test]
2838 fn reject_foreign_eapol_frames() {
2839 let mut h = TestHelper::new();
2840 let (supplicant, mock) = mock_psk_supplicant();
2841 let (cmd, _connect_txn_stream) = connect_command_wpa2(supplicant);
2842 let bss = cmd.bss.clone();
2843 let state = link_up_state(cmd);
2844 mock.set_on_eapol_frame_callback(|| {
2845 panic!("eapol frame should not have been processed");
2846 });
2847
2848 let foreign_bssid = Bssid::from([1; 6]);
2850 let eapol_ind = create_eapol_ind(foreign_bssid, test_utils::eapol_key_frame().into());
2851 let state = state.on_mlme_event(eapol_ind, &mut h.context);
2852
2853 assert_variant!(state, ClientState::Associated(state) => {
2855 assert_variant!(
2856 &state.link_state,
2857 LinkState::LinkUp(state) => assert_variant!(&state.protection, Protection::Rsna(_))
2858 )
2859 });
2860
2861 assert_data_tree!(h.inspector, root: {
2862 usme: contains {
2863 state_events: {},
2864 rsn_events: {
2865 "0" : {
2866 "@time": AnyNumericProperty,
2867 rx_eapol_frame: AnyBytesProperty,
2868 foreign_bssid: foreign_bssid.to_string(),
2869 current_bssid: bss.bssid.to_string(),
2870 status: "rejected (foreign BSS)"
2871 }
2872 }
2873 }
2874 });
2875 }
2876
2877 #[test]
2878 fn wrong_password_while_establishing_rsna() {
2879 let mut h = TestHelper::new();
2880 let (supplicant, suppl_mock) = mock_psk_supplicant();
2881 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2882 let bss = command.bss.clone();
2883 let state = establishing_rsna_state(command);
2884
2885 let update = SecAssocUpdate::Status(SecAssocStatus::WrongPassword);
2887 let state = on_eapol_ind(state, &mut h, bss.bssid, &suppl_mock, vec![update]);
2888
2889 expect_deauth_req(&mut h.mlme_stream, bss.bssid, fidl_ieee80211::ReasonCode::StaLeaving);
2890 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2891 assert_eq!(result, EstablishRsnaFailure {
2892 auth_method: Some(auth::MethodName::Psk),
2893 reason: EstablishRsnaFailureReason::InternalError,
2894 }
2895 .into());
2896 });
2897
2898 let deauth_conf = MlmeEvent::DeauthenticateConf {
2900 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: bss.bssid.to_array() },
2901 };
2902 let state = state.on_mlme_event(deauth_conf, &mut h.context);
2903 assert_idle(state);
2904
2905 assert_data_tree!(h.inspector, root: contains {
2906 usme: contains {
2907 state_events: {
2908 "0": {
2909 "@time": AnyNumericProperty,
2910 ctx: AnyStringProperty,
2911 from: RSNA_STATE,
2912 to: DISCONNECTING_STATE,
2913 },
2914 "1": {
2915 "@time": AnyNumericProperty,
2916 from: DISCONNECTING_STATE,
2917 to: IDLE_STATE,
2918 },
2919 },
2920 },
2921 });
2922 }
2923
2924 fn expect_next_event_at_deadline<E: std::fmt::Debug>(
2925 executor: &mut fuchsia_async::TestExecutor,
2926 mut timed_event_stream: impl Stream<Item = timer::Event<E>> + std::marker::Unpin,
2927 deadline: fuchsia_async::MonotonicInstant,
2928 ) -> timer::Event<E> {
2929 assert_variant!(executor.run_until_stalled(&mut timed_event_stream.next()), Poll::Pending);
2930 loop {
2931 let next_deadline = executor.wake_next_timer().expect("expected pending timer");
2932 executor.set_fake_time(next_deadline);
2933 if next_deadline == deadline {
2934 return assert_variant!(
2935 executor.run_until_stalled(&mut timed_event_stream.next()),
2936 Poll::Ready(Some(timed_event)) => timed_event
2937 );
2938 } else {
2939 assert_variant!(
2942 executor.run_until_stalled(&mut timed_event_stream.next()),
2943 Poll::Pending
2944 );
2945 }
2946 }
2947 }
2948
2949 #[test]
2950 fn simple_rsna_response_timeout_with_unresponsive_ap() {
2951 let mut h = TestHelper::new_with_fake_time();
2952 h.executor.set_fake_time(fuchsia_async::MonotonicInstant::from_nanos(0));
2953 let (supplicant, suppl_mock) = mock_psk_supplicant();
2954 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
2955 let bss = command.bss.clone();
2956
2957 let state = ClientState::from(testing::new_state(Connecting {
2959 cfg: ClientConfig::default(),
2960 cmd: command,
2961 protection_ie: None,
2962 reassociation_loop_count: 0,
2963 }));
2964 suppl_mock
2965 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
2966 let assoc_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
2967 let rsna_response_deadline =
2968 zx::MonotonicDuration::from_millis(event::RSNA_RESPONSE_TIMEOUT_MILLIS).after_now();
2969 let state = state.on_mlme_event(assoc_conf, &mut h.context);
2970 assert!(suppl_mock.is_supplicant_started());
2971
2972 let mut timed_event_stream = timer::make_async_timed_event_stream(h.time_stream);
2974 let timed_event = expect_next_event_at_deadline(
2975 &mut h.executor,
2976 &mut timed_event_stream,
2977 rsna_response_deadline,
2978 );
2979 assert_variant!(timed_event.event, Event::RsnaResponseTimeout(..));
2980 suppl_mock.set_on_rsna_response_timeout(EstablishRsnaFailureReason::RsnaResponseTimeout(
2981 wlan_rsn::Error::EapolHandshakeNotStarted,
2982 ));
2983 let state = state.handle_timeout(timed_event.event, &mut h.context);
2984
2985 expect_deauth_req(&mut h.mlme_stream, bss.bssid, fidl_ieee80211::ReasonCode::StaLeaving);
2987 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
2988 assert_eq!(result, EstablishRsnaFailure {
2989 auth_method: Some(auth::MethodName::Psk),
2990 reason: EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::EapolHandshakeNotStarted),
2991 }.into());
2992 });
2993
2994 let deauth_conf = MlmeEvent::DeauthenticateConf {
2996 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: bss.bssid.to_array() },
2997 };
2998 let state = state.on_mlme_event(deauth_conf, &mut h.context);
2999 assert_idle(state);
3000
3001 assert_data_tree!(h.inspector, root: contains {
3002 usme: contains {
3003 state_events: {
3004 "0": {
3005 "@time": AnyNumericProperty,
3006 ctx: AnyStringProperty,
3007 from: CONNECTING_STATE,
3008 to: RSNA_STATE,
3009 },
3010 "1": {
3011 "@time": AnyNumericProperty,
3012 ctx: AnyStringProperty,
3013 from: RSNA_STATE,
3014 to: DISCONNECTING_STATE,
3015 },
3016 "2": {
3017 "@time": AnyNumericProperty,
3018 from: DISCONNECTING_STATE,
3019 to: IDLE_STATE,
3020 },
3021 },
3022 },
3023 });
3024 }
3025
3026 #[test]
3027 fn simple_retransmission_timeout_with_responsive_ap() {
3028 let mut h = TestHelper::new_with_fake_time();
3029 h.executor.set_fake_time(fuchsia_async::MonotonicInstant::from_nanos(0));
3030
3031 let (supplicant, suppl_mock) = mock_psk_supplicant();
3032 let (command, _connect_txn_stream) = connect_command_wpa2(supplicant);
3033 let bssid = command.bss.bssid;
3034
3035 let state = ClientState::from(testing::new_state(Connecting {
3037 cfg: ClientConfig::default(),
3038 cmd: command,
3039 protection_ie: None,
3040 reassociation_loop_count: 0,
3041 }));
3042 suppl_mock
3043 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
3044 let assoc_conf = create_connect_conf(bssid, fidl_ieee80211::StatusCode::Success);
3045 let state = state.on_mlme_event(assoc_conf, &mut h.context);
3046 assert!(suppl_mock.is_supplicant_started());
3047
3048 let mut timed_event_stream = timer::make_async_timed_event_stream(h.time_stream);
3049
3050 let tx_eapol_frame_update_sink = vec![SecAssocUpdate::TxEapolKeyFrame {
3052 frame: test_utils::eapol_key_frame(),
3053 expect_response: true,
3054 }];
3055 suppl_mock.set_on_eapol_frame_updates(tx_eapol_frame_update_sink.clone());
3056
3057 let eapol_ind = MlmeEvent::EapolInd {
3059 ind: fidl_mlme::EapolIndication {
3060 src_addr: bssid.to_array(),
3061 dst_addr: fake_device_info().sta_addr,
3062 data: test_utils::eapol_key_frame().into(),
3063 },
3064 };
3065 let mut state = state.on_mlme_event(eapol_ind, &mut h.context);
3066
3067 let mock_number_of_retransmissions = 5;
3069 for i in 0..=mock_number_of_retransmissions {
3070 expect_eapol_req(&mut h.mlme_stream, bssid);
3071 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3072
3073 let rsna_retransmission_deadline =
3074 zx::MonotonicDuration::from_millis(event::RSNA_RETRANSMISSION_TIMEOUT_MILLIS)
3075 .after_now();
3076 let timed_event = expect_next_event_at_deadline(
3077 &mut h.executor,
3078 &mut timed_event_stream,
3079 rsna_retransmission_deadline,
3080 );
3081 assert_variant!(timed_event.event, Event::RsnaRetransmissionTimeout(_));
3082
3083 if i < mock_number_of_retransmissions {
3084 suppl_mock
3085 .set_on_rsna_retransmission_timeout_updates(tx_eapol_frame_update_sink.clone());
3086 }
3087 state = state.handle_timeout(timed_event.event, &mut h.context);
3088 }
3089
3090 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3092
3093 assert_data_tree!(h.inspector, root: contains {
3094 usme: contains {
3095 state_events: {
3096 "0": {
3097 "@time": AnyNumericProperty,
3098 ctx: AnyStringProperty,
3099 from: CONNECTING_STATE,
3100 to: RSNA_STATE,
3101 },
3102 },
3103 rsn_events: {
3104 "0": {
3105 "@time": AnyNumericProperty,
3106 "rsna_status": "PmkSaEstablished"
3107 },
3108 "1": {
3109 "@time": AnyNumericProperty,
3110 "rx_eapol_frame": AnyBytesProperty,
3111 "status": "processed",
3112 },
3113 "2": {
3114 "@time": AnyNumericProperty,
3115 "tx_eapol_frame": AnyBytesProperty,
3116 },
3117 "3": {
3118 "@time": AnyNumericProperty,
3119 "tx_eapol_frame": AnyBytesProperty,
3120 },
3121 "4": {
3122 "@time": AnyNumericProperty,
3123 "tx_eapol_frame": AnyBytesProperty,
3124 },
3125 "5": {
3126 "@time": AnyNumericProperty,
3127 "tx_eapol_frame": AnyBytesProperty,
3128 },
3129 "6": {
3130 "@time": AnyNumericProperty,
3131 "tx_eapol_frame": AnyBytesProperty,
3132 },
3133 "7": {
3134 "@time": AnyNumericProperty,
3135 "tx_eapol_frame": AnyBytesProperty,
3136 },
3137 }
3138 },
3139 });
3140 }
3141
3142 #[test]
3143 fn retransmission_timeouts_do_not_extend_response_timeout() {
3144 let mut h = TestHelper::new_with_fake_time();
3145 h.executor.set_fake_time(fuchsia_async::MonotonicInstant::from_nanos(0));
3146
3147 let (supplicant, suppl_mock) = mock_psk_supplicant();
3148 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
3149 let bss = command.bss.clone();
3150
3151 let state = ClientState::from(testing::new_state(Connecting {
3153 cfg: ClientConfig::default(),
3154 cmd: command,
3155 protection_ie: None,
3156 reassociation_loop_count: 0,
3157 }));
3158 suppl_mock
3159 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
3160 let assoc_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
3161 let state = state.on_mlme_event(assoc_conf, &mut h.context);
3162 assert!(suppl_mock.is_supplicant_started());
3163
3164 let mut timed_event_stream = timer::make_async_timed_event_stream(h.time_stream);
3165
3166 let tx_eapol_frame_update_sink = vec![SecAssocUpdate::TxEapolKeyFrame {
3168 frame: test_utils::eapol_key_frame(),
3169 expect_response: true,
3170 }];
3171 suppl_mock.set_on_eapol_frame_updates(tx_eapol_frame_update_sink.clone());
3172
3173 let eapol_ind = MlmeEvent::EapolInd {
3175 ind: fidl_mlme::EapolIndication {
3176 src_addr: bss.bssid.to_array(),
3177 dst_addr: fake_device_info().sta_addr,
3178 data: test_utils::eapol_key_frame().into(),
3179 },
3180 };
3181
3182 let rsna_response_deadline =
3183 zx::MonotonicDuration::from_millis(event::RSNA_RESPONSE_TIMEOUT_MILLIS).after_now();
3184
3185 let mut state = state.on_mlme_event(eapol_ind, &mut h.context);
3186
3187 let mock_number_of_retransmissions = 5;
3189 for i in 0..=mock_number_of_retransmissions {
3190 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
3191 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3192
3193 let rsna_retransmission_deadline =
3194 zx::MonotonicDuration::from_millis(event::RSNA_RETRANSMISSION_TIMEOUT_MILLIS)
3195 .after_now();
3196 let timed_event = expect_next_event_at_deadline(
3197 &mut h.executor,
3198 &mut timed_event_stream,
3199 rsna_retransmission_deadline,
3200 );
3201 assert_variant!(timed_event.event, Event::RsnaRetransmissionTimeout(_));
3202
3203 if i < mock_number_of_retransmissions {
3204 suppl_mock
3205 .set_on_rsna_retransmission_timeout_updates(tx_eapol_frame_update_sink.clone());
3206 }
3207 state = state.handle_timeout(timed_event.event, &mut h.context);
3208 }
3209
3210 let timeout = expect_next_event_at_deadline(
3212 &mut h.executor,
3213 &mut timed_event_stream,
3214 rsna_response_deadline,
3215 );
3216 assert_variant!(timeout.event, Event::RsnaResponseTimeout(..));
3217 suppl_mock.set_on_rsna_response_timeout(EstablishRsnaFailureReason::RsnaResponseTimeout(
3218 wlan_rsn::Error::EapolHandshakeIncomplete("PTKSA never initialized".to_string()),
3219 ));
3220 let state = state.handle_timeout(timeout.event, &mut h.context);
3221
3222 expect_deauth_req(&mut h.mlme_stream, bss.bssid, fidl_ieee80211::ReasonCode::StaLeaving);
3223 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
3224 assert_eq!(result, EstablishRsnaFailure {
3225 auth_method: Some(auth::MethodName::Psk),
3226 reason: EstablishRsnaFailureReason::RsnaResponseTimeout(wlan_rsn::Error::EapolHandshakeIncomplete("PTKSA never initialized".to_string())),
3227 }.into());
3228 });
3229
3230 let deauth_conf = MlmeEvent::DeauthenticateConf {
3232 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: bss.bssid.to_array() },
3233 };
3234 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3235 assert_idle(state);
3236
3237 assert_data_tree!(h.inspector, root: contains {
3238 usme: contains {
3239 state_events: {
3240 "0": {
3241 "@time": AnyNumericProperty,
3242 ctx: AnyStringProperty,
3243 from: CONNECTING_STATE,
3244 to: RSNA_STATE,
3245 },
3246 "1": {
3247 "@time": AnyNumericProperty,
3248 ctx: AnyStringProperty,
3249 from: RSNA_STATE,
3250 to: DISCONNECTING_STATE,
3251 },
3252 "2": {
3253 "@time": AnyNumericProperty,
3254 from: DISCONNECTING_STATE,
3255 to: IDLE_STATE,
3256 },
3257 },
3258 rsn_events: {
3259 "0": {
3260 "@time": AnyNumericProperty,
3261 "rsna_status": "PmkSaEstablished"
3262 },
3263 "1": {
3264 "@time": AnyNumericProperty,
3265 "rx_eapol_frame": AnyBytesProperty,
3266 "status": "processed",
3267 },
3268 "2": {
3269 "@time": AnyNumericProperty,
3270 "tx_eapol_frame": AnyBytesProperty,
3271 },
3272 "3": {
3273 "@time": AnyNumericProperty,
3274 "tx_eapol_frame": AnyBytesProperty,
3275 },
3276 "4": {
3277 "@time": AnyNumericProperty,
3278 "tx_eapol_frame": AnyBytesProperty,
3279 },
3280 "5": {
3281 "@time": AnyNumericProperty,
3282 "tx_eapol_frame": AnyBytesProperty,
3283 },
3284 "6": {
3285 "@time": AnyNumericProperty,
3286 "tx_eapol_frame": AnyBytesProperty,
3287 },
3288 "7": {
3289 "@time": AnyNumericProperty,
3290 "tx_eapol_frame": AnyBytesProperty,
3291 }
3292 }
3293 },
3294 });
3295 }
3296
3297 #[test]
3298 fn simple_completion_timeout_with_responsive_ap() {
3299 let mut h = TestHelper::new_with_fake_time();
3300 h.executor.set_fake_time(fuchsia_async::MonotonicInstant::from_nanos(0));
3301
3302 let (supplicant, suppl_mock) = mock_psk_supplicant();
3303 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
3304 let bss = command.bss.clone();
3305
3306 let state = ClientState::from(testing::new_state(Connecting {
3308 cfg: ClientConfig::default(),
3309 cmd: command,
3310 protection_ie: None,
3311 reassociation_loop_count: 0,
3312 }));
3313 suppl_mock
3314 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
3315 let assoc_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
3316 let state = state.on_mlme_event(assoc_conf, &mut h.context);
3317 assert!(suppl_mock.is_supplicant_started());
3318 let rsna_completion_deadline =
3319 zx::MonotonicDuration::from_millis(event::RSNA_COMPLETION_TIMEOUT_MILLIS).after_now();
3320
3321 let mut timed_event_stream = timer::make_async_timed_event_stream(h.time_stream);
3322
3323 let tx_eapol_frame_update_sink = vec![SecAssocUpdate::TxEapolKeyFrame {
3325 frame: test_utils::eapol_key_frame(),
3326 expect_response: true,
3327 }];
3328 suppl_mock.set_on_eapol_frame_updates(tx_eapol_frame_update_sink.clone());
3329
3330 let eapol_ind = fidl_mlme::EapolIndication {
3332 src_addr: bss.bssid.to_array(),
3333 dst_addr: fake_device_info().sta_addr,
3334 data: test_utils::eapol_key_frame().into(),
3335 };
3336 let mut state =
3337 state.on_mlme_event(MlmeEvent::EapolInd { ind: eapol_ind.clone() }, &mut h.context);
3338 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
3339 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3340
3341 let mut rsna_retransmission_deadline =
3342 zx::MonotonicDuration::from_millis(event::RSNA_RETRANSMISSION_TIMEOUT_MILLIS)
3343 .after_now();
3344 let mut rsna_response_deadline =
3345 zx::MonotonicDuration::from_millis(event::RSNA_RESPONSE_TIMEOUT_MILLIS).after_now();
3346
3347 let mock_just_before_progress_frames = 2;
3348 let just_before_duration = zx::MonotonicDuration::from_nanos(1);
3349 for _ in 0..mock_just_before_progress_frames {
3350 let timed_event = expect_next_event_at_deadline(
3352 &mut h.executor,
3353 &mut timed_event_stream,
3354 rsna_retransmission_deadline,
3355 );
3356 assert_variant!(timed_event.event, Event::RsnaRetransmissionTimeout(_));
3357 state = state.handle_timeout(timed_event.event, &mut h.context);
3358 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3359
3360 h.executor.set_fake_time(rsna_response_deadline - just_before_duration);
3362 assert!(!h.executor.wake_expired_timers());
3363 suppl_mock.set_on_eapol_frame_updates(tx_eapol_frame_update_sink.clone());
3365 state =
3366 state.on_mlme_event(MlmeEvent::EapolInd { ind: eapol_ind.clone() }, &mut h.context);
3367 expect_eapol_req(&mut h.mlme_stream, bss.bssid);
3368 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3369 rsna_retransmission_deadline =
3370 zx::MonotonicDuration::from_millis(event::RSNA_RETRANSMISSION_TIMEOUT_MILLIS)
3371 .after_now();
3372 rsna_response_deadline =
3373 zx::MonotonicDuration::from_millis(event::RSNA_RESPONSE_TIMEOUT_MILLIS).after_now();
3374 }
3375
3376 let timed_event = expect_next_event_at_deadline(
3378 &mut h.executor,
3379 &mut timed_event_stream,
3380 rsna_retransmission_deadline,
3381 );
3382 assert_variant!(timed_event.event, Event::RsnaRetransmissionTimeout(_));
3383 state = state.handle_timeout(timed_event.event, &mut h.context);
3384 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3385
3386 let timed_event = expect_next_event_at_deadline(
3388 &mut h.executor,
3389 &mut timed_event_stream,
3390 rsna_completion_deadline,
3391 );
3392 assert_variant!(timed_event.event, Event::RsnaCompletionTimeout(RsnaCompletionTimeout {}));
3393 suppl_mock.set_on_rsna_completion_timeout(
3394 EstablishRsnaFailureReason::RsnaCompletionTimeout(
3395 wlan_rsn::Error::EapolHandshakeIncomplete("PTKSA never initialized".to_string()),
3396 ),
3397 );
3398 let state = state.handle_timeout(timed_event.event, &mut h.context);
3399
3400 expect_deauth_req(&mut h.mlme_stream, bss.bssid, fidl_ieee80211::ReasonCode::StaLeaving);
3402 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
3403 assert_eq!(result, EstablishRsnaFailure {
3404 auth_method: Some(auth::MethodName::Psk),
3405 reason: EstablishRsnaFailureReason::RsnaCompletionTimeout(wlan_rsn::Error::EapolHandshakeIncomplete("PTKSA never initialized".to_string())),
3406 }.into());
3407 });
3408
3409 let deauth_conf = MlmeEvent::DeauthenticateConf {
3411 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: bss.bssid.to_array() },
3412 };
3413 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3414 assert_idle(state);
3415
3416 assert_data_tree!(h.inspector, root: contains {
3417 usme: contains {
3418 state_events: {
3419 "0": {
3420 "@time": AnyNumericProperty,
3421 ctx: AnyStringProperty,
3422 from: CONNECTING_STATE,
3423 to: RSNA_STATE,
3424 },
3425 "1": {
3426 "@time": AnyNumericProperty,
3427 ctx: AnyStringProperty,
3428 from: RSNA_STATE,
3429 to: DISCONNECTING_STATE,
3430 },
3431 "2": {
3432 "@time": AnyNumericProperty,
3433 from: DISCONNECTING_STATE,
3434 to: IDLE_STATE,
3435 },
3436 },
3437 rsn_events: {
3438 "0": {
3439 "@time": AnyNumericProperty,
3440 "rsna_status": "PmkSaEstablished"
3441 },
3442 "1": {
3443 "@time": AnyNumericProperty,
3444 "rx_eapol_frame": AnyBytesProperty,
3445 "status": "processed",
3446 },
3447 "2": {
3448 "@time": AnyNumericProperty,
3449 "tx_eapol_frame": AnyBytesProperty,
3450 },
3451 "3": {
3452 "@time": AnyNumericProperty,
3453 "rx_eapol_frame": AnyBytesProperty,
3454 "status": "processed",
3455 },
3456 "4": {
3457 "@time": AnyNumericProperty,
3458 "tx_eapol_frame": AnyBytesProperty,
3459 },
3460 "5": {
3461 "@time": AnyNumericProperty,
3462 "rx_eapol_frame": AnyBytesProperty,
3463 "status": "processed",
3464 },
3465 "6": {
3466 "@time": AnyNumericProperty,
3467 "tx_eapol_frame": AnyBytesProperty,
3468 },
3469 }
3470 },
3471 });
3472 }
3473
3474 #[test]
3475 fn gtk_rotation_during_link_up() {
3476 let mut h = TestHelper::new();
3477 let (supplicant, suppl_mock) = mock_psk_supplicant();
3478 let (cmd, mut connect_txn_stream) = connect_command_wpa2(supplicant);
3479 let bssid = cmd.bss.bssid;
3480 let state = link_up_state(cmd);
3481
3482 let key_frame = SecAssocUpdate::TxEapolKeyFrame {
3484 frame: test_utils::eapol_key_frame(),
3485 expect_response: true,
3486 };
3487 let gtk = SecAssocUpdate::Key(Key::Gtk(test_utils::gtk()));
3488 let mut state = on_eapol_ind(state, &mut h, bssid, &suppl_mock, vec![key_frame, gtk]);
3489
3490 expect_eapol_req(&mut h.mlme_stream, bssid);
3492 expect_set_gtk(&mut h.mlme_stream);
3493 expect_stream_empty(&mut h.mlme_stream, "unexpected event in mlme stream");
3494 assert_variant!(&state, ClientState::Associated(state) => {
3495 assert_variant!(&state.link_state, LinkState::LinkUp { .. });
3496 });
3497
3498 let (_, timed_event, _) = h.time_stream.try_next().unwrap().expect("expect timed event");
3500 state = state.handle_timeout(timed_event.event, &mut h.context);
3501 assert_variant!(&state, ClientState::Associated(state) => {
3502 assert_variant!(&state.link_state, LinkState::LinkUp { .. });
3503 });
3504
3505 assert_variant!(connect_txn_stream.try_next(), Err(_));
3507 }
3508
3509 #[test]
3510 fn connect_while_link_up() {
3511 let mut h = TestHelper::new();
3512 let (cmd1, mut connect_txn_stream1) = connect_command_one();
3513 let (cmd2, mut connect_txn_stream2) = connect_command_two();
3514 let state = link_up_state(cmd1);
3515 let state = state.connect(cmd2, &mut h.context);
3516 let state = exchange_deauth(state, &mut h);
3517
3518 assert_variant!(connect_txn_stream1.try_next(), Ok(None));
3520 assert_variant!(connect_txn_stream2.try_next(), Ok(Some(_)) | Err(_));
3522
3523 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(req))) => {
3525 assert_eq!(req.selected_bss, (*connect_command_two().0.bss).into());
3526 });
3527 assert_connecting(state, &connect_command_two().0.bss);
3528 }
3529
3530 fn expect_state_events_link_up_roaming_link_up(
3531 inspector: &Inspector,
3532 selected_bss: BssDescription,
3533 ) {
3534 assert_data_tree!(inspector, root: contains {
3535 usme: contains {
3536 state_events: {
3537 "0": {
3538 "@time": AnyNumericProperty,
3539 bssid: selected_bss.bssid.to_string(),
3540 ctx: AnyStringProperty,
3541 from: LINK_UP_STATE,
3542 to: ROAMING_STATE,
3543 },
3544 "1": {
3545 "@time": AnyNumericProperty,
3546 bssid: selected_bss.bssid.to_string(),
3547 ctx: AnyStringProperty,
3548 from: ROAMING_STATE,
3549 ssid: selected_bss.ssid.to_string(),
3550 to: LINK_UP_STATE,
3551 },
3552 },
3553 },
3554 });
3555 }
3556
3557 #[test]
3558 fn fullmac_initiated_roam_happy_path_unprotected() {
3559 let mut h = TestHelper::new();
3560 let (cmd, mut connect_txn_stream) = connect_command_one();
3561 let mut selected_bss = cmd.bss.clone();
3562 let state = link_up_state(cmd);
3563 let selected_bssid = [0, 1, 2, 3, 4, 5];
3564 selected_bss.bssid = selected_bssid.into();
3565 #[allow(
3566 clippy::redundant_field_names,
3567 reason = "mass allow for https://fxbug.dev/381896734"
3568 )]
3569 let roam_start_ind = MlmeEvent::RoamStartInd {
3570 ind: fidl_mlme::RoamStartIndication {
3571 selected_bssid: selected_bssid,
3572 original_association_maintained: false,
3573 selected_bss: (*selected_bss).clone().into(),
3574 },
3575 };
3576 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
3577 assert_roaming(&state);
3578
3579 let mut association_ies = vec![];
3580 association_ies.extend_from_slice(selected_bss.ies());
3581 let ind = fidl_mlme::RoamResultIndication {
3582 selected_bssid,
3583 status_code: fidl_ieee80211::StatusCode::Success,
3584 original_association_maintained: false,
3585 target_bss_authenticated: true,
3586 association_id: 42,
3587 association_ies,
3588 };
3589 let roam_result_ind_event = MlmeEvent::RoamResultInd { ind: ind.clone() };
3590 let state = state.on_mlme_event(roam_result_ind_event, &mut h.context);
3591 assert_variant!(&state, ClientState::Associated(state) => {
3592 assert_variant!(&state.link_state, LinkState::LinkUp { .. });
3593 });
3594
3595 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnRoamResult {result})) => {
3597 assert_eq!(result, RoamResult::Success(Box::new(*selected_bss.clone())));
3598 });
3599
3600 expect_state_events_link_up_roaming_link_up(&h.inspector, *selected_bss.clone());
3601 }
3602
3603 #[test]
3604 fn policy_initiated_roam_happy_path_unprotected() {
3605 let mut h = TestHelper::new();
3606 let (cmd, mut connect_txn_stream) = connect_command_one();
3607 let mut selected_bss = cmd.bss.clone();
3608 let state = link_up_state(cmd);
3609 let selected_bssid = [0, 1, 2, 3, 4, 5];
3610 selected_bss.bssid = selected_bssid.into();
3611
3612 let fidl_selected_bss = fidl_common::BssDescription::from(*selected_bss.clone());
3613 let state = state.roam(&mut h.context, fidl_selected_bss);
3614
3615 assert_roaming(&state);
3616
3617 let mut association_ies = vec![];
3618 association_ies.extend_from_slice(selected_bss.ies());
3619 let conf = fidl_mlme::RoamConfirm {
3620 selected_bssid,
3621 status_code: fidl_ieee80211::StatusCode::Success,
3622 original_association_maintained: false,
3623 target_bss_authenticated: true,
3624 association_id: 42,
3625 association_ies,
3626 };
3627 let roam_conf_event = MlmeEvent::RoamConf { conf: conf.clone() };
3628 let state = state.on_mlme_event(roam_conf_event, &mut h.context);
3629 assert_variant!(&state, ClientState::Associated(state) => {
3630 assert_variant!(&state.link_state, LinkState::LinkUp { .. });
3631 });
3632
3633 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnRoamResult {result})) => {
3635 assert_eq!(result, RoamResult::Success(Box::new(*selected_bss.clone())));
3636 });
3637
3638 expect_state_events_link_up_roaming_link_up(&h.inspector, *selected_bss.clone());
3639 }
3640
3641 fn expect_state_events_link_up_roaming_rsna(
3642 inspector: &Inspector,
3643 selected_bss: BssDescription,
3644 ) {
3645 assert_data_tree!(inspector, root: contains {
3646 usme: contains {
3647 state_events: {
3648 "0": {
3649 "@time": AnyNumericProperty,
3650 bssid: selected_bss.bssid.to_string(),
3651 ctx: AnyStringProperty,
3652 from: LINK_UP_STATE,
3653 to: ROAMING_STATE,
3654 },
3655 "1": {
3656 "@time": AnyNumericProperty,
3657 bssid: selected_bss.bssid.to_string(),
3658 ctx: AnyStringProperty,
3659 from: ROAMING_STATE,
3660 ssid: selected_bss.ssid.to_string(),
3661 to: RSNA_STATE,
3662 },
3663 },
3664 },
3665 });
3666 }
3667
3668 #[test]
3669 fn fullmac_initiated_roam_happy_path_protected() {
3670 let mut h = TestHelper::new();
3671 let (supplicant, suppl_mock) = mock_psk_supplicant();
3672
3673 let (cmd, _) = connect_command_wpa2(supplicant);
3674 let bss = (*cmd.bss).clone();
3675 let mut selected_bss = bss.clone();
3676 let selected_bssid = [1, 2, 3, 4, 5, 6];
3677 selected_bss.bssid = selected_bssid.into();
3678 let association_ies = selected_bss.ies().to_vec();
3679
3680 let state = link_up_state(cmd);
3681 #[allow(
3683 clippy::redundant_field_names,
3684 reason = "mass allow for https://fxbug.dev/381896734"
3685 )]
3686 let roam_start_ind = MlmeEvent::RoamStartInd {
3687 ind: fidl_mlme::RoamStartIndication {
3688 selected_bssid: selected_bssid,
3689 original_association_maintained: false,
3690 selected_bss: selected_bss.clone().into(),
3691 },
3692 };
3693 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
3694 assert_roaming(&state);
3695
3696 suppl_mock
3698 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
3699
3700 let ind = fidl_mlme::RoamResultIndication {
3701 selected_bssid,
3702 status_code: fidl_ieee80211::StatusCode::Success,
3703 original_association_maintained: false,
3704 target_bss_authenticated: true,
3705 association_id: 42,
3706 association_ies,
3707 };
3708 let roam_result_ind_event = MlmeEvent::RoamResultInd { ind: ind.clone() };
3709 let state = state.on_mlme_event(roam_result_ind_event, &mut h.context);
3710
3711 assert_variant!(&state, ClientState::Associated(state) => {
3712 assert_variant!(&state.link_state, LinkState::EstablishingRsna { .. });
3713 });
3714
3715 expect_state_events_link_up_roaming_rsna(&h.inspector, selected_bss);
3716
3717 }
3720
3721 #[test]
3722 fn policy_initiated_roam_happy_path_protected() {
3723 let mut h = TestHelper::new();
3724 let (supplicant, suppl_mock) = mock_psk_supplicant();
3725
3726 let (cmd, _) = connect_command_wpa2(supplicant);
3727 let bss = (*cmd.bss).clone();
3728 let mut selected_bss = bss.clone();
3729 let selected_bssid = [1, 2, 3, 4, 5, 6];
3730 selected_bss.bssid = selected_bssid.into();
3731 let association_ies = selected_bss.ies().to_vec();
3732
3733 let state = link_up_state(cmd);
3734 let fidl_selected_bss = fidl_common::BssDescription::from(selected_bss.clone());
3736 let state = state.roam(&mut h.context, fidl_selected_bss);
3737
3738 assert_roaming(&state);
3739
3740 suppl_mock
3742 .set_start_updates(vec![SecAssocUpdate::Status(SecAssocStatus::PmkSaEstablished)]);
3743
3744 let conf = fidl_mlme::RoamConfirm {
3745 selected_bssid,
3746 status_code: fidl_ieee80211::StatusCode::Success,
3747 original_association_maintained: false,
3748 target_bss_authenticated: true,
3749 association_id: 42,
3750 association_ies,
3751 };
3752 let roam_conf_event = MlmeEvent::RoamConf { conf: conf.clone() };
3753 let state = state.on_mlme_event(roam_conf_event, &mut h.context);
3754
3755 assert_variant!(&state, ClientState::Associated(state) => {
3756 assert_variant!(&state.link_state, LinkState::EstablishingRsna { .. });
3757 });
3758
3759 expect_state_events_link_up_roaming_rsna(&h.inspector, selected_bss);
3760
3761 }
3764
3765 fn expect_roam_failure_emitted(
3766 failure_type: RoamFailureType,
3767 status_code: fidl_ieee80211::StatusCode,
3768 selected_bssid: [u8; 6],
3769 mlme_event_name: fidl_sme::DisconnectMlmeEventName,
3770 connect_txn_stream: &mut ConnectTransactionStream,
3771 ) {
3772 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnRoamResult { result })) => {
3773 assert_variant!(result, RoamResult::Failed(failure) => {
3774 assert_eq!(failure.failure_type, failure_type);
3775 assert_eq!(failure.status_code, status_code);
3776 assert_eq!(failure.selected_bssid, selected_bssid.into());
3777 assert_variant!(failure.disconnect_info.disconnect_source, fidl_sme::DisconnectSource::Mlme(cause) => {
3778 assert_eq!(cause.mlme_event_name, mlme_event_name);
3779 });
3780 });
3781 });
3782 }
3783
3784 fn malformed_bss_description() -> fidl_common::BssDescription {
3786 fidl_common::BssDescription {
3787 bssid: [0, 0, 0, 0, 0, 0],
3788 bss_type: fidl_common::BssType::Infrastructure,
3789 beacon_period: 0,
3790 capability_info: 0,
3791 ies: Vec::new(),
3792 channel: fidl_common::WlanChannel {
3793 cbw: fidl_common::ChannelBandwidth::Cbw20,
3794 primary: 0,
3795 secondary80: 0,
3796 },
3797 rssi_dbm: 0,
3798 snr_db: 0,
3799 }
3800 }
3801
3802 fn expect_state_events_link_up_disconnecting_idle(inspector: &Inspector) {
3803 assert_data_tree!(inspector, root: contains {
3804 usme: contains {
3805 state_events: {
3806 "0": {
3807 "@time": AnyNumericProperty,
3808 ctx: AnyStringProperty,
3809 from: LINK_UP_STATE,
3810 to: DISCONNECTING_STATE,
3811 },
3812 "1": {
3813 "@time": AnyNumericProperty,
3814 from: DISCONNECTING_STATE,
3815 to: IDLE_STATE,
3816 },
3817 },
3818 },
3819 });
3820 }
3821
3822 #[test]
3823 fn malformed_roam_start_ind_causes_disconnect() {
3824 let mut h = TestHelper::new();
3825 let (cmd, mut connect_txn_stream) = connect_command_one();
3826 let state = link_up_state(cmd);
3827 let selected_bss = malformed_bss_description();
3830 let selected_bssid = [0, 1, 2, 3, 4, 5];
3832 let roam_start_ind = MlmeEvent::RoamStartInd {
3833 ind: fidl_mlme::RoamStartIndication {
3834 selected_bssid,
3835 original_association_maintained: false,
3836 selected_bss,
3837 },
3838 };
3839 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
3840
3841 expect_deauth_req(
3843 &mut h.mlme_stream,
3844 selected_bssid.into(),
3845 fidl_ieee80211::ReasonCode::StaLeaving,
3846 );
3847
3848 let deauth_conf = MlmeEvent::DeauthenticateConf {
3850 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: selected_bssid },
3851 };
3852 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3853 assert_idle(state);
3854
3855 expect_roam_failure_emitted(
3857 RoamFailureType::RoamStartMalformedFailure,
3858 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
3859 selected_bssid,
3860 fidl_sme::DisconnectMlmeEventName::RoamStartIndication,
3861 &mut connect_txn_stream,
3862 );
3863
3864 expect_state_events_link_up_disconnecting_idle(&h.inspector);
3865 }
3866
3867 #[test]
3868 fn malformed_roam_req_causes_disconnect() {
3869 let mut h = TestHelper::new();
3870 let (cmd, mut connect_txn_stream) = connect_command_one();
3871 let original_bssid = cmd.bss.bssid;
3872 let state = link_up_state(cmd);
3873 let selected_bss = malformed_bss_description();
3876
3877 let state = state.roam(&mut h.context, selected_bss.clone());
3878
3879 expect_deauth_req(
3881 &mut h.mlme_stream,
3882 original_bssid,
3883 fidl_ieee80211::ReasonCode::StaLeaving,
3884 );
3885
3886 let deauth_conf = MlmeEvent::DeauthenticateConf {
3888 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: original_bssid.to_array() },
3889 };
3890 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3891 assert_idle(state);
3892
3893 expect_roam_failure_emitted(
3895 RoamFailureType::RoamRequestMalformedFailure,
3896 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
3897 selected_bss.bssid,
3898 fidl_sme::DisconnectMlmeEventName::RoamRequest,
3899 &mut connect_txn_stream,
3900 );
3901
3902 expect_state_events_link_up_disconnecting_idle(&h.inspector);
3903 }
3904
3905 #[test]
3906 fn roam_req_with_incorrect_security_ies_causes_disconnect_on_protected_network() {
3907 let mut h = TestHelper::new();
3908 let (supplicant, _suppl_mock) = mock_psk_supplicant();
3909
3910 let (cmd, mut connect_txn_stream) = connect_command_wpa2(supplicant);
3911 let original_bssid = cmd.bss.bssid;
3912
3913 let mut selected_bss = fake_bss_description!(Wpa1, ssid: Ssid::try_from("wpa2").unwrap());
3915 let selected_bssid = [3, 2, 1, 0, 9, 8];
3916 selected_bss.bssid = selected_bssid.into();
3917
3918 let state = link_up_state(cmd);
3919
3920 let state = state.roam(&mut h.context, selected_bss.into());
3921
3922 expect_deauth_req(
3924 &mut h.mlme_stream,
3925 original_bssid,
3926 fidl_ieee80211::ReasonCode::StaLeaving,
3927 );
3928
3929 let deauth_conf = MlmeEvent::DeauthenticateConf {
3931 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: original_bssid.to_array() },
3932 };
3933 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3934 assert_idle(state);
3935
3936 expect_roam_failure_emitted(
3937 RoamFailureType::SelectNetworkFailure,
3938 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
3939 selected_bssid,
3940 fidl_sme::DisconnectMlmeEventName::RoamRequest,
3941 &mut connect_txn_stream,
3942 );
3943
3944 expect_state_events_link_up_disconnecting_idle(&h.inspector);
3945 }
3946
3947 #[test]
3948 fn roam_start_ind_with_incorrect_security_ies_causes_disconnect_on_protected_network() {
3949 let mut h = TestHelper::new();
3950 let (supplicant, _suppl_mock) = mock_psk_supplicant();
3951
3952 let (cmd, mut connect_txn_stream) = connect_command_wpa2(supplicant);
3953 let mut selected_bss = fake_bss_description!(Wpa1, ssid: Ssid::try_from("wpa2").unwrap());
3955 let selected_bssid = [3, 2, 1, 0, 9, 8];
3956 selected_bss.bssid = selected_bssid.into();
3957
3958 let state = link_up_state(cmd);
3959 let roam_start_ind = MlmeEvent::RoamStartInd {
3960 ind: fidl_mlme::RoamStartIndication {
3961 selected_bssid,
3962 original_association_maintained: false,
3963 selected_bss: selected_bss.into(),
3964 },
3965 };
3966 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
3967
3968 expect_deauth_req(
3970 &mut h.mlme_stream,
3971 selected_bssid.into(),
3972 fidl_ieee80211::ReasonCode::StaLeaving,
3973 );
3974
3975 let deauth_conf = MlmeEvent::DeauthenticateConf {
3977 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: selected_bssid },
3978 };
3979 let state = state.on_mlme_event(deauth_conf, &mut h.context);
3980 assert_idle(state);
3981
3982 expect_roam_failure_emitted(
3983 RoamFailureType::SelectNetworkFailure,
3984 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
3985 selected_bssid,
3986 fidl_sme::DisconnectMlmeEventName::RoamStartIndication,
3987 &mut connect_txn_stream,
3988 );
3989
3990 expect_state_events_link_up_disconnecting_idle(&h.inspector);
3991 }
3992
3993 #[test]
3994 fn roam_start_ind_ignored_while_idle() {
3995 let mut h = TestHelper::new();
3996 let (cmd, mut connect_txn_stream) = connect_command_one();
3997 let mut selected_bss = cmd.bss.clone();
3998 let state = idle_state();
3999 let selected_bssid = [0, 1, 2, 3, 4, 5];
4000 selected_bss.bssid = selected_bssid.into();
4001 let roam_start_ind = MlmeEvent::RoamStartInd {
4002 ind: fidl_mlme::RoamStartIndication {
4003 selected_bssid,
4004 original_association_maintained: false,
4005 selected_bss: (*selected_bss).into(),
4006 },
4007 };
4008 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
4009 assert_idle(state);
4010
4011 assert_variant!(connect_txn_stream.try_next(), Err(_));
4012 }
4013
4014 #[test]
4015 fn roam_req_ignored_while_idle() {
4016 let mut h = TestHelper::new();
4017 let (cmd, mut connect_txn_stream) = connect_command_one();
4018 let mut selected_bss = cmd.bss.clone();
4019 let state = idle_state();
4020 let selected_bssid = [0, 1, 2, 3, 4, 5];
4021 selected_bss.bssid = selected_bssid.into();
4022 let fidl_selected_bss = fidl_common::BssDescription::from(*selected_bss.clone());
4023
4024 let state = state.roam(&mut h.context, fidl_selected_bss);
4025
4026 assert_idle(state);
4027
4028 assert_variant!(connect_txn_stream.try_next(), Err(_));
4029 }
4030
4031 #[test]
4032 fn roam_start_ind_ignored_while_connecting() {
4033 let mut h = TestHelper::new();
4034 let (cmd, mut connect_txn_stream) = connect_command_one();
4035 let original_bss = cmd.bss.clone();
4036 let mut selected_bss = cmd.bss.clone();
4037 let state = connecting_state(cmd);
4038 let selected_bssid = [0, 1, 2, 3, 4, 5];
4039 selected_bss.bssid = selected_bssid.into();
4040 let roam_start_ind = MlmeEvent::RoamStartInd {
4041 ind: fidl_mlme::RoamStartIndication {
4042 selected_bssid,
4043 original_association_maintained: false,
4044 selected_bss: (*selected_bss).into(),
4045 },
4046 };
4047 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
4048 assert_connecting(state, &original_bss);
4049
4050 assert_variant!(connect_txn_stream.try_next(), Ok(None));
4052 }
4053
4054 #[test]
4055 fn roam_req_ignored_while_connecting() {
4056 let mut h = TestHelper::new();
4057 let (cmd, mut connect_txn_stream) = connect_command_one();
4058 let original_bss = cmd.bss.clone();
4059 let mut selected_bss = cmd.bss.clone();
4060 let state = connecting_state(cmd);
4061 let selected_bssid = [0, 1, 2, 3, 4, 5];
4062 selected_bss.bssid = selected_bssid.into();
4063 let fidl_selected_bss = fidl_common::BssDescription::from(*selected_bss.clone());
4064
4065 let state = state.roam(&mut h.context, fidl_selected_bss);
4066
4067 assert_connecting(state, &original_bss);
4068
4069 assert_variant!(connect_txn_stream.try_next(), Ok(None));
4071 }
4072
4073 #[test]
4074 fn roam_start_ind_ignored_while_disconnecting() {
4075 let mut h = TestHelper::new();
4076 let (cmd, mut connect_txn_stream) = connect_command_one();
4077 let mut selected_bss = cmd.bss.clone();
4078 let state = disconnecting_state(PostDisconnectAction::BeginConnect { cmd });
4079 let selected_bssid = [0, 1, 2, 3, 4, 5];
4080 selected_bss.bssid = selected_bssid.into();
4081 let roam_start_ind = MlmeEvent::RoamStartInd {
4082 ind: fidl_mlme::RoamStartIndication {
4083 selected_bssid,
4084 original_association_maintained: false,
4085 selected_bss: (*selected_bss).into(),
4086 },
4087 };
4088 let state = state.on_mlme_event(roam_start_ind, &mut h.context);
4089 assert_disconnecting(state);
4090
4091 assert_variant!(connect_txn_stream.try_next(), Ok(None));
4093 }
4094
4095 #[test]
4096 fn roam_req_ignored_while_disconnecting() {
4097 let mut h = TestHelper::new();
4098 let (cmd, mut connect_txn_stream) = connect_command_one();
4099 let mut selected_bss = cmd.bss.clone();
4100 let state = disconnecting_state(PostDisconnectAction::BeginConnect { cmd });
4101 let selected_bssid = [0, 1, 2, 3, 4, 5];
4102 selected_bss.bssid = selected_bssid.into();
4103 let fidl_selected_bss = fidl_common::BssDescription::from(*selected_bss.clone());
4104
4105 let state = state.roam(&mut h.context, fidl_selected_bss);
4106
4107 assert_disconnecting(state);
4108
4109 assert_variant!(connect_txn_stream.try_next(), Ok(None));
4111 }
4112
4113 fn expect_state_events_roaming_disconnecting_idle(inspector: &Inspector) {
4114 assert_data_tree!(inspector, root: contains {
4115 usme: contains {
4116 state_events: {
4117 "0": {
4118 "@time": AnyNumericProperty,
4119 ctx: AnyStringProperty,
4120 from: ROAMING_STATE,
4121 to: DISCONNECTING_STATE,
4122 },
4123 "1": {
4124 "@time": AnyNumericProperty,
4125 from: DISCONNECTING_STATE,
4126 to: IDLE_STATE,
4127 },
4128 },
4129 },
4130 });
4131 }
4132
4133 #[test]
4134 fn roam_result_ind_with_failure_causes_disconnect() {
4135 let mut h = TestHelper::new();
4136 let (cmd, mut connect_txn_stream) = connect_command_one();
4137 let selected_bssid = [1, 2, 3, 4, 5, 6];
4138 let state = roaming_state(cmd, selected_bssid.into());
4139 let status_code = fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported;
4140 #[allow(
4141 clippy::redundant_field_names,
4142 reason = "mass allow for https://fxbug.dev/381896734"
4143 )]
4144 let roam_result_ind = MlmeEvent::RoamResultInd {
4145 ind: fidl_mlme::RoamResultIndication {
4146 selected_bssid: selected_bssid,
4147 status_code,
4148 original_association_maintained: false,
4149 target_bss_authenticated: true,
4150 association_id: 0,
4151 association_ies: Vec::new(),
4152 },
4153 };
4154 let state = state.on_mlme_event(roam_result_ind, &mut h.context);
4155
4156 expect_deauth_req(
4158 &mut h.mlme_stream,
4159 selected_bssid.into(),
4160 fidl_ieee80211::ReasonCode::StaLeaving,
4161 );
4162
4163 let deauth_conf = MlmeEvent::DeauthenticateConf {
4165 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: selected_bssid },
4166 };
4167 let state = state.on_mlme_event(deauth_conf, &mut h.context);
4168 assert_idle(state);
4169
4170 expect_roam_failure_emitted(
4171 RoamFailureType::ReassociationFailure,
4172 status_code,
4173 selected_bssid,
4174 fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
4175 &mut connect_txn_stream,
4176 );
4177
4178 expect_state_events_roaming_disconnecting_idle(&h.inspector);
4179 }
4180
4181 #[test]
4182 fn malformed_roam_result_ind_causes_disconnect() {
4183 let mut h = TestHelper::new();
4184 let (cmd, mut connect_txn_stream) = connect_command_one();
4185 let selected_bssid = [1, 2, 3, 4, 5, 6];
4186 let mismatched_bssid = [9, 8, 7, 6, 5, 4];
4187 let state = roaming_state(cmd, selected_bssid.into());
4188 let status_code = fidl_ieee80211::StatusCode::Success;
4189 let roam_result_ind = MlmeEvent::RoamResultInd {
4190 ind: fidl_mlme::RoamResultIndication {
4191 selected_bssid: mismatched_bssid,
4192 status_code,
4193 original_association_maintained: false,
4194 target_bss_authenticated: true,
4195 association_id: 0,
4196 association_ies: Vec::new(),
4197 },
4198 };
4199 let state = state.on_mlme_event(roam_result_ind, &mut h.context);
4200
4201 expect_deauth_req(
4203 &mut h.mlme_stream,
4204 selected_bssid.into(),
4205 fidl_ieee80211::ReasonCode::StaLeaving,
4206 );
4207
4208 let deauth_conf = MlmeEvent::DeauthenticateConf {
4210 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: selected_bssid },
4211 };
4212 let state = state.on_mlme_event(deauth_conf, &mut h.context);
4213 assert_idle(state);
4214
4215 expect_roam_failure_emitted(
4216 RoamFailureType::RoamResultMalformedFailure,
4217 fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
4218 selected_bssid,
4219 fidl_sme::DisconnectMlmeEventName::RoamResultIndication,
4220 &mut connect_txn_stream,
4221 );
4222
4223 expect_state_events_roaming_disconnecting_idle(&h.inspector);
4224 }
4225
4226 fn make_disconnect_request(
4227 h: &mut TestHelper,
4228 ) -> (
4229 <fidl_sme::ClientSmeProxy as fidl_sme::ClientSmeProxyInterface>::DisconnectResponseFut,
4230 fidl_sme::ClientSmeDisconnectResponder,
4231 ) {
4232 let (proxy, mut stream) =
4233 fidl::endpoints::create_proxy_and_stream::<fidl_sme::ClientSmeMarker>();
4234 let mut disconnect_fut =
4235 proxy.disconnect(fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme);
4236 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Pending);
4237 let responder = assert_variant!(
4238 h.executor.run_singlethreaded(stream.next()).unwrap().unwrap(),
4239 fidl_sme::ClientSmeRequest::Disconnect{ responder, .. } => responder);
4240 (disconnect_fut, responder)
4241 }
4242
4243 #[test]
4244 fn disconnect_while_idle() {
4245 let mut h = TestHelper::new();
4246 let (mut disconnect_fut, responder) = make_disconnect_request(&mut h);
4247 let new_state = idle_state().disconnect(
4248 &mut h.context,
4249 fidl_sme::UserDisconnectReason::WlanSmeUnitTesting,
4250 responder,
4251 );
4252 assert_idle(new_state);
4253 assert!(h.mlme_stream.try_next().is_err());
4255 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(())));
4256
4257 assert_data_tree!(h.inspector, root: contains {
4258 usme: contains {
4259 state_events: {
4260 "0": {
4261 "@time": AnyNumericProperty,
4262 ctx: AnyStringProperty,
4263 from: IDLE_STATE,
4264 to: IDLE_STATE,
4265 },
4266 }
4267 },
4268 });
4269 }
4270
4271 #[test]
4272 fn disconnect_while_connecting() {
4273 let mut h = TestHelper::new();
4274 let (cmd, mut connect_txn_stream) = connect_command_one();
4275 let state = connecting_state(cmd);
4276 let state = disconnect(state, &mut h, fidl_sme::UserDisconnectReason::WlanSmeUnitTesting);
4277 let state = exchange_deauth(state, &mut h);
4278 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect: false })) => {
4279 assert_eq!(result, ConnectResult::Canceled);
4280 });
4281 assert_idle(state);
4282
4283 assert_data_tree!(h.inspector, root: contains {
4284 usme: contains {
4285 state_events: {
4286 "0": {
4287 "@time": AnyNumericProperty,
4288 ctx: AnyStringProperty,
4289 from: CONNECTING_STATE,
4290 to: DISCONNECTING_STATE,
4291 },
4292 "1": {
4293 "@time": AnyNumericProperty,
4294 from: DISCONNECTING_STATE,
4295 to: IDLE_STATE,
4296 },
4297 },
4298 },
4299 });
4300 }
4301
4302 #[test]
4303 fn disconnect_while_link_up() {
4304 let mut h = TestHelper::new();
4305 let state = link_up_state(connect_command_one().0);
4306 let state = disconnect(state, &mut h, fidl_sme::UserDisconnectReason::FailedToConnect);
4307 let state = exchange_deauth(state, &mut h);
4308 assert_idle(state);
4309
4310 assert_data_tree!(h.inspector, root: contains {
4311 usme: contains {
4312 state_events: {
4313 "0": {
4314 "@time": AnyNumericProperty,
4315 ctx: AnyStringProperty,
4316 from: LINK_UP_STATE,
4317 to: DISCONNECTING_STATE,
4318 },
4319 "1": {
4320 "@time": AnyNumericProperty,
4321 from: DISCONNECTING_STATE,
4322 to: IDLE_STATE,
4323 },
4324 },
4325 },
4326 });
4327 }
4328
4329 #[test]
4330 fn timeout_during_disconnect() {
4331 let mut h = TestHelper::new();
4332 let (cmd, _connect_txn_stream) = connect_command_one();
4333 let mut state = link_up_state(cmd);
4334
4335 let (mut disconnect_fut, responder) = make_disconnect_request(&mut h);
4336 state = state.disconnect(
4337 &mut h.context,
4338 fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme,
4339 responder,
4340 );
4341 assert_variant!(&state, ClientState::Disconnecting(_));
4342
4343 let timed_event =
4344 assert_variant!(h.time_stream.try_next(), Ok(Some((_, timed_event, _))) => timed_event);
4345 assert_variant!(timed_event.event, Event::DeauthenticateTimeout(..));
4346
4347 let state = state.handle_timeout(timed_event.event, &mut h.context);
4348 assert_idle(state);
4349 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(())));
4350
4351 assert_data_tree!(h.inspector, root: contains {
4352 usme: contains {
4353 state_events: {
4354 "0": {
4355 "@time": AnyNumericProperty,
4356 ctx: AnyStringProperty,
4357 from: LINK_UP_STATE,
4358 to: DISCONNECTING_STATE,
4359 },
4360 "1": {
4361 "@time": AnyNumericProperty,
4362 ctx: AnyStringProperty,
4363 from: DISCONNECTING_STATE,
4364 to: IDLE_STATE,
4365 },
4366 },
4367 },
4368 });
4369 }
4370
4371 #[test]
4372 fn new_connect_while_disconnecting() {
4373 let mut h = TestHelper::new();
4374 let (cmd1, _connect_txn_stream) = connect_command_one();
4375 let (cmd2, _connect_txn_stream) = connect_command_two();
4376 let bss2 = cmd2.bss.clone();
4377 let state = link_up_state(cmd1);
4378 let (mut disconnect_fut, responder) = make_disconnect_request(&mut h);
4379 let state = state.disconnect(
4380 &mut h.context,
4381 fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme,
4382 responder,
4383 );
4384
4385 let disconnecting =
4386 assert_variant!(&state, ClientState::Disconnecting(disconnecting) => disconnecting);
4387 assert_variant!(&disconnecting.action, PostDisconnectAction::RespondDisconnect { .. });
4388 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Pending);
4389
4390 let state = state.connect(cmd2, &mut h.context);
4391 let disconnecting =
4392 assert_variant!(&state, ClientState::Disconnecting(disconnecting) => disconnecting);
4393 assert_variant!(&disconnecting.action, PostDisconnectAction::BeginConnect { .. });
4394 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(())));
4395 let state = exchange_deauth(state, &mut h);
4396 assert_connecting(state, &connect_command_two().0.bss);
4397
4398 assert_data_tree!(h.inspector, root: contains {
4399 usme: contains {
4400 state_events: {
4401 "0": {
4402 "@time": AnyNumericProperty,
4403 ctx: AnyStringProperty,
4404 from: LINK_UP_STATE,
4405 to: DISCONNECTING_STATE,
4406 },
4407 "1": {
4408 "@time": AnyNumericProperty,
4409 ctx: AnyStringProperty,
4410 from: DISCONNECTING_STATE,
4411 to: CONNECTING_STATE,
4412 bssid: bss2.bssid.to_string(),
4413 ssid: bss2.ssid.to_string(),
4414 },
4415 },
4416 },
4417 });
4418 }
4419
4420 #[test]
4421 fn disconnect_while_disconnecting_for_pending_connect() {
4422 let mut h = TestHelper::new();
4423 let (cmd, mut connect_txn_stream) = connect_command_one();
4424 let state = disconnecting_state(PostDisconnectAction::BeginConnect { cmd });
4425
4426 let (_fut, responder) = make_disconnect_request(&mut h);
4427 let state = state.disconnect(
4428 &mut h.context,
4429 fidl_sme::UserDisconnectReason::DisconnectDetectedFromSme,
4430 responder,
4431 );
4432 let disconnecting =
4433 assert_variant!(&state, ClientState::Disconnecting(disconnecting) => disconnecting);
4434 assert_variant!(&disconnecting.action, PostDisconnectAction::RespondDisconnect { .. });
4435
4436 let result = assert_variant!(
4437 connect_txn_stream.try_next(),
4438 Ok(Some(ConnectTransactionEvent::OnConnectResult { result, .. })) => result
4439 );
4440 assert_eq!(result, ConnectResult::Canceled);
4441
4442 assert_data_tree!(h.inspector, root: contains {
4443 usme: contains {
4444 state_events: {
4445 "0": {
4446 "@time": AnyNumericProperty,
4447 ctx: AnyStringProperty,
4448 from: DISCONNECTING_STATE,
4449 to: DISCONNECTING_STATE,
4450 }
4451 },
4452 },
4453 });
4454 }
4455
4456 #[test]
4457 fn increment_att_id_on_connect() {
4458 let mut h = TestHelper::new();
4459 let state = idle_state();
4460 assert_eq!(h.context.att_id, 0);
4461
4462 let state = state.connect(connect_command_one().0, &mut h.context);
4463 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(_))));
4464 assert_eq!(h.context.att_id, 1);
4465
4466 let state = disconnect(state, &mut h, fidl_sme::UserDisconnectReason::WlanSmeUnitTesting);
4467 let state = exchange_deauth(state, &mut h);
4468 assert_eq!(h.context.att_id, 1);
4469
4470 let state = state.connect(connect_command_two().0, &mut h.context);
4471 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(_))));
4472 assert_eq!(h.context.att_id, 2);
4473
4474 let state = state.connect(connect_command_one().0, &mut h.context);
4475 let _state = exchange_deauth(state, &mut h);
4476 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Connect(_))));
4477 assert_eq!(h.context.att_id, 3);
4478 }
4479
4480 #[test]
4481 fn increment_att_id_on_disassociate_ind() {
4482 let mut h = TestHelper::new();
4483 let (cmd, _connect_txn_stream) = connect_command_one();
4484 let bss = cmd.bss.clone();
4485 let state = link_up_state(cmd);
4486 assert_eq!(h.context.att_id, 0);
4487
4488 let disassociate_ind = MlmeEvent::DisassociateInd {
4489 ind: fidl_mlme::DisassociateIndication {
4490 peer_sta_address: [0, 0, 0, 0, 0, 0],
4491 reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
4492 locally_initiated: false,
4493 },
4494 };
4495
4496 let state = state.on_mlme_event(disassociate_ind, &mut h.context);
4497 assert_variant!(&state, ClientState::Connecting(connecting) =>
4498 assert_eq!(connecting.reassociation_loop_count, 1));
4499 assert_connecting(state, &bss);
4500 assert_eq!(h.context.att_id, 1);
4501 }
4502
4503 #[test]
4504 fn abort_connect_after_max_associate_retries() {
4505 let mut h = TestHelper::new();
4506 let (supplicant, _suppl_mock) = mock_psk_supplicant();
4507 let (command, mut connect_txn_stream) = connect_command_wpa2(supplicant);
4508 let mut state = establishing_rsna_state(command);
4509
4510 match &mut state {
4511 ClientState::Associated(associated) => {
4512 associated.reassociation_loop_count = MAX_REASSOCIATIONS_WITHOUT_LINK_UP;
4513 }
4514 _ => unreachable!(),
4515 }
4516
4517 let disassociate_ind = MlmeEvent::DisassociateInd {
4518 ind: fidl_mlme::DisassociateIndication {
4519 peer_sta_address: [0, 0, 0, 0, 0, 0],
4520 reason_code: fidl_ieee80211::ReasonCode::UnacceptablePowerCapability,
4521 locally_initiated: true,
4522 },
4523 };
4524 let state = state.on_mlme_event(disassociate_ind, &mut h.context);
4525
4526 assert_disconnecting(state);
4528 let info = assert_variant!(
4529 connect_txn_stream.try_next(),
4530 Ok(Some(ConnectTransactionEvent::OnDisconnect { info })) => info
4531 );
4532 assert!(!info.is_sme_reconnecting);
4533
4534 assert_data_tree!(h.inspector, root: contains {
4535 usme: contains {
4536 state_events: {
4537 "0": {
4538 "@time": AnyNumericProperty,
4539 ctx: AnyStringProperty,
4540 from: RSNA_STATE,
4541 to: DISCONNECTING_STATE,
4542 }
4543 },
4544 },
4545 });
4546 }
4547
4548 #[test]
4549 fn do_not_log_disconnect_ctx_on_disassoc_from_non_link_up() {
4550 let mut h = TestHelper::new();
4551 let (supplicant, _suppl_mock) = mock_psk_supplicant();
4552 let (command, _connect_txn_stream) = connect_command_wpa2(supplicant);
4553 let state = establishing_rsna_state(command);
4554
4555 let disassociate_ind = MlmeEvent::DisassociateInd {
4556 ind: fidl_mlme::DisassociateIndication {
4557 peer_sta_address: [0, 0, 0, 0, 0, 0],
4558 reason_code: fidl_ieee80211::ReasonCode::UnacceptablePowerCapability,
4559 locally_initiated: true,
4560 },
4561 };
4562 let state = state.on_mlme_event(disassociate_ind, &mut h.context);
4563 assert_connecting(
4564 state,
4565 &fake_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap()),
4566 );
4567
4568 assert_data_tree!(h.inspector, root: contains {
4569 usme: contains {
4570 state_events: {
4571 "0": {
4572 "@time": AnyNumericProperty,
4573 ctx: AnyStringProperty,
4574 from: RSNA_STATE,
4575 to: CONNECTING_STATE,
4576 }
4577 },
4578 },
4579 });
4580 }
4581
4582 #[test]
4583 fn disconnect_reported_on_deauth_ind() {
4584 let mut h = TestHelper::new();
4585 let (cmd, mut connect_txn_stream) = connect_command_one();
4586 let state = link_up_state(cmd);
4587
4588 let deauth_ind = MlmeEvent::DeauthenticateInd {
4589 ind: fidl_mlme::DeauthenticateIndication {
4590 peer_sta_address: [0, 0, 0, 0, 0, 0],
4591 reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
4592 locally_initiated: true,
4593 },
4594 };
4595
4596 let _state = state.on_mlme_event(deauth_ind, &mut h.context);
4597 let fidl_info = assert_variant!(
4598 connect_txn_stream.try_next(),
4599 Ok(Some(ConnectTransactionEvent::OnDisconnect { info })) => info
4600 );
4601 assert!(!fidl_info.is_sme_reconnecting);
4602 assert_eq!(
4603 fidl_info.disconnect_source,
4604 fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
4605 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DeauthenticateIndication,
4606 reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
4607 })
4608 );
4609
4610 assert_data_tree!(h.inspector, root: contains {
4611 usme: contains {
4612 state_events: {
4613 "0": {
4614 "@time": AnyNumericProperty,
4615 ctx: AnyStringProperty,
4616 from: LINK_UP_STATE,
4617 to: IDLE_STATE,
4618 }
4619 },
4620 },
4621 });
4622 }
4623
4624 #[test]
4625 fn disconnect_reported_on_disassoc_ind_then_reconnect_successfully() {
4626 let mut h = TestHelper::new();
4627 let (cmd, mut connect_txn_stream) = connect_command_one();
4628 let bss = cmd.bss.clone();
4629 let state = link_up_state(cmd);
4630
4631 let deauth_ind = MlmeEvent::DisassociateInd {
4632 ind: fidl_mlme::DisassociateIndication {
4633 peer_sta_address: [0, 0, 0, 0, 0, 0],
4634 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
4635 locally_initiated: true,
4636 },
4637 };
4638
4639 let state = state.on_mlme_event(deauth_ind, &mut h.context);
4640 let info = assert_variant!(
4641 connect_txn_stream.try_next(),
4642 Ok(Some(ConnectTransactionEvent::OnDisconnect { info })) => info
4643 );
4644 assert!(info.is_sme_reconnecting);
4645 assert_eq!(
4646 info.disconnect_source,
4647 fidl_sme::DisconnectSource::Mlme(fidl_sme::DisconnectCause {
4648 mlme_event_name: fidl_sme::DisconnectMlmeEventName::DisassociateIndication,
4649 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
4650 })
4651 );
4652
4653 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Reconnect(req))) => {
4655 assert_eq!(&req.peer_sta_address, bss.bssid.as_array());
4656 });
4657
4658 let connect_conf = create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::Success);
4660 let _state = state.on_mlme_event(connect_conf, &mut h.context);
4661
4662 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect })) => {
4664 assert_eq!(result, ConnectResult::Success);
4665 assert!(is_reconnect);
4666 });
4667
4668 assert_data_tree!(h.inspector, root: contains {
4669 usme: contains {
4670 state_events: {
4671 "0": {
4672 "@time": AnyNumericProperty,
4673 ctx: AnyStringProperty,
4674 from: LINK_UP_STATE,
4675 to: CONNECTING_STATE,
4676 },
4677 "1": {
4678 "@time": AnyNumericProperty,
4679 ctx: AnyStringProperty,
4680 from: CONNECTING_STATE,
4681 to: LINK_UP_STATE,
4682 }
4683 },
4684 },
4685 });
4686 }
4687
4688 #[test]
4689 fn disconnect_reported_on_disassoc_ind_then_reconnect_unsuccessfully() {
4690 let mut h = TestHelper::new();
4691 let (cmd, mut connect_txn_stream) = connect_command_one();
4692 let bss = cmd.bss.clone();
4693 let state = link_up_state(cmd);
4694
4695 let disassoc_ind = MlmeEvent::DisassociateInd {
4696 ind: fidl_mlme::DisassociateIndication {
4697 peer_sta_address: [0, 0, 0, 0, 0, 0],
4698 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
4699 locally_initiated: true,
4700 },
4701 };
4702
4703 let state = state.on_mlme_event(disassoc_ind, &mut h.context);
4704 assert_variant!(
4705 connect_txn_stream.try_next(),
4706 Ok(Some(ConnectTransactionEvent::OnDisconnect { .. }))
4707 );
4708
4709 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::Reconnect(req))) => {
4711 assert_eq!(&req.peer_sta_address, bss.bssid.as_array());
4712 });
4713
4714 let connect_conf =
4716 create_connect_conf(bss.bssid, fidl_ieee80211::StatusCode::RefusedReasonUnspecified);
4717 let state = state.on_mlme_event(connect_conf, &mut h.context);
4718
4719 let state = exchange_deauth(state, &mut h);
4720 assert_idle(state);
4721
4722 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnConnectResult { result, is_reconnect })) => {
4724 assert_eq!(result, AssociationFailure {
4725 bss_protection: BssProtection::Open,
4726 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
4727 }.into());
4728 assert!(is_reconnect);
4729 });
4730
4731 assert_data_tree!(h.inspector, root: contains {
4732 usme: contains {
4733 state_events: {
4734 "0": {
4735 "@time": AnyNumericProperty,
4736 ctx: AnyStringProperty,
4737 from: LINK_UP_STATE,
4738 to: CONNECTING_STATE,
4739 },
4740 "1": {
4741 "@time": AnyNumericProperty,
4742 ctx: AnyStringProperty,
4743 from: CONNECTING_STATE,
4744 to: DISCONNECTING_STATE,
4745 },
4746 "2": {
4747 "@time": AnyNumericProperty,
4748 from: DISCONNECTING_STATE,
4749 to: IDLE_STATE,
4750 },
4751 },
4752 },
4753 });
4754 }
4755
4756 #[test]
4757 fn disconnect_reported_on_manual_disconnect() {
4758 let mut h = TestHelper::new();
4759 let (cmd, mut connect_txn_stream) = connect_command_one();
4760 let state = link_up_state(cmd);
4761
4762 let state = disconnect(state, &mut h, fidl_sme::UserDisconnectReason::WlanSmeUnitTesting);
4763 assert_idle(state);
4764 let info = assert_variant!(
4765 connect_txn_stream.try_next(),
4766 Ok(Some(ConnectTransactionEvent::OnDisconnect { info })) => info
4767 );
4768 assert!(!info.is_sme_reconnecting);
4769 assert_eq!(
4770 info.disconnect_source,
4771 fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::WlanSmeUnitTesting)
4772 );
4773
4774 assert_data_tree!(h.inspector, root: contains {
4775 usme: contains {
4776 state_events: {
4777 "0": {
4778 "@time": AnyNumericProperty,
4779 ctx: AnyStringProperty,
4780 from: LINK_UP_STATE,
4781 to: DISCONNECTING_STATE,
4782 },
4783 "1": {
4784 "@time": AnyNumericProperty,
4785 from: DISCONNECTING_STATE,
4786 to: IDLE_STATE,
4787 }
4788 },
4789 },
4790 });
4791 }
4792
4793 #[test]
4794 fn disconnect_reported_on_manual_disconnect_with_wsc() {
4795 let mut h = TestHelper::new();
4796 let (mut cmd, mut connect_txn_stream) = connect_command_one();
4797 cmd.bss = Box::new(fake_bss_description!(Open,
4798 ssid: Ssid::try_from("bar").unwrap(),
4799 bssid: [8; 6],
4800 rssi_dbm: 60,
4801 snr_db: 30,
4802 ies_overrides: IesOverrides::new().set_raw(
4803 get_vendor_ie_bytes_for_wsc_ie(&fake_probe_resp_wsc_ie_bytes()).expect("getting vendor ie bytes")
4804 )));
4805
4806 let state = link_up_state(cmd);
4807 let state = disconnect(state, &mut h, fidl_sme::UserDisconnectReason::WlanSmeUnitTesting);
4808 assert_idle(state);
4809
4810 let info = assert_variant!(
4811 connect_txn_stream.try_next(),
4812 Ok(Some(ConnectTransactionEvent::OnDisconnect { info })) => info
4813 );
4814 assert!(!info.is_sme_reconnecting);
4815 assert_eq!(
4816 info.disconnect_source,
4817 fidl_sme::DisconnectSource::User(fidl_sme::UserDisconnectReason::WlanSmeUnitTesting)
4818 );
4819
4820 assert_data_tree!(h.inspector, root: contains {
4821 usme: contains {
4822 state_events: {
4823 "0": {
4824 "@time": AnyNumericProperty,
4825 ctx: AnyStringProperty,
4826 from: LINK_UP_STATE,
4827 to: DISCONNECTING_STATE,
4828 },
4829 "1": {
4830 "@time": AnyNumericProperty,
4831 from: DISCONNECTING_STATE,
4832 to: IDLE_STATE,
4833 }
4834 },
4835 },
4836 });
4837 }
4838
4839 #[test]
4840 fn bss_channel_switch_ind() {
4841 let mut h = TestHelper::new();
4842 let (mut cmd, mut connect_txn_stream) = connect_command_one();
4843 cmd.bss = Box::new(fake_bss_description!(Open,
4844 ssid: Ssid::try_from("bar").unwrap(),
4845 bssid: [8; 6],
4846 channel: Channel::new(1, Cbw::Cbw20),
4847 ));
4848 let state = link_up_state(cmd);
4849
4850 let input_info = fidl_internal::ChannelSwitchInfo { new_channel: 36 };
4851 let switch_ind = MlmeEvent::OnChannelSwitched { info: input_info };
4852
4853 assert_variant!(&state, ClientState::Associated(state) => {
4854 assert_eq!(state.latest_ap_state.channel.primary, 1);
4855 });
4856 let state = state.on_mlme_event(switch_ind, &mut h.context);
4857 assert_variant!(state, ClientState::Associated(state) => {
4858 assert_eq!(state.latest_ap_state.channel.primary, 36);
4859 });
4860
4861 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnChannelSwitched { info })) => {
4862 assert_eq!(info, input_info);
4863 });
4864 }
4865
4866 #[test]
4867 fn connect_failure_rsne_wrapped_in_legacy_wpa() {
4868 let (supplicant, _suppl_mock) = mock_psk_supplicant();
4869
4870 let (mut command, _connect_txn_stream) = connect_command_wpa2(supplicant);
4871 let bss = command.bss.clone();
4872 if let Protection::Rsna(rsna) = command.protection {
4874 command.protection = Protection::LegacyWpa(rsna);
4875 } else {
4876 panic!("command is guaranteed to be contain legacy wpa");
4877 };
4878
4879 let mut h = TestHelper::new();
4880 let state = idle_state().connect(command, &mut h.context);
4881
4882 assert_variant!(state, ClientState::Idle(_));
4884
4885 assert_data_tree!(h.inspector, root: contains {
4886 usme: contains {
4887 state_events: {
4888 "0": {
4889 "@time": AnyNumericProperty,
4890 ctx: AnyStringProperty,
4891 from: IDLE_STATE,
4892 to: IDLE_STATE,
4893 bssid: bss.bssid.to_string(),
4894 ssid: bss.ssid.to_string(),
4895 }
4896 },
4897 },
4898 });
4899 }
4900
4901 #[test]
4902 fn connect_failure_legacy_wpa_wrapped_in_rsna() {
4903 let (supplicant, _suppl_mock) = mock_psk_supplicant();
4904
4905 let (mut command, _connect_txn_stream) = connect_command_wpa1(supplicant);
4906 let bss = command.bss.clone();
4907 if let Protection::LegacyWpa(rsna) = command.protection {
4909 command.protection = Protection::Rsna(rsna);
4910 } else {
4911 panic!("command is guaranteed to be contain legacy wpa");
4912 };
4913
4914 let mut h = TestHelper::new();
4915 let state = idle_state();
4916 let state = state.connect(command, &mut h.context);
4917
4918 assert_variant!(state, ClientState::Idle(_));
4920
4921 assert_data_tree!(h.inspector, root: contains {
4922 usme: contains {
4923 state_events: {
4924 "0": {
4925 "@time": AnyNumericProperty,
4926 ctx: AnyStringProperty,
4927 from: IDLE_STATE,
4928 to: IDLE_STATE,
4929 bssid: bss.bssid.to_string(),
4930 ssid: bss.ssid.to_string(),
4931 }
4932 },
4933 },
4934 });
4935 }
4936
4937 #[test]
4938 fn status_returns_last_rssi_snr() {
4939 let mut h = TestHelper::new();
4940 let time_a = now();
4941
4942 let (cmd, mut connect_txn_stream) = connect_command_one();
4943 let state = link_up_state(cmd);
4944 let input_ind = fidl_internal::SignalReportIndication { rssi_dbm: -42, snr_db: 20 };
4945 let state = state.on_mlme_event(MlmeEvent::SignalReport { ind: input_ind }, &mut h.context);
4946 let serving_ap_info = assert_variant!(state.status(),
4947 ClientSmeStatus::Connected(serving_ap_info) =>
4948 serving_ap_info);
4949 assert_eq!(serving_ap_info.rssi_dbm, -42);
4950 assert_eq!(serving_ap_info.snr_db, 20);
4951 assert!(serving_ap_info.signal_report_time > time_a);
4952 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnSignalReport { ind })) => {
4953 assert_eq!(input_ind, ind);
4954 });
4955
4956 let time_b = now();
4957 let signal_report_time = assert_variant!(state.status(),
4958 ClientSmeStatus::Connected(serving_ap_info) =>
4959 serving_ap_info.signal_report_time);
4960 assert!(signal_report_time < time_b);
4961
4962 let input_ind = fidl_internal::SignalReportIndication { rssi_dbm: -24, snr_db: 10 };
4963 let state = state.on_mlme_event(MlmeEvent::SignalReport { ind: input_ind }, &mut h.context);
4964 let serving_ap_info = assert_variant!(state.status(),
4965 ClientSmeStatus::Connected(serving_ap_info) =>
4966 serving_ap_info);
4967 assert_eq!(serving_ap_info.rssi_dbm, -24);
4968 assert_eq!(serving_ap_info.snr_db, 10);
4969 let signal_report_time = assert_variant!(state.status(),
4970 ClientSmeStatus::Connected(serving_ap_info) =>
4971 serving_ap_info.signal_report_time);
4972 assert!(signal_report_time > time_b);
4973 assert_variant!(connect_txn_stream.try_next(), Ok(Some(ConnectTransactionEvent::OnSignalReport { ind })) => {
4974 assert_eq!(input_ind, ind);
4975 });
4976
4977 let time_c = now();
4978 let signal_report_time = assert_variant!(state.status(),
4979 ClientSmeStatus::Connected(serving_ap_info) =>
4980 serving_ap_info.signal_report_time);
4981 assert!(signal_report_time < time_c);
4982 }
4983
4984 fn test_sae_frame_rx_tx(
4985 mock_supplicant_controller: MockSupplicantController,
4986 state: ClientState,
4987 ) -> ClientState {
4988 let mut h = TestHelper::new();
4989 let frame_rx = fidl_mlme::SaeFrame {
4990 peer_sta_address: [0xaa; 6],
4991 status_code: fidl_ieee80211::StatusCode::Success,
4992 seq_num: 1,
4993 sae_fields: vec![1, 2, 3, 4, 5],
4994 };
4995 let frame_tx = fidl_mlme::SaeFrame {
4996 peer_sta_address: [0xbb; 6],
4997 status_code: fidl_ieee80211::StatusCode::Success,
4998 seq_num: 2,
4999 sae_fields: vec![1, 2, 3, 4, 5, 6, 7, 8],
5000 };
5001 mock_supplicant_controller
5002 .set_on_sae_frame_rx_updates(vec![SecAssocUpdate::TxSaeFrame(frame_tx)]);
5003 let state =
5004 state.on_mlme_event(MlmeEvent::OnSaeFrameRx { frame: frame_rx }, &mut h.context);
5005 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::SaeFrameTx(_))));
5006 state
5007 }
5008
5009 #[test]
5010 fn sae_sends_frame_in_connecting() {
5011 let (supplicant, suppl_mock) = mock_psk_supplicant();
5012 let (cmd, _connect_txn_stream) = connect_command_wpa3(supplicant);
5013 let state = connecting_state(cmd);
5014 let end_state = test_sae_frame_rx_tx(suppl_mock, state);
5015 assert_variant!(end_state, ClientState::Connecting(_))
5016 }
5017
5018 fn test_sae_frame_ind_resp(
5019 mock_supplicant_controller: MockSupplicantController,
5020 state: ClientState,
5021 ) -> ClientState {
5022 let mut h = TestHelper::new();
5023 let ind = fidl_mlme::SaeHandshakeIndication { peer_sta_address: [0xaa; 6] };
5024 mock_supplicant_controller.set_on_sae_handshake_ind_updates(vec![
5026 SecAssocUpdate::SaeAuthStatus(AuthStatus::Success),
5027 ]);
5028 let state = state.on_mlme_event(MlmeEvent::OnSaeHandshakeInd { ind }, &mut h.context);
5029
5030 let resp = assert_variant!(
5031 h.mlme_stream.try_next(),
5032 Ok(Some(MlmeRequest::SaeHandshakeResp(resp))) => resp);
5033 assert_eq!(resp.status_code, fidl_ieee80211::StatusCode::Success);
5034 state
5035 }
5036
5037 #[test]
5038 fn sae_ind_in_connecting() {
5039 let (supplicant, suppl_mock) = mock_psk_supplicant();
5040 let (cmd, _connect_txn_stream) = connect_command_wpa3(supplicant);
5041 let state = connecting_state(cmd);
5042 let end_state = test_sae_frame_ind_resp(suppl_mock, state);
5043 assert_variant!(end_state, ClientState::Connecting(_))
5044 }
5045
5046 fn test_sae_timeout(
5047 mock_supplicant_controller: MockSupplicantController,
5048 state: ClientState,
5049 ) -> ClientState {
5050 let mut h = TestHelper::new();
5051 let frame_tx = fidl_mlme::SaeFrame {
5052 peer_sta_address: [0xbb; 6],
5053 status_code: fidl_ieee80211::StatusCode::Success,
5054 seq_num: 2,
5055 sae_fields: vec![1, 2, 3, 4, 5, 6, 7, 8],
5056 };
5057 mock_supplicant_controller
5058 .set_on_sae_timeout_updates(vec![SecAssocUpdate::TxSaeFrame(frame_tx)]);
5059 let state = state.handle_timeout(event::SaeTimeout(2).into(), &mut h.context);
5060 assert_variant!(h.mlme_stream.try_next(), Ok(Some(MlmeRequest::SaeFrameTx(_))));
5061 state
5062 }
5063
5064 fn test_sae_timeout_failure(
5065 mock_supplicant_controller: MockSupplicantController,
5066 state: ClientState,
5067 ) {
5068 let mut h = TestHelper::new();
5069 mock_supplicant_controller
5070 .set_on_sae_timeout_failure(anyhow::anyhow!("Failed to process timeout"));
5071 let state = state.handle_timeout(event::SaeTimeout(2).into(), &mut h.context);
5072 let state = exchange_deauth(state, &mut h);
5073 assert_variant!(state, ClientState::Idle(_))
5074 }
5075
5076 #[test]
5077 fn sae_timeout_in_connecting() {
5078 let (supplicant, suppl_mock) = mock_psk_supplicant();
5079 let (cmd, _connect_txn_stream) = connect_command_wpa3(supplicant);
5080 let state = connecting_state(cmd);
5081 let end_state = test_sae_timeout(suppl_mock, state);
5082 assert_variant!(end_state, ClientState::Connecting(_));
5083 }
5084
5085 #[test]
5086 fn sae_timeout_failure_in_connecting() {
5087 let (supplicant, suppl_mock) = mock_psk_supplicant();
5088 let (cmd, _connect_txn_stream) = connect_command_wpa3(supplicant);
5089 let state = connecting_state(cmd);
5090 test_sae_timeout_failure(suppl_mock, state);
5091 }
5092
5093 #[test]
5094 fn update_wmm_ac_params_new() {
5095 let mut h = TestHelper::new();
5096 let wmm_param = None;
5097 let state = link_up_state_with_wmm(connect_command_one().0, wmm_param);
5098
5099 let state = state.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_OK), &mut h.context);
5100 assert_variant!(state, ClientState::Associated(state) => {
5101 assert_variant!(state.wmm_param, Some(wmm_param) => {
5102 assert!(wmm_param.wmm_info.ap_wmm_info().uapsd());
5103 assert_wmm_param_acs(&wmm_param);
5104 })
5105 });
5106 }
5107
5108 #[test]
5109 fn update_wmm_ac_params_existing() {
5110 let mut h = TestHelper::new();
5111
5112 let existing_wmm_param =
5113 *ie::parse_wmm_param(&fake_wmm_param().bytes[..]).expect("parse wmm");
5114 existing_wmm_param.wmm_info.ap_wmm_info().set_uapsd(false);
5115 let state = link_up_state_with_wmm(connect_command_one().0, Some(existing_wmm_param));
5116
5117 let state = state.on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_OK), &mut h.context);
5118 assert_variant!(state, ClientState::Associated(state) => {
5119 assert_variant!(state.wmm_param, Some(wmm_param) => {
5120 assert!(wmm_param.wmm_info.ap_wmm_info().uapsd());
5121 assert_wmm_param_acs(&wmm_param);
5122 })
5123 });
5124 }
5125
5126 #[test]
5127 fn update_wmm_ac_params_fails() {
5128 let mut h = TestHelper::new();
5129
5130 let existing_wmm_param =
5131 *ie::parse_wmm_param(&fake_wmm_param().bytes[..]).expect("parse wmm");
5132 let state = link_up_state_with_wmm(connect_command_one().0, Some(existing_wmm_param));
5133
5134 let state = state
5135 .on_mlme_event(create_on_wmm_status_resp(zx::sys::ZX_ERR_UNAVAILABLE), &mut h.context);
5136 assert_variant!(state, ClientState::Associated(state) => {
5137 assert_variant!(state.wmm_param, Some(wmm_param) => {
5138 assert_eq!(wmm_param, existing_wmm_param);
5139 })
5140 });
5141 }
5142
5143 fn assert_wmm_param_acs(wmm_param: &ie::WmmParam) {
5144 assert_eq!(wmm_param.ac_be_params.aci_aifsn.aifsn(), 1);
5145 assert!(!wmm_param.ac_be_params.aci_aifsn.acm());
5146 assert_eq!(wmm_param.ac_be_params.ecw_min_max.ecw_min(), 2);
5147 assert_eq!(wmm_param.ac_be_params.ecw_min_max.ecw_max(), 3);
5148 assert_eq!({ wmm_param.ac_be_params.txop_limit }, 4);
5149
5150 assert_eq!(wmm_param.ac_bk_params.aci_aifsn.aifsn(), 5);
5151 assert!(!wmm_param.ac_bk_params.aci_aifsn.acm());
5152 assert_eq!(wmm_param.ac_bk_params.ecw_min_max.ecw_min(), 6);
5153 assert_eq!(wmm_param.ac_bk_params.ecw_min_max.ecw_max(), 7);
5154 assert_eq!({ wmm_param.ac_bk_params.txop_limit }, 8);
5155
5156 assert_eq!(wmm_param.ac_vi_params.aci_aifsn.aifsn(), 9);
5157 assert!(wmm_param.ac_vi_params.aci_aifsn.acm());
5158 assert_eq!(wmm_param.ac_vi_params.ecw_min_max.ecw_min(), 10);
5159 assert_eq!(wmm_param.ac_vi_params.ecw_min_max.ecw_max(), 11);
5160 assert_eq!({ wmm_param.ac_vi_params.txop_limit }, 12);
5161
5162 assert_eq!(wmm_param.ac_vo_params.aci_aifsn.aifsn(), 13);
5163 assert!(wmm_param.ac_vo_params.aci_aifsn.acm());
5164 assert_eq!(wmm_param.ac_vo_params.ecw_min_max.ecw_min(), 14);
5165 assert_eq!(wmm_param.ac_vo_params.ecw_min_max.ecw_max(), 15);
5166 assert_eq!({ wmm_param.ac_vo_params.txop_limit }, 16);
5167 }
5168
5169 struct TestHelper {
5171 mlme_stream: MlmeStream,
5172 time_stream: timer::EventStream<Event>,
5173 context: Context,
5174 inspector: Inspector,
5176 executor: fuchsia_async::TestExecutor,
5179 }
5180
5181 impl TestHelper {
5182 fn new_(with_fake_time: bool) -> Self {
5183 let executor = if with_fake_time {
5184 fuchsia_async::TestExecutor::new_with_fake_time()
5185 } else {
5186 fuchsia_async::TestExecutor::new()
5187 };
5188
5189 let (mlme_sink, mlme_stream) = mpsc::unbounded();
5190 let (timer, time_stream) = timer::create_timer();
5191 let inspector = Inspector::default();
5192 let context = Context {
5193 device_info: Arc::new(fake_device_info()),
5194 mlme_sink: MlmeSink::new(mlme_sink),
5195 timer,
5196 att_id: 0,
5197 inspect: Arc::new(inspect::SmeTree::new(
5198 inspector.clone(),
5199 inspector.root().create_child("usme"),
5200 &test_utils::fake_device_info([1u8; 6].into()),
5201 &fake_spectrum_management_support_empty(),
5202 )),
5203 security_support: fake_security_support(),
5204 };
5205 TestHelper { mlme_stream, time_stream, context, inspector, executor }
5206 }
5207 fn new() -> Self {
5208 Self::new_(false)
5209 }
5210 fn new_with_fake_time() -> Self {
5211 Self::new_(true)
5212 }
5213 }
5214
5215 fn on_eapol_ind(
5216 state: ClientState,
5217 helper: &mut TestHelper,
5218 bssid: Bssid,
5219 suppl_mock: &MockSupplicantController,
5220 update_sink: UpdateSink,
5221 ) -> ClientState {
5222 suppl_mock.set_on_eapol_frame_updates(update_sink);
5223 let eapol_ind = create_eapol_ind(bssid, test_utils::eapol_key_frame().into());
5225 state.on_mlme_event(eapol_ind, &mut helper.context)
5226 }
5227
5228 fn on_set_keys_conf(
5229 state: ClientState,
5230 helper: &mut TestHelper,
5231 key_ids: Vec<u16>,
5232 ) -> ClientState {
5233 state.on_mlme_event(
5234 MlmeEvent::SetKeysConf {
5235 conf: fidl_mlme::SetKeysConfirm {
5236 results: key_ids
5237 .into_iter()
5238 .map(|key_id| fidl_mlme::SetKeyResult {
5239 key_id,
5240 status: zx::Status::OK.into_raw(),
5241 })
5242 .collect(),
5243 },
5244 },
5245 &mut helper.context,
5246 )
5247 }
5248
5249 fn create_eapol_ind(bssid: Bssid, data: Vec<u8>) -> MlmeEvent {
5250 MlmeEvent::EapolInd {
5251 ind: fidl_mlme::EapolIndication {
5252 src_addr: bssid.to_array(),
5253 dst_addr: fake_device_info().sta_addr,
5254 data,
5255 },
5256 }
5257 }
5258
5259 fn exchange_deauth(state: ClientState, h: &mut TestHelper) -> ClientState {
5260 let peer_sta_address = assert_variant!(
5262 h.mlme_stream.try_next(),
5263 Ok(Some(MlmeRequest::Deauthenticate(req))) => req.peer_sta_address
5264 );
5265
5266 let deauth_conf = MlmeEvent::DeauthenticateConf {
5268 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address },
5269 };
5270 state.on_mlme_event(deauth_conf, &mut h.context)
5271 }
5272
5273 fn expect_set_ctrl_port(
5274 mlme_stream: &mut MlmeStream,
5275 bssid: Bssid,
5276 state: fidl_mlme::ControlledPortState,
5277 ) {
5278 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetCtrlPort(req))) => {
5279 assert_eq!(&req.peer_sta_address, bssid.as_array());
5280 assert_eq!(req.state, state);
5281 });
5282 }
5283
5284 fn expect_deauth_req(
5285 mlme_stream: &mut MlmeStream,
5286 bssid: Bssid,
5287 reason_code: fidl_ieee80211::ReasonCode,
5288 ) {
5289 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Deauthenticate(req))) => {
5291 assert_eq!(bssid.as_array(), &req.peer_sta_address);
5292 assert_eq!(reason_code, req.reason_code);
5293 });
5294 }
5295
5296 #[track_caller]
5297 fn expect_eapol_req(mlme_stream: &mut MlmeStream, bssid: Bssid) {
5298 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Eapol(req))) => {
5299 assert_eq!(req.src_addr, fake_device_info().sta_addr);
5300 assert_eq!(&req.dst_addr, bssid.as_array());
5301 assert_eq!(req.data, Vec::<u8>::from(test_utils::eapol_key_frame()));
5302 });
5303 }
5304
5305 fn expect_set_ptk(mlme_stream: &mut MlmeStream, bssid: Bssid) {
5306 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetKeys(set_keys_req))) => {
5307 assert_eq!(set_keys_req.keylist.len(), 1);
5308 let k = set_keys_req.keylist.first().expect("expect key descriptor");
5309 assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap() as usize]);
5310 assert_eq!(k.key_id, 0);
5311 assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
5312 assert_eq!(&k.address, bssid.as_array());
5313 assert_eq!(k.rsc, 0);
5314 assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
5315 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
5316 });
5317 }
5318
5319 fn expect_set_gtk(mlme_stream: &mut MlmeStream) {
5320 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetKeys(set_keys_req))) => {
5321 assert_eq!(set_keys_req.keylist.len(), 1);
5322 let k = set_keys_req.keylist.first().expect("expect key descriptor");
5323 assert_eq!(&k.key[..], &test_utils::gtk_bytes()[..]);
5324 assert_eq!(k.key_id, 2);
5325 assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
5326 assert_eq!(k.address, [0xFFu8; 6]);
5327 assert_eq!(k.rsc, 0);
5328 assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
5329 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
5330 });
5331 }
5332
5333 fn expect_set_wpa1_ptk(mlme_stream: &mut MlmeStream, bssid: Bssid) {
5334 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetKeys(set_keys_req))) => {
5335 assert_eq!(set_keys_req.keylist.len(), 1);
5336 let k = set_keys_req.keylist.first().expect("expect key descriptor");
5337 assert_eq!(k.key, vec![0xCCu8; test_utils::wpa1_cipher().tk_bytes().unwrap() as usize]);
5338 assert_eq!(k.key_id, 0);
5339 assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
5340 assert_eq!(&k.address, bssid.as_array());
5341 assert_eq!(k.rsc, 0);
5342 assert_eq!(k.cipher_suite_oui, [0x00, 0x50, 0xF2]);
5343 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(2));
5344 });
5345 }
5346
5347 fn expect_set_wpa1_gtk(mlme_stream: &mut MlmeStream) {
5348 assert_variant!(mlme_stream.try_next(), Ok(Some(MlmeRequest::SetKeys(set_keys_req))) => {
5349 assert_eq!(set_keys_req.keylist.len(), 1);
5350 let k = set_keys_req.keylist.first().expect("expect key descriptor");
5351 assert_eq!(&k.key[..], &test_utils::wpa1_gtk_bytes()[..]);
5352 assert_eq!(k.key_id, 2);
5353 assert_eq!(k.key_type, fidl_mlme::KeyType::Group);
5354 assert_eq!(k.address, [0xFFu8; 6]);
5355 assert_eq!(k.rsc, 0);
5356 assert_eq!(k.cipher_suite_oui, [0x00, 0x50, 0xF2]);
5357 assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(2));
5358 });
5359 }
5360
5361 fn connect_command_one() -> (ConnectCommand, ConnectTransactionStream) {
5362 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5363 let cmd = ConnectCommand {
5364 bss: Box::new(fake_bss_description!(Open,
5365 ssid: Ssid::try_from("foo").unwrap(),
5366 bssid: [7, 7, 7, 7, 7, 7],
5367 rssi_dbm: 60,
5368 snr_db: 30
5369 )),
5370 connect_txn_sink,
5371 protection: Protection::Open,
5372 authentication: Authentication { protocol: Protocol::Open, credentials: None },
5373 };
5374 (cmd, connect_txn_stream)
5375 }
5376
5377 fn connect_command_two() -> (ConnectCommand, ConnectTransactionStream) {
5378 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5379 let cmd = ConnectCommand {
5380 bss: Box::new(
5381 fake_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap(), bssid: [8, 8, 8, 8, 8, 8]),
5382 ),
5383 connect_txn_sink,
5384 protection: Protection::Open,
5385 authentication: Authentication { protocol: Protocol::Open, credentials: None },
5386 };
5387 (cmd, connect_txn_stream)
5388 }
5389
5390 fn connect_command_wep() -> (ConnectCommand, ConnectTransactionStream) {
5391 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5392 let cmd = ConnectCommand {
5393 bss: Box::new(fake_bss_description!(Wep, ssid: Ssid::try_from("wep").unwrap())),
5394 connect_txn_sink,
5395 protection: Protection::Wep(WepKey::Wep40([3; 5])),
5396 authentication: Authentication { protocol: Protocol::Wep, credentials: None },
5397 };
5398 (cmd, connect_txn_stream)
5399 }
5400
5401 fn connect_command_wpa1(
5402 supplicant: MockSupplicant,
5403 ) -> (ConnectCommand, ConnectTransactionStream) {
5404 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5405 let wpa_ie = make_wpa1_ie();
5406 let cmd = ConnectCommand {
5407 bss: Box::new(fake_bss_description!(Wpa1, ssid: Ssid::try_from("wpa1").unwrap())),
5408 connect_txn_sink,
5409 protection: Protection::LegacyWpa(Rsna {
5410 negotiated_protection: NegotiatedProtection::from_legacy_wpa(&wpa_ie)
5411 .expect("invalid NegotiatedProtection"),
5412 supplicant: Box::new(supplicant),
5413 }),
5414 authentication: Authentication { protocol: Protocol::Wpa1, credentials: None },
5415 };
5416 (cmd, connect_txn_stream)
5417 }
5418
5419 fn connect_command_wpa2(
5420 supplicant: MockSupplicant,
5421 ) -> (ConnectCommand, ConnectTransactionStream) {
5422 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5423 let bss = fake_bss_description!(Wpa2, ssid: Ssid::try_from("wpa2").unwrap());
5424 let rsne = Rsne::wpa2_rsne();
5425 let credentials = Some(Box::new(Credentials::Wpa(
5426 fidl_security::WpaCredentials::Passphrase("password".into()),
5427 )));
5428 let cmd = ConnectCommand {
5429 bss: Box::new(bss),
5430 connect_txn_sink,
5431 protection: Protection::Rsna(Rsna {
5432 negotiated_protection: NegotiatedProtection::from_rsne(&rsne)
5433 .expect("invalid NegotiatedProtection"),
5434 supplicant: Box::new(supplicant),
5435 }),
5436 authentication: Authentication { protocol: Protocol::Wpa2Personal, credentials },
5437 };
5438 (cmd, connect_txn_stream)
5439 }
5440
5441 fn connect_command_wpa3(
5442 supplicant: MockSupplicant,
5443 ) -> (ConnectCommand, ConnectTransactionStream) {
5444 let (connect_txn_sink, connect_txn_stream) = ConnectTransactionSink::new_unbounded();
5445 let bss = fake_bss_description!(Wpa3, ssid: Ssid::try_from("wpa3").unwrap());
5446 let rsne = Rsne::wpa3_rsne();
5447 let cmd = ConnectCommand {
5448 bss: Box::new(bss),
5449 connect_txn_sink,
5450 protection: Protection::Rsna(Rsna {
5451 negotiated_protection: NegotiatedProtection::from_rsne(&rsne)
5452 .expect("invalid NegotiatedProtection"),
5453 supplicant: Box::new(supplicant),
5454 }),
5455 authentication: Authentication { protocol: Protocol::Wpa3Personal, credentials: None },
5456 };
5457 (cmd, connect_txn_stream)
5458 }
5459
5460 fn idle_state() -> ClientState {
5461 testing::new_state(Idle { cfg: ClientConfig::default() }).into()
5462 }
5463
5464 fn assert_idle(state: ClientState) {
5465 assert_variant!(&state, ClientState::Idle(_));
5466 }
5467
5468 fn disconnect(
5469 mut state: ClientState,
5470 h: &mut TestHelper,
5471 reason: fidl_sme::UserDisconnectReason,
5472 ) -> ClientState {
5473 let bssid = match &state {
5474 ClientState::Connecting(state) => state.cmd.bss.bssid,
5475 ClientState::Associated(state) => state.latest_ap_state.bssid,
5476 other => panic!("Unexpected state {:?} when disconnecting", other),
5477 };
5478 let (mut disconnect_fut, responder) = make_disconnect_request(h);
5479 state = state.disconnect(&mut h.context, reason, responder);
5480 assert_variant!(&state, ClientState::Disconnecting(_));
5481 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Pending);
5482 let state = state.on_mlme_event(
5483 fidl_mlme::MlmeEvent::DeauthenticateConf {
5484 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: bssid.to_array() },
5485 },
5486 &mut h.context,
5487 );
5488 assert_variant!(h.executor.run_until_stalled(&mut disconnect_fut), Poll::Ready(Ok(())));
5489 state
5490 }
5491
5492 fn connecting_state(cmd: ConnectCommand) -> ClientState {
5493 testing::new_state(Connecting {
5494 cfg: ClientConfig::default(),
5495 cmd,
5496 protection_ie: None,
5497 reassociation_loop_count: 0,
5498 })
5499 .into()
5500 }
5501
5502 fn assert_connecting(state: ClientState, bss: &BssDescription) {
5503 assert_variant!(&state, ClientState::Connecting(connecting) => {
5504 assert_eq!(connecting.cmd.bss.as_ref(), bss);
5505 });
5506 }
5507
5508 fn assert_roaming(state: &ClientState) {
5509 assert_variant!(state, ClientState::Roaming(_));
5510 }
5511
5512 fn assert_disconnecting(state: ClientState) {
5513 assert_variant!(&state, ClientState::Disconnecting(_));
5514 }
5515
5516 fn establishing_rsna_state(cmd: ConnectCommand) -> ClientState {
5517 let auth_method = cmd.protection.rsn_auth_method();
5518 let rsna = assert_variant!(cmd.protection, Protection::Rsna(rsna) => rsna);
5519 let link_state = testing::new_state(EstablishingRsna {
5520 rsna,
5521 rsna_completion_timeout: None,
5522 rsna_response_timeout: None,
5523 rsna_retransmission_timeout: None,
5524 handshake_complete: false,
5525 pending_key_ids: Default::default(),
5526 })
5527 .into();
5528 testing::new_state(Associated {
5529 cfg: ClientConfig::default(),
5530 latest_ap_state: cmd.bss,
5531 auth_method,
5532 connect_txn_sink: cmd.connect_txn_sink,
5533 last_signal_report_time: zx::MonotonicInstant::ZERO,
5534 link_state,
5535 protection_ie: None,
5536 wmm_param: None,
5537 last_channel_switch_time: None,
5538 reassociation_loop_count: 0,
5539 authentication: cmd.authentication,
5540 roam_in_progress: None,
5541 })
5542 .into()
5543 }
5544
5545 fn link_up_state(cmd: ConnectCommand) -> ClientState {
5546 link_up_state_with_wmm(cmd, None)
5547 }
5548
5549 fn link_up_state_with_wmm(cmd: ConnectCommand, wmm_param: Option<ie::WmmParam>) -> ClientState {
5550 let auth_method = cmd.protection.rsn_auth_method();
5551 let link_state =
5552 testing::new_state(LinkUp { protection: cmd.protection, since: now() }).into();
5553 testing::new_state(Associated {
5554 cfg: ClientConfig::default(),
5555 connect_txn_sink: cmd.connect_txn_sink,
5556 latest_ap_state: cmd.bss,
5557 auth_method,
5558 last_signal_report_time: zx::MonotonicInstant::ZERO,
5559 link_state,
5560 protection_ie: None,
5561 wmm_param,
5562 last_channel_switch_time: None,
5563 reassociation_loop_count: 0,
5564 authentication: cmd.authentication,
5565 roam_in_progress: None,
5566 })
5567 .into()
5568 }
5569
5570 fn roaming_state(cmd: ConnectCommand, selected_bssid: Bssid) -> ClientState {
5571 let auth_method = cmd.protection.rsn_auth_method();
5572 let mut selected_bss = cmd.bss.clone();
5573 selected_bss.bssid = selected_bssid;
5574 testing::new_state(Roaming {
5575 cfg: ClientConfig::default(),
5576 cmd: ConnectCommand {
5577 bss: selected_bss,
5578 connect_txn_sink: cmd.connect_txn_sink,
5579 protection: cmd.protection,
5580 authentication: cmd.authentication,
5581 },
5582 auth_method,
5583 protection_ie: None,
5584 })
5585 .into()
5586 }
5587
5588 fn disconnecting_state(action: PostDisconnectAction) -> ClientState {
5589 testing::new_state(Disconnecting { cfg: ClientConfig::default(), action, _timeout: None })
5590 .into()
5591 }
5592
5593 fn fake_device_info() -> fidl_mlme::DeviceInfo {
5594 test_utils::fake_device_info([0, 1, 2, 3, 4, 5].into())
5595 }
5596}