1use crate::akm_algorithm as akm;
11use crate::block_ack::{BlockAckState, Closed};
12use crate::client::lost_bss::LostBssCounter;
13use crate::client::{BoundClient, ParsedAssociateResp, TimedEvent};
14use crate::ddk_converter::{get_rssi_dbm, softmac_key_configuration_from_mlme};
15use crate::device::DeviceOps;
16use crate::disconnect::LocallyInitiated;
17use crate::error::Error;
18use fuchsia_trace::Id as TraceId;
19use ieee80211::{Bssid, MacAddr, MacAddrBytes};
20use log::{debug, error, info, trace, warn};
21use wlan_common::buffer_reader::BufferReader;
22use wlan_common::capabilities::{ApCapabilities, StaCapabilities, intersect_with_ap_as_client};
23use wlan_common::energy::DecibelMilliWatt;
24use wlan_common::mac::{self, BeaconHdr};
25use wlan_common::stats::SignalStrengthAverage;
26use wlan_common::timer::{EventHandle, Timer};
27use wlan_common::{ie, tim};
28use wlan_statemachine::*;
29use zerocopy::SplitByteSlice;
30use {
31 fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_internal as fidl_internal,
32 fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_softmac as fidl_softmac,
33 wlan_trace as wtrace,
34};
35
36const RECONNECT_TIMEOUT_BCN_PERIODS: u16 = 10;
40
41pub const DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT: u32 = 100;
43
44pub const ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT: u32 = 10;
46
47#[derive(Debug)]
50pub struct Joined;
51
52impl Joined {
53 async fn start_authenticating<D: DeviceOps>(
58 &self,
59 sta: &mut BoundClient<'_, D>,
60 ) -> Result<akm::AkmAlgorithm, ()> {
61 let auth_type = sta.sta.connect_req.auth_type;
62 let algorithm_candidate = match auth_type {
63 fidl_mlme::AuthenticationTypes::OpenSystem => Ok(akm::AkmAlgorithm::OpenSupplicant),
64 fidl_mlme::AuthenticationTypes::Sae => Ok(akm::AkmAlgorithm::Sae),
65 _ => {
66 error!("Unhandled authentication algorithm: {:?}", auth_type);
67 Err(fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm)
68 }
69 };
70
71 let result = match algorithm_candidate {
72 Err(e) => Err(e),
73 Ok(mut algorithm) => match algorithm.initiate(sta) {
74 Ok(akm::AkmState::Failed) => {
75 Err(fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm)
76 }
77 Err(_) => Err(fidl_ieee80211::StatusCode::RefusedReasonUnspecified),
78 Ok(_) => Ok(algorithm),
79 },
80 };
81
82 match result {
83 Ok(algorithm) => Ok(algorithm),
84 Err(status_code) => {
85 sta.send_connect_conf_failure(status_code);
86 let _ = sta
87 .clear_association()
88 .await
89 .map_err(|e| error!("Failed to clear association: {}", e));
90 Err(())
91 }
92 }
93 }
94
95 async fn on_sme_deauthenticate<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) {
96 let _ =
97 sta.clear_association().await.map_err(|e| warn!("Failed to clear association: {}", e));
98 }
99}
100
101#[derive(Debug)]
102enum AuthProgress {
103 Complete,
104 InProgress,
105 Failed,
106}
107
108#[derive(Debug)]
112pub struct Authenticating {
113 algorithm: akm::AkmAlgorithm,
114}
115
116impl Authenticating {
117 fn new(algorithm: akm::AkmAlgorithm) -> Self {
118 Self { algorithm }
119 }
120
121 async fn akm_state_update_notify_sme<D: DeviceOps>(
122 &self,
123 sta: &mut BoundClient<'_, D>,
124 state: Result<akm::AkmState, anyhow::Error>,
125 ) -> AuthProgress {
126 match state {
127 Ok(akm::AkmState::AuthComplete) => match self.start_associating(sta) {
128 Ok(()) => AuthProgress::Complete,
129 Err(()) => AuthProgress::Failed,
130 },
131 Ok(akm::AkmState::InProgress) => AuthProgress::InProgress,
132 Ok(akm::AkmState::Failed) => {
133 error!("authentication with BSS failed");
134 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedReasonUnspecified);
136 let _ = sta
137 .clear_association()
138 .await
139 .map_err(|e| error!("Failed to clear association: {}", e));
140 AuthProgress::Failed
141 }
142 Err(e) => {
143 error!("Internal error while authenticating: {}", e);
144 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedReasonUnspecified);
146 let _ = sta
147 .clear_association()
148 .await
149 .map_err(|e| error!("Failed to clear association: {}", e));
150 AuthProgress::Failed
151 }
152 }
153 }
154
155 fn start_associating<D: DeviceOps>(&self, sta: &mut BoundClient<'_, D>) -> Result<(), ()> {
159 sta.send_assoc_req_frame().map_err(|e| {
160 error!("Error sending association request frame: {}", e);
161 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedTemporarily);
162 })
163 }
164
165 async fn on_auth_frame<B: SplitByteSlice, D: DeviceOps>(
172 &mut self,
173 sta: &mut BoundClient<'_, D>,
174 auth_frame: mac::AuthFrame<B>,
175 ) -> AuthProgress {
176 wtrace::duration!("Authenticating::on_auth_frame");
177
178 let state = self.algorithm.handle_auth_frame(sta, auth_frame);
179 self.akm_state_update_notify_sme(sta, state).await
180 }
181
182 async fn on_sme_sae_resp<D: DeviceOps>(
186 &mut self,
187 sta: &mut BoundClient<'_, D>,
188 resp: fidl_mlme::SaeHandshakeResponse,
189 ) -> AuthProgress {
190 let state = self.algorithm.handle_sae_resp(sta, resp.status_code);
191 self.akm_state_update_notify_sme(sta, state).await
192 }
193
194 async fn on_sme_sae_tx<D: DeviceOps>(
196 &mut self,
197 sta: &mut BoundClient<'_, D>,
198 tx: fidl_mlme::SaeFrame,
199 ) -> AuthProgress {
200 let state =
201 self.algorithm.handle_sme_sae_tx(sta, tx.seq_num, tx.status_code, &tx.sae_fields[..]);
202 self.akm_state_update_notify_sme(sta, state).await
203 }
204
205 async fn on_deauth_frame<D: DeviceOps>(
209 &mut self,
210 sta: &mut BoundClient<'_, D>,
211 deauth_hdr: &mac::DeauthHdr,
212 ) {
213 wtrace::duration!("Authenticating::on_deauth_frame");
214
215 info!(
216 "received spurious deauthentication frame while authenticating with BSS (unusual); \
217 authentication failed: {:?}",
218 { deauth_hdr.reason_code }
219 );
220
221 sta.sta.connect_timeout.take();
222 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc);
223 let _ =
224 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
225 }
226
227 async fn on_sme_deauthenticate<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) {
228 sta.sta.connect_timeout.take();
229 let _ =
230 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
231 }
232}
233
234#[derive(Debug)]
235pub struct Authenticated;
236
237impl Authenticated {
238 fn on_sme_reconnect<D: DeviceOps>(
239 &self,
240 sta: &mut BoundClient<'_, D>,
241 req: fidl_mlme::ReconnectRequest,
242 ) -> Result<EventHandle, ()> {
243 let peer_sta_address: Bssid = req.peer_sta_address.into();
244 if peer_sta_address == sta.sta.connect_req.selected_bss.bssid {
245 match sta.send_assoc_req_frame() {
246 Ok(()) => {
247 let duration = sta.sta.beacon_period() * RECONNECT_TIMEOUT_BCN_PERIODS;
250 Ok(sta.ctx.timer.schedule_after(duration, TimedEvent::Reassociating))
251 }
252 Err(e) => {
253 error!("Error sending association request frame: {}", e);
254 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedTemporarily);
255 Err(())
256 }
257 }
258 } else {
259 info!("received reconnect request for a different BSSID, ignoring");
260 sta.send_connect_conf_failure_with_bssid(
261 peer_sta_address,
262 fidl_ieee80211::StatusCode::NotInSameBss,
263 );
264 Err(())
265 }
266 }
267}
268
269#[derive(Debug, Default)]
272pub struct Associating {
273 reconnect_timeout: Option<EventHandle>,
275}
276
277impl Associating {
278 fn new_with_reconnect_timeout(reconnect_timeout: EventHandle) -> Self {
279 Self { reconnect_timeout: Some(reconnect_timeout) }
280 }
281
282 async fn on_assoc_resp_frame<B: SplitByteSlice, D: DeviceOps>(
288 &mut self,
289 sta: &mut BoundClient<'_, D>,
290 assoc_resp_frame: mac::AssocRespFrame<B>,
291 ) -> Result<Association, ()> {
292 wtrace::duration!("Associating::on_assoc_resp_frame");
293
294 match Option::<fidl_ieee80211::StatusCode>::from(
296 assoc_resp_frame.assoc_resp_hdr.status_code,
297 )
298 .unwrap_or(fidl_ieee80211::StatusCode::RefusedReasonUnspecified)
299 {
300 fidl_ieee80211::StatusCode::Success => (),
301 status_code => {
302 error!("association with BSS failed: {:?}", status_code);
303 sta.send_connect_conf_failure(status_code);
304 return Err(());
305 }
306 }
307
308 let aid = assoc_resp_frame.assoc_resp_hdr.aid;
309 let parsed_assoc_resp = ParsedAssociateResp::parse(&assoc_resp_frame);
310 let ap_capabilities = ApCapabilities(StaCapabilities {
311 capability_info: parsed_assoc_resp.capabilities,
312 rates: parsed_assoc_resp.rates.clone(),
313 ht_cap: parsed_assoc_resp.ht_cap.clone(),
314 vht_cap: parsed_assoc_resp.vht_cap.clone(),
315 });
316 let negotiated_cap =
317 match intersect_with_ap_as_client(&sta.sta.client_capabilities, &ap_capabilities) {
318 Ok(cap) => cap,
319 Err(e) => {
320 error!(
324 "Associate terminated because AP's capabilities in association \
325 response is different from beacon. Error: {}",
326 e
327 );
328 sta.send_connect_conf_failure(
329 fidl_ieee80211::StatusCode::RefusedCapabilitiesMismatch,
330 );
331 return Err(());
332 }
333 };
334
335 let (ap_ht_op, ap_vht_op) = extract_ht_vht_op(&assoc_resp_frame);
336
337 let main_channel = match sta.channel_state.get_main_channel() {
338 Some(main_channel) => main_channel,
339 None => {
340 error!("MLME in associating state but no main channel is set");
341 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedReasonUnspecified);
342 return Err(());
343 }
344 };
345
346 let qos = negotiated_cap.ht_cap.is_some();
353
354 let assoc_cfg = fidl_softmac::WlanAssociationConfig {
355 bssid: Some(sta.sta.bssid().to_array()),
356 aid: Some(aid),
357 listen_interval: Some(0),
360 channel: Some(main_channel),
361 qos: Some(qos),
362 wmm_params: None,
363 rates: Some(negotiated_cap.rates.iter().map(|r| r.0).collect()),
364 capability_info: Some(negotiated_cap.capability_info.raw()),
365 ht_cap: negotiated_cap.ht_cap.map(Into::into),
366 vht_cap: negotiated_cap.vht_cap.map(Into::into),
367 ht_op: ap_ht_op.clone().map(From::from),
368 vht_op: ap_vht_op.clone().map(From::from),
369 ..Default::default()
370 };
371
372 if let Err(status) = sta.ctx.device.notify_association_complete(assoc_cfg).await {
373 error!("device failed to configure association: {}", status);
375 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RefusedReasonUnspecified);
376 return Err(());
377 }
378
379 let (_, assoc_resp_body) = assoc_resp_frame.into_assoc_resp_body();
380 sta.send_connect_conf_success(aid, assoc_resp_body.deref());
381 let controlled_port_open = !sta.sta.eapol_required();
382 if controlled_port_open {
383 if let Err(e) = sta.ctx.device.set_ethernet_up().await {
384 error!("Cannot set ethernet to UP. Status: {}", e);
386 }
387 }
388 let lost_bss_counter = LostBssCounter::start(
389 sta.sta.beacon_period(),
390 DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
391 );
392
393 let status_check_timeout =
394 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
395
396 Ok(Association {
397 aid,
398 assoc_resp_ies: assoc_resp_body.to_vec(),
399 controlled_port_open,
400 ap_ht_op,
401 ap_vht_op,
402 qos: Qos::from(qos),
403 lost_bss_counter,
404 status_check_timeout,
405 signal_strength_average: SignalStrengthAverage::new(),
406 block_ack_state: StateMachine::new(BlockAckState::from(State::new(Closed))),
407 })
408 }
409
410 fn on_disassoc_frame<D: DeviceOps>(
416 &mut self,
417 sta: &mut BoundClient<'_, D>,
418 _disassoc_hdr: &mac::DisassocHdr,
419 ) {
420 wtrace::duration!("Associating::on_disassoc_frame");
421 warn!("received unexpected disassociation frame while associating");
422 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc);
423 }
424
425 async fn on_deauth_frame<D: DeviceOps>(
429 &mut self,
430 sta: &mut BoundClient<'_, D>,
431 deauth_hdr: &mac::DeauthHdr,
432 ) {
433 wtrace::duration!("Associating::on_deauth_frame");
434 info!(
435 "received spurious deauthentication frame while associating with BSS (unusual); \
436 association failed: {:?}",
437 { deauth_hdr.reason_code }
438 );
439 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc);
440 let _ =
441 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
442 }
443
444 async fn on_sme_deauthenticate<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) {
445 sta.sta.connect_timeout.take();
446 let _ =
447 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
448 }
449}
450
451fn extract_ht_vht_op<B: SplitByteSlice>(
454 assoc_resp_frame: &mac::AssocRespFrame<B>,
455) -> (Option<ie::HtOperation>, Option<ie::VhtOperation>) {
456 let mut ht_op = None;
457 let mut vht_op = None;
458 for (id, body) in assoc_resp_frame.ies() {
459 match id {
460 ie::Id::HT_OPERATION => match ie::parse_ht_operation(body) {
461 Ok(parsed_ht_op) => ht_op = Some(zerocopy::Ref::read(&parsed_ht_op)),
462 Err(e) => {
463 error!("Invalid HT Operation: {}", e);
464 continue;
465 }
466 },
467 ie::Id::VHT_OPERATION => match ie::parse_vht_operation(body) {
468 Ok(parsed_vht_op) => vht_op = Some(zerocopy::Ref::read(&parsed_vht_op)),
469 Err(e) => {
470 error!("Invalid VHT Operation: {}", e);
471 continue;
472 }
473 },
474 _ => (),
475 }
476 }
477 (ht_op, vht_op)
478}
479
480pub fn schedule_association_status_timeout(
481 beacon_period: zx::MonotonicDuration,
482 timer: &mut Timer<TimedEvent>,
483) -> StatusCheckTimeout {
484 let duration = beacon_period * ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT;
485 StatusCheckTimeout {
486 next_event: Some(timer.schedule_after(duration, TimedEvent::AssociationStatusCheck)),
487 }
488}
489
490#[derive(Debug, PartialEq)]
491pub enum Qos {
492 Enabled,
493 Disabled,
494}
495
496impl From<bool> for Qos {
497 fn from(b: bool) -> Self {
498 if b { Self::Enabled } else { Self::Disabled }
499 }
500}
501
502impl Qos {
503 fn is_enabled(&self) -> bool {
504 *self == Self::Enabled
505 }
506}
507
508#[derive(Debug)]
509pub struct StatusCheckTimeout {
510 next_event: Option<EventHandle>,
511}
512
513#[derive(Debug)]
514pub struct Association {
515 pub aid: mac::Aid,
516 pub assoc_resp_ies: Vec<u8>,
517
518 pub controlled_port_open: bool,
521
522 #[allow(dead_code)]
524 pub ap_ht_op: Option<ie::HtOperation>,
525 #[allow(dead_code)]
527 pub ap_vht_op: Option<ie::VhtOperation>,
528
529 pub qos: Qos,
532
533 pub lost_bss_counter: LostBssCounter,
536
537 pub status_check_timeout: StatusCheckTimeout,
542 pub signal_strength_average: SignalStrengthAverage,
543
544 #[allow(dead_code)]
546 pub block_ack_state: StateMachine<BlockAckState>,
547}
548
549#[derive(Debug)]
551pub struct Associated(pub Association);
552
553impl Associated {
554 async fn on_disassoc_frame<D: DeviceOps>(
557 &mut self,
558 sta: &mut BoundClient<'_, D>,
559 disassoc_hdr: &mac::DisassocHdr,
560 ) {
561 wtrace::duration!("Associated::on_disassoc_frame");
562 self.pre_leaving_associated_state(sta).await;
563 let reason_code = fidl_ieee80211::ReasonCode::from_primitive(disassoc_hdr.reason_code.0)
564 .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason);
565 sta.send_disassoc_ind(reason_code, LocallyInitiated(false));
566 }
567
568 async fn on_deauth_frame<D: DeviceOps>(
570 &mut self,
571 sta: &mut BoundClient<'_, D>,
572 deauth_hdr: &mac::DeauthHdr,
573 ) {
574 wtrace::duration!("Associated::on_deauth_frame");
575 self.pre_leaving_associated_state(sta).await;
576 let reason_code = fidl_ieee80211::ReasonCode::from_primitive(deauth_hdr.reason_code.0)
577 .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason);
578 sta.send_deauthenticate_ind(reason_code, LocallyInitiated(false));
579 let _ =
580 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
581 }
582
583 fn on_any_mgmt_frame<D: DeviceOps>(
586 &self,
587 sta: &mut BoundClient<'_, D>,
588 mgmt_hdr: &mac::MgmtHdr,
589 ) {
590 self.request_bu_if_available(sta, mgmt_hdr.frame_ctrl, mgmt_hdr.addr1);
591 }
592
593 fn request_bu_if_available<D: DeviceOps>(
596 &self,
597 sta: &mut BoundClient<'_, D>,
598 fc: mac::FrameControl,
599 dst_addr: MacAddr,
600 ) {
601 if !self.0.controlled_port_open {
602 return;
603 }
604 if fc.more_data() && dst_addr == sta.sta.iface_mac {
606 let _result = sta.send_ps_poll_frame(self.0.aid);
607 }
608 }
609
610 fn extract_and_record_signal_dbm(&mut self, rx_info: fidl_softmac::WlanRxInfo) {
611 get_rssi_dbm(rx_info)
612 .map(|rssi_dbm| self.0.signal_strength_average.add(DecibelMilliWatt(rssi_dbm)));
613 }
614
615 async fn on_beacon_frame<B: SplitByteSlice, D: DeviceOps>(
618 &mut self,
619 sta: &mut BoundClient<'_, D>,
620 header: &BeaconHdr,
621 elements: B,
622 ) {
623 wtrace::duration!("Associated::on_beacon_frame");
624 self.0.lost_bss_counter.reset();
625 if let Err(e) =
627 sta.channel_state.bind(sta.ctx, sta.scanner).handle_beacon(header, &elements[..]).await
628 {
629 warn!("Failed to handle channel switch announcement: {}", e);
630 }
631 for (id, body) in ie::Reader::new(elements) {
632 match id {
633 ie::Id::TIM => match ie::parse_tim(body) {
634 Ok(ie::TimView { header, bitmap }) => {
635 if tim::is_traffic_buffered(header.bmp_ctrl.offset(), &bitmap, self.0.aid) {
636 let _result = sta.send_ps_poll_frame(self.0.aid);
637 }
638 }
639 _ => (),
640 },
641
642 _ => (),
643 }
644 }
645 }
646
647 fn on_data_frame<B: SplitByteSlice, D: DeviceOps>(
656 &self,
657 sta: &mut BoundClient<'_, D>,
658 data_frame: mac::DataFrame<B>,
659 async_id: TraceId,
660 ) {
661 const MSDU_TRACE_NAME: &'static str = "States::on_data_frame => MSDU";
662
663 wtrace::duration!("States::on_data_frame");
664
665 self.request_bu_if_available(
666 sta,
667 data_frame.frame_ctrl(),
668 mac::data_dst_addr(&data_frame.fixed_fields),
669 );
670
671 if data_frame.data_subtype().null() {
673 if let Err(e) = sta.send_keep_alive_resp_frame() {
674 error!("error sending keep alive frame: {}", e);
675 }
676 }
677
678 for msdu in data_frame {
680 wtrace::duration_begin!(MSDU_TRACE_NAME);
681
682 match msdu.llc_frame.hdr.protocol_id.get() {
683 mac::ETHER_TYPE_EAPOL => {
685 let mac::Msdu { dst_addr, src_addr, llc_frame } = msdu;
686 if let Err(e) =
687 sta.send_eapol_indication(src_addr, dst_addr, &llc_frame.body[..])
688 {
689 wtrace::duration_end!(
690 MSDU_TRACE_NAME,
691 "status" => "failure sending EAPOL indication",
692 );
693 error!("error sending MLME-EAPOL.indication: {}", e);
694 } else {
695 wtrace::duration_end!(
696 MSDU_TRACE_NAME,
697 "status" => "sent EAPOL indication",
698 );
699 }
700 }
701 _ if self.0.controlled_port_open => {
703 if let Err(e) = sta.deliver_msdu(msdu) {
704 wtrace::duration_end!(
705 MSDU_TRACE_NAME,
706 "status" => "failure delivering MSDU",
707 );
708 error!("error while handling data frame: {}", e);
709 } else {
710 wtrace::duration_end!(
711 MSDU_TRACE_NAME,
712 "status" => "delivered MSDU",
713 );
714 }
715 }
716 _ => {
718 wtrace::duration_end!(
719 MSDU_TRACE_NAME,
720 "status" => "dropping MSDU. controlled port closed.",
721 );
722 }
723 }
724 }
725 wtrace::async_end_wlansoftmac_rx(async_id, "completed data frame processing");
726 }
727
728 fn on_eth_frame<B: SplitByteSlice, D: DeviceOps>(
729 &self,
730 sta: &mut BoundClient<'_, D>,
731 frame: B,
732 async_id: TraceId,
733 ) -> Result<(), Error> {
734 wtrace::duration!("Associated::on_eth_frame");
735 let mac::EthernetFrame { hdr, body } = match mac::EthernetFrame::parse(frame) {
736 Some(eth_frame) => eth_frame,
737 None => {
738 return Err(Error::Status(
739 format!("Ethernet frame too short"),
740 zx::Status::IO_DATA_INTEGRITY,
741 ));
742 }
743 };
744
745 if !self.0.controlled_port_open {
746 return Err(Error::Status(
747 format!("Ethernet dropped. RSN not established"),
748 zx::Status::BAD_STATE,
749 ));
750 }
751
752 sta.send_data_frame(
753 hdr.sa,
754 hdr.da,
755 sta.sta.eapol_required(),
756 self.0.qos.is_enabled(),
757 hdr.ether_type.get(),
758 &body,
759 Some(async_id),
760 )
761 }
762
763 fn on_block_ack_frame<B: SplitByteSlice, D>(
764 &mut self,
765 _sta: &mut BoundClient<'_, D>,
766 _action: mac::BlockAckAction,
767 _body: B,
768 ) {
769 }
775
776 async fn on_spectrum_mgmt_frame<B: SplitByteSlice, D: DeviceOps>(
777 &mut self,
778 sta: &mut BoundClient<'_, D>,
779 action: mac::SpectrumMgmtAction,
780 body: B,
781 ) {
782 match action {
783 mac::SpectrumMgmtAction::CHANNEL_SWITCH_ANNOUNCEMENT => {
784 if let Err(e) = sta
785 .channel_state
786 .bind(sta.ctx, sta.scanner)
787 .handle_announcement_frame(&body[..])
788 .await
789 {
790 warn!("Failed to handle channel switch announcement: {}", e);
791 }
792 }
793 _ => (),
794 }
795 }
796
797 fn on_sme_eapol<D: DeviceOps>(
798 &self,
799 sta: &mut BoundClient<'_, D>,
800 req: fidl_mlme::EapolRequest,
801 ) {
802 if !sta.sta.eapol_required() {
804 error!("Unexpected MLME-EAPOL.request message: BSS not protected");
805 return;
806 }
807 let protected = sta.sta.eapol_required() && self.0.controlled_port_open;
810 sta.send_eapol_frame(req.src_addr.into(), req.dst_addr.into(), protected, &req.data);
811 }
812
813 async fn on_sme_set_keys<D: DeviceOps>(
814 &self,
815 sta: &mut BoundClient<'_, D>,
816 req: fidl_mlme::SetKeysRequest,
817 ) {
818 if !sta.sta.eapol_required() {
819 error!("Unexpected MLME-SetKeys.request message: BSS not protected");
820 return;
821 }
822 let mut results = Vec::with_capacity(req.keylist.len());
823 for key_descriptor in req.keylist {
824 let key_id = key_descriptor.key_id;
825
826 match sta
827 .ctx
828 .device
829 .install_key(&softmac_key_configuration_from_mlme(key_descriptor))
830 .await
831 {
832 Ok(()) => results
833 .push(fidl_mlme::SetKeyResult { key_id, status: zx::Status::OK.into_raw() }),
834 Err(e) => {
835 error!("failed to set key: {}", e);
836 results.push(fidl_mlme::SetKeyResult { key_id, status: e.into_raw() })
837 }
838 }
839 }
840 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::SetKeysConf {
841 conf: fidl_mlme::SetKeysConfirm { results },
842 }) {
843 error!("Error sending SetKeysConfirm: {}", e);
844 }
845 }
846
847 async fn on_sme_set_controlled_port<D: DeviceOps>(
848 &mut self,
849 sta: &mut BoundClient<'_, D>,
850 req: fidl_mlme::SetControlledPortRequest,
851 ) {
852 if !sta.sta.eapol_required() {
853 error!("Unexpected MLME-SetControlledPort.request message: BSS not protected.");
854 return;
855 }
856 let should_open_controlled_port = req.state == fidl_mlme::ControlledPortState::Open;
857 if should_open_controlled_port == self.0.controlled_port_open {
858 return;
859 }
860 self.0.controlled_port_open = should_open_controlled_port;
861 if let Err(e) = sta.ctx.device.set_ethernet_status(req.state.into()).await {
862 error!(
863 "Error setting Ethernet port to {}: {}",
864 if should_open_controlled_port { "OPEN" } else { "CLOSED" },
865 e
866 );
867 }
868 }
869
870 async fn on_sme_deauthenticate<D: DeviceOps>(
871 &mut self,
872 sta: &mut BoundClient<'_, D>,
873 req: fidl_mlme::DeauthenticateRequest,
874 ) {
875 if let Err(e) = sta.send_deauth_frame(mac::ReasonCode(req.reason_code.into_primitive())) {
876 error!("Error sending deauthentication frame to BSS: {}", e);
877 }
878
879 self.pre_leaving_associated_state(sta).await;
880 let _ =
881 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
882
883 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::DeauthenticateConf {
884 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: sta.sta.bssid().to_array() },
885 }) {
886 error!("Error sending MLME-DEAUTHENTICATE.confirm: {}", e)
887 }
888 }
889
890 async fn pre_leaving_associated_state<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) {
891 self.0.status_check_timeout.next_event.take();
892 self.0.controlled_port_open = false;
893 if let Err(e) = sta.ctx.device.set_ethernet_down().await {
894 error!("Error disabling ethernet device offline: {}", e);
895 }
896 }
897
898 #[must_use]
899 async fn on_timeout<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) -> bool {
902 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::SignalReport {
903 ind: fidl_internal::SignalReportIndication {
904 rssi_dbm: self.0.signal_strength_average.avg_dbm().0,
905 snr_db: 0,
906 },
907 }) {
908 error!("Error sending MLME-SignalReport: {}", e)
909 }
910
911 let auto_deauth = self.0.lost_bss_counter.should_deauthenticate();
912 if auto_deauth {
913 sta.send_deauthenticate_ind(
914 fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
915 LocallyInitiated(true),
916 );
917 if let Err(e) =
918 sta.send_deauth_frame(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth.into())
919 {
920 warn!("Failed sending deauth frame {:?}", e);
921 }
922 self.pre_leaving_associated_state(sta).await;
923 } else {
924 self.0.lost_bss_counter.add_beacon_interval(ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT);
927 self.0.status_check_timeout =
928 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
929 }
930 auto_deauth
931 }
932}
933
934statemachine!(
935 #[derive(Debug)]
937 pub enum States,
938 () => Joined,
940 Joined => Authenticating,
941 Authenticating => Associating,
942 Associating => Associated,
943
944 Authenticating => Joined,
946 Associating => Joined,
947
948 Authenticating => Joined,
950 Associating => Joined,
951 Associated => Joined,
952
953 Associating => Authenticated, Associated => Authenticated,
956
957 Authenticated => Associating,
959);
960
961impl States {
962 pub fn new_initial() -> States {
964 States::from(State::new(Joined))
965 }
966
967 pub async fn start_connecting<D: DeviceOps>(self, sta: &mut BoundClient<'_, D>) -> States {
971 match self {
972 States::Joined(state) => {
973 let duration =
976 sta.sta.beacon_period() * sta.sta.connect_req.connect_failure_timeout;
977 let timeout = sta.ctx.timer.schedule_after(duration, TimedEvent::Connecting);
978 sta.sta.connect_timeout.replace(timeout);
979 match state.start_authenticating(sta).await {
980 Ok(algorithm) => state.transition_to(Authenticating::new(algorithm)).into(),
981 Err(()) => state.transition_to(Joined).into(),
982 }
983 }
984 other => {
985 warn!("Attempting to connect from a post-Joined state. Connect request ignored");
986 other
987 }
988 }
989 }
990
991 pub async fn on_mac_frame<B: SplitByteSlice, D: DeviceOps>(
999 mut self,
1000 sta: &mut BoundClient<'_, D>,
1001 bytes: B,
1002 rx_info: fidl_softmac::WlanRxInfo,
1003 async_id: TraceId,
1004 ) -> States {
1005 wtrace::duration!("States::on_mac_frame");
1006
1007 let body_aligned = (rx_info.rx_flags & fidl_softmac::WlanRxInfoFlags::FRAME_BODY_PADDING_4)
1008 != fidl_softmac::WlanRxInfoFlags::empty();
1009
1010 trace!("Parsing MAC frame:\n {:02x?}", bytes.deref());
1012 let mac_frame = match mac::MacFrame::parse(bytes, body_aligned) {
1013 Some(mac_frame) => mac_frame,
1014 None => {
1015 debug!("Dropping corrupt MAC frame.");
1016 wtrace::async_end_wlansoftmac_rx(async_id, "corrupt frame");
1017 return self;
1018 }
1019 };
1020
1021 if !sta.sta.should_handle_frame(&mac_frame) {
1022 warn!("Mac frame is either from a foreign BSS or not destined for us. Dropped.");
1023 wtrace::async_end_wlansoftmac_rx(async_id, "foreign BSS frame");
1024 return self;
1025 }
1026
1027 let frame_class = mac::FrameClass::from(&mac_frame);
1029 if !self.is_frame_class_permitted(frame_class) {
1030 debug!("Dropping MAC frame with prohibited frame class.");
1031 wtrace::async_end_wlansoftmac_rx(async_id, "frame with prohibited frame class");
1032 return self;
1033 }
1034
1035 match mac_frame {
1036 mac::MacFrame::Mgmt(mgmt_frame) => {
1037 let states = self.on_mgmt_frame(sta, mgmt_frame, rx_info).await;
1038 wtrace::async_end_wlansoftmac_rx(
1039 async_id,
1040 "management frame successfully received",
1041 );
1042 states
1043 }
1044 mac::MacFrame::Data(data_frame) => {
1045 if let States::Associated(state) = &mut self {
1046 state.on_data_frame(sta, data_frame, async_id);
1047 state.extract_and_record_signal_dbm(rx_info);
1048 } else {
1049 debug!("Dropping MAC data frame while not associated.");
1051 wtrace::async_end_wlansoftmac_rx(async_id, "data frame while not associated");
1052 }
1053 self
1054 }
1055 _ => {
1057 debug!("Dropping unsupported MAC control frame.");
1058 wtrace::async_end_wlansoftmac_rx(async_id, "unsupported control frame");
1059 self
1060 }
1061 }
1062 }
1063
1064 async fn on_mgmt_frame<B: SplitByteSlice, D: DeviceOps>(
1067 self,
1068 sta: &mut BoundClient<'_, D>,
1069 mgmt_frame: mac::MgmtFrame<B>,
1070 rx_info: fidl_softmac::WlanRxInfo,
1071 ) -> States {
1072 wtrace::duration!("States::on_mgmt_frame");
1073
1074 let (mgmt_hdr, mgmt_body) = match mgmt_frame.try_into_mgmt_body() {
1076 (mgmt_hdr, Some(mgmt_body)) => (mgmt_hdr, mgmt_body),
1077 (_, None) => return self,
1078 };
1079
1080 match self {
1081 States::Authenticating(mut state) => match mgmt_body {
1082 mac::MgmtBody::Authentication(auth_frame) => match state
1083 .on_auth_frame(sta, auth_frame)
1084 .await
1085 {
1086 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1087 AuthProgress::InProgress => state.into(),
1088 AuthProgress::Failed => state.transition_to(Joined).into(),
1089 },
1090 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1091 state.on_deauth_frame(sta, &deauth_hdr).await;
1092 state.transition_to(Joined).into()
1093 }
1094 _ => state.into(),
1095 },
1096 States::Associating(mut state) => match mgmt_body {
1097 mac::MgmtBody::AssociationResp(assoc_resp_frame) => {
1098 match state.on_assoc_resp_frame(sta, assoc_resp_frame).await {
1099 Ok(association) => state.transition_to(Associated(association)).into(),
1100 Err(()) => state.transition_to(Joined).into(),
1101 }
1102 }
1103 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1104 state.on_deauth_frame(sta, &deauth_hdr).await;
1105 state.transition_to(Joined).into()
1106 }
1107 mac::MgmtBody::Disassociation { disassoc_hdr, .. } => {
1110 state.on_disassoc_frame(sta, &disassoc_hdr);
1111 state.transition_to(Authenticated).into()
1112 }
1113 _ => state.into(),
1114 },
1115 States::Associated(mut state) => {
1116 state.extract_and_record_signal_dbm(rx_info);
1117 state.on_any_mgmt_frame(sta, &mgmt_hdr);
1118 match mgmt_body {
1119 mac::MgmtBody::Beacon { bcn_hdr, elements } => {
1120 state.on_beacon_frame(sta, &bcn_hdr, elements).await;
1121 state.into()
1122 }
1123 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1124 state.on_deauth_frame(sta, &deauth_hdr).await;
1125 state.transition_to(Joined).into()
1126 }
1127 mac::MgmtBody::Disassociation { disassoc_hdr, .. } => {
1128 state.on_disassoc_frame(sta, &disassoc_hdr).await;
1129 state.transition_to(Authenticated).into()
1130 }
1131 mac::MgmtBody::Action(action_frame) => {
1132 let mac::ActionBody { action_hdr, elements, .. } = action_frame.into_body();
1133 match action_hdr.action {
1134 mac::ActionCategory::BLOCK_ACK => {
1135 let reader = BufferReader::new(elements);
1136 if let Some(action) = reader.peek_unaligned::<mac::BlockAckAction>()
1137 {
1138 state.on_block_ack_frame(
1139 sta,
1140 action.get(),
1141 reader.into_remaining(),
1142 );
1143 }
1144 state.into()
1145 }
1146 mac::ActionCategory::SPECTRUM_MGMT => {
1147 let reader = BufferReader::new(elements);
1148 if let Some(action) =
1149 reader.peek_unaligned::<mac::SpectrumMgmtAction>()
1150 {
1151 state
1152 .on_spectrum_mgmt_frame(
1153 sta,
1154 action.get(),
1155 reader.into_remaining(),
1156 )
1157 .await;
1158 }
1159 state.into()
1160 }
1161 _ => state.into(),
1162 }
1163 }
1164 _ => state.into(),
1165 }
1166 }
1167 _ => self,
1168 }
1169 }
1170
1171 pub fn on_eth_frame<B: SplitByteSlice, D: DeviceOps>(
1172 &self,
1173 sta: &mut BoundClient<'_, D>,
1174 frame: B,
1175 async_id: TraceId,
1176 ) -> Result<(), Error> {
1177 wtrace::duration!("States::on_eth_frame");
1178 match self {
1179 States::Associated(state) => state.on_eth_frame(sta, frame, async_id),
1180 _ => Err(Error::Status(
1181 format!("Not associated. Ethernet dropped"),
1182 zx::Status::BAD_STATE,
1183 )),
1184 }
1185 }
1186
1187 pub async fn on_timed_event<D: DeviceOps>(
1189 self,
1190 sta: &mut BoundClient<'_, D>,
1191 event: TimedEvent,
1192 ) -> States {
1193 match event {
1194 TimedEvent::Connecting => {
1195 sta.sta.connect_timeout.take();
1196 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RejectedSequenceTimeout);
1197 let _ = sta
1198 .clear_association()
1199 .await
1200 .map_err(|e| error!("Failed to clear association: {}", e));
1201 match self {
1202 States::Authenticating(state) => state.transition_to(Joined).into(),
1203 States::Associating(state) => state.transition_to(Joined).into(),
1204 States::Associated(state) => state.transition_to(Joined).into(),
1205 _ => self,
1206 }
1207 }
1208 TimedEvent::Reassociating => match self {
1209 States::Associating(mut state) => {
1210 state.reconnect_timeout.take();
1211 sta.send_connect_conf_failure(
1212 fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1213 );
1214 state.transition_to(Authenticated).into()
1215 }
1216 _ => self,
1217 },
1218 TimedEvent::AssociationStatusCheck => match self {
1219 States::Associated(mut state) => {
1220 let should_auto_deauth = state.on_timeout(sta).await;
1221 match should_auto_deauth {
1222 true => state.transition_to(Joined).into(),
1223 false => state.into(),
1224 }
1225 }
1226 _ => self,
1227 },
1228 TimedEvent::ChannelSwitch => {
1229 if let Err(e) = sta
1230 .channel_state
1231 .bind(sta.ctx, sta.scanner)
1232 .handle_channel_switch_timeout()
1233 .await
1234 {
1235 error!("ChannelSwitch timeout handler failed: {}", e);
1236 }
1237 self
1238 }
1239 }
1240 }
1241
1242 pub async fn handle_mlme_req<D: DeviceOps>(
1243 self,
1244 sta: &mut BoundClient<'_, D>,
1245 req: wlan_sme::MlmeRequest,
1246 ) -> States {
1247 use wlan_sme::MlmeRequest as MlmeReq;
1248
1249 match self {
1250 States::Joined(mut state) => match req {
1251 MlmeReq::Deauthenticate(_) => {
1252 state.on_sme_deauthenticate(sta).await;
1253 state.into()
1254 }
1255 MlmeReq::Reconnect(req) => {
1256 sta.send_connect_conf_failure_with_bssid(
1257 req.peer_sta_address.into(),
1258 fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
1259 );
1260 state.into()
1261 }
1262 _ => state.into(),
1263 },
1264 States::Authenticating(mut state) => match req {
1265 MlmeReq::SaeHandshakeResp(resp) => match state.on_sme_sae_resp(sta, resp).await {
1266 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1267 AuthProgress::InProgress => state.into(),
1268 AuthProgress::Failed => state.transition_to(Joined).into(),
1269 },
1270 MlmeReq::SaeFrameTx(frame) => match state.on_sme_sae_tx(sta, frame).await {
1271 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1272 AuthProgress::InProgress => state.into(),
1273 AuthProgress::Failed => state.transition_to(Joined).into(),
1274 },
1275 MlmeReq::Deauthenticate(_) => {
1276 state.on_sme_deauthenticate(sta).await;
1277 state.transition_to(Joined).into()
1278 }
1279 MlmeReq::Reconnect(req) => {
1280 sta.send_connect_conf_failure_with_bssid(
1281 req.peer_sta_address.into(),
1282 fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
1283 );
1284 state.into()
1285 }
1286 _ => state.into(),
1287 },
1288 States::Authenticated(state) => match req {
1289 MlmeReq::Reconnect(req) => match state.on_sme_reconnect(sta, req) {
1290 Ok(timeout) => {
1291 state.transition_to(Associating::new_with_reconnect_timeout(timeout)).into()
1292 }
1293 Err(()) => state.into(),
1294 },
1295 _ => state.into(),
1296 },
1297 States::Associating(mut state) => match req {
1298 MlmeReq::Deauthenticate(_) => {
1299 state.on_sme_deauthenticate(sta).await;
1300 state.transition_to(Joined).into()
1301 }
1302 MlmeReq::Reconnect(req) => {
1303 let peer_sta_address: Bssid = req.peer_sta_address.into();
1304 if peer_sta_address != sta.sta.connect_req.selected_bss.bssid {
1305 sta.send_connect_conf_failure_with_bssid(
1306 peer_sta_address,
1307 fidl_ieee80211::StatusCode::NotInSameBss,
1308 );
1309 }
1310 state.into()
1311 }
1312 _ => state.into(),
1313 },
1314 States::Associated(mut state) => match req {
1315 MlmeReq::Eapol(req) => {
1316 state.on_sme_eapol(sta, req);
1317 state.into()
1318 }
1319 MlmeReq::SetKeys(req) => {
1320 state.on_sme_set_keys(sta, req).await;
1321 state.into()
1322 }
1323 MlmeReq::SetCtrlPort(req) => {
1324 state.on_sme_set_controlled_port(sta, req).await;
1325 state.into()
1326 }
1327 MlmeReq::Deauthenticate(req) => {
1328 state.on_sme_deauthenticate(sta, req).await;
1329 state.transition_to(Joined).into()
1330 }
1331 MlmeReq::Reconnect(req) => {
1332 let peer_sta_address: Bssid = req.peer_sta_address.into();
1333 if peer_sta_address != sta.sta.connect_req.selected_bss.bssid {
1334 sta.send_connect_conf_failure_with_bssid(
1335 peer_sta_address,
1336 fidl_ieee80211::StatusCode::NotInSameBss,
1337 );
1338 } else {
1339 sta.send_connect_conf_success(state.0.aid, &state.0.assoc_resp_ies[..]);
1340 }
1341 state.into()
1342 }
1343 _ => state.into(),
1344 },
1345 }
1346 }
1347
1348 fn is_frame_class_permitted(&self, class: mac::FrameClass) -> bool {
1350 wtrace::duration!("State::is_frame_class_permitted");
1351 match self {
1352 States::Joined(_) | States::Authenticating(_) => class == mac::FrameClass::Class1,
1353 States::Authenticated(_) | States::Associating(_) => class <= mac::FrameClass::Class2,
1354 States::Associated(_) => class <= mac::FrameClass::Class3,
1355 }
1356 }
1357}
1358
1359#[cfg(test)]
1360mod free_function_tests {
1361 use super::*;
1362 use wlan_common::mac::IntoBytesExt as _;
1363
1364 fn assoc_resp_frame_from_ies(elements: &[u8]) -> mac::AssocRespFrame<&[u8]> {
1365 mac::AssocRespFrame {
1366 assoc_resp_hdr: mac::AssocRespHdr {
1367 capabilities: mac::CapabilityInfo(0u16),
1368 status_code: mac::StatusCode(0u16),
1369 aid: 0u16,
1370 }
1371 .as_bytes_ref(),
1372 elements,
1373 }
1374 }
1375
1376 #[test]
1377 fn test_extract_ht_vht_op_success() {
1378 let mut buffer = Vec::new();
1379 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1380 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1381 let (ht_operation, vht_operation) =
1382 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1383 assert_eq!(ht_operation.unwrap(), ie::fake_ht_operation());
1384 assert_eq!(vht_operation.unwrap(), ie::fake_vht_operation());
1385 }
1386
1387 #[test]
1388 fn test_extract_ht_op_too_short() {
1389 let mut buffer = Vec::<u8>::new();
1390 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1391 buffer[1] -= 1; buffer.truncate(buffer.len() - 1);
1393 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1394 let (ht_operation, vht_operation) =
1395 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1396 assert_eq!(ht_operation, None);
1397 assert_eq!(vht_operation.unwrap(), ie::fake_vht_operation());
1398 }
1399
1400 #[test]
1401 fn test_extract_vht_op_too_short() {
1402 let mut buffer = Vec::new();
1403 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1404 let ht_end = buffer.len();
1405 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1406 buffer[ht_end + 1] -= 1; buffer.truncate(buffer.len() - 1);
1408 let (ht_operation, vht_operation) =
1409 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1410 assert_eq!(ht_operation.unwrap(), ie::fake_ht_operation());
1411 assert_eq!(vht_operation, None);
1412 }
1413}
1414
1415#[cfg(test)]
1416mod tests {
1417 use super::*;
1418 use crate::block_ack::{ADDBA_REQ_FRAME_LEN, write_addba_req_body};
1419 use crate::client::channel_switch::ChannelState;
1420 use crate::client::scanner::Scanner;
1421 use crate::client::test_utils::drain_timeouts;
1422 use crate::client::{Client, Context, ParsedConnectRequest, TimedEventClass};
1423 use crate::device::{FakeDevice, FakeDeviceState};
1424 use crate::test_utils::{MockWlanRxInfo, fake_set_keys_req, fake_wlan_channel};
1425 use akm::AkmAlgorithm;
1426 use assert_matches::assert_matches;
1427 use fuchsia_sync::Mutex;
1428 use std::sync::{Arc, LazyLock};
1429 use test_case::test_case;
1430 use wlan_common::buffer_writer::BufferWriter;
1431 use wlan_common::ie::IeType;
1432 use wlan_common::mac::IntoBytesExt as _;
1433 use wlan_common::sequence::SequenceManager;
1434 use wlan_common::test_utils::fake_capabilities::fake_client_capabilities;
1435 use wlan_common::test_utils::fake_frames::*;
1436 use wlan_common::test_utils::fake_stas::IesOverrides;
1437 use wlan_common::timer::{self, create_timer};
1438 use wlan_common::{fake_bss_description, mgmt_writer};
1439 use wlan_frame_writer::append_frame_to;
1440 use {fidl_fuchsia_wlan_common as fidl_common, wlan_statemachine as statemachine};
1441
1442 static BSSID: LazyLock<Bssid> = LazyLock::new(|| [6u8; 6].into());
1443 static IFACE_MAC: LazyLock<MacAddr> = LazyLock::new(|| [3u8; 6].into());
1444
1445 struct MockObjects {
1446 fake_device: FakeDevice,
1447 fake_device_state: Arc<Mutex<FakeDeviceState>>,
1448 timer: Option<Timer<TimedEvent>>,
1449 time_stream: timer::EventStream<TimedEvent>,
1450 scanner: Scanner,
1451 channel_state: ChannelState,
1452 }
1453
1454 impl MockObjects {
1455 async fn new() -> Self {
1459 let (timer, time_stream) = create_timer();
1460 let (fake_device, fake_device_state) = FakeDevice::new().await;
1461 Self {
1462 fake_device,
1463 fake_device_state,
1464 timer: Some(timer),
1465 time_stream,
1466 scanner: Scanner::new(*IFACE_MAC),
1467 channel_state: ChannelState::new_with_main_channel(fake_wlan_channel().into()),
1468 }
1469 }
1470
1471 async fn make_ctx(&mut self) -> Context<FakeDevice> {
1472 self.fake_device
1473 .set_channel(fake_wlan_channel().into())
1474 .await
1475 .expect("fake device is obedient");
1476 self.make_base_ctx()
1477 }
1478
1479 async fn make_ctx_with_bss(&mut self) -> Context<FakeDevice> {
1480 self.fake_device
1481 .set_channel(fake_wlan_channel().into())
1482 .await
1483 .expect("fake device is obedient");
1484 self.fake_device
1485 .join_bss(&fidl_common::JoinBssRequest {
1486 bssid: Some([1, 2, 3, 4, 5, 6]),
1487 bss_type: Some(fidl_common::BssType::Personal),
1488 remote: Some(true),
1489 beacon_period: Some(100),
1490 ..Default::default()
1491 })
1492 .await
1493 .expect("error configuring bss");
1494 self.make_base_ctx()
1495 }
1496
1497 fn make_base_ctx(&mut self) -> Context<FakeDevice> {
1498 Context {
1499 _config: Default::default(),
1500 device: self.fake_device.clone(),
1501 timer: self.timer.take().unwrap(),
1502 seq_mgr: SequenceManager::new(),
1503 }
1504 }
1505 }
1506
1507 fn make_client_station() -> Client {
1508 let connect_req = ParsedConnectRequest {
1509 selected_bss: fake_bss_description!(Open, bssid: BSSID.to_array()),
1510 connect_failure_timeout: 10,
1511 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1512 sae_password: vec![],
1513 wep_key: None,
1514 security_ie: vec![],
1515 owe_public_key: None,
1516 };
1517 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1518 }
1519
1520 fn make_protected_client_station() -> Client {
1521 let connect_req = ParsedConnectRequest {
1522 selected_bss: fake_bss_description!(Wpa2, bssid: BSSID.to_array()),
1523 connect_failure_timeout: 10,
1524 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1525 sae_password: vec![],
1526 wep_key: None,
1527 security_ie: vec![
1528 0x30, 0x14, 1, 0, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0xa8, 0x04, ],
1537 owe_public_key: None,
1538 };
1539 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1540 }
1541
1542 fn connect_conf_failure(result_code: fidl_ieee80211::StatusCode) -> fidl_mlme::ConnectConfirm {
1543 fidl_mlme::ConnectConfirm {
1544 peer_sta_address: BSSID.to_array(),
1545 result_code,
1546 association_id: 0,
1547 association_ies: vec![],
1548 }
1549 }
1550
1551 fn empty_association(sta: &mut BoundClient<'_, FakeDevice>) -> Association {
1552 let status_check_timeout =
1553 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
1554 Association {
1555 controlled_port_open: false,
1556 aid: 0,
1557 assoc_resp_ies: vec![],
1558 ap_ht_op: None,
1559 ap_vht_op: None,
1560 lost_bss_counter: LostBssCounter::start(
1561 sta.sta.beacon_period(),
1562 DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
1563 ),
1564 qos: Qos::Disabled,
1565 status_check_timeout,
1566 signal_strength_average: SignalStrengthAverage::new(),
1567 block_ack_state: StateMachine::new(BlockAckState::from(State::new(Closed))),
1568 }
1569 }
1570
1571 fn fake_assoc_cfg() -> fidl_softmac::WlanAssociationConfig {
1572 fidl_softmac::WlanAssociationConfig {
1573 bssid: Some(BSSID.to_array()),
1574 aid: Some(42),
1575 channel: Some(fidl_ieee80211::WlanChannel {
1576 primary: 149,
1577 cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
1578 secondary80: 42,
1579 }),
1580 rates: None,
1581 capability_info: None,
1582 ..Default::default()
1583 }
1584 }
1585
1586 fn fake_deauth_req() -> wlan_sme::MlmeRequest {
1587 wlan_sme::MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1588 peer_sta_address: BSSID.to_array(),
1589 reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1590 })
1591 }
1592
1593 fn open_authenticating(sta: &mut BoundClient<'_, FakeDevice>) -> Authenticating {
1594 let mut auth = Authenticating::new(AkmAlgorithm::OpenSupplicant);
1595 auth.algorithm.initiate(sta).expect("Failed to initiate open auth");
1596 auth
1597 }
1598
1599 #[fuchsia::test(allow_stalls = false)]
1600 async fn connect_authenticate_tx_failure() {
1601 let mut m = MockObjects::new().await;
1602 m.fake_device_state.lock().config.send_wlan_frame_fails = true;
1603 let mut ctx = m.make_ctx().await;
1604 let mut sta = make_client_station();
1605 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1606
1607 let state = Joined;
1608 let _state =
1609 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1610
1611 assert!(m.time_stream.try_next().is_err());
1613
1614 let msg = m
1616 .fake_device_state
1617 .lock()
1618 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1619 .expect("expect msg");
1620 assert_eq!(
1621 msg,
1622 fidl_mlme::ConnectConfirm {
1623 peer_sta_address: BSSID.to_array(),
1624 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1625 association_id: 0,
1626 association_ies: vec![],
1627 }
1628 );
1629 }
1630
1631 #[fuchsia::test(allow_stalls = false)]
1632 async fn joined_no_authentication_algorithm() {
1633 let mut m = MockObjects::new().await;
1634 let mut ctx = m.make_ctx_with_bss().await;
1635 let connect_req = ParsedConnectRequest {
1636 selected_bss: fake_bss_description!(Open, bssid: BSSID.to_array()),
1637 connect_failure_timeout: 10,
1638 auth_type: fidl_mlme::AuthenticationTypes::SharedKey,
1640 sae_password: vec![],
1641 wep_key: None,
1642 security_ie: vec![],
1643 owe_public_key: None,
1644 };
1645 let mut sta = Client::new(connect_req, *IFACE_MAC, fake_client_capabilities());
1646 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1647 let state = Joined;
1648
1649 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1650 let _state =
1651 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1652
1653 let msg = m
1655 .fake_device_state
1656 .lock()
1657 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1658 .expect("expect msg");
1659 assert_eq!(
1660 msg,
1661 fidl_mlme::ConnectConfirm {
1662 peer_sta_address: [6, 6, 6, 6, 6, 6],
1663 result_code: fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm,
1664 association_id: 0,
1665 association_ies: vec![],
1666 }
1667 );
1668
1669 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1670 }
1671
1672 #[fuchsia::test(allow_stalls = false)]
1673 async fn authenticating_state_auth_rejected() {
1674 let mut m = MockObjects::new().await;
1675 let mut ctx = m.make_ctx_with_bss().await;
1676 let mut sta = make_client_station();
1677 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1678 let mut state = open_authenticating(&mut sta);
1679
1680 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1681 assert_matches!(
1683 state
1684 .on_auth_frame(
1685 &mut sta,
1686 mac::AuthFrame {
1687 auth_hdr: mac::AuthHdr {
1688 auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
1689 auth_txn_seq_num: 2,
1690 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1691 }
1692 .as_bytes_ref(),
1693 elements: &[],
1694 },
1695 )
1696 .await,
1697 AuthProgress::Failed
1698 );
1699
1700 let msg = m
1702 .fake_device_state
1703 .lock()
1704 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1705 .expect("expect msg");
1706 assert_eq!(
1707 msg,
1708 fidl_mlme::ConnectConfirm {
1709 peer_sta_address: BSSID.to_array(),
1710 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1711 association_id: 0,
1712 association_ies: vec![],
1713 }
1714 );
1715 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1716 }
1717
1718 #[fuchsia::test(allow_stalls = false)]
1719 async fn authenticating_state_deauth_frame() {
1720 let mut m = MockObjects::new().await;
1721 let mut ctx = m.make_ctx_with_bss().await;
1722 let mut sta = make_client_station();
1723 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1724 let mut state = open_authenticating(&mut sta);
1725
1726 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1727 state
1728 .on_deauth_frame(
1729 &mut sta,
1730 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::NoMoreStas.into() },
1731 )
1732 .await;
1733
1734 let msg = m
1736 .fake_device_state
1737 .lock()
1738 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1739 .expect("expect msg");
1740 assert_eq!(
1741 msg,
1742 fidl_mlme::ConnectConfirm {
1743 peer_sta_address: BSSID.to_array(),
1744 result_code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
1745 association_id: 0,
1746 association_ies: vec![],
1747 }
1748 );
1749 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1750 }
1751
1752 #[fuchsia::test(allow_stalls = false)]
1753 async fn associating_success_unprotected() {
1754 let mut m = MockObjects::new().await;
1755 let mut ctx = m.make_ctx_with_bss().await;
1756 let mut sta = make_client_station();
1757 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1758
1759 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1760 let mut state = Associating::default();
1761 let assoc_resp_ies = fake_bss_description!(Wpa2, ies_overrides: IesOverrides::new()
1762 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1763 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1764 )
1765 .ies()
1766 .to_vec();
1767 let Association { aid, controlled_port_open, .. } = state
1768 .on_assoc_resp_frame(
1769 &mut sta,
1770 mac::AssocRespFrame {
1771 assoc_resp_hdr: mac::AssocRespHdr {
1772 aid: 42,
1773 capabilities: mac::CapabilityInfo(52),
1774 status_code: fidl_ieee80211::StatusCode::Success.into(),
1775 }
1776 .as_bytes_ref(),
1777 elements: &assoc_resp_ies[..],
1778 },
1779 )
1780 .await
1781 .expect("failed processing association response frame");
1782 assert_eq!(aid, 42);
1783 assert_eq!(true, controlled_port_open);
1784
1785 let msg = m
1787 .fake_device_state
1788 .lock()
1789 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1790 .expect("no message");
1791 assert_eq!(
1792 msg,
1793 fidl_mlme::ConnectConfirm {
1794 peer_sta_address: BSSID.to_array(),
1795 result_code: fidl_ieee80211::StatusCode::Success,
1796 association_id: 42,
1797 association_ies: assoc_resp_ies,
1798 }
1799 );
1800 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1801 }
1802
1803 #[fuchsia::test(allow_stalls = false)]
1804 async fn associating_success_protected() {
1805 let mut m = MockObjects::new().await;
1806 let mut ctx = m.make_ctx_with_bss().await;
1807 let mut sta = make_protected_client_station();
1808 sta.client_capabilities.0.capability_info =
1809 mac::CapabilityInfo(0).with_ess(true).with_ibss(true);
1810 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1811 let mut state = Associating::default();
1812
1813 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1814 let assoc_resp_ies =
1815 fake_bss_description!(Wpa2, bssid: BSSID.to_array(), ies_overrides: IesOverrides::new()
1816 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1817 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1818 )
1819 .ies()
1820 .to_vec();
1821 let Association { aid, controlled_port_open, .. } = state
1822 .on_assoc_resp_frame(
1823 &mut sta,
1824 mac::AssocRespFrame {
1825 assoc_resp_hdr: mac::AssocRespHdr {
1826 aid: 42,
1827 capabilities: mac::CapabilityInfo(0).with_ibss(true).with_cf_pollable(true),
1828 status_code: fidl_ieee80211::StatusCode::Success.into(),
1829 }
1830 .as_bytes_ref(),
1831 elements: &assoc_resp_ies[..],
1832 },
1833 )
1834 .await
1835 .expect("failed processing association response frame");
1836 assert_eq!(aid, 42);
1837 assert_eq!(false, controlled_port_open);
1838
1839 assert_eq!(m.fake_device_state.lock().assocs.len(), 1);
1841
1842 let assoc_cfg = m
1843 .fake_device_state
1844 .lock()
1845 .assocs
1846 .get(&(*BSSID).into())
1847 .expect("expect assoc ctx to be set")
1848 .clone();
1849 assert_eq!(assoc_cfg.aid, Some(42));
1850 assert_eq!(assoc_cfg.qos, Some(true));
1851 assert_eq!(
1852 assoc_cfg.rates,
1853 Some(vec![0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c])
1854 );
1855 assert_eq!(assoc_cfg.capability_info, Some(2));
1856 assert!(assoc_cfg.ht_cap.is_some());
1857 assert!(assoc_cfg.vht_cap.is_some());
1858 assert!(assoc_cfg.ht_op.is_some());
1859 assert!(assoc_cfg.vht_op.is_some());
1860
1861 let msg = m
1863 .fake_device_state
1864 .lock()
1865 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1866 .expect("no message");
1867 assert_eq!(
1868 msg,
1869 fidl_mlme::ConnectConfirm {
1870 peer_sta_address: BSSID.to_array(),
1871 result_code: fidl_ieee80211::StatusCode::Success,
1872 association_id: 42,
1873 association_ies: assoc_resp_ies,
1874 }
1875 );
1876 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1877 }
1878
1879 #[fuchsia::test(allow_stalls = false)]
1880 async fn associating_failure_due_to_failed_status_code() {
1881 let mut m = MockObjects::new().await;
1882 let mut ctx = m.make_ctx_with_bss().await;
1883 let mut sta = make_client_station();
1884 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1885
1886 let mut state = Associating::default();
1887
1888 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1889 state
1891 .on_assoc_resp_frame(
1892 &mut sta,
1893 mac::AssocRespFrame {
1894 assoc_resp_hdr: mac::AssocRespHdr {
1895 aid: 42,
1896 capabilities: mac::CapabilityInfo(52),
1897 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1898 }
1899 .as_bytes_ref(),
1900 elements: &[][..],
1901 },
1902 )
1903 .await
1904 .expect_err("expected failure processing association response frame");
1905
1906 let msg = m
1908 .fake_device_state
1909 .lock()
1910 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1911 .expect("no message");
1912 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::NotInSameBss));
1913 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1914 }
1915
1916 #[fuchsia::test(allow_stalls = false)]
1917 async fn associating_failure_due_to_incompatibility() {
1918 let mut m = MockObjects::new().await;
1919 let mut ctx = m.make_ctx_with_bss().await;
1920 let mut sta = make_client_station();
1921 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1922
1923 let mut state = Associating::default();
1924
1925 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1926 state
1927 .on_assoc_resp_frame(
1928 &mut sta,
1929 mac::AssocRespFrame {
1930 assoc_resp_hdr: mac::AssocRespHdr {
1931 aid: 42,
1932 capabilities: mac::CapabilityInfo(52),
1933 status_code: fidl_ieee80211::StatusCode::Success.into(),
1934 }
1935 .as_bytes_ref(),
1936 elements: fake_bss_description!(Wpa2, rates: vec![0x81]).ies(),
1937 },
1938 )
1939 .await
1940 .expect_err("expected failure processing association response frame");
1941
1942 let msg = m
1944 .fake_device_state
1945 .lock()
1946 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1947 .expect("no message");
1948 assert_eq!(
1949 msg,
1950 connect_conf_failure(fidl_ieee80211::StatusCode::RefusedCapabilitiesMismatch)
1951 );
1952 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1953 }
1954
1955 #[fuchsia::test(allow_stalls = false)]
1956 async fn associating_deauth_frame() {
1957 let mut m = MockObjects::new().await;
1958 let mut ctx = m.make_ctx_with_bss().await;
1959 let mut sta = make_client_station();
1960 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1961
1962 let mut state = Associating::default();
1963
1964 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1965 state
1966 .on_deauth_frame(
1967 &mut sta,
1968 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1969 )
1970 .await;
1971
1972 let msg = m
1974 .fake_device_state
1975 .lock()
1976 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1977 .expect("no message");
1978 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
1979 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1980 }
1981
1982 #[fuchsia::test(allow_stalls = false)]
1983 async fn associating_disassociation() {
1984 let mut m = MockObjects::new().await;
1985 let mut ctx = m.make_ctx_with_bss().await;
1986 let mut sta = make_client_station();
1987 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1988
1989 let mut state = Associating::default();
1990
1991 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1992 state.on_disassoc_frame(
1993 &mut sta,
1994 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1995 );
1996
1997 let msg = m
1999 .fake_device_state
2000 .lock()
2001 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2002 .expect("no message");
2003 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
2004 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2005 }
2006
2007 fn mock_rx_info<'a>(client: &BoundClient<'a, FakeDevice>) -> fidl_softmac::WlanRxInfo {
2008 let channel = client.channel_state.get_main_channel().unwrap();
2009 MockWlanRxInfo::with_channel(channel).into()
2010 }
2011
2012 #[fuchsia::test(allow_stalls = false)]
2013 async fn associated_block_ack_frame() {
2014 let mut mock = MockObjects::new().await;
2015 let mut ctx = mock.make_ctx().await;
2016 let mut station = make_client_station();
2017 let mut client = station.bind(&mut ctx, &mut mock.scanner, &mut mock.channel_state);
2018
2019 let frame = {
2020 let mut buffer = [0u8; ADDBA_REQ_FRAME_LEN];
2021 let writer = BufferWriter::new(&mut buffer[..]);
2022 let mut writer = append_frame_to!(
2023 writer,
2024 {
2025 headers: {
2026 mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
2027 mac::FrameControl(0)
2028 .with_frame_type(mac::FrameType::MGMT)
2029 .with_mgmt_subtype(mac::MgmtSubtype::ACTION),
2030 client.sta.iface_mac,
2031 client.sta.bssid(),
2032 mac::SequenceControl(0)
2033 .with_seq_num(client.ctx.seq_mgr.next_sns1(&client.sta.bssid().into()) as u16),
2034 ),
2035 },
2036 }
2037 )
2038 .unwrap();
2039 write_addba_req_body(&mut writer, 1).unwrap();
2040 buffer
2041 };
2042
2043 let state = States::from(statemachine::testing::new_state(Associated(empty_association(
2044 &mut client,
2045 ))));
2046 let rx_info = mock_rx_info(&client);
2047 match state.on_mac_frame(&mut client, &frame[..], rx_info, 0.into()).await {
2048 States::Associated(state) => {
2049 let (_, associated) = state.release_data();
2050 match *associated.0.block_ack_state.as_ref() {
2057 BlockAckState::Closed(_) => {}
2058 _ => panic!("client has transitioned BlockAck"),
2059 }
2060 }
2061 _ => panic!("client no longer associated"),
2062 }
2063 }
2064
2065 #[fuchsia::test(allow_stalls = false)]
2066 async fn associated_deauth_frame() {
2067 let mut m = MockObjects::new().await;
2068 let mut ctx = m.make_ctx_with_bss().await;
2069 let mut sta = make_client_station();
2070 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2071 let mut state = Associated(empty_association(&mut sta));
2072
2073 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2074
2075 sta.ctx
2077 .device
2078 .notify_association_complete(fake_assoc_cfg())
2079 .await
2080 .expect("valid assoc_cfg should succeed");
2081 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2082
2083 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2084 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2085
2086 let _joined = state
2087 .on_deauth_frame(
2088 &mut sta,
2089 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2090 )
2091 .await;
2092
2093 let msg = m
2095 .fake_device_state
2096 .lock()
2097 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
2098 .expect("no message");
2099 assert_eq!(
2100 msg,
2101 fidl_mlme::DeauthenticateIndication {
2102 peer_sta_address: BSSID.to_array(),
2103 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2104 locally_initiated: false,
2105 }
2106 );
2107 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2109 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2110 }
2111
2112 #[fuchsia::test(allow_stalls = false)]
2113 async fn associated_disassociation() {
2114 let mut m = MockObjects::new().await;
2115 let mut ctx = m.make_ctx_with_bss().await;
2116 let mut sta = make_client_station();
2117 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2118 let mut state = Associated(empty_association(&mut sta));
2119
2120 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2121 state.0.controlled_port_open = true;
2122
2123 sta.ctx
2124 .device
2125 .notify_association_complete(fake_assoc_cfg())
2126 .await
2127 .expect("valid assoc_cfg should succeed");
2128 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2129
2130 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2131 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2132
2133 let _authenticated = state
2134 .on_disassoc_frame(
2135 &mut sta,
2136 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2137 )
2138 .await;
2139
2140 let msg = m
2142 .fake_device_state
2143 .lock()
2144 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2145 .expect("no message");
2146 assert_eq!(
2147 msg,
2148 fidl_mlme::DisassociateIndication {
2149 peer_sta_address: BSSID.to_array(),
2150 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2151 locally_initiated: false,
2152 }
2153 );
2154
2155 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2157 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2158 }
2159
2160 #[fuchsia::test(allow_stalls = false)]
2161 async fn associated_move_data_closed_controlled_port() {
2162 let mut m = MockObjects::new().await;
2163 let mut ctx = m.make_ctx().await;
2164 let mut sta = make_client_station();
2165 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2166 let state = Associated(empty_association(&mut sta));
2167
2168 let bytes = make_data_frame_single_llc(None, None);
2169 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2170 state.on_data_frame(&mut sta, data_frame, 0.into());
2171
2172 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2174 }
2175
2176 #[fuchsia::test(allow_stalls = false)]
2177 async fn associated_move_data_opened_controlled_port() {
2178 let mut m = MockObjects::new().await;
2179 let mut ctx = m.make_ctx().await;
2180 let mut sta = make_client_station();
2181 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2182 let state =
2183 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2184
2185 let bytes = make_data_frame_single_llc(None, None);
2186 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2187 state.on_data_frame(&mut sta, data_frame, 0.into());
2188
2189 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
2191 #[rustfmt::skip]
2192 assert_eq!(m.fake_device_state.lock().eth_queue[0], [
2193 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 9, 10, 11, 11, 11, ]);
2198 }
2199
2200 #[fuchsia::test(allow_stalls = false)]
2201 async fn associated_skip_empty_data() {
2202 let mut m = MockObjects::new().await;
2203 let mut ctx = m.make_ctx().await;
2204 let mut sta = make_client_station();
2205 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2206 let state =
2207 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2208
2209 let bytes = make_data_frame_single_llc_payload(None, None, &[]);
2210 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2211 state.on_data_frame(&mut sta, data_frame, 0.into());
2212
2213 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2215 }
2216
2217 #[test_case(true, true; "port open and protected")]
2218 #[test_case(false, true; "port closed and protected")]
2219 #[test_case(true, false; "port open and unprotected")]
2220 #[test_case(false, false; "port closed and unprotected (not a typical state)")]
2221 #[fuchsia::test(allow_stalls = false)]
2222 async fn associated_send_keep_alive_after_null_data_frame(
2223 controlled_port_open: bool,
2224 protected: bool,
2225 ) {
2226 let mut m = MockObjects::new().await;
2227 let mut ctx = m.make_ctx().await;
2228 let mut sta =
2229 if protected { make_protected_client_station() } else { make_client_station() };
2230 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2231 let state = Associated(Association { controlled_port_open, ..empty_association(&mut sta) });
2232
2233 let bytes = make_null_data_frame();
2234 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2235 state.on_data_frame(&mut sta, data_frame, 0.into());
2236
2237 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2239 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2240 let bytes = &m.fake_device_state.lock().wlan_queue[0].0;
2241 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2242 let frame_ctrl = data_frame.frame_ctrl();
2243 assert_eq!(frame_ctrl.to_ds(), true);
2244 assert_eq!(frame_ctrl.from_ds(), false);
2245 assert!(data_frame.body.is_empty());
2246 }
2247
2248 #[fuchsia::test(allow_stalls = false)]
2249 async fn associated_handle_eapol_closed_controlled_port() {
2250 let mut m = MockObjects::new().await;
2251 let mut ctx = m.make_ctx().await;
2252 let mut sta = make_protected_client_station();
2253 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2254 let state = Associated(empty_association(&mut sta));
2255
2256 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2257 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2258 state.on_data_frame(&mut sta, data_frame, 0.into());
2259
2260 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2262
2263 let eapol_ind = m
2265 .fake_device_state
2266 .lock()
2267 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2268 .expect("error reading EAPOL.indication");
2269 assert_eq!(
2270 eapol_ind,
2271 fidl_mlme::EapolIndication {
2272 src_addr: src_addr.to_array(),
2273 dst_addr: dst_addr.to_array(),
2274 data: EAPOL_PDU.to_vec()
2275 }
2276 );
2277 }
2278
2279 #[fuchsia::test(allow_stalls = false)]
2280 async fn associated_handle_eapol_open_controlled_port() {
2281 let mut m = MockObjects::new().await;
2282 let mut ctx = m.make_ctx().await;
2283 let mut sta = make_protected_client_station();
2284 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2285 let state = Associated(empty_association(&mut sta));
2286
2287 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2288 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2289 state.on_data_frame(&mut sta, data_frame, 0.into());
2290
2291 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2293
2294 let eapol_ind = m
2296 .fake_device_state
2297 .lock()
2298 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2299 .expect("error reading EAPOL.indication");
2300 assert_eq!(
2301 eapol_ind,
2302 fidl_mlme::EapolIndication {
2303 src_addr: src_addr.to_array(),
2304 dst_addr: dst_addr.to_array(),
2305 data: EAPOL_PDU.to_vec()
2306 }
2307 );
2308 }
2309
2310 #[fuchsia::test(allow_stalls = false)]
2311 async fn associated_handle_amsdus_open_controlled_port() {
2312 let mut m = MockObjects::new().await;
2313 let mut ctx = m.make_ctx().await;
2314 let mut sta = make_protected_client_station();
2315 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2316 let state =
2317 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2318
2319 let bytes = make_data_frame_amsdu();
2320 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2321 state.on_data_frame(&mut sta, data_frame, 0.into());
2322
2323 let queue = &m.fake_device_state.lock().eth_queue;
2324 assert_eq!(queue.len(), 2);
2325 #[rustfmt::skip]
2326 let mut expected_first_eth_frame = vec![
2327 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, 0x08, 0x00, ];
2331 expected_first_eth_frame.extend_from_slice(MSDU_1_PAYLOAD);
2332 assert_eq!(queue[0], &expected_first_eth_frame[..]);
2333 #[rustfmt::skip]
2334 let mut expected_second_eth_frame = vec![
2335 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x04, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, 0x08, 0x01, ];
2339 expected_second_eth_frame.extend_from_slice(MSDU_2_PAYLOAD);
2340 assert_eq!(queue[1], &expected_second_eth_frame[..]);
2341 }
2342
2343 #[fuchsia::test(allow_stalls = false)]
2344 async fn associated_request_bu_data_frame() {
2345 let mut m = MockObjects::new().await;
2346 let mut ctx = m.make_ctx().await;
2347 let mut sta = make_client_station();
2348 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2349 let state = Associated(Association {
2350 aid: 42,
2351 controlled_port_open: true,
2352 ..empty_association(&mut sta)
2353 });
2354
2355 let mut bytes = make_data_frame_single_llc(None, None);
2356 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2357 data_frame.fixed_fields.frame_ctrl =
2358 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2359 state.on_data_frame(&mut sta, data_frame, 0.into());
2360
2361 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2362 #[rustfmt::skip]
2363 assert_eq!(&m
2364 .fake_device_state.lock().wlan_queue[0].0[..], &[
2365 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2371 }
2372
2373 #[fuchsia::test(allow_stalls = false)]
2374 async fn associated_request_bu_mgmt_frame() {
2375 let mut m = MockObjects::new().await;
2376 let mut ctx = m.make_ctx().await;
2377 let mut sta = make_client_station();
2378 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2379 let state = Associated(Association {
2380 aid: 42,
2381 controlled_port_open: true,
2382 ..empty_association(&mut sta)
2383 });
2384
2385 state.on_any_mgmt_frame(
2386 &mut sta,
2387 &mac::MgmtHdr {
2388 frame_ctrl: mac::FrameControl(0)
2389 .with_frame_type(mac::FrameType::MGMT)
2390 .with_mgmt_subtype(mac::MgmtSubtype::BEACON)
2391 .with_more_data(true),
2392 duration: 0,
2393 addr1: [3; 6].into(),
2394 addr2: (*BSSID).into(),
2395 addr3: (*BSSID).into(),
2396 seq_ctrl: mac::SequenceControl(0),
2397 },
2398 );
2399
2400 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2401 #[rustfmt::skip]
2402 assert_eq!(&m
2403 .fake_device_state.lock().wlan_queue[0].0[..], &[
2404 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2410 }
2411
2412 #[fuchsia::test(allow_stalls = false)]
2413 async fn associated_no_bu_request() {
2414 let mut m = MockObjects::new().await;
2415 let mut ctx = m.make_ctx().await;
2416 let mut sta = make_client_station();
2417 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2418
2419 let state = Associated(empty_association(&mut sta));
2421 let mut bytes = make_data_frame_single_llc(None, None);
2422 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2423 data_frame.fixed_fields.frame_ctrl =
2424 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2425 state.on_data_frame(&mut sta, data_frame, 0.into());
2426 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2427
2428 let state = States::from(statemachine::testing::new_state(Associated(Association {
2430 controlled_port_open: true,
2431 ..empty_association(&mut sta)
2432 })));
2433 #[rustfmt::skip]
2434 let beacon = vec![
2435 0b1000_00_00, 0b00100000, 0, 0, 3, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 0x10, 0, ];
2444 let rx_info = mock_rx_info(&sta);
2445 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
2446 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2447 }
2448
2449 #[fuchsia::test(allow_stalls = false)]
2450 async fn associated_drop_foreign_data_frames() {
2451 let mut m = MockObjects::new().await;
2452 let mut ctx = m.make_ctx().await;
2453 let mut sta = make_client_station();
2454 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2455
2456 let state = States::from(statemachine::testing::new_state(Associated(Association {
2458 aid: 42,
2459 controlled_port_open: true,
2460 ..empty_association(&mut sta)
2461 })));
2462 let fc = mac::FrameControl(0)
2463 .with_frame_type(mac::FrameType::DATA)
2464 .with_data_subtype(mac::DataSubtype(0))
2465 .with_from_ds(true);
2466 let fc = fc.0.to_le_bytes();
2467 let bytes = vec![
2469 fc[0], fc[1], 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 9, 10, 11, 11, 11,
2482 ];
2483 let rx_info = mock_rx_info(&sta);
2484 state.on_mac_frame(&mut sta, &bytes[..], rx_info, 0.into()).await;
2485 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2486 }
2487
2488 #[fuchsia::test(allow_stalls = false)]
2489 async fn state_transitions_joined_state_reconnect_denied() {
2490 let mut m = MockObjects::new().await;
2491 let mut ctx = m.make_ctx().await;
2492 let mut sta = make_client_station();
2493 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2494 let mut state = States::from(statemachine::testing::new_state(Joined));
2495
2496 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2498 peer_sta_address: [1, 2, 3, 4, 5, 6],
2499 });
2500 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2501
2502 assert_matches!(state, States::Joined(_), "not in joined state");
2503
2504 let msg = m
2506 .fake_device_state
2507 .lock()
2508 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2509 .expect("expect msg");
2510 assert_eq!(
2511 msg,
2512 fidl_mlme::ConnectConfirm {
2513 peer_sta_address: [1, 2, 3, 4, 5, 6],
2514 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2515 association_id: 0,
2516 association_ies: vec![],
2517 }
2518 );
2519 }
2520
2521 #[fuchsia::test(allow_stalls = false)]
2522 async fn state_transitions_authing_success() {
2523 let mut m = MockObjects::new().await;
2524 let mut ctx = m.make_ctx().await;
2525 let mut sta = make_client_station();
2526 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2527 let mut state =
2528 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2529
2530 #[rustfmt::skip]
2532 let auth_resp_success = vec![
2533 0b1011_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 2, 0, 0, 0, ];
2545 let rx_info = mock_rx_info(&sta);
2546 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2547 assert_matches!(state, States::Associating(_), "not in associating state");
2548 }
2549
2550 #[fuchsia::test(allow_stalls = false)]
2551 async fn state_transitions_authing_failure() {
2552 let mut m = MockObjects::new().await;
2553 let mut ctx = m.make_ctx_with_bss().await;
2554 let mut sta = make_client_station();
2555 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2556 let mut state =
2557 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2558
2559 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2560 #[rustfmt::skip]
2562 let auth_resp_failure = vec![
2563 0b1011_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 2, 0, 42, 0, ];
2575 let rx_info = mock_rx_info(&sta);
2576 state = state.on_mac_frame(&mut sta, &auth_resp_failure[..], rx_info, 0.into()).await;
2577 assert_matches!(state, States::Joined(_), "not in joined state");
2578 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2579 }
2580
2581 #[fuchsia::test(allow_stalls = false)]
2582 async fn state_transitions_authing_deauth() {
2583 let mut m = MockObjects::new().await;
2584 let mut ctx = m.make_ctx_with_bss().await;
2585 let mut sta = make_client_station();
2586 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2587 let mut state =
2588 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2589
2590 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2591 #[rustfmt::skip]
2593 let deauth = vec![
2594 0b1100_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 5, 0, ];
2604 let rx_info = mock_rx_info(&sta);
2605 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2606 assert_matches!(state, States::Joined(_), "not in joined state");
2607 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2608 }
2609
2610 #[fuchsia::test(allow_stalls = false)]
2611 async fn state_transitions_foreign_auth_resp() {
2612 let mut m = MockObjects::new().await;
2613 let mut ctx = m.make_ctx().await;
2614 let mut sta = make_client_station();
2615 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2616 let mut state =
2617 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2618
2619 #[rustfmt::skip]
2621 let auth_resp_success = vec![
2622 0b1011_00_00, 0b00000000, 0, 0, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 5, 0x10, 0, 0, 0, 2, 0, 0, 0, ];
2634 let rx_info = mock_rx_info(&sta);
2635 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2636 assert_matches!(state, States::Authenticating(_), "not in authenticating state");
2637
2638 #[rustfmt::skip]
2641 let auth_resp_success = vec![
2642 0b1011_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 2, 0, 0, 0, ];
2654 let rx_info = mock_rx_info(&sta);
2655 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2656 assert_matches!(state, States::Associating(_), "not in associating state");
2657 }
2658
2659 #[fuchsia::test(allow_stalls = false)]
2660 async fn state_transitions_authing_state_reconnect_denied() {
2661 let mut m = MockObjects::new().await;
2662 let mut ctx = m.make_ctx().await;
2663 let mut sta = make_client_station();
2664 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2665 let mut state =
2666 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2667
2668 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2670 peer_sta_address: [1, 2, 3, 4, 5, 6],
2671 });
2672 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2673
2674 assert_matches!(state, States::Authenticating(_), "not in authenticating state");
2675
2676 let msg = m
2678 .fake_device_state
2679 .lock()
2680 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2681 .expect("expect msg");
2682 assert_eq!(
2683 msg,
2684 fidl_mlme::ConnectConfirm {
2685 peer_sta_address: [1, 2, 3, 4, 5, 6],
2686 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2687 association_id: 0,
2688 association_ies: vec![],
2689 }
2690 );
2691 }
2692
2693 #[fuchsia::test(allow_stalls = false)]
2694 async fn state_transitions_authing_state_wrong_algorithm() {
2695 let mut m = MockObjects::new().await;
2696 let mut ctx = m.make_ctx().await;
2697 let mut sta = make_client_station();
2698 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2699 let mut state =
2700 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2701
2702 #[rustfmt::skip]
2703 let auth_resp_wrong = vec![
2704 0b1011_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 8, 0, 2, 0, 0, 0, ];
2716 let rx_info = mock_rx_info(&sta);
2717 state = state.on_mac_frame(&mut sta, &auth_resp_wrong[..], rx_info, 0.into()).await;
2718 assert_matches!(state, States::Joined(_), "not in joined state");
2719 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2720 }
2721
2722 #[fuchsia::test(allow_stalls = false)]
2723 async fn state_transitions_associng_success() {
2724 let mut m = MockObjects::new().await;
2725 let mut ctx = m.make_ctx().await;
2726 let mut sta = make_client_station();
2727 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2728 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2729
2730 #[rustfmt::skip]
2732 let assoc_resp_success = vec![
2733 0b0001_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
2747 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2751 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2755 let rx_info = mock_rx_info(&sta);
2756 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2757 assert_matches!(state, States::Associated(_), "not in associated state");
2758 }
2759
2760 #[fuchsia::test(allow_stalls = false)]
2761 async fn state_transitions_associng_failure() {
2762 let mut m = MockObjects::new().await;
2763 let mut ctx = m.make_ctx_with_bss().await;
2764 let mut sta = make_client_station();
2765 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2766 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2767
2768 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2769 #[rustfmt::skip]
2771 let assoc_resp_failure = vec![
2772 0b0001_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 2, 0, 0, 0, ];
2784 let rx_info = mock_rx_info(&sta);
2785 state = state.on_mac_frame(&mut sta, &assoc_resp_failure[..], rx_info, 0.into()).await;
2786 assert_matches!(state, States::Joined(_), "not in joined state");
2787 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2788 }
2789
2790 #[fuchsia::test(allow_stalls = false)]
2791 async fn state_transitions_associng_deauthing() {
2792 let mut m = MockObjects::new().await;
2793 let mut ctx = m.make_ctx_with_bss().await;
2794 let mut sta = make_client_station();
2795 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2796 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2797
2798 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2799 #[rustfmt::skip]
2801 let deauth = vec![
2802 0b1100_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 4, 0, ];
2812 let rx_info = mock_rx_info(&sta);
2813 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2814 assert_matches!(state, States::Joined(_), "not in joined state");
2815 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2816 }
2817
2818 #[fuchsia::test(allow_stalls = false)]
2819 async fn state_transitions_associng_reconnect_no_op() {
2820 let mut m = MockObjects::new().await;
2821 let mut ctx = m.make_ctx_with_bss().await;
2822 let mut sta = make_client_station();
2823 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2824 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2825
2826 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2827 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2829 peer_sta_address: BSSID.to_array(),
2830 });
2831 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2832 assert_matches!(state, States::Associating(_), "not in associating state");
2833 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2834
2835 m.fake_device_state
2837 .lock()
2838 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2839 .expect_err("unexpected Connect.confirm");
2840 }
2841
2842 #[fuchsia::test(allow_stalls = false)]
2843 async fn state_transitions_associng_reconnect_denied() {
2844 let mut m = MockObjects::new().await;
2845 let mut ctx = m.make_ctx_with_bss().await;
2846 let mut sta = make_client_station();
2847 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2848 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2849
2850 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2851 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
2853 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2854 peer_sta_address: sus_bssid,
2855 });
2856 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2857 assert_matches!(state, States::Associating(_), "not in associating state");
2858 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2859
2860 let connect_conf = m
2862 .fake_device_state
2863 .lock()
2864 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2865 .expect("error reading Connect.confirm");
2866 assert_eq!(
2867 connect_conf,
2868 fidl_mlme::ConnectConfirm {
2869 peer_sta_address: sus_bssid,
2870 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
2871 association_id: 0,
2872 association_ies: vec![],
2873 }
2874 );
2875 }
2876
2877 #[fuchsia::test(allow_stalls = false)]
2878 async fn state_transitions_assoced_disassoc_connect_success() {
2879 let mut m = MockObjects::new().await;
2880 let mut ctx = m.make_ctx_with_bss().await;
2881 let mut sta = make_client_station();
2882 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2883 let mut state =
2884 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2885
2886 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2887 #[rustfmt::skip]
2889 let disassoc = vec![
2890 0b1010_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 4, 0, ];
2900 let rx_info = mock_rx_info(&sta);
2901 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
2902 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
2903 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2904
2905 let disassoc_ind = m
2907 .fake_device_state
2908 .lock()
2909 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2910 .expect("error reading Disassociate.ind");
2911 assert_eq!(
2912 disassoc_ind,
2913 fidl_mlme::DisassociateIndication {
2914 peer_sta_address: BSSID.to_array(),
2915 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
2916 locally_initiated: false,
2917 }
2918 );
2919
2920 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2922 peer_sta_address: BSSID.to_array(),
2923 });
2924 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2925 assert_matches!(state, States::Associating(_), "not in associating state");
2926
2927 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2929 assert_eq!(
2930 &m.fake_device_state.lock().wlan_queue[0].0[..22],
2931 &[
2932 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, ][..]
2939 );
2940
2941 #[rustfmt::skip]
2943 let assoc_resp_success = vec![
2944 0b0001_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0, 0, 0, 0, 11, 0, 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
2958 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2962 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2966 let rx_info = mock_rx_info(&sta);
2967 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2968 assert_matches!(state, States::Associated(_), "not in associated state");
2969
2970 let connect_conf = m
2972 .fake_device_state
2973 .lock()
2974 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2975 .expect("error reading Connect.confirm");
2976 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
2977 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
2978 assert_eq!(connect_conf.association_id, 11);
2979 }
2980
2981 #[fuchsia::test(allow_stalls = false)]
2982 async fn state_transitions_assoced_disassoc_reconnect_timeout() {
2983 let mut m = MockObjects::new().await;
2984 let mut ctx = m.make_ctx_with_bss().await;
2985 let mut sta = make_client_station();
2986 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2987 let mut state =
2988 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2989
2990 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2991 #[rustfmt::skip]
2993 let disassoc = vec![
2994 0b1010_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 4, 0, ];
3004 let rx_info = mock_rx_info(&sta);
3005 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3006 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3007 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3008
3009 let _disassoc_ind = m
3011 .fake_device_state
3012 .lock()
3013 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3014 .expect("error reading Disassociate.ind");
3015
3016 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3018 peer_sta_address: BSSID.to_array(),
3019 });
3020 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3021 assert_matches!(state, States::Associating(_), "not in associating state");
3022
3023 let (event, _id) = assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Reassociating), Some(ids) => {
3025 assert_eq!(ids.len(), 1);
3026 ids[0].clone()
3027 });
3028
3029 let state = state.on_timed_event(&mut sta, event).await;
3031 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3032 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3033
3034 let connect_conf = m
3036 .fake_device_state
3037 .lock()
3038 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3039 .expect("error reading Connect.confirm");
3040 assert_eq!(
3041 connect_conf,
3042 fidl_mlme::ConnectConfirm {
3043 peer_sta_address: BSSID.to_array(),
3044 result_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
3045 association_id: 0,
3046 association_ies: vec![],
3047 }
3048 );
3049 }
3050
3051 #[fuchsia::test(allow_stalls = false)]
3052 async fn state_transitions_assoced_disassoc_reconnect_denied() {
3053 let mut m = MockObjects::new().await;
3054 let mut ctx = m.make_ctx_with_bss().await;
3055 let mut sta = make_client_station();
3056 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3057 let mut state =
3058 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3059
3060 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3061 #[rustfmt::skip]
3063 let disassoc = vec![
3064 0b1010_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 4, 0, ];
3074 let rx_info = mock_rx_info(&sta);
3075 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3076 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3077 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3078
3079 let _disassoc_ind = m
3081 .fake_device_state
3082 .lock()
3083 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3084 .expect("error reading Disassociate.ind");
3085
3086 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
3088 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3089 peer_sta_address: sus_bssid,
3090 });
3091 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3092 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3093
3094 let connect_conf = m
3096 .fake_device_state
3097 .lock()
3098 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3099 .expect("error reading Connect.confirm");
3100 assert_eq!(
3101 connect_conf,
3102 fidl_mlme::ConnectConfirm {
3103 peer_sta_address: sus_bssid,
3104 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
3105 association_id: 0,
3106 association_ies: vec![],
3107 }
3108 );
3109 }
3110
3111 #[fuchsia::test(allow_stalls = false)]
3112 async fn state_transitions_assoced_reconnect_no_op() {
3113 let mut m = MockObjects::new().await;
3114 let mut ctx = m.make_ctx_with_bss().await;
3115 let mut sta = make_client_station();
3116 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3117 let association = Association {
3118 aid: 42,
3119 assoc_resp_ies: vec![
3120 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
3122 ],
3123 ..empty_association(&mut sta)
3124 };
3125 let mut state = States::from(statemachine::testing::new_state(Associated(association)));
3126
3127 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3128
3129 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3131 peer_sta_address: BSSID.to_array(),
3132 });
3133 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3134 assert_matches!(state, States::Associated(_), "not in associated state");
3135 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3136
3137 let connect_conf = m
3139 .fake_device_state
3140 .lock()
3141 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3142 .expect("error reading Connect.confirm");
3143 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
3144 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
3145 assert_eq!(connect_conf.association_id, 42);
3146 }
3147
3148 #[fuchsia::test(allow_stalls = false)]
3149 async fn state_transitions_assoced_deauthing() {
3150 let mut m = MockObjects::new().await;
3151 let mut ctx = m.make_ctx_with_bss().await;
3152 let mut sta = make_client_station();
3153 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3154 let mut state =
3155 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3156
3157 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3158 #[rustfmt::skip]
3160 let deauth = vec![
3161 0b1100_00_00, 0b00000000, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 4, 0, ];
3171 let rx_info = mock_rx_info(&sta);
3172 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
3173 assert_matches!(state, States::Joined(_), "not in joined state");
3174 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3175 }
3176
3177 #[test_case(false, false; "unprotected bss, not scanning")]
3178 #[test_case(true, false; "protected bss, not scanning")]
3179 #[test_case(false, true; "unprotected bss, scanning")]
3180 #[test_case(true, true; "protected bss, scanning")]
3181 #[fuchsia::test(allow_stalls = false)]
3182 async fn assoc_send_eth_frame_becomes_data_frame(protected: bool, scanning: bool) {
3183 let mut m = MockObjects::new().await;
3184 let mut ctx = m.make_ctx().await;
3185 let mut sta =
3186 if protected { make_protected_client_station() } else { make_client_station() };
3187 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3188 let state = States::from(statemachine::testing::new_state(Associated(Association {
3189 controlled_port_open: true,
3190 ..empty_association(&mut sta)
3191 })));
3192
3193 if scanning {
3194 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3195 bound_scanner
3196 .on_sme_scan(fidl_mlme::ScanRequest {
3197 txn_id: 1337,
3198 scan_type: fidl_mlme::ScanTypes::Passive,
3199 channel_list: vec![1],
3200 ssid_list: vec![],
3201 probe_delay: 0,
3202 min_channel_time: 100,
3203 max_channel_time: 300,
3204 })
3205 .await
3206 .expect("Failed to start scan");
3207 assert!(sta.scanner.is_scanning());
3208 }
3209
3210 let eth_frame = [
3211 1, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 3, 0x0d, 0x05, 21, 22, 23, 24, 25, 26, 27, 28, 29, ];
3217
3218 state.on_eth_frame(&mut sta, ð_frame[..], 0.into()).expect("all good");
3219
3220 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3221 let (data_frame, _tx_flags) = m.fake_device_state.lock().wlan_queue.remove(0);
3222 let mut fc_byte_2 = 0b00000001;
3223 if protected {
3224 fc_byte_2 |= 0b01000000;
3225 }
3226 assert_eq!(
3227 &data_frame[..],
3228 &[
3229 0b00001000, fc_byte_2, 0, 0, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 1, 2, 3, 4, 5, 6, 0x10, 0, 0xAA, 0xAA, 0x03, 0, 0, 0, 0x0d, 0x05, 21, 22, 23, 24, 25, 26, 27, 28, 29, ][..]
3243 )
3244 }
3245
3246 #[fuchsia::test(allow_stalls = false)]
3247 async fn eth_frame_dropped_when_off_channel() {
3248 let mut m = MockObjects::new().await;
3249 let mut ctx = m.make_ctx().await;
3250 let mut sta = make_client_station();
3251 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3252 let state =
3253 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3254
3255 sta.ctx
3256 .device
3257 .set_channel(fidl_ieee80211::WlanChannel {
3258 primary: 42,
3259 cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
3260 secondary80: 0,
3261 })
3262 .await
3263 .expect("fake device is obedient");
3264 let eth_frame = &[100; 14]; let error = state
3267 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3268 .expect_err("Ethernet frame is dropped when client is off channel");
3269 assert_matches!(error, Error::Status(_str, status) =>
3270 assert_eq!(status, zx::Status::BAD_STATE),
3271 "error should contain a status"
3272 );
3273 }
3274
3275 #[fuchsia::test(allow_stalls = false)]
3276 async fn assoc_eth_frame_too_short_dropped() {
3277 let mut m = MockObjects::new().await;
3278 let mut ctx = m.make_ctx().await;
3279 let mut sta = make_client_station();
3280 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3281 let state =
3282 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3283
3284 let eth_frame = &[100; 13]; let error = state
3287 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3288 .expect_err("Ethernet frame is too short");
3289 assert_matches!(error, Error::Status(_str, status) =>
3290 assert_eq!(status, zx::Status::IO_DATA_INTEGRITY),
3291 "error should contain a status"
3292 );
3293 }
3294
3295 #[fuchsia::test(allow_stalls = false)]
3296 async fn assoc_controlled_port_closed_eth_frame_dropped() {
3297 let mut m = MockObjects::new().await;
3298 let mut ctx = m.make_ctx().await;
3299 let mut sta = make_client_station();
3300 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3301 let state =
3302 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3303
3304 let eth_frame = &[100; 14]; let error = state
3307 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3308 .expect_err("Ethernet frame canot be sent when controlled port is closed");
3309 assert_matches!(error, Error::Status(_str, status) =>
3310 assert_eq!(status, zx::Status::BAD_STATE),
3311 "Error should contain status"
3312 );
3313 }
3314
3315 #[fuchsia::test(allow_stalls = false)]
3316 async fn not_assoc_eth_frame_dropped() {
3317 let mut m = MockObjects::new().await;
3318 let mut ctx = m.make_ctx().await;
3319 let mut sta = make_client_station();
3320 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3321 let state = States::from(statemachine::testing::new_state(Joined));
3322
3323 let eth_frame = &[100; 14]; let error = state
3326 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3327 .expect_err("Ethernet frame cannot be sent in Joined state");
3328 assert_matches!(error, Error::Status(_str, status) =>
3329 assert_eq!(status, zx::Status::BAD_STATE),
3330 "Error should contain status"
3331 );
3332 }
3333
3334 #[fuchsia::test(allow_stalls = false)]
3335 async fn joined_sme_deauth() {
3336 let mut m = MockObjects::new().await;
3337 let mut ctx = m.make_ctx_with_bss().await;
3338 let mut sta = make_client_station();
3339 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3340 let state = States::from(statemachine::testing::new_state(Joined));
3341
3342 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3343 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3344 assert_matches!(state, States::Joined(_), "Joined should stay in Joined");
3345 m.fake_device_state
3347 .lock()
3348 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
3349 .expect_err("should be no outgoing message");
3350 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3351 }
3352
3353 #[fuchsia::test(allow_stalls = false)]
3354 async fn authenticating_sme_deauth() {
3355 let mut m = MockObjects::new().await;
3356 let mut ctx = m.make_ctx_with_bss().await;
3357 let mut sta = make_client_station();
3358 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3359 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3360
3361 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3362 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3363
3364 assert_matches!(state, States::Joined(_), "should transition to Joined");
3365
3366 m.fake_device_state
3368 .lock()
3369 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3370 .expect_err("should not see more MLME messages");
3371 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3372 }
3373
3374 #[fuchsia::test(allow_stalls = false)]
3375 async fn associating_sme_deauth() {
3376 let mut m = MockObjects::new().await;
3377 let mut ctx = m.make_ctx_with_bss().await;
3378 let mut sta = make_client_station();
3379 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3380 let state = States::from(statemachine::testing::new_state(Associating::default()));
3381
3382 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3383 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3384
3385 assert_matches!(state, States::Joined(_), "should transition to Joined");
3386
3387 m.fake_device_state
3389 .lock()
3390 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3391 .expect_err("should not see more MLME messages");
3392 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3393 }
3394
3395 #[fuchsia::test(allow_stalls = false)]
3396 async fn associated_sme_deauth() {
3397 let mut m = MockObjects::new().await;
3398 let mut ctx = m.make_ctx_with_bss().await;
3399 let mut sta = make_client_station();
3400 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3401 let state = States::from(statemachine::testing::new_state(Associated(Association {
3402 controlled_port_open: true,
3403 ..empty_association(&mut sta)
3404 })));
3405
3406 sta.ctx
3407 .device
3408 .notify_association_complete(fake_assoc_cfg())
3409 .await
3410 .expect("valid assoc ctx should not fail");
3411 assert_eq!(1, m.fake_device_state.lock().assocs.len());
3412
3413 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3414 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
3415 assert_eq!(crate::device::LinkStatus::UP, m.fake_device_state.lock().link_status);
3416
3417 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3418 assert_matches!(state, States::Joined(_), "should transition to Joined");
3419
3420 let deauth_conf = m
3422 .fake_device_state
3423 .lock()
3424 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3425 .expect("should see deauth conf");
3426 assert_eq!(
3427 deauth_conf,
3428 fidl_mlme::DeauthenticateConfirm { peer_sta_address: BSSID.to_array() }
3429 );
3430 m.fake_device_state
3431 .lock()
3432 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3433 .expect_err("should not see more MLME messages");
3434 assert_eq!(0, m.fake_device_state.lock().assocs.len());
3436 assert_eq!(crate::device::LinkStatus::DOWN, m.fake_device_state.lock().link_status);
3438 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3439 }
3440
3441 fn fake_eapol_req() -> wlan_sme::MlmeRequest {
3442 wlan_sme::MlmeRequest::Eapol(fidl_mlme::EapolRequest {
3443 dst_addr: BSSID.to_array(),
3444 src_addr: IFACE_MAC.to_array(),
3445 data: vec![1, 2, 3, 4],
3446 })
3447 }
3448
3449 #[allow(deprecated)]
3450 #[fuchsia::test(allow_stalls = false)]
3451 async fn mlme_eapol_not_associated() {
3452 let mut m = MockObjects::new().await;
3453 let mut ctx = m.make_ctx().await;
3454 let mut sta = make_protected_client_station();
3455 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3456
3457 let state = States::from(statemachine::testing::new_state(Joined));
3458 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3459 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3460
3461 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3462 m.fake_device_state.lock().wlan_queue.clear();
3463 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3464 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3465
3466 let state = States::from(statemachine::testing::new_state(Associating::default()));
3467 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3468 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3469 }
3470
3471 #[allow(deprecated)]
3472 #[fuchsia::test(allow_stalls = false)]
3473 async fn mlme_eapol_associated_not_protected() {
3474 let mut m = MockObjects::new().await;
3475 let mut ctx = m.make_ctx().await;
3476 let mut sta = make_client_station();
3477 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3478
3479 let state =
3480 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3481 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3482 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3483 }
3484
3485 #[allow(deprecated)]
3486 #[fuchsia::test(allow_stalls = false)]
3487 async fn mlme_eapol_associated() {
3488 let mut m = MockObjects::new().await;
3489 let mut ctx = m.make_ctx().await;
3490 let mut sta = make_protected_client_station();
3491 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3492
3493 let state =
3494 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3495 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3496 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3497 assert_eq!(
3498 &m.fake_device_state.lock().wlan_queue[0].0[..],
3499 &[
3500 0b00001000, 0b00000001, 0, 0, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, 0x10, 0, 0xAA, 0xAA, 0x03, 0, 0, 0, 0x88, 0x8E, 1, 2, 3, 4, ][..]
3513 );
3514 }
3515
3516 #[allow(deprecated)]
3517 #[fuchsia::test(allow_stalls = false)]
3518 async fn mlme_set_keys_not_associated() {
3519 let mut m = MockObjects::new().await;
3520 let mut ctx = m.make_ctx().await;
3521 let mut sta = make_protected_client_station();
3522 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3523
3524 let state = States::from(statemachine::testing::new_state(Joined));
3525 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3526 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3527
3528 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3529 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3530 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3531
3532 let state = States::from(statemachine::testing::new_state(Associating::default()));
3533 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3534 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3535 }
3536
3537 #[allow(deprecated)]
3538 #[fuchsia::test(allow_stalls = false)]
3539 async fn mlme_set_keys_associated_not_protected() {
3540 let mut m = MockObjects::new().await;
3541 let mut ctx = m.make_ctx().await;
3542 let mut sta = make_client_station();
3543 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3544
3545 let state =
3546 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3547 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3548 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3549 }
3550
3551 #[allow(deprecated)]
3552 #[fuchsia::test(allow_stalls = false)]
3553 async fn mlme_set_keys_associated() {
3554 let mut m = MockObjects::new().await;
3555 let mut ctx = m.make_ctx().await;
3556 let mut sta = make_protected_client_station();
3557 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3558
3559 let state =
3560 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3561 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3562 assert_eq!(m.fake_device_state.lock().keys.len(), 1);
3563 let conf = assert_matches!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3564 assert_eq!(conf.results.len(), 1);
3565 assert_eq!(
3566 conf.results[0],
3567 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::OK.into_raw() }
3568 );
3569
3570 assert_eq!(
3571 m.fake_device_state.lock().keys,
3572 vec![fidl_softmac::WlanKeyConfiguration {
3573 protection: Some(fidl_softmac::WlanProtection::RxTx),
3574 cipher_oui: Some([1, 2, 3]),
3575 cipher_type: Some(4),
3576 key_type: Some(fidl_ieee80211::KeyType::Pairwise),
3577 peer_addr: Some((*BSSID).to_array()),
3578 key_idx: Some(6),
3579 key: Some(vec![1, 2, 3, 4, 5, 6, 7]),
3580 rsc: Some(8),
3581 ..Default::default()
3582 }]
3583 );
3584 }
3585
3586 #[allow(deprecated)]
3587 #[fuchsia::test(allow_stalls = false)]
3588 async fn mlme_set_keys_failure() {
3589 let mut m = MockObjects::new().await;
3590 let mut ctx = m.make_ctx().await;
3591 let mut sta = make_protected_client_station();
3592 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3593
3594 let state =
3595 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3596 m.fake_device_state.lock().install_key_results.push_back(Err(zx::Status::BAD_STATE));
3597 m.fake_device_state.lock().install_key_results.push_back(Ok(()));
3598 let mut set_keys_req = fake_set_keys_req((*BSSID).into());
3600 match &mut set_keys_req {
3601 wlan_sme::MlmeRequest::SetKeys(req) => {
3602 req.keylist
3603 .push(fidl_mlme::SetKeyDescriptor { key_id: 4, ..req.keylist[0].clone() });
3604 }
3605 _ => panic!(),
3606 }
3607 let _state = state.handle_mlme_req(&mut sta, set_keys_req).await;
3608 let conf = assert_matches!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3609 assert_eq!(conf.results.len(), 2);
3610 assert_eq!(
3611 conf.results[0],
3612 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::BAD_STATE.into_raw() }
3613 );
3614 assert_eq!(
3615 conf.results[1],
3616 fidl_mlme::SetKeyResult { key_id: 4, status: zx::Status::OK.into_raw() }
3617 );
3618 }
3619
3620 fn fake_set_ctrl_port_open(open: bool) -> wlan_sme::MlmeRequest {
3621 wlan_sme::MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
3622 peer_sta_address: BSSID.to_array(),
3623 state: match open {
3624 true => fidl_mlme::ControlledPortState::Open,
3625 false => fidl_mlme::ControlledPortState::Closed,
3626 },
3627 })
3628 }
3629
3630 #[fuchsia::test(allow_stalls = false)]
3631 async fn mlme_set_controlled_port_not_associated() {
3632 let mut m = MockObjects::new().await;
3633 let mut ctx = m.make_ctx().await;
3634 let mut sta = make_protected_client_station();
3635 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3636
3637 let state = States::from(statemachine::testing::new_state(Joined));
3638 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3639 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3640
3641 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3642 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3643 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3644
3645 let state = States::from(statemachine::testing::new_state(Associating::default()));
3646 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3647 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3648 }
3649
3650 #[fuchsia::test(allow_stalls = false)]
3651 async fn mlme_set_controlled_port_associated_not_protected() {
3652 let mut m = MockObjects::new().await;
3653 let mut ctx = m.make_ctx().await;
3654 let mut sta = make_client_station();
3655 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3656
3657 let state =
3658 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3659 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3660 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3661 }
3662
3663 #[fuchsia::test(allow_stalls = false)]
3664 async fn mlme_set_controlled_port_associated() {
3665 let mut m = MockObjects::new().await;
3666 let mut ctx = m.make_ctx().await;
3667 let mut sta = make_protected_client_station();
3668 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3669
3670 let state =
3671 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3672 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3673 let state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3674 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
3675 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(false)).await;
3676 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3677 }
3678
3679 #[test_case(true; "while scanning")]
3680 #[test_case(false; "while not scanning")]
3681 #[fuchsia::test(allow_stalls = false)]
3682 async fn associated_rx_succeeds(scanning: bool) {
3683 let mut m = MockObjects::new().await;
3684 let mut ctx = m.make_ctx().await;
3685 let mut sta = make_client_station();
3686 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3687 let state = States::from(statemachine::testing::new_state(Associated(Association {
3688 aid: 1,
3689 controlled_port_open: true,
3690 ..empty_association(&mut sta)
3691 })));
3692
3693 if scanning {
3694 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3695 bound_scanner
3696 .on_sme_scan(fidl_mlme::ScanRequest {
3697 txn_id: 1337,
3698 scan_type: fidl_mlme::ScanTypes::Passive,
3699 channel_list: vec![1],
3700 ssid_list: vec![],
3701 probe_delay: 0,
3702 min_channel_time: 100,
3703 max_channel_time: 300,
3704 })
3705 .await
3706 .expect("Failed to start scan");
3707 assert!(sta.scanner.is_scanning());
3708 }
3709
3710 let fc = mac::FrameControl(0)
3711 .with_frame_type(mac::FrameType::DATA)
3712 .with_data_subtype(mac::DataSubtype(0))
3713 .with_from_ds(true);
3714 let fc = fc.0.to_le_bytes();
3715
3716 let data_frame = vec![
3717 fc[0], fc[1], 0, 0, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 42, 42, 42, 42, 42, 42, 0x10, 0, 7, 7, 7, 8, 8, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, ];
3730
3731 let rx_info = mock_rx_info(&sta);
3732 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3733 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3734 }
3735
3736 #[fuchsia::test(allow_stalls = false)]
3737 async fn associated_rx_with_wrong_cbw_succeeds() {
3738 let mut m = MockObjects::new().await;
3739 let mut ctx = m.make_ctx().await;
3740 let mut sta = make_client_station();
3741 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3742 let state = States::from(statemachine::testing::new_state(Associated(Association {
3743 aid: 1,
3744 controlled_port_open: true,
3745 ..empty_association(&mut sta)
3746 })));
3747
3748 let fc = mac::FrameControl(0)
3749 .with_frame_type(mac::FrameType::DATA)
3750 .with_data_subtype(mac::DataSubtype(0))
3751 .with_from_ds(true);
3752 let fc = fc.0.to_le_bytes();
3753
3754 let data_frame = vec![
3755 fc[0], fc[1], 0, 0, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 42, 42, 42, 42, 42, 42, 0x10, 0, 7, 7, 7, 8, 8, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, ];
3768
3769 let mut rx_info = mock_rx_info(&sta);
3770 rx_info.channel.cbw = fidl_ieee80211::ChannelBandwidth::Cbw80;
3773 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3774 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3775 }
3776
3777 #[fuchsia::test(allow_stalls = false)]
3778 async fn associated_request_bu_if_tim_indicates_buffered_frame() {
3779 let mut m = MockObjects::new().await;
3780 let mut ctx = m.make_ctx().await;
3781 let mut sta = make_client_station();
3782 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3783 let state = States::from(statemachine::testing::new_state(Associated(Association {
3784 aid: 1,
3785 ..empty_association(&mut sta)
3786 })));
3787
3788 let beacon = [
3789 0b10000000, 0, 0, 0, 3, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 33, 0, 5, 4, 0, 0, 0, 0b00000010, ];
3802
3803 let rx_info = mock_rx_info(&sta);
3804 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3805
3806 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3807 assert_eq!(
3808 &m.fake_device_state.lock().wlan_queue[0].0[..],
3809 &[
3810 0b10100100, 0, 1, 0b11000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]
3815 );
3816 }
3817
3818 #[fuchsia::test(allow_stalls = false)]
3819 async fn associated_does_not_request_bu_if_tim_indicates_no_buffered_frame() {
3820 let mut m = MockObjects::new().await;
3821 let mut ctx = m.make_ctx().await;
3822 let mut sta = make_client_station();
3823 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3824 let state = States::from(statemachine::testing::new_state(Associated(Association {
3825 aid: 1,
3826 ..empty_association(&mut sta)
3827 })));
3828
3829 let beacon = [
3830 0b10000000, 0, 0, 0, 3, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 33, 0, 5, 4, 0, 0, 0, 0, ];
3843 let rx_info = mock_rx_info(&sta);
3844 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3845
3846 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3847 }
3848
3849 fn rx_info_with_dbm<'a>(
3850 client: &BoundClient<'a, FakeDevice>,
3851 rssi_dbm: i8,
3852 ) -> fidl_softmac::WlanRxInfo {
3853 let mut rx_info = fidl_softmac::WlanRxInfo { rssi_dbm, ..mock_rx_info(client) };
3854 rx_info.valid_fields |= fidl_softmac::WlanRxInfoValid::RSSI;
3855 rx_info
3856 }
3857
3858 #[fuchsia::test(allow_stalls = false)]
3859 async fn signal_report() {
3860 let mut m = MockObjects::new().await;
3861 let mut ctx = m.make_ctx().await;
3862 let mut sta = make_protected_client_station();
3863 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3864
3865 let state = States::from(State::from(statemachine::testing::new_state(Associated(
3866 empty_association(&mut sta),
3867 ))));
3868
3869 let (_, timed_event, _) =
3870 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3871 let state = state.on_timed_event(&mut sta, timed_event.event).await;
3872
3873 let signal_ind = m
3874 .fake_device_state
3875 .lock()
3876 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3877 .expect("should see a signal report");
3878
3879 assert_eq!(signal_ind.rssi_dbm, -128);
3881
3882 let beacon = [
3883 0b10000000, 0, 0, 0, 3, 3, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 33, 0, ];
3895
3896 const EXPECTED_DBM: i8 = -32;
3897 let rx_info = rx_info_with_dbm(&sta, EXPECTED_DBM);
3898 let state = state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3899
3900 let (_, timed_event, _) =
3901 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3902 let _state = state.on_timed_event(&mut sta, timed_event.event).await;
3903
3904 let signal_ind = m
3905 .fake_device_state
3906 .lock()
3907 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3908 .expect("should see a signal report");
3909
3910 assert_eq!(signal_ind.rssi_dbm, EXPECTED_DBM);
3911 }
3912}