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!(c"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!(c"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!(c"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!(c"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!(c"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!(c"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!(c"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!(c"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 std::ffi::CStr = c"States::on_data_frame => MSDU";
662
663 wtrace::duration!(c"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!(c"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!(c"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!(c"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!(c"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!(c"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 };
1516 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1517 }
1518
1519 fn make_protected_client_station() -> Client {
1520 let connect_req = ParsedConnectRequest {
1521 selected_bss: fake_bss_description!(Wpa2, bssid: BSSID.to_array()),
1522 connect_failure_timeout: 10,
1523 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1524 sae_password: vec![],
1525 wep_key: None,
1526 security_ie: vec![
1527 0x30, 0x14, 1, 0, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0xa8, 0x04, ],
1536 };
1537 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1538 }
1539
1540 fn connect_conf_failure(result_code: fidl_ieee80211::StatusCode) -> fidl_mlme::ConnectConfirm {
1541 fidl_mlme::ConnectConfirm {
1542 peer_sta_address: BSSID.to_array(),
1543 result_code,
1544 association_id: 0,
1545 association_ies: vec![],
1546 }
1547 }
1548
1549 fn empty_association(sta: &mut BoundClient<'_, FakeDevice>) -> Association {
1550 let status_check_timeout =
1551 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
1552 Association {
1553 controlled_port_open: false,
1554 aid: 0,
1555 assoc_resp_ies: vec![],
1556 ap_ht_op: None,
1557 ap_vht_op: None,
1558 lost_bss_counter: LostBssCounter::start(
1559 sta.sta.beacon_period(),
1560 DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
1561 ),
1562 qos: Qos::Disabled,
1563 status_check_timeout,
1564 signal_strength_average: SignalStrengthAverage::new(),
1565 block_ack_state: StateMachine::new(BlockAckState::from(State::new(Closed))),
1566 }
1567 }
1568
1569 fn fake_assoc_cfg() -> fidl_softmac::WlanAssociationConfig {
1570 fidl_softmac::WlanAssociationConfig {
1571 bssid: Some(BSSID.to_array()),
1572 aid: Some(42),
1573 channel: Some(fidl_ieee80211::WlanChannel {
1574 primary: 149,
1575 cbw: fidl_ieee80211::ChannelBandwidth::Cbw40,
1576 secondary80: 42,
1577 }),
1578 rates: None,
1579 capability_info: None,
1580 ..Default::default()
1581 }
1582 }
1583
1584 fn fake_deauth_req() -> wlan_sme::MlmeRequest {
1585 wlan_sme::MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1586 peer_sta_address: BSSID.to_array(),
1587 reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1588 })
1589 }
1590
1591 fn open_authenticating(sta: &mut BoundClient<'_, FakeDevice>) -> Authenticating {
1592 let mut auth = Authenticating::new(AkmAlgorithm::OpenSupplicant);
1593 auth.algorithm.initiate(sta).expect("Failed to initiate open auth");
1594 auth
1595 }
1596
1597 #[fuchsia::test(allow_stalls = false)]
1598 async fn connect_authenticate_tx_failure() {
1599 let mut m = MockObjects::new().await;
1600 m.fake_device_state.lock().config.send_wlan_frame_fails = true;
1601 let mut ctx = m.make_ctx().await;
1602 let mut sta = make_client_station();
1603 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1604
1605 let state = Joined;
1606 let _state =
1607 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1608
1609 assert!(m.time_stream.try_next().is_err());
1611
1612 let msg = m
1614 .fake_device_state
1615 .lock()
1616 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1617 .expect("expect msg");
1618 assert_eq!(
1619 msg,
1620 fidl_mlme::ConnectConfirm {
1621 peer_sta_address: BSSID.to_array(),
1622 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1623 association_id: 0,
1624 association_ies: vec![],
1625 }
1626 );
1627 }
1628
1629 #[fuchsia::test(allow_stalls = false)]
1630 async fn joined_no_authentication_algorithm() {
1631 let mut m = MockObjects::new().await;
1632 let mut ctx = m.make_ctx_with_bss().await;
1633 let connect_req = ParsedConnectRequest {
1634 selected_bss: fake_bss_description!(Open, bssid: BSSID.to_array()),
1635 connect_failure_timeout: 10,
1636 auth_type: fidl_mlme::AuthenticationTypes::SharedKey,
1638 sae_password: vec![],
1639 wep_key: None,
1640 security_ie: vec![],
1641 };
1642 let mut sta = Client::new(connect_req, *IFACE_MAC, fake_client_capabilities());
1643 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1644 let state = Joined;
1645
1646 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1647 let _state =
1648 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1649
1650 let msg = m
1652 .fake_device_state
1653 .lock()
1654 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1655 .expect("expect msg");
1656 assert_eq!(
1657 msg,
1658 fidl_mlme::ConnectConfirm {
1659 peer_sta_address: [6, 6, 6, 6, 6, 6],
1660 result_code: fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm,
1661 association_id: 0,
1662 association_ies: vec![],
1663 }
1664 );
1665
1666 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1667 }
1668
1669 #[fuchsia::test(allow_stalls = false)]
1670 async fn authenticating_state_auth_rejected() {
1671 let mut m = MockObjects::new().await;
1672 let mut ctx = m.make_ctx_with_bss().await;
1673 let mut sta = make_client_station();
1674 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1675 let mut state = open_authenticating(&mut sta);
1676
1677 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1678 assert_matches!(
1680 state
1681 .on_auth_frame(
1682 &mut sta,
1683 mac::AuthFrame {
1684 auth_hdr: mac::AuthHdr {
1685 auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
1686 auth_txn_seq_num: 2,
1687 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1688 }
1689 .as_bytes_ref(),
1690 elements: &[],
1691 },
1692 )
1693 .await,
1694 AuthProgress::Failed
1695 );
1696
1697 let msg = m
1699 .fake_device_state
1700 .lock()
1701 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1702 .expect("expect msg");
1703 assert_eq!(
1704 msg,
1705 fidl_mlme::ConnectConfirm {
1706 peer_sta_address: BSSID.to_array(),
1707 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1708 association_id: 0,
1709 association_ies: vec![],
1710 }
1711 );
1712 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1713 }
1714
1715 #[fuchsia::test(allow_stalls = false)]
1716 async fn authenticating_state_deauth_frame() {
1717 let mut m = MockObjects::new().await;
1718 let mut ctx = m.make_ctx_with_bss().await;
1719 let mut sta = make_client_station();
1720 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1721 let mut state = open_authenticating(&mut sta);
1722
1723 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1724 state
1725 .on_deauth_frame(
1726 &mut sta,
1727 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::NoMoreStas.into() },
1728 )
1729 .await;
1730
1731 let msg = m
1733 .fake_device_state
1734 .lock()
1735 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1736 .expect("expect msg");
1737 assert_eq!(
1738 msg,
1739 fidl_mlme::ConnectConfirm {
1740 peer_sta_address: BSSID.to_array(),
1741 result_code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
1742 association_id: 0,
1743 association_ies: vec![],
1744 }
1745 );
1746 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1747 }
1748
1749 #[fuchsia::test(allow_stalls = false)]
1750 async fn associating_success_unprotected() {
1751 let mut m = MockObjects::new().await;
1752 let mut ctx = m.make_ctx_with_bss().await;
1753 let mut sta = make_client_station();
1754 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1755
1756 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1757 let mut state = Associating::default();
1758 let assoc_resp_ies = fake_bss_description!(Wpa2, ies_overrides: IesOverrides::new()
1759 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1760 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1761 )
1762 .ies()
1763 .to_vec();
1764 let Association { aid, controlled_port_open, .. } = state
1765 .on_assoc_resp_frame(
1766 &mut sta,
1767 mac::AssocRespFrame {
1768 assoc_resp_hdr: mac::AssocRespHdr {
1769 aid: 42,
1770 capabilities: mac::CapabilityInfo(52),
1771 status_code: fidl_ieee80211::StatusCode::Success.into(),
1772 }
1773 .as_bytes_ref(),
1774 elements: &assoc_resp_ies[..],
1775 },
1776 )
1777 .await
1778 .expect("failed processing association response frame");
1779 assert_eq!(aid, 42);
1780 assert_eq!(true, controlled_port_open);
1781
1782 let msg = m
1784 .fake_device_state
1785 .lock()
1786 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1787 .expect("no message");
1788 assert_eq!(
1789 msg,
1790 fidl_mlme::ConnectConfirm {
1791 peer_sta_address: BSSID.to_array(),
1792 result_code: fidl_ieee80211::StatusCode::Success,
1793 association_id: 42,
1794 association_ies: assoc_resp_ies,
1795 }
1796 );
1797 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1798 }
1799
1800 #[fuchsia::test(allow_stalls = false)]
1801 async fn associating_success_protected() {
1802 let mut m = MockObjects::new().await;
1803 let mut ctx = m.make_ctx_with_bss().await;
1804 let mut sta = make_protected_client_station();
1805 sta.client_capabilities.0.capability_info =
1806 mac::CapabilityInfo(0).with_ess(true).with_ibss(true);
1807 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1808 let mut state = Associating::default();
1809
1810 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1811 let assoc_resp_ies =
1812 fake_bss_description!(Wpa2, bssid: BSSID.to_array(), ies_overrides: IesOverrides::new()
1813 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1814 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1815 )
1816 .ies()
1817 .to_vec();
1818 let Association { aid, controlled_port_open, .. } = state
1819 .on_assoc_resp_frame(
1820 &mut sta,
1821 mac::AssocRespFrame {
1822 assoc_resp_hdr: mac::AssocRespHdr {
1823 aid: 42,
1824 capabilities: mac::CapabilityInfo(0).with_ibss(true).with_cf_pollable(true),
1825 status_code: fidl_ieee80211::StatusCode::Success.into(),
1826 }
1827 .as_bytes_ref(),
1828 elements: &assoc_resp_ies[..],
1829 },
1830 )
1831 .await
1832 .expect("failed processing association response frame");
1833 assert_eq!(aid, 42);
1834 assert_eq!(false, controlled_port_open);
1835
1836 assert_eq!(m.fake_device_state.lock().assocs.len(), 1);
1838
1839 let assoc_cfg = m
1840 .fake_device_state
1841 .lock()
1842 .assocs
1843 .get(&(*BSSID).into())
1844 .expect("expect assoc ctx to be set")
1845 .clone();
1846 assert_eq!(assoc_cfg.aid, Some(42));
1847 assert_eq!(assoc_cfg.qos, Some(true));
1848 assert_eq!(
1849 assoc_cfg.rates,
1850 Some(vec![0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c])
1851 );
1852 assert_eq!(assoc_cfg.capability_info, Some(2));
1853 assert!(assoc_cfg.ht_cap.is_some());
1854 assert!(assoc_cfg.vht_cap.is_some());
1855 assert!(assoc_cfg.ht_op.is_some());
1856 assert!(assoc_cfg.vht_op.is_some());
1857
1858 let msg = m
1860 .fake_device_state
1861 .lock()
1862 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1863 .expect("no message");
1864 assert_eq!(
1865 msg,
1866 fidl_mlme::ConnectConfirm {
1867 peer_sta_address: BSSID.to_array(),
1868 result_code: fidl_ieee80211::StatusCode::Success,
1869 association_id: 42,
1870 association_ies: assoc_resp_ies,
1871 }
1872 );
1873 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1874 }
1875
1876 #[fuchsia::test(allow_stalls = false)]
1877 async fn associating_failure_due_to_failed_status_code() {
1878 let mut m = MockObjects::new().await;
1879 let mut ctx = m.make_ctx_with_bss().await;
1880 let mut sta = make_client_station();
1881 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1882
1883 let mut state = Associating::default();
1884
1885 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1886 state
1888 .on_assoc_resp_frame(
1889 &mut sta,
1890 mac::AssocRespFrame {
1891 assoc_resp_hdr: mac::AssocRespHdr {
1892 aid: 42,
1893 capabilities: mac::CapabilityInfo(52),
1894 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1895 }
1896 .as_bytes_ref(),
1897 elements: &[][..],
1898 },
1899 )
1900 .await
1901 .expect_err("expected failure processing association response frame");
1902
1903 let msg = m
1905 .fake_device_state
1906 .lock()
1907 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1908 .expect("no message");
1909 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::NotInSameBss));
1910 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1911 }
1912
1913 #[fuchsia::test(allow_stalls = false)]
1914 async fn associating_failure_due_to_incompatibility() {
1915 let mut m = MockObjects::new().await;
1916 let mut ctx = m.make_ctx_with_bss().await;
1917 let mut sta = make_client_station();
1918 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1919
1920 let mut state = Associating::default();
1921
1922 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1923 state
1924 .on_assoc_resp_frame(
1925 &mut sta,
1926 mac::AssocRespFrame {
1927 assoc_resp_hdr: mac::AssocRespHdr {
1928 aid: 42,
1929 capabilities: mac::CapabilityInfo(52),
1930 status_code: fidl_ieee80211::StatusCode::Success.into(),
1931 }
1932 .as_bytes_ref(),
1933 elements: fake_bss_description!(Wpa2, rates: vec![0x81]).ies(),
1934 },
1935 )
1936 .await
1937 .expect_err("expected failure processing association response frame");
1938
1939 let msg = m
1941 .fake_device_state
1942 .lock()
1943 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1944 .expect("no message");
1945 assert_eq!(
1946 msg,
1947 connect_conf_failure(fidl_ieee80211::StatusCode::RefusedCapabilitiesMismatch)
1948 );
1949 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1950 }
1951
1952 #[fuchsia::test(allow_stalls = false)]
1953 async fn associating_deauth_frame() {
1954 let mut m = MockObjects::new().await;
1955 let mut ctx = m.make_ctx_with_bss().await;
1956 let mut sta = make_client_station();
1957 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1958
1959 let mut state = Associating::default();
1960
1961 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1962 state
1963 .on_deauth_frame(
1964 &mut sta,
1965 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1966 )
1967 .await;
1968
1969 let msg = m
1971 .fake_device_state
1972 .lock()
1973 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1974 .expect("no message");
1975 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
1976 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1977 }
1978
1979 #[fuchsia::test(allow_stalls = false)]
1980 async fn associating_disassociation() {
1981 let mut m = MockObjects::new().await;
1982 let mut ctx = m.make_ctx_with_bss().await;
1983 let mut sta = make_client_station();
1984 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1985
1986 let mut state = Associating::default();
1987
1988 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1989 state.on_disassoc_frame(
1990 &mut sta,
1991 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1992 );
1993
1994 let msg = m
1996 .fake_device_state
1997 .lock()
1998 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1999 .expect("no message");
2000 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
2001 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2002 }
2003
2004 fn mock_rx_info<'a>(client: &BoundClient<'a, FakeDevice>) -> fidl_softmac::WlanRxInfo {
2005 let channel = client.channel_state.get_main_channel().unwrap();
2006 MockWlanRxInfo::with_channel(channel).into()
2007 }
2008
2009 #[fuchsia::test(allow_stalls = false)]
2010 async fn associated_block_ack_frame() {
2011 let mut mock = MockObjects::new().await;
2012 let mut ctx = mock.make_ctx().await;
2013 let mut station = make_client_station();
2014 let mut client = station.bind(&mut ctx, &mut mock.scanner, &mut mock.channel_state);
2015
2016 let frame = {
2017 let mut buffer = [0u8; ADDBA_REQ_FRAME_LEN];
2018 let writer = BufferWriter::new(&mut buffer[..]);
2019 let mut writer = append_frame_to!(
2020 writer,
2021 {
2022 headers: {
2023 mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
2024 mac::FrameControl(0)
2025 .with_frame_type(mac::FrameType::MGMT)
2026 .with_mgmt_subtype(mac::MgmtSubtype::ACTION),
2027 client.sta.iface_mac,
2028 client.sta.bssid(),
2029 mac::SequenceControl(0)
2030 .with_seq_num(client.ctx.seq_mgr.next_sns1(&client.sta.bssid().into()) as u16),
2031 ),
2032 },
2033 }
2034 )
2035 .unwrap();
2036 write_addba_req_body(&mut writer, 1).unwrap();
2037 buffer
2038 };
2039
2040 let state = States::from(statemachine::testing::new_state(Associated(empty_association(
2041 &mut client,
2042 ))));
2043 let rx_info = mock_rx_info(&client);
2044 match state.on_mac_frame(&mut client, &frame[..], rx_info, 0.into()).await {
2045 States::Associated(state) => {
2046 let (_, associated) = state.release_data();
2047 match *associated.0.block_ack_state.as_ref() {
2054 BlockAckState::Closed(_) => {}
2055 _ => panic!("client has transitioned BlockAck"),
2056 }
2057 }
2058 _ => panic!("client no longer associated"),
2059 }
2060 }
2061
2062 #[fuchsia::test(allow_stalls = false)]
2063 async fn associated_deauth_frame() {
2064 let mut m = MockObjects::new().await;
2065 let mut ctx = m.make_ctx_with_bss().await;
2066 let mut sta = make_client_station();
2067 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2068 let mut state = Associated(empty_association(&mut sta));
2069
2070 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2071
2072 sta.ctx
2074 .device
2075 .notify_association_complete(fake_assoc_cfg())
2076 .await
2077 .expect("valid assoc_cfg should succeed");
2078 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2079
2080 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2081 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2082
2083 let _joined = state
2084 .on_deauth_frame(
2085 &mut sta,
2086 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2087 )
2088 .await;
2089
2090 let msg = m
2092 .fake_device_state
2093 .lock()
2094 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
2095 .expect("no message");
2096 assert_eq!(
2097 msg,
2098 fidl_mlme::DeauthenticateIndication {
2099 peer_sta_address: BSSID.to_array(),
2100 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2101 locally_initiated: false,
2102 }
2103 );
2104 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2106 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2107 }
2108
2109 #[fuchsia::test(allow_stalls = false)]
2110 async fn associated_disassociation() {
2111 let mut m = MockObjects::new().await;
2112 let mut ctx = m.make_ctx_with_bss().await;
2113 let mut sta = make_client_station();
2114 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2115 let mut state = Associated(empty_association(&mut sta));
2116
2117 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2118 state.0.controlled_port_open = true;
2119
2120 sta.ctx
2121 .device
2122 .notify_association_complete(fake_assoc_cfg())
2123 .await
2124 .expect("valid assoc_cfg should succeed");
2125 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2126
2127 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2128 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2129
2130 let _authenticated = state
2131 .on_disassoc_frame(
2132 &mut sta,
2133 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2134 )
2135 .await;
2136
2137 let msg = m
2139 .fake_device_state
2140 .lock()
2141 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2142 .expect("no message");
2143 assert_eq!(
2144 msg,
2145 fidl_mlme::DisassociateIndication {
2146 peer_sta_address: BSSID.to_array(),
2147 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2148 locally_initiated: false,
2149 }
2150 );
2151
2152 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2154 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2155 }
2156
2157 #[fuchsia::test(allow_stalls = false)]
2158 async fn associated_move_data_closed_controlled_port() {
2159 let mut m = MockObjects::new().await;
2160 let mut ctx = m.make_ctx().await;
2161 let mut sta = make_client_station();
2162 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2163 let state = Associated(empty_association(&mut sta));
2164
2165 let bytes = make_data_frame_single_llc(None, None);
2166 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2167 state.on_data_frame(&mut sta, data_frame, 0.into());
2168
2169 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2171 }
2172
2173 #[fuchsia::test(allow_stalls = false)]
2174 async fn associated_move_data_opened_controlled_port() {
2175 let mut m = MockObjects::new().await;
2176 let mut ctx = m.make_ctx().await;
2177 let mut sta = make_client_station();
2178 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2179 let state =
2180 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2181
2182 let bytes = make_data_frame_single_llc(None, None);
2183 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2184 state.on_data_frame(&mut sta, data_frame, 0.into());
2185
2186 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
2188 #[rustfmt::skip]
2189 assert_eq!(m.fake_device_state.lock().eth_queue[0], [
2190 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 9, 10, 11, 11, 11, ]);
2195 }
2196
2197 #[fuchsia::test(allow_stalls = false)]
2198 async fn associated_skip_empty_data() {
2199 let mut m = MockObjects::new().await;
2200 let mut ctx = m.make_ctx().await;
2201 let mut sta = make_client_station();
2202 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2203 let state =
2204 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2205
2206 let bytes = make_data_frame_single_llc_payload(None, None, &[]);
2207 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2208 state.on_data_frame(&mut sta, data_frame, 0.into());
2209
2210 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2212 }
2213
2214 #[test_case(true, true; "port open and protected")]
2215 #[test_case(false, true; "port closed and protected")]
2216 #[test_case(true, false; "port open and unprotected")]
2217 #[test_case(false, false; "port closed and unprotected (not a typical state)")]
2218 #[fuchsia::test(allow_stalls = false)]
2219 async fn associated_send_keep_alive_after_null_data_frame(
2220 controlled_port_open: bool,
2221 protected: bool,
2222 ) {
2223 let mut m = MockObjects::new().await;
2224 let mut ctx = m.make_ctx().await;
2225 let mut sta =
2226 if protected { make_protected_client_station() } else { make_client_station() };
2227 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2228 let state = Associated(Association { controlled_port_open, ..empty_association(&mut sta) });
2229
2230 let bytes = make_null_data_frame();
2231 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2232 state.on_data_frame(&mut sta, data_frame, 0.into());
2233
2234 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2236 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2237 let bytes = &m.fake_device_state.lock().wlan_queue[0].0;
2238 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2239 let frame_ctrl = data_frame.frame_ctrl();
2240 assert_eq!(frame_ctrl.to_ds(), true);
2241 assert_eq!(frame_ctrl.from_ds(), false);
2242 assert!(data_frame.body.is_empty());
2243 }
2244
2245 #[fuchsia::test(allow_stalls = false)]
2246 async fn associated_handle_eapol_closed_controlled_port() {
2247 let mut m = MockObjects::new().await;
2248 let mut ctx = m.make_ctx().await;
2249 let mut sta = make_protected_client_station();
2250 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2251 let state = Associated(empty_association(&mut sta));
2252
2253 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2254 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2255 state.on_data_frame(&mut sta, data_frame, 0.into());
2256
2257 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2259
2260 let eapol_ind = m
2262 .fake_device_state
2263 .lock()
2264 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2265 .expect("error reading EAPOL.indication");
2266 assert_eq!(
2267 eapol_ind,
2268 fidl_mlme::EapolIndication {
2269 src_addr: src_addr.to_array(),
2270 dst_addr: dst_addr.to_array(),
2271 data: EAPOL_PDU.to_vec()
2272 }
2273 );
2274 }
2275
2276 #[fuchsia::test(allow_stalls = false)]
2277 async fn associated_handle_eapol_open_controlled_port() {
2278 let mut m = MockObjects::new().await;
2279 let mut ctx = m.make_ctx().await;
2280 let mut sta = make_protected_client_station();
2281 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2282 let state = Associated(empty_association(&mut sta));
2283
2284 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2285 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2286 state.on_data_frame(&mut sta, data_frame, 0.into());
2287
2288 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2290
2291 let eapol_ind = m
2293 .fake_device_state
2294 .lock()
2295 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2296 .expect("error reading EAPOL.indication");
2297 assert_eq!(
2298 eapol_ind,
2299 fidl_mlme::EapolIndication {
2300 src_addr: src_addr.to_array(),
2301 dst_addr: dst_addr.to_array(),
2302 data: EAPOL_PDU.to_vec()
2303 }
2304 );
2305 }
2306
2307 #[fuchsia::test(allow_stalls = false)]
2308 async fn associated_handle_amsdus_open_controlled_port() {
2309 let mut m = MockObjects::new().await;
2310 let mut ctx = m.make_ctx().await;
2311 let mut sta = make_protected_client_station();
2312 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2313 let state =
2314 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2315
2316 let bytes = make_data_frame_amsdu();
2317 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2318 state.on_data_frame(&mut sta, data_frame, 0.into());
2319
2320 let queue = &m.fake_device_state.lock().eth_queue;
2321 assert_eq!(queue.len(), 2);
2322 #[rustfmt::skip]
2323 let mut expected_first_eth_frame = vec![
2324 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, 0x08, 0x00, ];
2328 expected_first_eth_frame.extend_from_slice(MSDU_1_PAYLOAD);
2329 assert_eq!(queue[0], &expected_first_eth_frame[..]);
2330 #[rustfmt::skip]
2331 let mut expected_second_eth_frame = vec![
2332 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x04, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, 0x08, 0x01, ];
2336 expected_second_eth_frame.extend_from_slice(MSDU_2_PAYLOAD);
2337 assert_eq!(queue[1], &expected_second_eth_frame[..]);
2338 }
2339
2340 #[fuchsia::test(allow_stalls = false)]
2341 async fn associated_request_bu_data_frame() {
2342 let mut m = MockObjects::new().await;
2343 let mut ctx = m.make_ctx().await;
2344 let mut sta = make_client_station();
2345 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2346 let state = Associated(Association {
2347 aid: 42,
2348 controlled_port_open: true,
2349 ..empty_association(&mut sta)
2350 });
2351
2352 let mut bytes = make_data_frame_single_llc(None, None);
2353 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2354 data_frame.fixed_fields.frame_ctrl =
2355 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2356 state.on_data_frame(&mut sta, data_frame, 0.into());
2357
2358 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2359 #[rustfmt::skip]
2360 assert_eq!(&m
2361 .fake_device_state.lock().wlan_queue[0].0[..], &[
2362 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2368 }
2369
2370 #[fuchsia::test(allow_stalls = false)]
2371 async fn associated_request_bu_mgmt_frame() {
2372 let mut m = MockObjects::new().await;
2373 let mut ctx = m.make_ctx().await;
2374 let mut sta = make_client_station();
2375 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2376 let state = Associated(Association {
2377 aid: 42,
2378 controlled_port_open: true,
2379 ..empty_association(&mut sta)
2380 });
2381
2382 state.on_any_mgmt_frame(
2383 &mut sta,
2384 &mac::MgmtHdr {
2385 frame_ctrl: mac::FrameControl(0)
2386 .with_frame_type(mac::FrameType::MGMT)
2387 .with_mgmt_subtype(mac::MgmtSubtype::BEACON)
2388 .with_more_data(true),
2389 duration: 0,
2390 addr1: [3; 6].into(),
2391 addr2: (*BSSID).into(),
2392 addr3: (*BSSID).into(),
2393 seq_ctrl: mac::SequenceControl(0),
2394 },
2395 );
2396
2397 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2398 #[rustfmt::skip]
2399 assert_eq!(&m
2400 .fake_device_state.lock().wlan_queue[0].0[..], &[
2401 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2407 }
2408
2409 #[fuchsia::test(allow_stalls = false)]
2410 async fn associated_no_bu_request() {
2411 let mut m = MockObjects::new().await;
2412 let mut ctx = m.make_ctx().await;
2413 let mut sta = make_client_station();
2414 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2415
2416 let state = Associated(empty_association(&mut sta));
2418 let mut bytes = make_data_frame_single_llc(None, None);
2419 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2420 data_frame.fixed_fields.frame_ctrl =
2421 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2422 state.on_data_frame(&mut sta, data_frame, 0.into());
2423 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2424
2425 let state = States::from(statemachine::testing::new_state(Associated(Association {
2427 controlled_port_open: true,
2428 ..empty_association(&mut sta)
2429 })));
2430 #[rustfmt::skip]
2431 let beacon = vec![
2432 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, ];
2441 let rx_info = mock_rx_info(&sta);
2442 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
2443 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2444 }
2445
2446 #[fuchsia::test(allow_stalls = false)]
2447 async fn associated_drop_foreign_data_frames() {
2448 let mut m = MockObjects::new().await;
2449 let mut ctx = m.make_ctx().await;
2450 let mut sta = make_client_station();
2451 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2452
2453 let state = States::from(statemachine::testing::new_state(Associated(Association {
2455 aid: 42,
2456 controlled_port_open: true,
2457 ..empty_association(&mut sta)
2458 })));
2459 let fc = mac::FrameControl(0)
2460 .with_frame_type(mac::FrameType::DATA)
2461 .with_data_subtype(mac::DataSubtype(0))
2462 .with_from_ds(true);
2463 let fc = fc.0.to_le_bytes();
2464 let bytes = vec![
2466 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,
2479 ];
2480 let rx_info = mock_rx_info(&sta);
2481 state.on_mac_frame(&mut sta, &bytes[..], rx_info, 0.into()).await;
2482 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2483 }
2484
2485 #[fuchsia::test(allow_stalls = false)]
2486 async fn state_transitions_joined_state_reconnect_denied() {
2487 let mut m = MockObjects::new().await;
2488 let mut ctx = m.make_ctx().await;
2489 let mut sta = make_client_station();
2490 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2491 let mut state = States::from(statemachine::testing::new_state(Joined));
2492
2493 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2495 peer_sta_address: [1, 2, 3, 4, 5, 6],
2496 });
2497 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2498
2499 assert_matches!(state, States::Joined(_), "not in joined state");
2500
2501 let msg = m
2503 .fake_device_state
2504 .lock()
2505 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2506 .expect("expect msg");
2507 assert_eq!(
2508 msg,
2509 fidl_mlme::ConnectConfirm {
2510 peer_sta_address: [1, 2, 3, 4, 5, 6],
2511 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2512 association_id: 0,
2513 association_ies: vec![],
2514 }
2515 );
2516 }
2517
2518 #[fuchsia::test(allow_stalls = false)]
2519 async fn state_transitions_authing_success() {
2520 let mut m = MockObjects::new().await;
2521 let mut ctx = m.make_ctx().await;
2522 let mut sta = make_client_station();
2523 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2524 let mut state =
2525 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2526
2527 #[rustfmt::skip]
2529 let auth_resp_success = vec![
2530 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, ];
2542 let rx_info = mock_rx_info(&sta);
2543 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2544 assert_matches!(state, States::Associating(_), "not in associating state");
2545 }
2546
2547 #[fuchsia::test(allow_stalls = false)]
2548 async fn state_transitions_authing_failure() {
2549 let mut m = MockObjects::new().await;
2550 let mut ctx = m.make_ctx_with_bss().await;
2551 let mut sta = make_client_station();
2552 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2553 let mut state =
2554 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2555
2556 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2557 #[rustfmt::skip]
2559 let auth_resp_failure = vec![
2560 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, ];
2572 let rx_info = mock_rx_info(&sta);
2573 state = state.on_mac_frame(&mut sta, &auth_resp_failure[..], rx_info, 0.into()).await;
2574 assert_matches!(state, States::Joined(_), "not in joined state");
2575 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2576 }
2577
2578 #[fuchsia::test(allow_stalls = false)]
2579 async fn state_transitions_authing_deauth() {
2580 let mut m = MockObjects::new().await;
2581 let mut ctx = m.make_ctx_with_bss().await;
2582 let mut sta = make_client_station();
2583 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2584 let mut state =
2585 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2586
2587 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2588 #[rustfmt::skip]
2590 let deauth = vec![
2591 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, ];
2601 let rx_info = mock_rx_info(&sta);
2602 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2603 assert_matches!(state, States::Joined(_), "not in joined state");
2604 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2605 }
2606
2607 #[fuchsia::test(allow_stalls = false)]
2608 async fn state_transitions_foreign_auth_resp() {
2609 let mut m = MockObjects::new().await;
2610 let mut ctx = m.make_ctx().await;
2611 let mut sta = make_client_station();
2612 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2613 let mut state =
2614 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2615
2616 #[rustfmt::skip]
2618 let auth_resp_success = vec![
2619 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, ];
2631 let rx_info = mock_rx_info(&sta);
2632 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2633 assert_matches!(state, States::Authenticating(_), "not in authenticating state");
2634
2635 #[rustfmt::skip]
2638 let auth_resp_success = vec![
2639 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, ];
2651 let rx_info = mock_rx_info(&sta);
2652 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2653 assert_matches!(state, States::Associating(_), "not in associating state");
2654 }
2655
2656 #[fuchsia::test(allow_stalls = false)]
2657 async fn state_transitions_authing_state_reconnect_denied() {
2658 let mut m = MockObjects::new().await;
2659 let mut ctx = m.make_ctx().await;
2660 let mut sta = make_client_station();
2661 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2662 let mut state =
2663 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2664
2665 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2667 peer_sta_address: [1, 2, 3, 4, 5, 6],
2668 });
2669 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2670
2671 assert_matches!(state, States::Authenticating(_), "not in authenticating state");
2672
2673 let msg = m
2675 .fake_device_state
2676 .lock()
2677 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2678 .expect("expect msg");
2679 assert_eq!(
2680 msg,
2681 fidl_mlme::ConnectConfirm {
2682 peer_sta_address: [1, 2, 3, 4, 5, 6],
2683 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2684 association_id: 0,
2685 association_ies: vec![],
2686 }
2687 );
2688 }
2689
2690 #[fuchsia::test(allow_stalls = false)]
2691 async fn state_transitions_authing_state_wrong_algorithm() {
2692 let mut m = MockObjects::new().await;
2693 let mut ctx = m.make_ctx().await;
2694 let mut sta = make_client_station();
2695 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2696 let mut state =
2697 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2698
2699 #[rustfmt::skip]
2700 let auth_resp_wrong = vec![
2701 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, ];
2713 let rx_info = mock_rx_info(&sta);
2714 state = state.on_mac_frame(&mut sta, &auth_resp_wrong[..], rx_info, 0.into()).await;
2715 assert_matches!(state, States::Joined(_), "not in joined state");
2716 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2717 }
2718
2719 #[fuchsia::test(allow_stalls = false)]
2720 async fn state_transitions_associng_success() {
2721 let mut m = MockObjects::new().await;
2722 let mut ctx = m.make_ctx().await;
2723 let mut sta = make_client_station();
2724 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2725 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2726
2727 #[rustfmt::skip]
2729 let assoc_resp_success = vec![
2730 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,
2744 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2748 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2752 let rx_info = mock_rx_info(&sta);
2753 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2754 assert_matches!(state, States::Associated(_), "not in associated state");
2755 }
2756
2757 #[fuchsia::test(allow_stalls = false)]
2758 async fn state_transitions_associng_failure() {
2759 let mut m = MockObjects::new().await;
2760 let mut ctx = m.make_ctx_with_bss().await;
2761 let mut sta = make_client_station();
2762 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2763 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2764
2765 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2766 #[rustfmt::skip]
2768 let assoc_resp_failure = vec![
2769 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, ];
2781 let rx_info = mock_rx_info(&sta);
2782 state = state.on_mac_frame(&mut sta, &assoc_resp_failure[..], rx_info, 0.into()).await;
2783 assert_matches!(state, States::Joined(_), "not in joined state");
2784 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2785 }
2786
2787 #[fuchsia::test(allow_stalls = false)]
2788 async fn state_transitions_associng_deauthing() {
2789 let mut m = MockObjects::new().await;
2790 let mut ctx = m.make_ctx_with_bss().await;
2791 let mut sta = make_client_station();
2792 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2793 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2794
2795 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2796 #[rustfmt::skip]
2798 let deauth = vec![
2799 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, ];
2809 let rx_info = mock_rx_info(&sta);
2810 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2811 assert_matches!(state, States::Joined(_), "not in joined state");
2812 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2813 }
2814
2815 #[fuchsia::test(allow_stalls = false)]
2816 async fn state_transitions_associng_reconnect_no_op() {
2817 let mut m = MockObjects::new().await;
2818 let mut ctx = m.make_ctx_with_bss().await;
2819 let mut sta = make_client_station();
2820 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2821 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2822
2823 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2824 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2826 peer_sta_address: BSSID.to_array(),
2827 });
2828 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2829 assert_matches!(state, States::Associating(_), "not in associating state");
2830 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2831
2832 m.fake_device_state
2834 .lock()
2835 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2836 .expect_err("unexpected Connect.confirm");
2837 }
2838
2839 #[fuchsia::test(allow_stalls = false)]
2840 async fn state_transitions_associng_reconnect_denied() {
2841 let mut m = MockObjects::new().await;
2842 let mut ctx = m.make_ctx_with_bss().await;
2843 let mut sta = make_client_station();
2844 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2845 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2846
2847 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2848 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
2850 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2851 peer_sta_address: sus_bssid,
2852 });
2853 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2854 assert_matches!(state, States::Associating(_), "not in associating state");
2855 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2856
2857 let connect_conf = m
2859 .fake_device_state
2860 .lock()
2861 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2862 .expect("error reading Connect.confirm");
2863 assert_eq!(
2864 connect_conf,
2865 fidl_mlme::ConnectConfirm {
2866 peer_sta_address: sus_bssid,
2867 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
2868 association_id: 0,
2869 association_ies: vec![],
2870 }
2871 );
2872 }
2873
2874 #[fuchsia::test(allow_stalls = false)]
2875 async fn state_transitions_assoced_disassoc_connect_success() {
2876 let mut m = MockObjects::new().await;
2877 let mut ctx = m.make_ctx_with_bss().await;
2878 let mut sta = make_client_station();
2879 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2880 let mut state =
2881 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2882
2883 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2884 #[rustfmt::skip]
2886 let disassoc = vec![
2887 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, ];
2897 let rx_info = mock_rx_info(&sta);
2898 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
2899 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
2900 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2901
2902 let disassoc_ind = m
2904 .fake_device_state
2905 .lock()
2906 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2907 .expect("error reading Disassociate.ind");
2908 assert_eq!(
2909 disassoc_ind,
2910 fidl_mlme::DisassociateIndication {
2911 peer_sta_address: BSSID.to_array(),
2912 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
2913 locally_initiated: false,
2914 }
2915 );
2916
2917 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2919 peer_sta_address: BSSID.to_array(),
2920 });
2921 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2922 assert_matches!(state, States::Associating(_), "not in associating state");
2923
2924 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2926 assert_eq!(
2927 &m.fake_device_state.lock().wlan_queue[0].0[..22],
2928 &[
2929 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, ][..]
2936 );
2937
2938 #[rustfmt::skip]
2940 let assoc_resp_success = vec![
2941 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,
2955 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2959 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2963 let rx_info = mock_rx_info(&sta);
2964 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2965 assert_matches!(state, States::Associated(_), "not in associated state");
2966
2967 let connect_conf = m
2969 .fake_device_state
2970 .lock()
2971 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2972 .expect("error reading Connect.confirm");
2973 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
2974 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
2975 assert_eq!(connect_conf.association_id, 11);
2976 }
2977
2978 #[fuchsia::test(allow_stalls = false)]
2979 async fn state_transitions_assoced_disassoc_reconnect_timeout() {
2980 let mut m = MockObjects::new().await;
2981 let mut ctx = m.make_ctx_with_bss().await;
2982 let mut sta = make_client_station();
2983 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2984 let mut state =
2985 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2986
2987 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2988 #[rustfmt::skip]
2990 let disassoc = vec![
2991 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, ];
3001 let rx_info = mock_rx_info(&sta);
3002 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3003 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3004 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3005
3006 let _disassoc_ind = m
3008 .fake_device_state
3009 .lock()
3010 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3011 .expect("error reading Disassociate.ind");
3012
3013 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3015 peer_sta_address: BSSID.to_array(),
3016 });
3017 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3018 assert_matches!(state, States::Associating(_), "not in associating state");
3019
3020 let (event, _id) = assert_matches!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Reassociating), Some(ids) => {
3022 assert_eq!(ids.len(), 1);
3023 ids[0].clone()
3024 });
3025
3026 let state = state.on_timed_event(&mut sta, event).await;
3028 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3029 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3030
3031 let connect_conf = m
3033 .fake_device_state
3034 .lock()
3035 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3036 .expect("error reading Connect.confirm");
3037 assert_eq!(
3038 connect_conf,
3039 fidl_mlme::ConnectConfirm {
3040 peer_sta_address: BSSID.to_array(),
3041 result_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
3042 association_id: 0,
3043 association_ies: vec![],
3044 }
3045 );
3046 }
3047
3048 #[fuchsia::test(allow_stalls = false)]
3049 async fn state_transitions_assoced_disassoc_reconnect_denied() {
3050 let mut m = MockObjects::new().await;
3051 let mut ctx = m.make_ctx_with_bss().await;
3052 let mut sta = make_client_station();
3053 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3054 let mut state =
3055 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3056
3057 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3058 #[rustfmt::skip]
3060 let disassoc = vec![
3061 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, ];
3071 let rx_info = mock_rx_info(&sta);
3072 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3073 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3074 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3075
3076 let _disassoc_ind = m
3078 .fake_device_state
3079 .lock()
3080 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3081 .expect("error reading Disassociate.ind");
3082
3083 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
3085 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3086 peer_sta_address: sus_bssid,
3087 });
3088 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3089 assert_matches!(state, States::Authenticated(_), "not in auth'd state");
3090
3091 let connect_conf = m
3093 .fake_device_state
3094 .lock()
3095 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3096 .expect("error reading Connect.confirm");
3097 assert_eq!(
3098 connect_conf,
3099 fidl_mlme::ConnectConfirm {
3100 peer_sta_address: sus_bssid,
3101 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
3102 association_id: 0,
3103 association_ies: vec![],
3104 }
3105 );
3106 }
3107
3108 #[fuchsia::test(allow_stalls = false)]
3109 async fn state_transitions_assoced_reconnect_no_op() {
3110 let mut m = MockObjects::new().await;
3111 let mut ctx = m.make_ctx_with_bss().await;
3112 let mut sta = make_client_station();
3113 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3114 let association = Association {
3115 aid: 42,
3116 assoc_resp_ies: vec![
3117 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
3119 ],
3120 ..empty_association(&mut sta)
3121 };
3122 let mut state = States::from(statemachine::testing::new_state(Associated(association)));
3123
3124 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3125
3126 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3128 peer_sta_address: BSSID.to_array(),
3129 });
3130 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3131 assert_matches!(state, States::Associated(_), "not in associated state");
3132 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3133
3134 let connect_conf = m
3136 .fake_device_state
3137 .lock()
3138 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3139 .expect("error reading Connect.confirm");
3140 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
3141 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
3142 assert_eq!(connect_conf.association_id, 42);
3143 }
3144
3145 #[fuchsia::test(allow_stalls = false)]
3146 async fn state_transitions_assoced_deauthing() {
3147 let mut m = MockObjects::new().await;
3148 let mut ctx = m.make_ctx_with_bss().await;
3149 let mut sta = make_client_station();
3150 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3151 let mut state =
3152 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3153
3154 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3155 #[rustfmt::skip]
3157 let deauth = vec![
3158 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, ];
3168 let rx_info = mock_rx_info(&sta);
3169 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
3170 assert_matches!(state, States::Joined(_), "not in joined state");
3171 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3172 }
3173
3174 #[test_case(false, false; "unprotected bss, not scanning")]
3175 #[test_case(true, false; "protected bss, not scanning")]
3176 #[test_case(false, true; "unprotected bss, scanning")]
3177 #[test_case(true, true; "protected bss, scanning")]
3178 #[fuchsia::test(allow_stalls = false)]
3179 async fn assoc_send_eth_frame_becomes_data_frame(protected: bool, scanning: bool) {
3180 let mut m = MockObjects::new().await;
3181 let mut ctx = m.make_ctx().await;
3182 let mut sta =
3183 if protected { make_protected_client_station() } else { make_client_station() };
3184 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3185 let state = States::from(statemachine::testing::new_state(Associated(Association {
3186 controlled_port_open: true,
3187 ..empty_association(&mut sta)
3188 })));
3189
3190 if scanning {
3191 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3192 bound_scanner
3193 .on_sme_scan(fidl_mlme::ScanRequest {
3194 txn_id: 1337,
3195 scan_type: fidl_mlme::ScanTypes::Passive,
3196 channel_list: vec![1],
3197 ssid_list: vec![],
3198 probe_delay: 0,
3199 min_channel_time: 100,
3200 max_channel_time: 300,
3201 })
3202 .await
3203 .expect("Failed to start scan");
3204 assert!(sta.scanner.is_scanning());
3205 }
3206
3207 let eth_frame = [
3208 1, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 3, 0x0d, 0x05, 21, 22, 23, 24, 25, 26, 27, 28, 29, ];
3214
3215 state.on_eth_frame(&mut sta, ð_frame[..], 0.into()).expect("all good");
3216
3217 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3218 let (data_frame, _tx_flags) = m.fake_device_state.lock().wlan_queue.remove(0);
3219 let mut fc_byte_2 = 0b00000001;
3220 if protected {
3221 fc_byte_2 |= 0b01000000;
3222 }
3223 assert_eq!(
3224 &data_frame[..],
3225 &[
3226 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, ][..]
3240 )
3241 }
3242
3243 #[fuchsia::test(allow_stalls = false)]
3244 async fn eth_frame_dropped_when_off_channel() {
3245 let mut m = MockObjects::new().await;
3246 let mut ctx = m.make_ctx().await;
3247 let mut sta = make_client_station();
3248 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3249 let state =
3250 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3251
3252 sta.ctx
3253 .device
3254 .set_channel(fidl_ieee80211::WlanChannel {
3255 primary: 42,
3256 cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
3257 secondary80: 0,
3258 })
3259 .await
3260 .expect("fake device is obedient");
3261 let eth_frame = &[100; 14]; let error = state
3264 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3265 .expect_err("Ethernet frame is dropped when client is off channel");
3266 assert_matches!(error, Error::Status(_str, status) =>
3267 assert_eq!(status, zx::Status::BAD_STATE),
3268 "error should contain a status"
3269 );
3270 }
3271
3272 #[fuchsia::test(allow_stalls = false)]
3273 async fn assoc_eth_frame_too_short_dropped() {
3274 let mut m = MockObjects::new().await;
3275 let mut ctx = m.make_ctx().await;
3276 let mut sta = make_client_station();
3277 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3278 let state =
3279 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3280
3281 let eth_frame = &[100; 13]; let error = state
3284 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3285 .expect_err("Ethernet frame is too short");
3286 assert_matches!(error, Error::Status(_str, status) =>
3287 assert_eq!(status, zx::Status::IO_DATA_INTEGRITY),
3288 "error should contain a status"
3289 );
3290 }
3291
3292 #[fuchsia::test(allow_stalls = false)]
3293 async fn assoc_controlled_port_closed_eth_frame_dropped() {
3294 let mut m = MockObjects::new().await;
3295 let mut ctx = m.make_ctx().await;
3296 let mut sta = make_client_station();
3297 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3298 let state =
3299 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3300
3301 let eth_frame = &[100; 14]; let error = state
3304 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3305 .expect_err("Ethernet frame canot be sent when controlled port is closed");
3306 assert_matches!(error, Error::Status(_str, status) =>
3307 assert_eq!(status, zx::Status::BAD_STATE),
3308 "Error should contain status"
3309 );
3310 }
3311
3312 #[fuchsia::test(allow_stalls = false)]
3313 async fn not_assoc_eth_frame_dropped() {
3314 let mut m = MockObjects::new().await;
3315 let mut ctx = m.make_ctx().await;
3316 let mut sta = make_client_station();
3317 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3318 let state = States::from(statemachine::testing::new_state(Joined));
3319
3320 let eth_frame = &[100; 14]; let error = state
3323 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3324 .expect_err("Ethernet frame cannot be sent in Joined state");
3325 assert_matches!(error, Error::Status(_str, status) =>
3326 assert_eq!(status, zx::Status::BAD_STATE),
3327 "Error should contain status"
3328 );
3329 }
3330
3331 #[fuchsia::test(allow_stalls = false)]
3332 async fn joined_sme_deauth() {
3333 let mut m = MockObjects::new().await;
3334 let mut ctx = m.make_ctx_with_bss().await;
3335 let mut sta = make_client_station();
3336 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3337 let state = States::from(statemachine::testing::new_state(Joined));
3338
3339 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3340 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3341 assert_matches!(state, States::Joined(_), "Joined should stay in Joined");
3342 m.fake_device_state
3344 .lock()
3345 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
3346 .expect_err("should be no outgoing message");
3347 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3348 }
3349
3350 #[fuchsia::test(allow_stalls = false)]
3351 async fn authenticating_sme_deauth() {
3352 let mut m = MockObjects::new().await;
3353 let mut ctx = m.make_ctx_with_bss().await;
3354 let mut sta = make_client_station();
3355 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3356 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3357
3358 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3359 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3360
3361 assert_matches!(state, States::Joined(_), "should transition to Joined");
3362
3363 m.fake_device_state
3365 .lock()
3366 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3367 .expect_err("should not see more MLME messages");
3368 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3369 }
3370
3371 #[fuchsia::test(allow_stalls = false)]
3372 async fn associating_sme_deauth() {
3373 let mut m = MockObjects::new().await;
3374 let mut ctx = m.make_ctx_with_bss().await;
3375 let mut sta = make_client_station();
3376 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3377 let state = States::from(statemachine::testing::new_state(Associating::default()));
3378
3379 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3380 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3381
3382 assert_matches!(state, States::Joined(_), "should transition to Joined");
3383
3384 m.fake_device_state
3386 .lock()
3387 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3388 .expect_err("should not see more MLME messages");
3389 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3390 }
3391
3392 #[fuchsia::test(allow_stalls = false)]
3393 async fn associated_sme_deauth() {
3394 let mut m = MockObjects::new().await;
3395 let mut ctx = m.make_ctx_with_bss().await;
3396 let mut sta = make_client_station();
3397 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3398 let state = States::from(statemachine::testing::new_state(Associated(Association {
3399 controlled_port_open: true,
3400 ..empty_association(&mut sta)
3401 })));
3402
3403 sta.ctx
3404 .device
3405 .notify_association_complete(fake_assoc_cfg())
3406 .await
3407 .expect("valid assoc ctx should not fail");
3408 assert_eq!(1, m.fake_device_state.lock().assocs.len());
3409
3410 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3411 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
3412 assert_eq!(crate::device::LinkStatus::UP, m.fake_device_state.lock().link_status);
3413
3414 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3415 assert_matches!(state, States::Joined(_), "should transition to Joined");
3416
3417 let deauth_conf = m
3419 .fake_device_state
3420 .lock()
3421 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3422 .expect("should see deauth conf");
3423 assert_eq!(
3424 deauth_conf,
3425 fidl_mlme::DeauthenticateConfirm { peer_sta_address: BSSID.to_array() }
3426 );
3427 m.fake_device_state
3428 .lock()
3429 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3430 .expect_err("should not see more MLME messages");
3431 assert_eq!(0, m.fake_device_state.lock().assocs.len());
3433 assert_eq!(crate::device::LinkStatus::DOWN, m.fake_device_state.lock().link_status);
3435 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3436 }
3437
3438 fn fake_eapol_req() -> wlan_sme::MlmeRequest {
3439 wlan_sme::MlmeRequest::Eapol(fidl_mlme::EapolRequest {
3440 dst_addr: BSSID.to_array(),
3441 src_addr: IFACE_MAC.to_array(),
3442 data: vec![1, 2, 3, 4],
3443 })
3444 }
3445
3446 #[allow(deprecated)]
3447 #[fuchsia::test(allow_stalls = false)]
3448 async fn mlme_eapol_not_associated() {
3449 let mut m = MockObjects::new().await;
3450 let mut ctx = m.make_ctx().await;
3451 let mut sta = make_protected_client_station();
3452 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3453
3454 let state = States::from(statemachine::testing::new_state(Joined));
3455 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3456 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3457
3458 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3459 m.fake_device_state.lock().wlan_queue.clear();
3460 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3461 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3462
3463 let state = States::from(statemachine::testing::new_state(Associating::default()));
3464 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3465 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3466 }
3467
3468 #[allow(deprecated)]
3469 #[fuchsia::test(allow_stalls = false)]
3470 async fn mlme_eapol_associated_not_protected() {
3471 let mut m = MockObjects::new().await;
3472 let mut ctx = m.make_ctx().await;
3473 let mut sta = make_client_station();
3474 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3475
3476 let state =
3477 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3478 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3479 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3480 }
3481
3482 #[allow(deprecated)]
3483 #[fuchsia::test(allow_stalls = false)]
3484 async fn mlme_eapol_associated() {
3485 let mut m = MockObjects::new().await;
3486 let mut ctx = m.make_ctx().await;
3487 let mut sta = make_protected_client_station();
3488 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3489
3490 let state =
3491 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3492 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3493 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3494 assert_eq!(
3495 &m.fake_device_state.lock().wlan_queue[0].0[..],
3496 &[
3497 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, ][..]
3510 );
3511 }
3512
3513 #[allow(deprecated)]
3514 #[fuchsia::test(allow_stalls = false)]
3515 async fn mlme_set_keys_not_associated() {
3516 let mut m = MockObjects::new().await;
3517 let mut ctx = m.make_ctx().await;
3518 let mut sta = make_protected_client_station();
3519 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3520
3521 let state = States::from(statemachine::testing::new_state(Joined));
3522 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3523 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3524
3525 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3526 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3527 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3528
3529 let state = States::from(statemachine::testing::new_state(Associating::default()));
3530 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3531 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3532 }
3533
3534 #[allow(deprecated)]
3535 #[fuchsia::test(allow_stalls = false)]
3536 async fn mlme_set_keys_associated_not_protected() {
3537 let mut m = MockObjects::new().await;
3538 let mut ctx = m.make_ctx().await;
3539 let mut sta = make_client_station();
3540 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3541
3542 let state =
3543 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3544 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3545 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3546 }
3547
3548 #[allow(deprecated)]
3549 #[fuchsia::test(allow_stalls = false)]
3550 async fn mlme_set_keys_associated() {
3551 let mut m = MockObjects::new().await;
3552 let mut ctx = m.make_ctx().await;
3553 let mut sta = make_protected_client_station();
3554 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3555
3556 let state =
3557 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3558 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3559 assert_eq!(m.fake_device_state.lock().keys.len(), 1);
3560 let conf = assert_matches!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3561 assert_eq!(conf.results.len(), 1);
3562 assert_eq!(
3563 conf.results[0],
3564 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::OK.into_raw() }
3565 );
3566
3567 assert_eq!(
3568 m.fake_device_state.lock().keys,
3569 vec![fidl_softmac::WlanKeyConfiguration {
3570 protection: Some(fidl_softmac::WlanProtection::RxTx),
3571 cipher_oui: Some([1, 2, 3]),
3572 cipher_type: Some(4),
3573 key_type: Some(fidl_ieee80211::KeyType::Pairwise),
3574 peer_addr: Some((*BSSID).to_array()),
3575 key_idx: Some(6),
3576 key: Some(vec![1, 2, 3, 4, 5, 6, 7]),
3577 rsc: Some(8),
3578 ..Default::default()
3579 }]
3580 );
3581 }
3582
3583 #[allow(deprecated)]
3584 #[fuchsia::test(allow_stalls = false)]
3585 async fn mlme_set_keys_failure() {
3586 let mut m = MockObjects::new().await;
3587 let mut ctx = m.make_ctx().await;
3588 let mut sta = make_protected_client_station();
3589 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3590
3591 let state =
3592 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3593 m.fake_device_state.lock().install_key_results.push_back(Err(zx::Status::BAD_STATE));
3594 m.fake_device_state.lock().install_key_results.push_back(Ok(()));
3595 let mut set_keys_req = fake_set_keys_req((*BSSID).into());
3597 match &mut set_keys_req {
3598 wlan_sme::MlmeRequest::SetKeys(req) => {
3599 req.keylist
3600 .push(fidl_mlme::SetKeyDescriptor { key_id: 4, ..req.keylist[0].clone() });
3601 }
3602 _ => panic!(),
3603 }
3604 let _state = state.handle_mlme_req(&mut sta, set_keys_req).await;
3605 let conf = assert_matches!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3606 assert_eq!(conf.results.len(), 2);
3607 assert_eq!(
3608 conf.results[0],
3609 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::BAD_STATE.into_raw() }
3610 );
3611 assert_eq!(
3612 conf.results[1],
3613 fidl_mlme::SetKeyResult { key_id: 4, status: zx::Status::OK.into_raw() }
3614 );
3615 }
3616
3617 fn fake_set_ctrl_port_open(open: bool) -> wlan_sme::MlmeRequest {
3618 wlan_sme::MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
3619 peer_sta_address: BSSID.to_array(),
3620 state: match open {
3621 true => fidl_mlme::ControlledPortState::Open,
3622 false => fidl_mlme::ControlledPortState::Closed,
3623 },
3624 })
3625 }
3626
3627 #[fuchsia::test(allow_stalls = false)]
3628 async fn mlme_set_controlled_port_not_associated() {
3629 let mut m = MockObjects::new().await;
3630 let mut ctx = m.make_ctx().await;
3631 let mut sta = make_protected_client_station();
3632 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3633
3634 let state = States::from(statemachine::testing::new_state(Joined));
3635 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3636 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3637
3638 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3639 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3640 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3641
3642 let state = States::from(statemachine::testing::new_state(Associating::default()));
3643 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3644 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3645 }
3646
3647 #[fuchsia::test(allow_stalls = false)]
3648 async fn mlme_set_controlled_port_associated_not_protected() {
3649 let mut m = MockObjects::new().await;
3650 let mut ctx = m.make_ctx().await;
3651 let mut sta = make_client_station();
3652 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3653
3654 let state =
3655 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3656 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3657 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3658 }
3659
3660 #[fuchsia::test(allow_stalls = false)]
3661 async fn mlme_set_controlled_port_associated() {
3662 let mut m = MockObjects::new().await;
3663 let mut ctx = m.make_ctx().await;
3664 let mut sta = make_protected_client_station();
3665 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3666
3667 let state =
3668 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3669 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3670 let state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3671 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
3672 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(false)).await;
3673 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3674 }
3675
3676 #[test_case(true; "while scanning")]
3677 #[test_case(false; "while not scanning")]
3678 #[fuchsia::test(allow_stalls = false)]
3679 async fn associated_rx_succeeds(scanning: bool) {
3680 let mut m = MockObjects::new().await;
3681 let mut ctx = m.make_ctx().await;
3682 let mut sta = make_client_station();
3683 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3684 let state = States::from(statemachine::testing::new_state(Associated(Association {
3685 aid: 1,
3686 controlled_port_open: true,
3687 ..empty_association(&mut sta)
3688 })));
3689
3690 if scanning {
3691 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3692 bound_scanner
3693 .on_sme_scan(fidl_mlme::ScanRequest {
3694 txn_id: 1337,
3695 scan_type: fidl_mlme::ScanTypes::Passive,
3696 channel_list: vec![1],
3697 ssid_list: vec![],
3698 probe_delay: 0,
3699 min_channel_time: 100,
3700 max_channel_time: 300,
3701 })
3702 .await
3703 .expect("Failed to start scan");
3704 assert!(sta.scanner.is_scanning());
3705 }
3706
3707 let fc = mac::FrameControl(0)
3708 .with_frame_type(mac::FrameType::DATA)
3709 .with_data_subtype(mac::DataSubtype(0))
3710 .with_from_ds(true);
3711 let fc = fc.0.to_le_bytes();
3712
3713 let data_frame = vec![
3714 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, ];
3727
3728 let rx_info = mock_rx_info(&sta);
3729 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3730 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3731 }
3732
3733 #[fuchsia::test(allow_stalls = false)]
3734 async fn associated_rx_with_wrong_cbw_succeeds() {
3735 let mut m = MockObjects::new().await;
3736 let mut ctx = m.make_ctx().await;
3737 let mut sta = make_client_station();
3738 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3739 let state = States::from(statemachine::testing::new_state(Associated(Association {
3740 aid: 1,
3741 controlled_port_open: true,
3742 ..empty_association(&mut sta)
3743 })));
3744
3745 let fc = mac::FrameControl(0)
3746 .with_frame_type(mac::FrameType::DATA)
3747 .with_data_subtype(mac::DataSubtype(0))
3748 .with_from_ds(true);
3749 let fc = fc.0.to_le_bytes();
3750
3751 let data_frame = vec![
3752 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, ];
3765
3766 let mut rx_info = mock_rx_info(&sta);
3767 rx_info.channel.cbw = fidl_ieee80211::ChannelBandwidth::Cbw80;
3770 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3771 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3772 }
3773
3774 #[fuchsia::test(allow_stalls = false)]
3775 async fn associated_request_bu_if_tim_indicates_buffered_frame() {
3776 let mut m = MockObjects::new().await;
3777 let mut ctx = m.make_ctx().await;
3778 let mut sta = make_client_station();
3779 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3780 let state = States::from(statemachine::testing::new_state(Associated(Association {
3781 aid: 1,
3782 ..empty_association(&mut sta)
3783 })));
3784
3785 let beacon = [
3786 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, ];
3799
3800 let rx_info = mock_rx_info(&sta);
3801 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3802
3803 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3804 assert_eq!(
3805 &m.fake_device_state.lock().wlan_queue[0].0[..],
3806 &[
3807 0b10100100, 0, 1, 0b11000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]
3812 );
3813 }
3814
3815 #[fuchsia::test(allow_stalls = false)]
3816 async fn associated_does_not_request_bu_if_tim_indicates_no_buffered_frame() {
3817 let mut m = MockObjects::new().await;
3818 let mut ctx = m.make_ctx().await;
3819 let mut sta = make_client_station();
3820 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3821 let state = States::from(statemachine::testing::new_state(Associated(Association {
3822 aid: 1,
3823 ..empty_association(&mut sta)
3824 })));
3825
3826 let beacon = [
3827 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, ];
3840 let rx_info = mock_rx_info(&sta);
3841 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3842
3843 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3844 }
3845
3846 fn rx_info_with_dbm<'a>(
3847 client: &BoundClient<'a, FakeDevice>,
3848 rssi_dbm: i8,
3849 ) -> fidl_softmac::WlanRxInfo {
3850 let mut rx_info = fidl_softmac::WlanRxInfo { rssi_dbm, ..mock_rx_info(client) };
3851 rx_info.valid_fields |= fidl_softmac::WlanRxInfoValid::RSSI;
3852 rx_info
3853 }
3854
3855 #[fuchsia::test(allow_stalls = false)]
3856 async fn signal_report() {
3857 let mut m = MockObjects::new().await;
3858 let mut ctx = m.make_ctx().await;
3859 let mut sta = make_protected_client_station();
3860 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3861
3862 let state = States::from(State::from(statemachine::testing::new_state(Associated(
3863 empty_association(&mut sta),
3864 ))));
3865
3866 let (_, timed_event, _) =
3867 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3868 let state = state.on_timed_event(&mut sta, timed_event.event).await;
3869
3870 let signal_ind = m
3871 .fake_device_state
3872 .lock()
3873 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3874 .expect("should see a signal report");
3875
3876 assert_eq!(signal_ind.rssi_dbm, -128);
3878
3879 let beacon = [
3880 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, ];
3892
3893 const EXPECTED_DBM: i8 = -32;
3894 let rx_info = rx_info_with_dbm(&sta, EXPECTED_DBM);
3895 let state = state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3896
3897 let (_, timed_event, _) =
3898 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3899 let _state = state.on_timed_event(&mut sta, timed_event.event).await;
3900
3901 let signal_ind = m
3902 .fake_device_state
3903 .lock()
3904 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3905 .expect("should see a signal report");
3906
3907 assert_eq!(signal_ind.rssi_dbm, EXPECTED_DBM);
3908 }
3909}