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::{intersect_with_ap_as_client, ApCapabilities, StaCapabilities};
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 {
499 Self::Enabled
500 } else {
501 Self::Disabled
502 }
503 }
504}
505
506impl Qos {
507 fn is_enabled(&self) -> bool {
508 *self == Self::Enabled
509 }
510}
511
512#[derive(Debug)]
513pub struct StatusCheckTimeout {
514 next_event: Option<EventHandle>,
515}
516
517#[derive(Debug)]
518pub struct Association {
519 pub aid: mac::Aid,
520 pub assoc_resp_ies: Vec<u8>,
521
522 pub controlled_port_open: bool,
525
526 #[allow(dead_code)]
528 pub ap_ht_op: Option<ie::HtOperation>,
529 #[allow(dead_code)]
531 pub ap_vht_op: Option<ie::VhtOperation>,
532
533 pub qos: Qos,
536
537 pub lost_bss_counter: LostBssCounter,
540
541 pub status_check_timeout: StatusCheckTimeout,
546 pub signal_strength_average: SignalStrengthAverage,
547
548 #[allow(dead_code)]
550 pub block_ack_state: StateMachine<BlockAckState>,
551}
552
553#[derive(Debug)]
555pub struct Associated(pub Association);
556
557impl Associated {
558 async fn on_disassoc_frame<D: DeviceOps>(
561 &mut self,
562 sta: &mut BoundClient<'_, D>,
563 disassoc_hdr: &mac::DisassocHdr,
564 ) {
565 wtrace::duration!(c"Associated::on_disassoc_frame");
566 self.pre_leaving_associated_state(sta).await;
567 let reason_code = fidl_ieee80211::ReasonCode::from_primitive(disassoc_hdr.reason_code.0)
568 .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason);
569 sta.send_disassoc_ind(reason_code, LocallyInitiated(false));
570 }
571
572 async fn on_deauth_frame<D: DeviceOps>(
574 &mut self,
575 sta: &mut BoundClient<'_, D>,
576 deauth_hdr: &mac::DeauthHdr,
577 ) {
578 wtrace::duration!(c"Associated::on_deauth_frame");
579 self.pre_leaving_associated_state(sta).await;
580 let reason_code = fidl_ieee80211::ReasonCode::from_primitive(deauth_hdr.reason_code.0)
581 .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason);
582 sta.send_deauthenticate_ind(reason_code, LocallyInitiated(false));
583 let _ =
584 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
585 }
586
587 fn on_any_mgmt_frame<D: DeviceOps>(
590 &self,
591 sta: &mut BoundClient<'_, D>,
592 mgmt_hdr: &mac::MgmtHdr,
593 ) {
594 self.request_bu_if_available(sta, mgmt_hdr.frame_ctrl, mgmt_hdr.addr1);
595 }
596
597 fn request_bu_if_available<D: DeviceOps>(
600 &self,
601 sta: &mut BoundClient<'_, D>,
602 fc: mac::FrameControl,
603 dst_addr: MacAddr,
604 ) {
605 if !self.0.controlled_port_open {
606 return;
607 }
608 if fc.more_data() && dst_addr == sta.sta.iface_mac {
610 let _result = sta.send_ps_poll_frame(self.0.aid);
611 }
612 }
613
614 fn extract_and_record_signal_dbm(&mut self, rx_info: fidl_softmac::WlanRxInfo) {
615 get_rssi_dbm(rx_info)
616 .map(|rssi_dbm| self.0.signal_strength_average.add(DecibelMilliWatt(rssi_dbm)));
617 }
618
619 async fn on_beacon_frame<B: SplitByteSlice, D: DeviceOps>(
622 &mut self,
623 sta: &mut BoundClient<'_, D>,
624 header: &BeaconHdr,
625 elements: B,
626 ) {
627 wtrace::duration!(c"Associated::on_beacon_frame");
628 self.0.lost_bss_counter.reset();
629 if let Err(e) =
631 sta.channel_state.bind(sta.ctx, sta.scanner).handle_beacon(header, &elements[..]).await
632 {
633 warn!("Failed to handle channel switch announcement: {}", e);
634 }
635 for (id, body) in ie::Reader::new(elements) {
636 match id {
637 ie::Id::TIM => match ie::parse_tim(body) {
638 Ok(ie::TimView { header, bitmap }) => {
639 if tim::is_traffic_buffered(header.bmp_ctrl.offset(), &bitmap, self.0.aid) {
640 let _result = sta.send_ps_poll_frame(self.0.aid);
641 }
642 }
643 _ => (),
644 },
645
646 _ => (),
647 }
648 }
649 }
650
651 fn on_data_frame<B: SplitByteSlice, D: DeviceOps>(
660 &self,
661 sta: &mut BoundClient<'_, D>,
662 data_frame: mac::DataFrame<B>,
663 async_id: TraceId,
664 ) {
665 const MSDU_TRACE_NAME: &'static std::ffi::CStr = c"States::on_data_frame => MSDU";
666
667 wtrace::duration!(c"States::on_data_frame");
668
669 self.request_bu_if_available(
670 sta,
671 data_frame.frame_ctrl(),
672 mac::data_dst_addr(&data_frame.fixed_fields),
673 );
674
675 if data_frame.data_subtype().null() {
677 if let Err(e) = sta.send_keep_alive_resp_frame() {
678 error!("error sending keep alive frame: {}", e);
679 }
680 }
681
682 for msdu in data_frame {
684 wtrace::duration_begin!(MSDU_TRACE_NAME);
685
686 match msdu.llc_frame.hdr.protocol_id.to_native() {
687 mac::ETHER_TYPE_EAPOL => {
689 let mac::Msdu { dst_addr, src_addr, llc_frame } = msdu;
690 if let Err(e) =
691 sta.send_eapol_indication(src_addr, dst_addr, &llc_frame.body[..])
692 {
693 wtrace::duration_end!(
694 MSDU_TRACE_NAME,
695 "status" => "failure sending EAPOL indication",
696 );
697 error!("error sending MLME-EAPOL.indication: {}", e);
698 } else {
699 wtrace::duration_end!(
700 MSDU_TRACE_NAME,
701 "status" => "sent EAPOL indication",
702 );
703 }
704 }
705 _ if self.0.controlled_port_open => {
707 if let Err(e) = sta.deliver_msdu(msdu) {
708 wtrace::duration_end!(
709 MSDU_TRACE_NAME,
710 "status" => "failure delivering MSDU",
711 );
712 error!("error while handling data frame: {}", e);
713 } else {
714 wtrace::duration_end!(
715 MSDU_TRACE_NAME,
716 "status" => "delivered MSDU",
717 );
718 }
719 }
720 _ => {
722 wtrace::duration_end!(
723 MSDU_TRACE_NAME,
724 "status" => "dropping MSDU. controlled port closed.",
725 );
726 }
727 }
728 }
729 wtrace::async_end_wlansoftmac_rx(async_id, "completed data frame processing");
730 }
731
732 fn on_eth_frame<B: SplitByteSlice, D: DeviceOps>(
733 &self,
734 sta: &mut BoundClient<'_, D>,
735 frame: B,
736 async_id: TraceId,
737 ) -> Result<(), Error> {
738 wtrace::duration!(c"Associated::on_eth_frame");
739 let mac::EthernetFrame { hdr, body } = match mac::EthernetFrame::parse(frame) {
740 Some(eth_frame) => eth_frame,
741 None => {
742 return Err(Error::Status(
743 format!("Ethernet frame too short"),
744 zx::Status::IO_DATA_INTEGRITY,
745 ));
746 }
747 };
748
749 if !self.0.controlled_port_open {
750 return Err(Error::Status(
751 format!("Ethernet dropped. RSN not established"),
752 zx::Status::BAD_STATE,
753 ));
754 }
755
756 sta.send_data_frame(
757 hdr.sa,
758 hdr.da,
759 sta.sta.eapol_required(),
760 self.0.qos.is_enabled(),
761 hdr.ether_type.to_native(),
762 &body,
763 Some(async_id),
764 )
765 }
766
767 fn on_block_ack_frame<B: SplitByteSlice, D>(
768 &mut self,
769 _sta: &mut BoundClient<'_, D>,
770 _action: mac::BlockAckAction,
771 _body: B,
772 ) {
773 }
779
780 async fn on_spectrum_mgmt_frame<B: SplitByteSlice, D: DeviceOps>(
781 &mut self,
782 sta: &mut BoundClient<'_, D>,
783 action: mac::SpectrumMgmtAction,
784 body: B,
785 ) {
786 match action {
787 mac::SpectrumMgmtAction::CHANNEL_SWITCH_ANNOUNCEMENT => {
788 if let Err(e) = sta
789 .channel_state
790 .bind(sta.ctx, sta.scanner)
791 .handle_announcement_frame(&body[..])
792 .await
793 {
794 warn!("Failed to handle channel switch announcement: {}", e);
795 }
796 }
797 _ => (),
798 }
799 }
800
801 fn on_sme_eapol<D: DeviceOps>(
802 &self,
803 sta: &mut BoundClient<'_, D>,
804 req: fidl_mlme::EapolRequest,
805 ) {
806 if !sta.sta.eapol_required() {
808 error!("Unexpected MLME-EAPOL.request message: BSS not protected");
809 return;
810 }
811 let protected = sta.sta.eapol_required() && self.0.controlled_port_open;
814 sta.send_eapol_frame(req.src_addr.into(), req.dst_addr.into(), protected, &req.data);
815 }
816
817 async fn on_sme_set_keys<D: DeviceOps>(
818 &self,
819 sta: &mut BoundClient<'_, D>,
820 req: fidl_mlme::SetKeysRequest,
821 ) {
822 if !sta.sta.eapol_required() {
823 error!("Unexpected MLME-SetKeys.request message: BSS not protected");
824 return;
825 }
826 let mut results = Vec::with_capacity(req.keylist.len());
827 for key_descriptor in req.keylist {
828 let key_id = key_descriptor.key_id;
829
830 match sta
831 .ctx
832 .device
833 .install_key(&softmac_key_configuration_from_mlme(key_descriptor))
834 .await
835 {
836 Ok(()) => results
837 .push(fidl_mlme::SetKeyResult { key_id, status: zx::Status::OK.into_raw() }),
838 Err(e) => {
839 error!("failed to set key: {}", e);
840 results.push(fidl_mlme::SetKeyResult { key_id, status: e.into_raw() })
841 }
842 }
843 }
844 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::SetKeysConf {
845 conf: fidl_mlme::SetKeysConfirm { results },
846 }) {
847 error!("Error sending SetKeysConfirm: {}", e);
848 }
849 }
850
851 async fn on_sme_set_controlled_port<D: DeviceOps>(
852 &mut self,
853 sta: &mut BoundClient<'_, D>,
854 req: fidl_mlme::SetControlledPortRequest,
855 ) {
856 if !sta.sta.eapol_required() {
857 error!("Unexpected MLME-SetControlledPort.request message: BSS not protected.");
858 return;
859 }
860 let should_open_controlled_port = req.state == fidl_mlme::ControlledPortState::Open;
861 if should_open_controlled_port == self.0.controlled_port_open {
862 return;
863 }
864 self.0.controlled_port_open = should_open_controlled_port;
865 if let Err(e) = sta.ctx.device.set_ethernet_status(req.state.into()).await {
866 error!(
867 "Error setting Ethernet port to {}: {}",
868 if should_open_controlled_port { "OPEN" } else { "CLOSED" },
869 e
870 );
871 }
872 }
873
874 async fn on_sme_deauthenticate<D: DeviceOps>(
875 &mut self,
876 sta: &mut BoundClient<'_, D>,
877 req: fidl_mlme::DeauthenticateRequest,
878 ) {
879 if let Err(e) = sta.send_deauth_frame(mac::ReasonCode(req.reason_code.into_primitive())) {
880 error!("Error sending deauthentication frame to BSS: {}", e);
881 }
882
883 self.pre_leaving_associated_state(sta).await;
884 let _ =
885 sta.clear_association().await.map_err(|e| error!("Failed to clear association: {}", e));
886
887 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::DeauthenticateConf {
888 resp: fidl_mlme::DeauthenticateConfirm { peer_sta_address: sta.sta.bssid().to_array() },
889 }) {
890 error!("Error sending MLME-DEAUTHENTICATE.confirm: {}", e)
891 }
892 }
893
894 async fn pre_leaving_associated_state<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) {
895 self.0.status_check_timeout.next_event.take();
896 self.0.controlled_port_open = false;
897 if let Err(e) = sta.ctx.device.set_ethernet_down().await {
898 error!("Error disabling ethernet device offline: {}", e);
899 }
900 }
901
902 #[must_use]
903 async fn on_timeout<D: DeviceOps>(&mut self, sta: &mut BoundClient<'_, D>) -> bool {
906 if let Err(e) = sta.ctx.device.send_mlme_event(fidl_mlme::MlmeEvent::SignalReport {
907 ind: fidl_internal::SignalReportIndication {
908 rssi_dbm: self.0.signal_strength_average.avg_dbm().0,
909 snr_db: 0,
910 },
911 }) {
912 error!("Error sending MLME-SignalReport: {}", e)
913 }
914
915 let auto_deauth = self.0.lost_bss_counter.should_deauthenticate();
916 if auto_deauth {
917 sta.send_deauthenticate_ind(
918 fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
919 LocallyInitiated(true),
920 );
921 if let Err(e) =
922 sta.send_deauth_frame(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth.into())
923 {
924 warn!("Failed sending deauth frame {:?}", e);
925 }
926 self.pre_leaving_associated_state(sta).await;
927 } else {
928 self.0.lost_bss_counter.add_beacon_interval(ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT);
931 self.0.status_check_timeout =
932 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
933 }
934 auto_deauth
935 }
936}
937
938statemachine!(
939 pub enum States,
941 () => Joined,
943 Joined => Authenticating,
944 Authenticating => Associating,
945 Associating => Associated,
946
947 Authenticating => Joined,
949 Associating => Joined,
950
951 Authenticating => Joined,
953 Associating => Joined,
954 Associated => Joined,
955
956 Associating => Authenticated, Associated => Authenticated,
959
960 Authenticated => Associating,
962);
963
964impl States {
965 pub fn new_initial() -> States {
967 States::from(State::new(Joined))
968 }
969
970 pub async fn start_connecting<D: DeviceOps>(self, sta: &mut BoundClient<'_, D>) -> States {
974 match self {
975 States::Joined(state) => {
976 let duration =
979 sta.sta.beacon_period() * sta.sta.connect_req.connect_failure_timeout;
980 let timeout = sta.ctx.timer.schedule_after(duration, TimedEvent::Connecting);
981 sta.sta.connect_timeout.replace(timeout);
982 match state.start_authenticating(sta).await {
983 Ok(algorithm) => state.transition_to(Authenticating::new(algorithm)).into(),
984 Err(()) => state.transition_to(Joined).into(),
985 }
986 }
987 other => {
988 warn!("Attempting to connect from a post-Joined state. Connect request ignored");
989 other
990 }
991 }
992 }
993
994 pub async fn on_mac_frame<B: SplitByteSlice, D: DeviceOps>(
1002 mut self,
1003 sta: &mut BoundClient<'_, D>,
1004 bytes: B,
1005 rx_info: fidl_softmac::WlanRxInfo,
1006 async_id: TraceId,
1007 ) -> States {
1008 wtrace::duration!(c"States::on_mac_frame");
1009
1010 let body_aligned = (rx_info.rx_flags & fidl_softmac::WlanRxInfoFlags::FRAME_BODY_PADDING_4)
1011 != fidl_softmac::WlanRxInfoFlags::empty();
1012
1013 trace!("Parsing MAC frame:\n {:02x?}", bytes.deref());
1015 let mac_frame = match mac::MacFrame::parse(bytes, body_aligned) {
1016 Some(mac_frame) => mac_frame,
1017 None => {
1018 debug!("Dropping corrupt MAC frame.");
1019 wtrace::async_end_wlansoftmac_rx(async_id, "corrupt frame");
1020 return self;
1021 }
1022 };
1023
1024 if !sta.sta.should_handle_frame(&mac_frame) {
1025 warn!("Mac frame is either from a foreign BSS or not destined for us. Dropped.");
1026 wtrace::async_end_wlansoftmac_rx(async_id, "foreign BSS frame");
1027 return self;
1028 }
1029
1030 let frame_class = mac::FrameClass::from(&mac_frame);
1032 if !self.is_frame_class_permitted(frame_class) {
1033 debug!("Dropping MAC frame with prohibited frame class.");
1034 wtrace::async_end_wlansoftmac_rx(async_id, "frame with prohibited frame class");
1035 return self;
1036 }
1037
1038 match mac_frame {
1039 mac::MacFrame::Mgmt(mgmt_frame) => {
1040 let states = self.on_mgmt_frame(sta, mgmt_frame, rx_info).await;
1041 wtrace::async_end_wlansoftmac_rx(
1042 async_id,
1043 "management frame successfully received",
1044 );
1045 states
1046 }
1047 mac::MacFrame::Data(data_frame) => {
1048 if let States::Associated(state) = &mut self {
1049 state.on_data_frame(sta, data_frame, async_id);
1050 state.extract_and_record_signal_dbm(rx_info);
1051 } else {
1052 debug!("Dropping MAC data frame while not associated.");
1054 wtrace::async_end_wlansoftmac_rx(async_id, "data frame while not associated");
1055 }
1056 self
1057 }
1058 _ => {
1060 debug!("Dropping unsupported MAC control frame.");
1061 wtrace::async_end_wlansoftmac_rx(async_id, "unsupported control frame");
1062 self
1063 }
1064 }
1065 }
1066
1067 async fn on_mgmt_frame<B: SplitByteSlice, D: DeviceOps>(
1070 self,
1071 sta: &mut BoundClient<'_, D>,
1072 mgmt_frame: mac::MgmtFrame<B>,
1073 rx_info: fidl_softmac::WlanRxInfo,
1074 ) -> States {
1075 wtrace::duration!(c"States::on_mgmt_frame");
1076
1077 let (mgmt_hdr, mgmt_body) = match mgmt_frame.try_into_mgmt_body() {
1079 (mgmt_hdr, Some(mgmt_body)) => (mgmt_hdr, mgmt_body),
1080 (_, None) => return self,
1081 };
1082
1083 match self {
1084 States::Authenticating(mut state) => match mgmt_body {
1085 mac::MgmtBody::Authentication(auth_frame) => match state
1086 .on_auth_frame(sta, auth_frame)
1087 .await
1088 {
1089 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1090 AuthProgress::InProgress => state.into(),
1091 AuthProgress::Failed => state.transition_to(Joined).into(),
1092 },
1093 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1094 state.on_deauth_frame(sta, &deauth_hdr).await;
1095 state.transition_to(Joined).into()
1096 }
1097 _ => state.into(),
1098 },
1099 States::Associating(mut state) => match mgmt_body {
1100 mac::MgmtBody::AssociationResp(assoc_resp_frame) => {
1101 match state.on_assoc_resp_frame(sta, assoc_resp_frame).await {
1102 Ok(association) => state.transition_to(Associated(association)).into(),
1103 Err(()) => state.transition_to(Joined).into(),
1104 }
1105 }
1106 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1107 state.on_deauth_frame(sta, &deauth_hdr).await;
1108 state.transition_to(Joined).into()
1109 }
1110 mac::MgmtBody::Disassociation { disassoc_hdr, .. } => {
1113 state.on_disassoc_frame(sta, &disassoc_hdr);
1114 state.transition_to(Authenticated).into()
1115 }
1116 _ => state.into(),
1117 },
1118 States::Associated(mut state) => {
1119 state.extract_and_record_signal_dbm(rx_info);
1120 state.on_any_mgmt_frame(sta, &mgmt_hdr);
1121 match mgmt_body {
1122 mac::MgmtBody::Beacon { bcn_hdr, elements } => {
1123 state.on_beacon_frame(sta, &bcn_hdr, elements).await;
1124 state.into()
1125 }
1126 mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
1127 state.on_deauth_frame(sta, &deauth_hdr).await;
1128 state.transition_to(Joined).into()
1129 }
1130 mac::MgmtBody::Disassociation { disassoc_hdr, .. } => {
1131 state.on_disassoc_frame(sta, &disassoc_hdr).await;
1132 state.transition_to(Authenticated).into()
1133 }
1134 mac::MgmtBody::Action(action_frame) => {
1135 let mac::ActionBody { action_hdr, elements, .. } = action_frame.into_body();
1136 match action_hdr.action {
1137 mac::ActionCategory::BLOCK_ACK => {
1138 let reader = BufferReader::new(elements);
1139 if let Some(action) = reader.peek_unaligned::<mac::BlockAckAction>()
1140 {
1141 state.on_block_ack_frame(
1142 sta,
1143 action.get(),
1144 reader.into_remaining(),
1145 );
1146 }
1147 state.into()
1148 }
1149 mac::ActionCategory::SPECTRUM_MGMT => {
1150 let reader = BufferReader::new(elements);
1151 if let Some(action) =
1152 reader.peek_unaligned::<mac::SpectrumMgmtAction>()
1153 {
1154 state
1155 .on_spectrum_mgmt_frame(
1156 sta,
1157 action.get(),
1158 reader.into_remaining(),
1159 )
1160 .await;
1161 }
1162 state.into()
1163 }
1164 _ => state.into(),
1165 }
1166 }
1167 _ => state.into(),
1168 }
1169 }
1170 _ => self,
1171 }
1172 }
1173
1174 pub fn on_eth_frame<B: SplitByteSlice, D: DeviceOps>(
1175 &self,
1176 sta: &mut BoundClient<'_, D>,
1177 frame: B,
1178 async_id: TraceId,
1179 ) -> Result<(), Error> {
1180 wtrace::duration!(c"States::on_eth_frame");
1181 match self {
1182 States::Associated(state) => state.on_eth_frame(sta, frame, async_id),
1183 _ => Err(Error::Status(
1184 format!("Not associated. Ethernet dropped"),
1185 zx::Status::BAD_STATE,
1186 )),
1187 }
1188 }
1189
1190 pub async fn on_timed_event<D: DeviceOps>(
1192 self,
1193 sta: &mut BoundClient<'_, D>,
1194 event: TimedEvent,
1195 ) -> States {
1196 match event {
1197 TimedEvent::Connecting => {
1198 sta.sta.connect_timeout.take();
1199 sta.send_connect_conf_failure(fidl_ieee80211::StatusCode::RejectedSequenceTimeout);
1200 let _ = sta
1201 .clear_association()
1202 .await
1203 .map_err(|e| error!("Failed to clear association: {}", e));
1204 match self {
1205 States::Authenticating(state) => state.transition_to(Joined).into(),
1206 States::Associating(state) => state.transition_to(Joined).into(),
1207 States::Associated(state) => state.transition_to(Joined).into(),
1208 _ => self,
1209 }
1210 }
1211 TimedEvent::Reassociating => match self {
1212 States::Associating(mut state) => {
1213 state.reconnect_timeout.take();
1214 sta.send_connect_conf_failure(
1215 fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
1216 );
1217 state.transition_to(Authenticated).into()
1218 }
1219 _ => self,
1220 },
1221 TimedEvent::AssociationStatusCheck => match self {
1222 States::Associated(mut state) => {
1223 let should_auto_deauth = state.on_timeout(sta).await;
1224 match should_auto_deauth {
1225 true => state.transition_to(Joined).into(),
1226 false => state.into(),
1227 }
1228 }
1229 _ => self,
1230 },
1231 TimedEvent::ChannelSwitch => {
1232 if let Err(e) = sta
1233 .channel_state
1234 .bind(sta.ctx, sta.scanner)
1235 .handle_channel_switch_timeout()
1236 .await
1237 {
1238 error!("ChannelSwitch timeout handler failed: {}", e);
1239 }
1240 self
1241 }
1242 }
1243 }
1244
1245 pub async fn handle_mlme_req<D: DeviceOps>(
1246 self,
1247 sta: &mut BoundClient<'_, D>,
1248 req: wlan_sme::MlmeRequest,
1249 ) -> States {
1250 use wlan_sme::MlmeRequest as MlmeReq;
1251
1252 match self {
1253 States::Joined(mut state) => match req {
1254 MlmeReq::Deauthenticate(_) => {
1255 state.on_sme_deauthenticate(sta).await;
1256 state.into()
1257 }
1258 MlmeReq::Reconnect(req) => {
1259 sta.send_connect_conf_failure_with_bssid(
1260 req.peer_sta_address.into(),
1261 fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
1262 );
1263 state.into()
1264 }
1265 _ => state.into(),
1266 },
1267 States::Authenticating(mut state) => match req {
1268 MlmeReq::SaeHandshakeResp(resp) => match state.on_sme_sae_resp(sta, resp).await {
1269 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1270 AuthProgress::InProgress => state.into(),
1271 AuthProgress::Failed => state.transition_to(Joined).into(),
1272 },
1273 MlmeReq::SaeFrameTx(frame) => match state.on_sme_sae_tx(sta, frame).await {
1274 AuthProgress::Complete => state.transition_to(Associating::default()).into(),
1275 AuthProgress::InProgress => state.into(),
1276 AuthProgress::Failed => state.transition_to(Joined).into(),
1277 },
1278 MlmeReq::Deauthenticate(_) => {
1279 state.on_sme_deauthenticate(sta).await;
1280 state.transition_to(Joined).into()
1281 }
1282 MlmeReq::Reconnect(req) => {
1283 sta.send_connect_conf_failure_with_bssid(
1284 req.peer_sta_address.into(),
1285 fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
1286 );
1287 state.into()
1288 }
1289 _ => state.into(),
1290 },
1291 States::Authenticated(state) => match req {
1292 MlmeReq::Reconnect(req) => match state.on_sme_reconnect(sta, req) {
1293 Ok(timeout) => {
1294 state.transition_to(Associating::new_with_reconnect_timeout(timeout)).into()
1295 }
1296 Err(()) => state.into(),
1297 },
1298 _ => state.into(),
1299 },
1300 States::Associating(mut state) => match req {
1301 MlmeReq::Deauthenticate(_) => {
1302 state.on_sme_deauthenticate(sta).await;
1303 state.transition_to(Joined).into()
1304 }
1305 MlmeReq::Reconnect(req) => {
1306 let peer_sta_address: Bssid = req.peer_sta_address.into();
1307 if peer_sta_address != sta.sta.connect_req.selected_bss.bssid {
1308 sta.send_connect_conf_failure_with_bssid(
1309 peer_sta_address,
1310 fidl_ieee80211::StatusCode::NotInSameBss,
1311 );
1312 }
1313 state.into()
1314 }
1315 _ => state.into(),
1316 },
1317 States::Associated(mut state) => match req {
1318 MlmeReq::Eapol(req) => {
1319 state.on_sme_eapol(sta, req);
1320 state.into()
1321 }
1322 MlmeReq::SetKeys(req) => {
1323 state.on_sme_set_keys(sta, req).await;
1324 state.into()
1325 }
1326 MlmeReq::SetCtrlPort(req) => {
1327 state.on_sme_set_controlled_port(sta, req).await;
1328 state.into()
1329 }
1330 MlmeReq::Deauthenticate(req) => {
1331 state.on_sme_deauthenticate(sta, req).await;
1332 state.transition_to(Joined).into()
1333 }
1334 MlmeReq::Reconnect(req) => {
1335 let peer_sta_address: Bssid = req.peer_sta_address.into();
1336 if peer_sta_address != sta.sta.connect_req.selected_bss.bssid {
1337 sta.send_connect_conf_failure_with_bssid(
1338 peer_sta_address,
1339 fidl_ieee80211::StatusCode::NotInSameBss,
1340 );
1341 } else {
1342 sta.send_connect_conf_success(state.0.aid, &state.0.assoc_resp_ies[..]);
1343 }
1344 state.into()
1345 }
1346 _ => state.into(),
1347 },
1348 }
1349 }
1350
1351 fn is_frame_class_permitted(&self, class: mac::FrameClass) -> bool {
1353 wtrace::duration!(c"State::is_frame_class_permitted");
1354 match self {
1355 States::Joined(_) | States::Authenticating(_) => class == mac::FrameClass::Class1,
1356 States::Authenticated(_) | States::Associating(_) => class <= mac::FrameClass::Class2,
1357 States::Associated(_) => class <= mac::FrameClass::Class3,
1358 }
1359 }
1360}
1361
1362#[cfg(test)]
1363mod free_function_tests {
1364 use super::*;
1365 use wlan_common::mac::IntoBytesExt as _;
1366
1367 fn assoc_resp_frame_from_ies(elements: &[u8]) -> mac::AssocRespFrame<&[u8]> {
1368 mac::AssocRespFrame {
1369 assoc_resp_hdr: mac::AssocRespHdr {
1370 capabilities: mac::CapabilityInfo(0u16),
1371 status_code: mac::StatusCode(0u16),
1372 aid: 0u16,
1373 }
1374 .as_bytes_ref(),
1375 elements,
1376 }
1377 }
1378
1379 #[test]
1380 fn test_extract_ht_vht_op_success() {
1381 let mut buffer = Vec::new();
1382 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1383 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1384 let (ht_operation, vht_operation) =
1385 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1386 assert_eq!(ht_operation.unwrap(), ie::fake_ht_operation());
1387 assert_eq!(vht_operation.unwrap(), ie::fake_vht_operation());
1388 }
1389
1390 #[test]
1391 fn test_extract_ht_op_too_short() {
1392 let mut buffer = Vec::<u8>::new();
1393 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1394 buffer[1] -= 1; buffer.truncate(buffer.len() - 1);
1396 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1397 let (ht_operation, vht_operation) =
1398 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1399 assert_eq!(ht_operation, None);
1400 assert_eq!(vht_operation.unwrap(), ie::fake_vht_operation());
1401 }
1402
1403 #[test]
1404 fn test_extract_vht_op_too_short() {
1405 let mut buffer = Vec::new();
1406 ie::write_ht_operation(&mut buffer, &ie::fake_ht_operation()).expect("valid HT Op");
1407 let ht_end = buffer.len();
1408 ie::write_vht_operation(&mut buffer, &ie::fake_vht_operation()).expect("valid VHT Op");
1409 buffer[ht_end + 1] -= 1; buffer.truncate(buffer.len() - 1);
1411 let (ht_operation, vht_operation) =
1412 extract_ht_vht_op(&assoc_resp_frame_from_ies(&buffer[..]));
1413 assert_eq!(ht_operation.unwrap(), ie::fake_ht_operation());
1414 assert_eq!(vht_operation, None);
1415 }
1416}
1417
1418#[cfg(test)]
1419mod tests {
1420 use super::*;
1421 use crate::block_ack::{write_addba_req_body, ADDBA_REQ_FRAME_LEN};
1422 use crate::client::channel_switch::ChannelState;
1423 use crate::client::scanner::Scanner;
1424 use crate::client::test_utils::drain_timeouts;
1425 use crate::client::{Client, Context, ParsedConnectRequest, TimedEventClass};
1426 use crate::device::{FakeDevice, FakeDeviceState};
1427 use crate::test_utils::{fake_set_keys_req, fake_wlan_channel, MockWlanRxInfo};
1428 use akm::AkmAlgorithm;
1429 use fuchsia_sync::Mutex;
1430 use lazy_static::lazy_static;
1431 use std::sync::Arc;
1432 use test_case::test_case;
1433 use wlan_common::buffer_writer::BufferWriter;
1434 use wlan_common::ie::IeType;
1435 use wlan_common::mac::IntoBytesExt as _;
1436 use wlan_common::sequence::SequenceManager;
1437 use wlan_common::test_utils::fake_capabilities::fake_client_capabilities;
1438 use wlan_common::test_utils::fake_frames::*;
1439 use wlan_common::test_utils::fake_stas::IesOverrides;
1440 use wlan_common::timer::{self, create_timer};
1441 use wlan_common::{assert_variant, fake_bss_description, mgmt_writer};
1442 use wlan_frame_writer::append_frame_to;
1443 use {fidl_fuchsia_wlan_common as fidl_common, wlan_statemachine as statemachine};
1444
1445 lazy_static! {
1446 static ref BSSID: Bssid = [6u8; 6].into();
1447 static ref IFACE_MAC: MacAddr = [3u8; 6].into();
1448 }
1449
1450 struct MockObjects {
1451 fake_device: FakeDevice,
1452 fake_device_state: Arc<Mutex<FakeDeviceState>>,
1453 timer: Option<Timer<TimedEvent>>,
1454 time_stream: timer::EventStream<TimedEvent>,
1455 scanner: Scanner,
1456 channel_state: ChannelState,
1457 }
1458
1459 impl MockObjects {
1460 async fn new() -> Self {
1464 let (timer, time_stream) = create_timer();
1465 let (fake_device, fake_device_state) = FakeDevice::new().await;
1466 Self {
1467 fake_device,
1468 fake_device_state,
1469 timer: Some(timer),
1470 time_stream,
1471 scanner: Scanner::new(*IFACE_MAC),
1472 channel_state: ChannelState::new_with_main_channel(fake_wlan_channel().into()),
1473 }
1474 }
1475
1476 async fn make_ctx(&mut self) -> Context<FakeDevice> {
1477 self.fake_device
1478 .set_channel(fake_wlan_channel().into())
1479 .await
1480 .expect("fake device is obedient");
1481 self.make_base_ctx()
1482 }
1483
1484 async fn make_ctx_with_bss(&mut self) -> Context<FakeDevice> {
1485 self.fake_device
1486 .set_channel(fake_wlan_channel().into())
1487 .await
1488 .expect("fake device is obedient");
1489 self.fake_device
1490 .join_bss(&fidl_common::JoinBssRequest {
1491 bssid: Some([1, 2, 3, 4, 5, 6]),
1492 bss_type: Some(fidl_common::BssType::Personal),
1493 remote: Some(true),
1494 beacon_period: Some(100),
1495 ..Default::default()
1496 })
1497 .await
1498 .expect("error configuring bss");
1499 self.make_base_ctx()
1500 }
1501
1502 fn make_base_ctx(&mut self) -> Context<FakeDevice> {
1503 Context {
1504 _config: Default::default(),
1505 device: self.fake_device.clone(),
1506 timer: self.timer.take().unwrap(),
1507 seq_mgr: SequenceManager::new(),
1508 }
1509 }
1510 }
1511
1512 fn make_client_station() -> Client {
1513 let connect_req = ParsedConnectRequest {
1514 selected_bss: fake_bss_description!(Open, bssid: BSSID.to_array()),
1515 connect_failure_timeout: 10,
1516 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1517 sae_password: vec![],
1518 wep_key: None,
1519 security_ie: vec![],
1520 };
1521 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1522 }
1523
1524 fn make_protected_client_station() -> Client {
1525 let connect_req = ParsedConnectRequest {
1526 selected_bss: fake_bss_description!(Wpa2, bssid: BSSID.to_array()),
1527 connect_failure_timeout: 10,
1528 auth_type: fidl_mlme::AuthenticationTypes::OpenSystem,
1529 sae_password: vec![],
1530 wep_key: None,
1531 security_ie: vec![
1532 0x30, 0x14, 1, 0, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x04, 0x01, 0x00, 0x00, 0x0f, 0xac, 0x02, 0xa8, 0x04, ],
1541 };
1542 Client::new(connect_req, *IFACE_MAC, fake_client_capabilities())
1543 }
1544
1545 fn connect_conf_failure(result_code: fidl_ieee80211::StatusCode) -> fidl_mlme::ConnectConfirm {
1546 fidl_mlme::ConnectConfirm {
1547 peer_sta_address: BSSID.to_array(),
1548 result_code,
1549 association_id: 0,
1550 association_ies: vec![],
1551 }
1552 }
1553
1554 fn empty_association(sta: &mut BoundClient<'_, FakeDevice>) -> Association {
1555 let status_check_timeout =
1556 schedule_association_status_timeout(sta.sta.beacon_period(), &mut sta.ctx.timer);
1557 Association {
1558 controlled_port_open: false,
1559 aid: 0,
1560 assoc_resp_ies: vec![],
1561 ap_ht_op: None,
1562 ap_vht_op: None,
1563 lost_bss_counter: LostBssCounter::start(
1564 sta.sta.beacon_period(),
1565 DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT,
1566 ),
1567 qos: Qos::Disabled,
1568 status_check_timeout,
1569 signal_strength_average: SignalStrengthAverage::new(),
1570 block_ack_state: StateMachine::new(BlockAckState::from(State::new(Closed))),
1571 }
1572 }
1573
1574 fn fake_assoc_cfg() -> fidl_softmac::WlanAssociationConfig {
1575 fidl_softmac::WlanAssociationConfig {
1576 bssid: Some(BSSID.to_array()),
1577 aid: Some(42),
1578 channel: Some(fidl_common::WlanChannel {
1579 primary: 149,
1580 cbw: fidl_common::ChannelBandwidth::Cbw40,
1581 secondary80: 42,
1582 }),
1583 rates: None,
1584 capability_info: None,
1585 ..Default::default()
1586 }
1587 }
1588
1589 fn fake_deauth_req() -> wlan_sme::MlmeRequest {
1590 wlan_sme::MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1591 peer_sta_address: BSSID.to_array(),
1592 reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1593 })
1594 }
1595
1596 fn open_authenticating(sta: &mut BoundClient<'_, FakeDevice>) -> Authenticating {
1597 let mut auth = Authenticating::new(AkmAlgorithm::OpenSupplicant);
1598 auth.algorithm.initiate(sta).expect("Failed to initiate open auth");
1599 auth
1600 }
1601
1602 #[fuchsia::test(allow_stalls = false)]
1603 async fn connect_authenticate_tx_failure() {
1604 let mut m = MockObjects::new().await;
1605 m.fake_device_state.lock().config.send_wlan_frame_fails = true;
1606 let mut ctx = m.make_ctx().await;
1607 let mut sta = make_client_station();
1608 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1609
1610 let state = Joined;
1611 let _state =
1612 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1613
1614 assert!(m.time_stream.try_next().is_err());
1616
1617 let msg = m
1619 .fake_device_state
1620 .lock()
1621 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1622 .expect("expect msg");
1623 assert_eq!(
1624 msg,
1625 fidl_mlme::ConnectConfirm {
1626 peer_sta_address: BSSID.to_array(),
1627 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1628 association_id: 0,
1629 association_ies: vec![],
1630 }
1631 );
1632 }
1633
1634 #[fuchsia::test(allow_stalls = false)]
1635 async fn joined_no_authentication_algorithm() {
1636 let mut m = MockObjects::new().await;
1637 let mut ctx = m.make_ctx_with_bss().await;
1638 let connect_req = ParsedConnectRequest {
1639 selected_bss: fake_bss_description!(Open, bssid: BSSID.to_array()),
1640 connect_failure_timeout: 10,
1641 auth_type: fidl_mlme::AuthenticationTypes::SharedKey,
1643 sae_password: vec![],
1644 wep_key: None,
1645 security_ie: vec![],
1646 };
1647 let mut sta = Client::new(connect_req, *IFACE_MAC, fake_client_capabilities());
1648 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1649 let state = Joined;
1650
1651 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1652 let _state =
1653 state.start_authenticating(&mut sta).await.expect_err("should fail authenticating");
1654
1655 let msg = m
1657 .fake_device_state
1658 .lock()
1659 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1660 .expect("expect msg");
1661 assert_eq!(
1662 msg,
1663 fidl_mlme::ConnectConfirm {
1664 peer_sta_address: [6, 6, 6, 6, 6, 6],
1665 result_code: fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm,
1666 association_id: 0,
1667 association_ies: vec![],
1668 }
1669 );
1670
1671 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1672 }
1673
1674 #[fuchsia::test(allow_stalls = false)]
1675 async fn authenticating_state_auth_rejected() {
1676 let mut m = MockObjects::new().await;
1677 let mut ctx = m.make_ctx_with_bss().await;
1678 let mut sta = make_client_station();
1679 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1680 let mut state = open_authenticating(&mut sta);
1681
1682 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1683 assert_variant!(
1685 state
1686 .on_auth_frame(
1687 &mut sta,
1688 mac::AuthFrame {
1689 auth_hdr: mac::AuthHdr {
1690 auth_alg_num: mac::AuthAlgorithmNumber::OPEN,
1691 auth_txn_seq_num: 2,
1692 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1693 }
1694 .as_bytes_ref(),
1695 elements: &[],
1696 },
1697 )
1698 .await,
1699 AuthProgress::Failed
1700 );
1701
1702 let msg = m
1704 .fake_device_state
1705 .lock()
1706 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1707 .expect("expect msg");
1708 assert_eq!(
1709 msg,
1710 fidl_mlme::ConnectConfirm {
1711 peer_sta_address: BSSID.to_array(),
1712 result_code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1713 association_id: 0,
1714 association_ies: vec![],
1715 }
1716 );
1717 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1718 }
1719
1720 #[fuchsia::test(allow_stalls = false)]
1721 async fn authenticating_state_deauth_frame() {
1722 let mut m = MockObjects::new().await;
1723 let mut ctx = m.make_ctx_with_bss().await;
1724 let mut sta = make_client_station();
1725 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1726 let mut state = open_authenticating(&mut sta);
1727
1728 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1729 state
1730 .on_deauth_frame(
1731 &mut sta,
1732 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::NoMoreStas.into() },
1733 )
1734 .await;
1735
1736 let msg = m
1738 .fake_device_state
1739 .lock()
1740 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1741 .expect("expect msg");
1742 assert_eq!(
1743 msg,
1744 fidl_mlme::ConnectConfirm {
1745 peer_sta_address: BSSID.to_array(),
1746 result_code: fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc,
1747 association_id: 0,
1748 association_ies: vec![],
1749 }
1750 );
1751 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1752 }
1753
1754 #[fuchsia::test(allow_stalls = false)]
1755 async fn associating_success_unprotected() {
1756 let mut m = MockObjects::new().await;
1757 let mut ctx = m.make_ctx_with_bss().await;
1758 let mut sta = make_client_station();
1759 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1760
1761 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1762 let mut state = Associating::default();
1763 let assoc_resp_ies = fake_bss_description!(Wpa2, ies_overrides: IesOverrides::new()
1764 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1765 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1766 )
1767 .ies()
1768 .to_vec();
1769 let Association { aid, controlled_port_open, .. } = state
1770 .on_assoc_resp_frame(
1771 &mut sta,
1772 mac::AssocRespFrame {
1773 assoc_resp_hdr: mac::AssocRespHdr {
1774 aid: 42,
1775 capabilities: mac::CapabilityInfo(52),
1776 status_code: fidl_ieee80211::StatusCode::Success.into(),
1777 }
1778 .as_bytes_ref(),
1779 elements: &assoc_resp_ies[..],
1780 },
1781 )
1782 .await
1783 .expect("failed processing association response frame");
1784 assert_eq!(aid, 42);
1785 assert_eq!(true, controlled_port_open);
1786
1787 let msg = m
1789 .fake_device_state
1790 .lock()
1791 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1792 .expect("no message");
1793 assert_eq!(
1794 msg,
1795 fidl_mlme::ConnectConfirm {
1796 peer_sta_address: BSSID.to_array(),
1797 result_code: fidl_ieee80211::StatusCode::Success,
1798 association_id: 42,
1799 association_ies: assoc_resp_ies,
1800 }
1801 );
1802 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1803 }
1804
1805 #[fuchsia::test(allow_stalls = false)]
1806 async fn associating_success_protected() {
1807 let mut m = MockObjects::new().await;
1808 let mut ctx = m.make_ctx_with_bss().await;
1809 let mut sta = make_protected_client_station();
1810 sta.client_capabilities.0.capability_info =
1811 mac::CapabilityInfo(0).with_ess(true).with_ibss(true);
1812 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1813 let mut state = Associating::default();
1814
1815 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1816 let assoc_resp_ies =
1817 fake_bss_description!(Wpa2, bssid: BSSID.to_array(), ies_overrides: IesOverrides::new()
1818 .set(IeType::HT_CAPABILITIES, ie::fake_ht_cap_bytes().to_vec())
1819 .set(IeType::VHT_CAPABILITIES, ie::fake_vht_cap_bytes().to_vec())
1820 )
1821 .ies()
1822 .to_vec();
1823 let Association { aid, controlled_port_open, .. } = state
1824 .on_assoc_resp_frame(
1825 &mut sta,
1826 mac::AssocRespFrame {
1827 assoc_resp_hdr: mac::AssocRespHdr {
1828 aid: 42,
1829 capabilities: mac::CapabilityInfo(0).with_ibss(true).with_cf_pollable(true),
1830 status_code: fidl_ieee80211::StatusCode::Success.into(),
1831 }
1832 .as_bytes_ref(),
1833 elements: &assoc_resp_ies[..],
1834 },
1835 )
1836 .await
1837 .expect("failed processing association response frame");
1838 assert_eq!(aid, 42);
1839 assert_eq!(false, controlled_port_open);
1840
1841 assert_eq!(m.fake_device_state.lock().assocs.len(), 1);
1843
1844 let assoc_cfg = m
1845 .fake_device_state
1846 .lock()
1847 .assocs
1848 .get(&(*BSSID).into())
1849 .expect("expect assoc ctx to be set")
1850 .clone();
1851 assert_eq!(assoc_cfg.aid, Some(42));
1852 assert_eq!(assoc_cfg.qos, Some(true));
1853 assert_eq!(
1854 assoc_cfg.rates,
1855 Some(vec![0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c])
1856 );
1857 assert_eq!(assoc_cfg.capability_info, Some(2));
1858 assert!(assoc_cfg.ht_cap.is_some());
1859 assert!(assoc_cfg.vht_cap.is_some());
1860 assert!(assoc_cfg.ht_op.is_some());
1861 assert!(assoc_cfg.vht_op.is_some());
1862
1863 let msg = m
1865 .fake_device_state
1866 .lock()
1867 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1868 .expect("no message");
1869 assert_eq!(
1870 msg,
1871 fidl_mlme::ConnectConfirm {
1872 peer_sta_address: BSSID.to_array(),
1873 result_code: fidl_ieee80211::StatusCode::Success,
1874 association_id: 42,
1875 association_ies: assoc_resp_ies,
1876 }
1877 );
1878 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1879 }
1880
1881 #[fuchsia::test(allow_stalls = false)]
1882 async fn associating_failure_due_to_failed_status_code() {
1883 let mut m = MockObjects::new().await;
1884 let mut ctx = m.make_ctx_with_bss().await;
1885 let mut sta = make_client_station();
1886 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1887
1888 let mut state = Associating::default();
1889
1890 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1891 state
1893 .on_assoc_resp_frame(
1894 &mut sta,
1895 mac::AssocRespFrame {
1896 assoc_resp_hdr: mac::AssocRespHdr {
1897 aid: 42,
1898 capabilities: mac::CapabilityInfo(52),
1899 status_code: fidl_ieee80211::StatusCode::NotInSameBss.into(),
1900 }
1901 .as_bytes_ref(),
1902 elements: &[][..],
1903 },
1904 )
1905 .await
1906 .expect_err("expected failure processing association response frame");
1907
1908 let msg = m
1910 .fake_device_state
1911 .lock()
1912 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1913 .expect("no message");
1914 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::NotInSameBss));
1915 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1916 }
1917
1918 #[fuchsia::test(allow_stalls = false)]
1919 async fn associating_failure_due_to_incompatibility() {
1920 let mut m = MockObjects::new().await;
1921 let mut ctx = m.make_ctx_with_bss().await;
1922 let mut sta = make_client_station();
1923 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1924
1925 let mut state = Associating::default();
1926
1927 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1928 state
1929 .on_assoc_resp_frame(
1930 &mut sta,
1931 mac::AssocRespFrame {
1932 assoc_resp_hdr: mac::AssocRespHdr {
1933 aid: 42,
1934 capabilities: mac::CapabilityInfo(52),
1935 status_code: fidl_ieee80211::StatusCode::Success.into(),
1936 }
1937 .as_bytes_ref(),
1938 elements: fake_bss_description!(Wpa2, rates: vec![0x81]).ies(),
1939 },
1940 )
1941 .await
1942 .expect_err("expected failure processing association response frame");
1943
1944 let msg = m
1946 .fake_device_state
1947 .lock()
1948 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1949 .expect("no message");
1950 assert_eq!(
1951 msg,
1952 connect_conf_failure(fidl_ieee80211::StatusCode::RefusedCapabilitiesMismatch)
1953 );
1954 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1955 }
1956
1957 #[fuchsia::test(allow_stalls = false)]
1958 async fn associating_deauth_frame() {
1959 let mut m = MockObjects::new().await;
1960 let mut ctx = m.make_ctx_with_bss().await;
1961 let mut sta = make_client_station();
1962 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1963
1964 let mut state = Associating::default();
1965
1966 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1967 state
1968 .on_deauth_frame(
1969 &mut sta,
1970 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1971 )
1972 .await;
1973
1974 let msg = m
1976 .fake_device_state
1977 .lock()
1978 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
1979 .expect("no message");
1980 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
1981 assert!(m.fake_device_state.lock().join_bss_request.is_none());
1982 }
1983
1984 #[fuchsia::test(allow_stalls = false)]
1985 async fn associating_disassociation() {
1986 let mut m = MockObjects::new().await;
1987 let mut ctx = m.make_ctx_with_bss().await;
1988 let mut sta = make_client_station();
1989 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
1990
1991 let mut state = Associating::default();
1992
1993 assert!(m.fake_device_state.lock().join_bss_request.is_some());
1994 state.on_disassoc_frame(
1995 &mut sta,
1996 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
1997 );
1998
1999 let msg = m
2001 .fake_device_state
2002 .lock()
2003 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2004 .expect("no message");
2005 assert_eq!(msg, connect_conf_failure(fidl_ieee80211::StatusCode::SpuriousDeauthOrDisassoc));
2006 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2007 }
2008
2009 fn mock_rx_info<'a>(client: &BoundClient<'a, FakeDevice>) -> fidl_softmac::WlanRxInfo {
2010 let channel = client.channel_state.get_main_channel().unwrap();
2011 MockWlanRxInfo::with_channel(channel).into()
2012 }
2013
2014 #[fuchsia::test(allow_stalls = false)]
2015 async fn associated_block_ack_frame() {
2016 let mut mock = MockObjects::new().await;
2017 let mut ctx = mock.make_ctx().await;
2018 let mut station = make_client_station();
2019 let mut client = station.bind(&mut ctx, &mut mock.scanner, &mut mock.channel_state);
2020
2021 let frame = {
2022 let mut buffer = [0u8; ADDBA_REQ_FRAME_LEN];
2023 let writer = BufferWriter::new(&mut buffer[..]);
2024 let mut writer = append_frame_to!(
2025 writer,
2026 {
2027 headers: {
2028 mac::MgmtHdr: &mgmt_writer::mgmt_hdr_from_ap(
2029 mac::FrameControl(0)
2030 .with_frame_type(mac::FrameType::MGMT)
2031 .with_mgmt_subtype(mac::MgmtSubtype::ACTION),
2032 client.sta.iface_mac,
2033 client.sta.bssid(),
2034 mac::SequenceControl(0)
2035 .with_seq_num(client.ctx.seq_mgr.next_sns1(&client.sta.bssid().into()) as u16),
2036 ),
2037 },
2038 }
2039 )
2040 .unwrap();
2041 write_addba_req_body(&mut writer, 1).unwrap();
2042 buffer
2043 };
2044
2045 let state = States::from(statemachine::testing::new_state(Associated(empty_association(
2046 &mut client,
2047 ))));
2048 let rx_info = mock_rx_info(&client);
2049 match state.on_mac_frame(&mut client, &frame[..], rx_info, 0.into()).await {
2050 States::Associated(state) => {
2051 let (_, associated) = state.release_data();
2052 match *associated.0.block_ack_state.as_ref() {
2059 BlockAckState::Closed(_) => {}
2060 _ => panic!("client has transitioned BlockAck"),
2061 }
2062 }
2063 _ => panic!("client no longer associated"),
2064 }
2065 }
2066
2067 #[fuchsia::test(allow_stalls = false)]
2068 async fn associated_deauth_frame() {
2069 let mut m = MockObjects::new().await;
2070 let mut ctx = m.make_ctx_with_bss().await;
2071 let mut sta = make_client_station();
2072 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2073 let mut state = Associated(empty_association(&mut sta));
2074
2075 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2076
2077 sta.ctx
2079 .device
2080 .notify_association_complete(fake_assoc_cfg())
2081 .await
2082 .expect("valid assoc_cfg should succeed");
2083 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2084
2085 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2086 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2087
2088 let _joined = state
2089 .on_deauth_frame(
2090 &mut sta,
2091 &mac::DeauthHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2092 )
2093 .await;
2094
2095 let msg = m
2097 .fake_device_state
2098 .lock()
2099 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
2100 .expect("no message");
2101 assert_eq!(
2102 msg,
2103 fidl_mlme::DeauthenticateIndication {
2104 peer_sta_address: BSSID.to_array(),
2105 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2106 locally_initiated: false,
2107 }
2108 );
2109 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2111 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2112 }
2113
2114 #[fuchsia::test(allow_stalls = false)]
2115 async fn associated_disassociation() {
2116 let mut m = MockObjects::new().await;
2117 let mut ctx = m.make_ctx_with_bss().await;
2118 let mut sta = make_client_station();
2119 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2120 let mut state = Associated(empty_association(&mut sta));
2121
2122 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2123 state.0.controlled_port_open = true;
2124
2125 sta.ctx
2126 .device
2127 .notify_association_complete(fake_assoc_cfg())
2128 .await
2129 .expect("valid assoc_cfg should succeed");
2130 assert_eq!(1, m.fake_device_state.lock().assocs.len());
2131
2132 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
2133 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
2134
2135 let _authenticated = state
2136 .on_disassoc_frame(
2137 &mut sta,
2138 &mac::DisassocHdr { reason_code: fidl_ieee80211::ReasonCode::ApInitiated.into() },
2139 )
2140 .await;
2141
2142 let msg = m
2144 .fake_device_state
2145 .lock()
2146 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2147 .expect("no message");
2148 assert_eq!(
2149 msg,
2150 fidl_mlme::DisassociateIndication {
2151 peer_sta_address: BSSID.to_array(),
2152 reason_code: fidl_ieee80211::ReasonCode::ApInitiated,
2153 locally_initiated: false,
2154 }
2155 );
2156
2157 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
2159 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2160 }
2161
2162 #[fuchsia::test(allow_stalls = false)]
2163 async fn associated_move_data_closed_controlled_port() {
2164 let mut m = MockObjects::new().await;
2165 let mut ctx = m.make_ctx().await;
2166 let mut sta = make_client_station();
2167 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2168 let state = Associated(empty_association(&mut sta));
2169
2170 let bytes = make_data_frame_single_llc(None, None);
2171 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2172 state.on_data_frame(&mut sta, data_frame, 0.into());
2173
2174 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2176 }
2177
2178 #[fuchsia::test(allow_stalls = false)]
2179 async fn associated_move_data_opened_controlled_port() {
2180 let mut m = MockObjects::new().await;
2181 let mut ctx = m.make_ctx().await;
2182 let mut sta = make_client_station();
2183 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2184 let state =
2185 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2186
2187 let bytes = make_data_frame_single_llc(None, None);
2188 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2189 state.on_data_frame(&mut sta, data_frame, 0.into());
2190
2191 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
2193 #[rustfmt::skip]
2194 assert_eq!(m.fake_device_state.lock().eth_queue[0], [
2195 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 9, 10, 11, 11, 11, ]);
2200 }
2201
2202 #[fuchsia::test(allow_stalls = false)]
2203 async fn associated_skip_empty_data() {
2204 let mut m = MockObjects::new().await;
2205 let mut ctx = m.make_ctx().await;
2206 let mut sta = make_client_station();
2207 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2208 let state =
2209 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2210
2211 let bytes = make_data_frame_single_llc_payload(None, None, &[]);
2212 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2213 state.on_data_frame(&mut sta, data_frame, 0.into());
2214
2215 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2217 }
2218
2219 #[test_case(true, true; "port open and protected")]
2220 #[test_case(false, true; "port closed and protected")]
2221 #[test_case(true, false; "port open and unprotected")]
2222 #[test_case(false, false; "port closed and unprotected (not a typical state)")]
2223 #[fuchsia::test(allow_stalls = false)]
2224 async fn associated_send_keep_alive_after_null_data_frame(
2225 controlled_port_open: bool,
2226 protected: bool,
2227 ) {
2228 let mut m = MockObjects::new().await;
2229 let mut ctx = m.make_ctx().await;
2230 let mut sta =
2231 if protected { make_protected_client_station() } else { make_client_station() };
2232 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2233 let state = Associated(Association { controlled_port_open, ..empty_association(&mut sta) });
2234
2235 let bytes = make_null_data_frame();
2236 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2237 state.on_data_frame(&mut sta, data_frame, 0.into());
2238
2239 assert!(m.fake_device_state.lock().eth_queue.is_empty());
2241 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2242 let bytes = &m.fake_device_state.lock().wlan_queue[0].0;
2243 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2244 let frame_ctrl = data_frame.frame_ctrl();
2245 assert_eq!(frame_ctrl.to_ds(), true);
2246 assert_eq!(frame_ctrl.from_ds(), false);
2247 assert!(data_frame.body.is_empty());
2248 }
2249
2250 #[fuchsia::test(allow_stalls = false)]
2251 async fn associated_handle_eapol_closed_controlled_port() {
2252 let mut m = MockObjects::new().await;
2253 let mut ctx = m.make_ctx().await;
2254 let mut sta = make_protected_client_station();
2255 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2256 let state = Associated(empty_association(&mut sta));
2257
2258 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2259 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2260 state.on_data_frame(&mut sta, data_frame, 0.into());
2261
2262 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2264
2265 let eapol_ind = m
2267 .fake_device_state
2268 .lock()
2269 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2270 .expect("error reading EAPOL.indication");
2271 assert_eq!(
2272 eapol_ind,
2273 fidl_mlme::EapolIndication {
2274 src_addr: src_addr.to_array(),
2275 dst_addr: dst_addr.to_array(),
2276 data: EAPOL_PDU.to_vec()
2277 }
2278 );
2279 }
2280
2281 #[fuchsia::test(allow_stalls = false)]
2282 async fn associated_handle_eapol_open_controlled_port() {
2283 let mut m = MockObjects::new().await;
2284 let mut ctx = m.make_ctx().await;
2285 let mut sta = make_protected_client_station();
2286 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2287 let state = Associated(empty_association(&mut sta));
2288
2289 let (src_addr, dst_addr, bytes) = make_eapol_frame(*IFACE_MAC);
2290 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2291 state.on_data_frame(&mut sta, data_frame, 0.into());
2292
2293 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2295
2296 let eapol_ind = m
2298 .fake_device_state
2299 .lock()
2300 .next_mlme_msg::<fidl_mlme::EapolIndication>()
2301 .expect("error reading EAPOL.indication");
2302 assert_eq!(
2303 eapol_ind,
2304 fidl_mlme::EapolIndication {
2305 src_addr: src_addr.to_array(),
2306 dst_addr: dst_addr.to_array(),
2307 data: EAPOL_PDU.to_vec()
2308 }
2309 );
2310 }
2311
2312 #[fuchsia::test(allow_stalls = false)]
2313 async fn associated_handle_amsdus_open_controlled_port() {
2314 let mut m = MockObjects::new().await;
2315 let mut ctx = m.make_ctx().await;
2316 let mut sta = make_protected_client_station();
2317 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2318 let state =
2319 Associated(Association { controlled_port_open: true, ..empty_association(&mut sta) });
2320
2321 let bytes = make_data_frame_amsdu();
2322 let data_frame = mac::DataFrame::parse(bytes.as_slice(), false).unwrap();
2323 state.on_data_frame(&mut sta, data_frame, 0.into());
2324
2325 let queue = &m.fake_device_state.lock().eth_queue;
2326 assert_eq!(queue.len(), 2);
2327 #[rustfmt::skip]
2328 let mut expected_first_eth_frame = vec![
2329 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, 0x08, 0x00, ];
2333 expected_first_eth_frame.extend_from_slice(MSDU_1_PAYLOAD);
2334 assert_eq!(queue[0], &expected_first_eth_frame[..]);
2335 #[rustfmt::skip]
2336 let mut expected_second_eth_frame = vec![
2337 0x78, 0x8a, 0x20, 0x0d, 0x67, 0x04, 0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, 0x08, 0x01, ];
2341 expected_second_eth_frame.extend_from_slice(MSDU_2_PAYLOAD);
2342 assert_eq!(queue[1], &expected_second_eth_frame[..]);
2343 }
2344
2345 #[fuchsia::test(allow_stalls = false)]
2346 async fn associated_request_bu_data_frame() {
2347 let mut m = MockObjects::new().await;
2348 let mut ctx = m.make_ctx().await;
2349 let mut sta = make_client_station();
2350 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2351 let state = Associated(Association {
2352 aid: 42,
2353 controlled_port_open: true,
2354 ..empty_association(&mut sta)
2355 });
2356
2357 let mut bytes = make_data_frame_single_llc(None, None);
2358 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2359 data_frame.fixed_fields.frame_ctrl =
2360 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2361 state.on_data_frame(&mut sta, data_frame, 0.into());
2362
2363 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2364 #[rustfmt::skip]
2365 assert_eq!(&m
2366 .fake_device_state.lock().wlan_queue[0].0[..], &[
2367 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2373 }
2374
2375 #[fuchsia::test(allow_stalls = false)]
2376 async fn associated_request_bu_mgmt_frame() {
2377 let mut m = MockObjects::new().await;
2378 let mut ctx = m.make_ctx().await;
2379 let mut sta = make_client_station();
2380 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2381 let state = Associated(Association {
2382 aid: 42,
2383 controlled_port_open: true,
2384 ..empty_association(&mut sta)
2385 });
2386
2387 state.on_any_mgmt_frame(
2388 &mut sta,
2389 &mac::MgmtHdr {
2390 frame_ctrl: mac::FrameControl(0)
2391 .with_frame_type(mac::FrameType::MGMT)
2392 .with_mgmt_subtype(mac::MgmtSubtype::BEACON)
2393 .with_more_data(true),
2394 duration: 0,
2395 addr1: [3; 6].into(),
2396 addr2: (*BSSID).into(),
2397 addr3: (*BSSID).into(),
2398 seq_ctrl: mac::SequenceControl(0),
2399 },
2400 );
2401
2402 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2403 #[rustfmt::skip]
2404 assert_eq!(&m
2405 .fake_device_state.lock().wlan_queue[0].0[..], &[
2406 0b10100100, 0b00000000, 42, 0b11_000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]);
2412 }
2413
2414 #[fuchsia::test(allow_stalls = false)]
2415 async fn associated_no_bu_request() {
2416 let mut m = MockObjects::new().await;
2417 let mut ctx = m.make_ctx().await;
2418 let mut sta = make_client_station();
2419 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2420
2421 let state = Associated(empty_association(&mut sta));
2423 let mut bytes = make_data_frame_single_llc(None, None);
2424 let mut data_frame = mac::DataFrame::parse(bytes.as_mut_slice(), false).unwrap();
2425 data_frame.fixed_fields.frame_ctrl =
2426 data_frame.fixed_fields.frame_ctrl.with_more_data(true);
2427 state.on_data_frame(&mut sta, data_frame, 0.into());
2428 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2429
2430 let state = States::from(statemachine::testing::new_state(Associated(Association {
2432 controlled_port_open: true,
2433 ..empty_association(&mut sta)
2434 })));
2435 #[rustfmt::skip]
2436 let beacon = vec![
2437 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, ];
2446 let rx_info = mock_rx_info(&sta);
2447 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
2448 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
2449 }
2450
2451 #[fuchsia::test(allow_stalls = false)]
2452 async fn associated_drop_foreign_data_frames() {
2453 let mut m = MockObjects::new().await;
2454 let mut ctx = m.make_ctx().await;
2455 let mut sta = make_client_station();
2456 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2457
2458 let state = States::from(statemachine::testing::new_state(Associated(Association {
2460 aid: 42,
2461 controlled_port_open: true,
2462 ..empty_association(&mut sta)
2463 })));
2464 let fc = mac::FrameControl(0)
2465 .with_frame_type(mac::FrameType::DATA)
2466 .with_data_subtype(mac::DataSubtype(0))
2467 .with_from_ds(true);
2468 let fc = fc.0.to_le_bytes();
2469 let bytes = vec![
2471 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,
2484 ];
2485 let rx_info = mock_rx_info(&sta);
2486 state.on_mac_frame(&mut sta, &bytes[..], rx_info, 0.into()).await;
2487 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2488 }
2489
2490 #[fuchsia::test(allow_stalls = false)]
2491 async fn state_transitions_joined_state_reconnect_denied() {
2492 let mut m = MockObjects::new().await;
2493 let mut ctx = m.make_ctx().await;
2494 let mut sta = make_client_station();
2495 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2496 let mut state = States::from(statemachine::testing::new_state(Joined));
2497
2498 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2500 peer_sta_address: [1, 2, 3, 4, 5, 6],
2501 });
2502 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2503
2504 assert_variant!(state, States::Joined(_), "not in joined state");
2505
2506 let msg = m
2508 .fake_device_state
2509 .lock()
2510 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2511 .expect("expect msg");
2512 assert_eq!(
2513 msg,
2514 fidl_mlme::ConnectConfirm {
2515 peer_sta_address: [1, 2, 3, 4, 5, 6],
2516 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2517 association_id: 0,
2518 association_ies: vec![],
2519 }
2520 );
2521 }
2522
2523 #[fuchsia::test(allow_stalls = false)]
2524 async fn state_transitions_authing_success() {
2525 let mut m = MockObjects::new().await;
2526 let mut ctx = m.make_ctx().await;
2527 let mut sta = make_client_station();
2528 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2529 let mut state =
2530 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2531
2532 #[rustfmt::skip]
2534 let auth_resp_success = vec![
2535 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, ];
2547 let rx_info = mock_rx_info(&sta);
2548 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2549 assert_variant!(state, States::Associating(_), "not in associating state");
2550 }
2551
2552 #[fuchsia::test(allow_stalls = false)]
2553 async fn state_transitions_authing_failure() {
2554 let mut m = MockObjects::new().await;
2555 let mut ctx = m.make_ctx_with_bss().await;
2556 let mut sta = make_client_station();
2557 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2558 let mut state =
2559 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2560
2561 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2562 #[rustfmt::skip]
2564 let auth_resp_failure = vec![
2565 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, ];
2577 let rx_info = mock_rx_info(&sta);
2578 state = state.on_mac_frame(&mut sta, &auth_resp_failure[..], rx_info, 0.into()).await;
2579 assert_variant!(state, States::Joined(_), "not in joined state");
2580 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2581 }
2582
2583 #[fuchsia::test(allow_stalls = false)]
2584 async fn state_transitions_authing_deauth() {
2585 let mut m = MockObjects::new().await;
2586 let mut ctx = m.make_ctx_with_bss().await;
2587 let mut sta = make_client_station();
2588 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2589 let mut state =
2590 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2591
2592 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2593 #[rustfmt::skip]
2595 let deauth = vec![
2596 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, ];
2606 let rx_info = mock_rx_info(&sta);
2607 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2608 assert_variant!(state, States::Joined(_), "not in joined state");
2609 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2610 }
2611
2612 #[fuchsia::test(allow_stalls = false)]
2613 async fn state_transitions_foreign_auth_resp() {
2614 let mut m = MockObjects::new().await;
2615 let mut ctx = m.make_ctx().await;
2616 let mut sta = make_client_station();
2617 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2618 let mut state =
2619 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2620
2621 #[rustfmt::skip]
2623 let auth_resp_success = vec![
2624 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, ];
2636 let rx_info = mock_rx_info(&sta);
2637 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2638 assert_variant!(state, States::Authenticating(_), "not in authenticating state");
2639
2640 #[rustfmt::skip]
2643 let auth_resp_success = vec![
2644 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, ];
2656 let rx_info = mock_rx_info(&sta);
2657 state = state.on_mac_frame(&mut sta, &auth_resp_success[..], rx_info, 0.into()).await;
2658 assert_variant!(state, States::Associating(_), "not in associating state");
2659 }
2660
2661 #[fuchsia::test(allow_stalls = false)]
2662 async fn state_transitions_authing_state_reconnect_denied() {
2663 let mut m = MockObjects::new().await;
2664 let mut ctx = m.make_ctx().await;
2665 let mut sta = make_client_station();
2666 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2667 let mut state =
2668 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2669
2670 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2672 peer_sta_address: [1, 2, 3, 4, 5, 6],
2673 });
2674 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2675
2676 assert_variant!(state, States::Authenticating(_), "not in authenticating state");
2677
2678 let msg = m
2680 .fake_device_state
2681 .lock()
2682 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2683 .expect("expect msg");
2684 assert_eq!(
2685 msg,
2686 fidl_mlme::ConnectConfirm {
2687 peer_sta_address: [1, 2, 3, 4, 5, 6],
2688 result_code: fidl_ieee80211::StatusCode::DeniedNoAssociationExists,
2689 association_id: 0,
2690 association_ies: vec![],
2691 }
2692 );
2693 }
2694
2695 #[fuchsia::test(allow_stalls = false)]
2696 async fn state_transitions_authing_state_wrong_algorithm() {
2697 let mut m = MockObjects::new().await;
2698 let mut ctx = m.make_ctx().await;
2699 let mut sta = make_client_station();
2700 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2701 let mut state =
2702 States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
2703
2704 #[rustfmt::skip]
2705 let auth_resp_wrong = vec![
2706 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, ];
2718 let rx_info = mock_rx_info(&sta);
2719 state = state.on_mac_frame(&mut sta, &auth_resp_wrong[..], rx_info, 0.into()).await;
2720 assert_variant!(state, States::Joined(_), "not in joined state");
2721 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2722 }
2723
2724 #[fuchsia::test(allow_stalls = false)]
2725 async fn state_transitions_associng_success() {
2726 let mut m = MockObjects::new().await;
2727 let mut ctx = m.make_ctx().await;
2728 let mut sta = make_client_station();
2729 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2730 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2731
2732 #[rustfmt::skip]
2734 let assoc_resp_success = vec![
2735 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,
2749 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2753 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2757 let rx_info = mock_rx_info(&sta);
2758 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2759 assert_variant!(state, States::Associated(_), "not in associated state");
2760 }
2761
2762 #[fuchsia::test(allow_stalls = false)]
2763 async fn state_transitions_associng_failure() {
2764 let mut m = MockObjects::new().await;
2765 let mut ctx = m.make_ctx_with_bss().await;
2766 let mut sta = make_client_station();
2767 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2768 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2769
2770 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2771 #[rustfmt::skip]
2773 let assoc_resp_failure = vec![
2774 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, ];
2786 let rx_info = mock_rx_info(&sta);
2787 state = state.on_mac_frame(&mut sta, &assoc_resp_failure[..], rx_info, 0.into()).await;
2788 assert_variant!(state, States::Joined(_), "not in joined state");
2789 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2790 }
2791
2792 #[fuchsia::test(allow_stalls = false)]
2793 async fn state_transitions_associng_deauthing() {
2794 let mut m = MockObjects::new().await;
2795 let mut ctx = m.make_ctx_with_bss().await;
2796 let mut sta = make_client_station();
2797 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2798 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2799
2800 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2801 #[rustfmt::skip]
2803 let deauth = vec![
2804 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, ];
2814 let rx_info = mock_rx_info(&sta);
2815 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
2816 assert_variant!(state, States::Joined(_), "not in joined state");
2817 assert!(m.fake_device_state.lock().join_bss_request.is_none());
2818 }
2819
2820 #[fuchsia::test(allow_stalls = false)]
2821 async fn state_transitions_associng_reconnect_no_op() {
2822 let mut m = MockObjects::new().await;
2823 let mut ctx = m.make_ctx_with_bss().await;
2824 let mut sta = make_client_station();
2825 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2826 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2827
2828 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2829 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2831 peer_sta_address: BSSID.to_array(),
2832 });
2833 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2834 assert_variant!(state, States::Associating(_), "not in associating state");
2835 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2836
2837 m.fake_device_state
2839 .lock()
2840 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2841 .expect_err("unexpected Connect.confirm");
2842 }
2843
2844 #[fuchsia::test(allow_stalls = false)]
2845 async fn state_transitions_associng_reconnect_denied() {
2846 let mut m = MockObjects::new().await;
2847 let mut ctx = m.make_ctx_with_bss().await;
2848 let mut sta = make_client_station();
2849 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2850 let mut state = States::from(statemachine::testing::new_state(Associating::default()));
2851
2852 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2853 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
2855 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2856 peer_sta_address: sus_bssid,
2857 });
2858 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2859 assert_variant!(state, States::Associating(_), "not in associating state");
2860 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2861
2862 let connect_conf = m
2864 .fake_device_state
2865 .lock()
2866 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2867 .expect("error reading Connect.confirm");
2868 assert_eq!(
2869 connect_conf,
2870 fidl_mlme::ConnectConfirm {
2871 peer_sta_address: sus_bssid,
2872 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
2873 association_id: 0,
2874 association_ies: vec![],
2875 }
2876 );
2877 }
2878
2879 #[fuchsia::test(allow_stalls = false)]
2880 async fn state_transitions_assoced_disassoc_connect_success() {
2881 let mut m = MockObjects::new().await;
2882 let mut ctx = m.make_ctx_with_bss().await;
2883 let mut sta = make_client_station();
2884 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2885 let mut state =
2886 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2887
2888 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2889 #[rustfmt::skip]
2891 let disassoc = vec![
2892 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, ];
2902 let rx_info = mock_rx_info(&sta);
2903 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
2904 assert_variant!(state, States::Authenticated(_), "not in auth'd state");
2905 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2906
2907 let disassoc_ind = m
2909 .fake_device_state
2910 .lock()
2911 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2912 .expect("error reading Disassociate.ind");
2913 assert_eq!(
2914 disassoc_ind,
2915 fidl_mlme::DisassociateIndication {
2916 peer_sta_address: BSSID.to_array(),
2917 reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
2918 locally_initiated: false,
2919 }
2920 );
2921
2922 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
2924 peer_sta_address: BSSID.to_array(),
2925 });
2926 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
2927 assert_variant!(state, States::Associating(_), "not in associating state");
2928
2929 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
2931 assert_eq!(
2932 &m.fake_device_state.lock().wlan_queue[0].0[..22],
2933 &[
2934 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, 6, 6, 6, 6, 6, 6, ][..]
2941 );
2942
2943 #[rustfmt::skip]
2945 let assoc_resp_success = vec![
2946 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,
2960 0x2d, 0x1a, 0xef, 0x09, 0x17, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2964 0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, 0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, ];
2968 let rx_info = mock_rx_info(&sta);
2969 state = state.on_mac_frame(&mut sta, &assoc_resp_success[..], rx_info, 0.into()).await;
2970 assert_variant!(state, States::Associated(_), "not in associated state");
2971
2972 let connect_conf = m
2974 .fake_device_state
2975 .lock()
2976 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
2977 .expect("error reading Connect.confirm");
2978 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
2979 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
2980 assert_eq!(connect_conf.association_id, 11);
2981 }
2982
2983 #[fuchsia::test(allow_stalls = false)]
2984 async fn state_transitions_assoced_disassoc_reconnect_timeout() {
2985 let mut m = MockObjects::new().await;
2986 let mut ctx = m.make_ctx_with_bss().await;
2987 let mut sta = make_client_station();
2988 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
2989 let mut state =
2990 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
2991
2992 assert!(m.fake_device_state.lock().join_bss_request.is_some());
2993 #[rustfmt::skip]
2995 let disassoc = vec![
2996 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, ];
3006 let rx_info = mock_rx_info(&sta);
3007 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3008 assert_variant!(state, States::Authenticated(_), "not in auth'd state");
3009 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3010
3011 let _disassoc_ind = m
3013 .fake_device_state
3014 .lock()
3015 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3016 .expect("error reading Disassociate.ind");
3017
3018 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3020 peer_sta_address: BSSID.to_array(),
3021 });
3022 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3023 assert_variant!(state, States::Associating(_), "not in associating state");
3024
3025 let (event, _id) = assert_variant!(drain_timeouts(&mut m.time_stream).get(&TimedEventClass::Reassociating), Some(ids) => {
3027 assert_eq!(ids.len(), 1);
3028 ids[0].clone()
3029 });
3030
3031 let state = state.on_timed_event(&mut sta, event).await;
3033 assert_variant!(state, States::Authenticated(_), "not in auth'd state");
3034 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3035
3036 let connect_conf = m
3038 .fake_device_state
3039 .lock()
3040 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3041 .expect("error reading Connect.confirm");
3042 assert_eq!(
3043 connect_conf,
3044 fidl_mlme::ConnectConfirm {
3045 peer_sta_address: BSSID.to_array(),
3046 result_code: fidl_ieee80211::StatusCode::RejectedSequenceTimeout,
3047 association_id: 0,
3048 association_ies: vec![],
3049 }
3050 );
3051 }
3052
3053 #[fuchsia::test(allow_stalls = false)]
3054 async fn state_transitions_assoced_disassoc_reconnect_denied() {
3055 let mut m = MockObjects::new().await;
3056 let mut ctx = m.make_ctx_with_bss().await;
3057 let mut sta = make_client_station();
3058 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3059 let mut state =
3060 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3061
3062 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3063 #[rustfmt::skip]
3065 let disassoc = vec![
3066 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, ];
3076 let rx_info = mock_rx_info(&sta);
3077 state = state.on_mac_frame(&mut sta, &disassoc[..], rx_info, 0.into()).await;
3078 assert_variant!(state, States::Authenticated(_), "not in auth'd state");
3079 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3080
3081 let _disassoc_ind = m
3083 .fake_device_state
3084 .lock()
3085 .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
3086 .expect("error reading Disassociate.ind");
3087
3088 let sus_bssid = [b's', b'u', b's', b'r', b'e', b'q'];
3090 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3091 peer_sta_address: sus_bssid,
3092 });
3093 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3094 assert_variant!(state, States::Authenticated(_), "not in auth'd state");
3095
3096 let connect_conf = m
3098 .fake_device_state
3099 .lock()
3100 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3101 .expect("error reading Connect.confirm");
3102 assert_eq!(
3103 connect_conf,
3104 fidl_mlme::ConnectConfirm {
3105 peer_sta_address: sus_bssid,
3106 result_code: fidl_ieee80211::StatusCode::NotInSameBss,
3107 association_id: 0,
3108 association_ies: vec![],
3109 }
3110 );
3111 }
3112
3113 #[fuchsia::test(allow_stalls = false)]
3114 async fn state_transitions_assoced_reconnect_no_op() {
3115 let mut m = MockObjects::new().await;
3116 let mut ctx = m.make_ctx_with_bss().await;
3117 let mut sta = make_client_station();
3118 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3119 let association = Association {
3120 aid: 42,
3121 assoc_resp_ies: vec![
3122 0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
3124 ],
3125 ..empty_association(&mut sta)
3126 };
3127 let mut state = States::from(statemachine::testing::new_state(Associated(association)));
3128
3129 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3130
3131 let reconnect_req = wlan_sme::MlmeRequest::Reconnect(fidl_mlme::ReconnectRequest {
3133 peer_sta_address: BSSID.to_array(),
3134 });
3135 state = state.handle_mlme_req(&mut sta, reconnect_req).await;
3136 assert_variant!(state, States::Associated(_), "not in associated state");
3137 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3138
3139 let connect_conf = m
3141 .fake_device_state
3142 .lock()
3143 .next_mlme_msg::<fidl_mlme::ConnectConfirm>()
3144 .expect("error reading Connect.confirm");
3145 assert_eq!(&connect_conf.peer_sta_address, BSSID.as_array());
3146 assert_eq!(connect_conf.result_code, fidl_ieee80211::StatusCode::Success);
3147 assert_eq!(connect_conf.association_id, 42);
3148 }
3149
3150 #[fuchsia::test(allow_stalls = false)]
3151 async fn state_transitions_assoced_deauthing() {
3152 let mut m = MockObjects::new().await;
3153 let mut ctx = m.make_ctx_with_bss().await;
3154 let mut sta = make_client_station();
3155 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3156 let mut state =
3157 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3158
3159 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3160 #[rustfmt::skip]
3162 let deauth = vec![
3163 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, ];
3173 let rx_info = mock_rx_info(&sta);
3174 state = state.on_mac_frame(&mut sta, &deauth[..], rx_info, 0.into()).await;
3175 assert_variant!(state, States::Joined(_), "not in joined state");
3176 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3177 }
3178
3179 #[test_case(false, false; "unprotected bss, not scanning")]
3180 #[test_case(true, false; "protected bss, not scanning")]
3181 #[test_case(false, true; "unprotected bss, scanning")]
3182 #[test_case(true, true; "protected bss, scanning")]
3183 #[fuchsia::test(allow_stalls = false)]
3184 async fn assoc_send_eth_frame_becomes_data_frame(protected: bool, scanning: bool) {
3185 let mut m = MockObjects::new().await;
3186 let mut ctx = m.make_ctx().await;
3187 let mut sta =
3188 if protected { make_protected_client_station() } else { make_client_station() };
3189 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3190 let state = States::from(statemachine::testing::new_state(Associated(Association {
3191 controlled_port_open: true,
3192 ..empty_association(&mut sta)
3193 })));
3194
3195 if scanning {
3196 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3197 bound_scanner
3198 .on_sme_scan(fidl_mlme::ScanRequest {
3199 txn_id: 1337,
3200 scan_type: fidl_mlme::ScanTypes::Passive,
3201 channel_list: vec![1],
3202 ssid_list: vec![],
3203 probe_delay: 0,
3204 min_channel_time: 100,
3205 max_channel_time: 300,
3206 })
3207 .await
3208 .expect("Failed to start scan");
3209 assert!(sta.scanner.is_scanning());
3210 }
3211
3212 let eth_frame = [
3213 1, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 3, 0x0d, 0x05, 21, 22, 23, 24, 25, 26, 27, 28, 29, ];
3219
3220 state.on_eth_frame(&mut sta, ð_frame[..], 0.into()).expect("all good");
3221
3222 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3223 let (data_frame, _tx_flags) = m.fake_device_state.lock().wlan_queue.remove(0);
3224 let mut fc_byte_2 = 0b00000001;
3225 if protected {
3226 fc_byte_2 |= 0b01000000;
3227 }
3228 assert_eq!(
3229 &data_frame[..],
3230 &[
3231 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, ][..]
3245 )
3246 }
3247
3248 #[fuchsia::test(allow_stalls = false)]
3249 async fn eth_frame_dropped_when_off_channel() {
3250 let mut m = MockObjects::new().await;
3251 let mut ctx = m.make_ctx().await;
3252 let mut sta = make_client_station();
3253 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3254 let state =
3255 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3256
3257 sta.ctx
3258 .device
3259 .set_channel(fidl_common::WlanChannel {
3260 primary: 42,
3261 cbw: fidl_common::ChannelBandwidth::Cbw20,
3262 secondary80: 0,
3263 })
3264 .await
3265 .expect("fake device is obedient");
3266 let eth_frame = &[100; 14]; let error = state
3269 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3270 .expect_err("Ethernet frame is dropped when client is off channel");
3271 assert_variant!(error, Error::Status(_str, status) =>
3272 assert_eq!(status, zx::Status::BAD_STATE),
3273 "error should contain a status"
3274 );
3275 }
3276
3277 #[fuchsia::test(allow_stalls = false)]
3278 async fn assoc_eth_frame_too_short_dropped() {
3279 let mut m = MockObjects::new().await;
3280 let mut ctx = m.make_ctx().await;
3281 let mut sta = make_client_station();
3282 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3283 let state =
3284 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3285
3286 let eth_frame = &[100; 13]; let error = state
3289 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3290 .expect_err("Ethernet frame is too short");
3291 assert_variant!(error, Error::Status(_str, status) =>
3292 assert_eq!(status, zx::Status::IO_DATA_INTEGRITY),
3293 "error should contain a status"
3294 );
3295 }
3296
3297 #[fuchsia::test(allow_stalls = false)]
3298 async fn assoc_controlled_port_closed_eth_frame_dropped() {
3299 let mut m = MockObjects::new().await;
3300 let mut ctx = m.make_ctx().await;
3301 let mut sta = make_client_station();
3302 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3303 let state =
3304 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3305
3306 let eth_frame = &[100; 14]; let error = state
3309 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3310 .expect_err("Ethernet frame canot be sent when controlled port is closed");
3311 assert_variant!(error, Error::Status(_str, status) =>
3312 assert_eq!(status, zx::Status::BAD_STATE),
3313 "Error should contain status"
3314 );
3315 }
3316
3317 #[fuchsia::test(allow_stalls = false)]
3318 async fn not_assoc_eth_frame_dropped() {
3319 let mut m = MockObjects::new().await;
3320 let mut ctx = m.make_ctx().await;
3321 let mut sta = make_client_station();
3322 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3323 let state = States::from(statemachine::testing::new_state(Joined));
3324
3325 let eth_frame = &[100; 14]; let error = state
3328 .on_eth_frame(&mut sta, ð_frame[..], 0.into())
3329 .expect_err("Ethernet frame cannot be sent in Joined state");
3330 assert_variant !(error, Error::Status(_str, status) =>
3331 assert_eq!(status, zx::Status::BAD_STATE),
3332 "Error should contain status"
3333 );
3334 }
3335
3336 #[fuchsia::test(allow_stalls = false)]
3337 async fn joined_sme_deauth() {
3338 let mut m = MockObjects::new().await;
3339 let mut ctx = m.make_ctx_with_bss().await;
3340 let mut sta = make_client_station();
3341 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3342 let state = States::from(statemachine::testing::new_state(Joined));
3343
3344 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3345 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3346 assert_variant!(state, States::Joined(_), "Joined should stay in Joined");
3347 m.fake_device_state
3349 .lock()
3350 .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
3351 .expect_err("should be no outgoing message");
3352 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3353 }
3354
3355 #[fuchsia::test(allow_stalls = false)]
3356 async fn authenticating_sme_deauth() {
3357 let mut m = MockObjects::new().await;
3358 let mut ctx = m.make_ctx_with_bss().await;
3359 let mut sta = make_client_station();
3360 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3361 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3362
3363 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3364 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3365
3366 assert_variant!(state, States::Joined(_), "should transition to Joined");
3367
3368 m.fake_device_state
3370 .lock()
3371 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3372 .expect_err("should not see more MLME messages");
3373 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3374 }
3375
3376 #[fuchsia::test(allow_stalls = false)]
3377 async fn associating_sme_deauth() {
3378 let mut m = MockObjects::new().await;
3379 let mut ctx = m.make_ctx_with_bss().await;
3380 let mut sta = make_client_station();
3381 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3382 let state = States::from(statemachine::testing::new_state(Associating::default()));
3383
3384 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3385 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3386
3387 assert_variant!(state, States::Joined(_), "should transition to Joined");
3388
3389 m.fake_device_state
3391 .lock()
3392 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3393 .expect_err("should not see more MLME messages");
3394 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3395 }
3396
3397 #[fuchsia::test(allow_stalls = false)]
3398 async fn associated_sme_deauth() {
3399 let mut m = MockObjects::new().await;
3400 let mut ctx = m.make_ctx_with_bss().await;
3401 let mut sta = make_client_station();
3402 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3403 let state = States::from(statemachine::testing::new_state(Associated(Association {
3404 controlled_port_open: true,
3405 ..empty_association(&mut sta)
3406 })));
3407
3408 sta.ctx
3409 .device
3410 .notify_association_complete(fake_assoc_cfg())
3411 .await
3412 .expect("valid assoc ctx should not fail");
3413 assert_eq!(1, m.fake_device_state.lock().assocs.len());
3414
3415 assert!(m.fake_device_state.lock().join_bss_request.is_some());
3416 sta.ctx.device.set_ethernet_up().await.expect("should succeed");
3417 assert_eq!(crate::device::LinkStatus::UP, m.fake_device_state.lock().link_status);
3418
3419 let state = state.handle_mlme_req(&mut sta, fake_deauth_req()).await;
3420 assert_variant!(state, States::Joined(_), "should transition to Joined");
3421
3422 let deauth_conf = m
3424 .fake_device_state
3425 .lock()
3426 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3427 .expect("should see deauth conf");
3428 assert_eq!(
3429 deauth_conf,
3430 fidl_mlme::DeauthenticateConfirm { peer_sta_address: BSSID.to_array() }
3431 );
3432 m.fake_device_state
3433 .lock()
3434 .next_mlme_msg::<fidl_mlme::DeauthenticateConfirm>()
3435 .expect_err("should not see more MLME messages");
3436 assert_eq!(0, m.fake_device_state.lock().assocs.len());
3438 assert_eq!(crate::device::LinkStatus::DOWN, m.fake_device_state.lock().link_status);
3440 assert!(m.fake_device_state.lock().join_bss_request.is_none());
3441 }
3442
3443 fn fake_eapol_req() -> wlan_sme::MlmeRequest {
3444 wlan_sme::MlmeRequest::Eapol(fidl_mlme::EapolRequest {
3445 dst_addr: BSSID.to_array(),
3446 src_addr: IFACE_MAC.to_array(),
3447 data: vec![1, 2, 3, 4],
3448 })
3449 }
3450
3451 #[allow(deprecated)]
3452 #[fuchsia::test(allow_stalls = false)]
3453 async fn mlme_eapol_not_associated() {
3454 let mut m = MockObjects::new().await;
3455 let mut ctx = m.make_ctx().await;
3456 let mut sta = make_protected_client_station();
3457 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3458
3459 let state = States::from(statemachine::testing::new_state(Joined));
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(open_authenticating(&mut sta)));
3464 m.fake_device_state.lock().wlan_queue.clear();
3465 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3466 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3467
3468 let state = States::from(statemachine::testing::new_state(Associating::default()));
3469 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3470 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3471 }
3472
3473 #[allow(deprecated)]
3474 #[fuchsia::test(allow_stalls = false)]
3475 async fn mlme_eapol_associated_not_protected() {
3476 let mut m = MockObjects::new().await;
3477 let mut ctx = m.make_ctx().await;
3478 let mut sta = make_client_station();
3479 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3480
3481 let state =
3482 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3483 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3484 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3485 }
3486
3487 #[allow(deprecated)]
3488 #[fuchsia::test(allow_stalls = false)]
3489 async fn mlme_eapol_associated() {
3490 let mut m = MockObjects::new().await;
3491 let mut ctx = m.make_ctx().await;
3492 let mut sta = make_protected_client_station();
3493 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3494
3495 let state =
3496 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3497 let _state = state.handle_mlme_req(&mut sta, fake_eapol_req()).await;
3498 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3499 assert_eq!(
3500 &m.fake_device_state.lock().wlan_queue[0].0[..],
3501 &[
3502 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, ][..]
3515 );
3516 }
3517
3518 #[allow(deprecated)]
3519 #[fuchsia::test(allow_stalls = false)]
3520 async fn mlme_set_keys_not_associated() {
3521 let mut m = MockObjects::new().await;
3522 let mut ctx = m.make_ctx().await;
3523 let mut sta = make_protected_client_station();
3524 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3525
3526 let state = States::from(statemachine::testing::new_state(Joined));
3527 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3528 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3529
3530 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3531 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3532 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3533
3534 let state = States::from(statemachine::testing::new_state(Associating::default()));
3535 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3536 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3537 }
3538
3539 #[allow(deprecated)]
3540 #[fuchsia::test(allow_stalls = false)]
3541 async fn mlme_set_keys_associated_not_protected() {
3542 let mut m = MockObjects::new().await;
3543 let mut ctx = m.make_ctx().await;
3544 let mut sta = make_client_station();
3545 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3546
3547 let state =
3548 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3549 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3550 assert_eq!(m.fake_device_state.lock().keys.len(), 0);
3551 }
3552
3553 #[allow(deprecated)]
3554 #[fuchsia::test(allow_stalls = false)]
3555 async fn mlme_set_keys_associated() {
3556 let mut m = MockObjects::new().await;
3557 let mut ctx = m.make_ctx().await;
3558 let mut sta = make_protected_client_station();
3559 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3560
3561 let state =
3562 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3563 let _state = state.handle_mlme_req(&mut sta, fake_set_keys_req((*BSSID).into())).await;
3564 assert_eq!(m.fake_device_state.lock().keys.len(), 1);
3565 let conf = assert_variant!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3566 assert_eq!(conf.results.len(), 1);
3567 assert_eq!(
3568 conf.results[0],
3569 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::OK.into_raw() }
3570 );
3571
3572 assert_eq!(
3573 m.fake_device_state.lock().keys,
3574 vec![fidl_softmac::WlanKeyConfiguration {
3575 protection: Some(fidl_softmac::WlanProtection::RxTx),
3576 cipher_oui: Some([1, 2, 3]),
3577 cipher_type: Some(4),
3578 key_type: Some(fidl_ieee80211::KeyType::Pairwise),
3579 peer_addr: Some((*BSSID).to_array()),
3580 key_idx: Some(6),
3581 key: Some(vec![1, 2, 3, 4, 5, 6, 7]),
3582 rsc: Some(8),
3583 ..Default::default()
3584 }]
3585 );
3586 }
3587
3588 #[allow(deprecated)]
3589 #[fuchsia::test(allow_stalls = false)]
3590 async fn mlme_set_keys_failure() {
3591 let mut m = MockObjects::new().await;
3592 let mut ctx = m.make_ctx().await;
3593 let mut sta = make_protected_client_station();
3594 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3595
3596 let state =
3597 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3598 m.fake_device_state.lock().install_key_results.push_back(Err(zx::Status::BAD_STATE));
3599 m.fake_device_state.lock().install_key_results.push_back(Ok(()));
3600 let mut set_keys_req = fake_set_keys_req((*BSSID).into());
3602 match &mut set_keys_req {
3603 wlan_sme::MlmeRequest::SetKeys(req) => {
3604 req.keylist
3605 .push(fidl_mlme::SetKeyDescriptor { key_id: 4, ..req.keylist[0].clone() });
3606 }
3607 _ => panic!(),
3608 }
3609 let _state = state.handle_mlme_req(&mut sta, set_keys_req).await;
3610 let conf = assert_variant!(m.fake_device_state.lock().next_mlme_msg::<fidl_mlme::SetKeysConfirm>(), Ok(conf) => conf);
3611 assert_eq!(conf.results.len(), 2);
3612 assert_eq!(
3613 conf.results[0],
3614 fidl_mlme::SetKeyResult { key_id: 6, status: zx::Status::BAD_STATE.into_raw() }
3615 );
3616 assert_eq!(
3617 conf.results[1],
3618 fidl_mlme::SetKeyResult { key_id: 4, status: zx::Status::OK.into_raw() }
3619 );
3620 }
3621
3622 fn fake_set_ctrl_port_open(open: bool) -> wlan_sme::MlmeRequest {
3623 wlan_sme::MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
3624 peer_sta_address: BSSID.to_array(),
3625 state: match open {
3626 true => fidl_mlme::ControlledPortState::Open,
3627 false => fidl_mlme::ControlledPortState::Closed,
3628 },
3629 })
3630 }
3631
3632 #[fuchsia::test(allow_stalls = false)]
3633 async fn mlme_set_controlled_port_not_associated() {
3634 let mut m = MockObjects::new().await;
3635 let mut ctx = m.make_ctx().await;
3636 let mut sta = make_protected_client_station();
3637 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3638
3639 let state = States::from(statemachine::testing::new_state(Joined));
3640 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3641 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3642
3643 let state = States::from(statemachine::testing::new_state(open_authenticating(&mut sta)));
3644 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3645 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3646
3647 let state = States::from(statemachine::testing::new_state(Associating::default()));
3648 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3649 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3650 }
3651
3652 #[fuchsia::test(allow_stalls = false)]
3653 async fn mlme_set_controlled_port_associated_not_protected() {
3654 let mut m = MockObjects::new().await;
3655 let mut ctx = m.make_ctx().await;
3656 let mut sta = make_client_station();
3657 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3658
3659 let state =
3660 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3661 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3662 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3663 }
3664
3665 #[fuchsia::test(allow_stalls = false)]
3666 async fn mlme_set_controlled_port_associated() {
3667 let mut m = MockObjects::new().await;
3668 let mut ctx = m.make_ctx().await;
3669 let mut sta = make_protected_client_station();
3670 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3671
3672 let state =
3673 States::from(statemachine::testing::new_state(Associated(empty_association(&mut sta))));
3674 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3675 let state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(true)).await;
3676 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::UP);
3677 let _state = state.handle_mlme_req(&mut sta, fake_set_ctrl_port_open(false)).await;
3678 assert_eq!(m.fake_device_state.lock().link_status, crate::device::LinkStatus::DOWN);
3679 }
3680
3681 #[test_case(true; "while scanning")]
3682 #[test_case(false; "while not scanning")]
3683 #[fuchsia::test(allow_stalls = false)]
3684 async fn associated_rx_succeeds(scanning: bool) {
3685 let mut m = MockObjects::new().await;
3686 let mut ctx = m.make_ctx().await;
3687 let mut sta = make_client_station();
3688 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3689 let state = States::from(statemachine::testing::new_state(Associated(Association {
3690 aid: 1,
3691 controlled_port_open: true,
3692 ..empty_association(&mut sta)
3693 })));
3694
3695 if scanning {
3696 let mut bound_scanner = sta.scanner.bind(sta.ctx);
3697 bound_scanner
3698 .on_sme_scan(fidl_mlme::ScanRequest {
3699 txn_id: 1337,
3700 scan_type: fidl_mlme::ScanTypes::Passive,
3701 channel_list: vec![1],
3702 ssid_list: vec![],
3703 probe_delay: 0,
3704 min_channel_time: 100,
3705 max_channel_time: 300,
3706 })
3707 .await
3708 .expect("Failed to start scan");
3709 assert!(sta.scanner.is_scanning());
3710 }
3711
3712 let fc = mac::FrameControl(0)
3713 .with_frame_type(mac::FrameType::DATA)
3714 .with_data_subtype(mac::DataSubtype(0))
3715 .with_from_ds(true);
3716 let fc = fc.0.to_le_bytes();
3717
3718 let data_frame = vec![
3719 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, ];
3732
3733 let rx_info = mock_rx_info(&sta);
3734 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3735 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3736 }
3737
3738 #[fuchsia::test(allow_stalls = false)]
3739 async fn associated_rx_with_wrong_cbw_succeeds() {
3740 let mut m = MockObjects::new().await;
3741 let mut ctx = m.make_ctx().await;
3742 let mut sta = make_client_station();
3743 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3744 let state = States::from(statemachine::testing::new_state(Associated(Association {
3745 aid: 1,
3746 controlled_port_open: true,
3747 ..empty_association(&mut sta)
3748 })));
3749
3750 let fc = mac::FrameControl(0)
3751 .with_frame_type(mac::FrameType::DATA)
3752 .with_data_subtype(mac::DataSubtype(0))
3753 .with_from_ds(true);
3754 let fc = fc.0.to_le_bytes();
3755
3756 let data_frame = vec![
3757 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, ];
3770
3771 let mut rx_info = mock_rx_info(&sta);
3772 rx_info.channel.cbw = fidl_common::ChannelBandwidth::Cbw80;
3775 state.on_mac_frame(&mut sta, &data_frame[..], rx_info, 0.into()).await;
3776 assert_eq!(m.fake_device_state.lock().eth_queue.len(), 1);
3777 }
3778
3779 #[fuchsia::test(allow_stalls = false)]
3780 async fn associated_request_bu_if_tim_indicates_buffered_frame() {
3781 let mut m = MockObjects::new().await;
3782 let mut ctx = m.make_ctx().await;
3783 let mut sta = make_client_station();
3784 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3785 let state = States::from(statemachine::testing::new_state(Associated(Association {
3786 aid: 1,
3787 ..empty_association(&mut sta)
3788 })));
3789
3790 let beacon = [
3791 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, ];
3804
3805 let rx_info = mock_rx_info(&sta);
3806 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3807
3808 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 1);
3809 assert_eq!(
3810 &m.fake_device_state.lock().wlan_queue[0].0[..],
3811 &[
3812 0b10100100, 0, 1, 0b11000000, 6, 6, 6, 6, 6, 6, 3, 3, 3, 3, 3, 3, ][..]
3817 );
3818 }
3819
3820 #[fuchsia::test(allow_stalls = false)]
3821 async fn associated_does_not_request_bu_if_tim_indicates_no_buffered_frame() {
3822 let mut m = MockObjects::new().await;
3823 let mut ctx = m.make_ctx().await;
3824 let mut sta = make_client_station();
3825 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3826 let state = States::from(statemachine::testing::new_state(Associated(Association {
3827 aid: 1,
3828 ..empty_association(&mut sta)
3829 })));
3830
3831 let beacon = [
3832 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, ];
3845 let rx_info = mock_rx_info(&sta);
3846 state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3847
3848 assert_eq!(m.fake_device_state.lock().wlan_queue.len(), 0);
3849 }
3850
3851 fn rx_info_with_dbm<'a>(
3852 client: &BoundClient<'a, FakeDevice>,
3853 rssi_dbm: i8,
3854 ) -> fidl_softmac::WlanRxInfo {
3855 let mut rx_info = fidl_softmac::WlanRxInfo { rssi_dbm, ..mock_rx_info(client) };
3856 rx_info.valid_fields |= fidl_softmac::WlanRxInfoValid::RSSI;
3857 rx_info
3858 }
3859
3860 #[fuchsia::test(allow_stalls = false)]
3861 async fn signal_report() {
3862 let mut m = MockObjects::new().await;
3863 let mut ctx = m.make_ctx().await;
3864 let mut sta = make_protected_client_station();
3865 let mut sta = sta.bind(&mut ctx, &mut m.scanner, &mut m.channel_state);
3866
3867 let state = States::from(State::from(statemachine::testing::new_state(Associated(
3868 empty_association(&mut sta),
3869 ))));
3870
3871 let (_, timed_event, _) =
3872 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3873 let state = state.on_timed_event(&mut sta, timed_event.event).await;
3874
3875 let signal_ind = m
3876 .fake_device_state
3877 .lock()
3878 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3879 .expect("should see a signal report");
3880
3881 assert_eq!(signal_ind.rssi_dbm, -128);
3883
3884 let beacon = [
3885 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, ];
3897
3898 const EXPECTED_DBM: i8 = -32;
3899 let rx_info = rx_info_with_dbm(&sta, EXPECTED_DBM);
3900 let state = state.on_mac_frame(&mut sta, &beacon[..], rx_info, 0.into()).await;
3901
3902 let (_, timed_event, _) =
3903 m.time_stream.try_next().unwrap().expect("Should have scheduled signal report timeout");
3904 let _state = state.on_timed_event(&mut sta, timed_event.event).await;
3905
3906 let signal_ind = m
3907 .fake_device_state
3908 .lock()
3909 .next_mlme_msg::<fidl_internal::SignalReportIndication>()
3910 .expect("should see a signal report");
3911
3912 assert_eq!(signal_ind.rssi_dbm, EXPECTED_DBM);
3913 }
3914}