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