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