wlan_mlme/client/
state.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A state machine for associating a Client to a BSS.
6//! Note: This implementation only supports simultaneous authentication with exactly one STA, the
7//! AP. While 802.11 explicitly allows - and sometime requires - authentication with more than one
8//! STA, Fuchsia does intentionally not yet support this use-case.
9
10use 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
36/// Reconnect timeout in Beacon periods.
37/// If no association response was received from the BSS within this time window, an association is
38/// considered to have failed.
39const RECONNECT_TIMEOUT_BCN_PERIODS: u16 = 10;
40
41/// Number of beacon intervals which beacon is not seen before we declare BSS as lost
42pub const DEFAULT_AUTO_DEAUTH_TIMEOUT_BEACON_COUNT: u32 = 100;
43
44/// Number of beacon intervals between association status check (signal report or auto-deatuh).
45pub const ASSOCIATION_STATUS_TIMEOUT_BEACON_COUNT: u32 = 10;
46
47/// Client joined a BSS (synchronized timers and prepared its underlying hardware).
48/// At this point the Client is able to listen to frames on the BSS' channel.
49#[derive(Debug)]
50pub struct Joined;
51
52impl Joined {
53    /// Initiates an open authentication with the currently joined BSS.
54    /// The returned state is unchanged in an error case. Otherwise, the state transitions into
55    /// "Authenticating".
56    /// Returns Ok(AkmAlgorithm) if authentication request was sent successfully, Err(()) otherwise.
57    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/// Client issued an authentication request frame to its joined BSS prior to joining this state.
109/// At this point the client is waiting for an authentication response frame from the client.
110/// Note: This assumes Open System authentication.
111#[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                // TODO(https://fxbug.dev/42164548): pass the status code from the original auth frame
135                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                // TODO(https://fxbug.dev/42164548): pass the status code from the original auth frame
145                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    /// Initiates an association with the currently joined BSS.
156    /// Returns Ok(()) if association request was sent successfully.
157    /// Otherwise an Err(()) is returned and a CONNECT.confirm message to its SME peer.
158    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    /// Processes an inbound authentication frame.
166    /// SME will be notified via a CONNECT.confirm message if there's a failure.
167    /// Returns AuthProgress::Complete if the authentication was successful and
168    //  association has started.
169    /// Returns AuthProgress::InProgress if authentication is still ongoing.
170    /// Returns AuthProgress::Failed if failed to authenticate or start association request.
171    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    /// Processes an SAE response from SME.
183    /// This indicates that an SAE handshake has completed, successful or otherwise.
184    /// On success, authentication is complete and association has started.
185    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    /// Processes a request from SME to transmit an SAE authentication frame to a peer.
195    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    /// Processes an inbound deauthentication frame.
206    /// This always results in an MLME-AUTHENTICATE.confirm message to MLME's SME peer.
207    /// The pending authentication timeout will be canceled in this process.
208    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                    // Setting timeout in term of beacon period allows us to adjust the realtime
248                    // timeout in hw-sim, where we set a longer duration in case of a slowbot.
249                    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/// Client has sent an association request frame to the AP.
270/// At this point, client is waiting for an association response frame from the AP.
271#[derive(Debug, Default)]
272pub struct Associating {
273    /// This field is only populated when MLME is reconnecting after a disassociation.
274    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    /// Processes an inbound association response frame.
283    /// SME will be notified via an MLME-ASSOCIATE.confirm message whether the association
284    /// with the BSS was successful.
285    /// Returns Ok(()) if the association was successful, otherwise Err(()).
286    /// Note: The pending authentication timeout will be canceled in any case.
287    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        // TODO(https://fxbug.dev/42172907): All reserved values mapped to REFUSED_REASON_UNSPECIFIED.
295        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                    // This is unlikely to happen with any spec-compliant AP. In case the
321                    // user somehow decided to connect to a malicious AP, reject and reset.
322                    // Log at ERROR level to raise visibility of when this event occurs.
323                    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        // TODO(https://fxbug.dev/42104064): Determine for each outbound data frame,
347        // given the result of the dynamic capability negotiation, data frame
348        // classification, and QoS policy.
349        //
350        // Aruba / Ubiquiti are confirmed to be compatible with QoS field for the
351        // BlockAck session, independently of 40MHz operation.
352        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            // In the association request we sent out earlier, listen_interval is always set to 0,
358            // indicating the client never enters power save mode.
359            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            // Device cannot handle this association. Something is seriously wrong.
374            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                // TODO(https://fxbug.dev/42175857) - Consider returning an Err here.
385                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    /// Processes an inbound disassociation frame.
411    /// Note: APs should never send disassociation frames without having established a valid
412    /// association with the Client. However, to maximize interoperability disassociation frames
413    /// are handled in this state as well and treated similar to unsuccessful association responses.
414    /// This always results in an MLME-ASSOCIATE.confirm message to MLME's SME peer.
415    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    /// Processes an inbound deauthentication frame.
426    /// This always results in an MLME-ASSOCIATE.confirm message to MLME's SME peer.
427    /// The pending association timeout will be canceled in this process.
428    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
451/// Extract HT Operation and VHT Operation IEs from the association response frame.
452/// If either IE is of an incorrect length, it will be ignored.
453fn 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    /// Represents an 802.1X controlled port.
523    /// A closed controlled port only processes EAP frames while an open one processes any frames.
524    pub controlled_port_open: bool,
525
526    // TODO(https://fxbug.dev/335283785): Remove or explain unused code.
527    #[allow(dead_code)]
528    pub ap_ht_op: Option<ie::HtOperation>,
529    // TODO(https://fxbug.dev/335283785): Remove or explain unused code.
530    #[allow(dead_code)]
531    pub ap_vht_op: Option<ie::VhtOperation>,
532
533    /// Whether to set QoS bit when MLME constructs an outgoing WLAN data frame.
534    /// Currently, QoS is enabled if the associated PHY is HT or VHT.
535    pub qos: Qos,
536
537    /// `lost_bss_counter` is used to determine if the BSS is still alive nearby. It is started
538    /// when the client is associated.
539    pub lost_bss_counter: LostBssCounter,
540
541    /// |timeout| is the timeout that is scheduled for the association status check, which includes
542    /// a) sending signal strength report to SME and b) triggering auto-deauth if necessary.
543    /// It will be cancelled when the client go off-channel for scanning and scheduled again when
544    /// back on channel.
545    pub status_check_timeout: StatusCheckTimeout,
546    pub signal_strength_average: SignalStrengthAverage,
547
548    // TODO(https://fxbug.dev/335283785): Remove or explain unused code.
549    #[allow(dead_code)]
550    pub block_ack_state: StateMachine<BlockAckState>,
551}
552
553/// Client received a "successful" association response from the BSS.
554#[derive(Debug)]
555pub struct Associated(pub Association);
556
557impl Associated {
558    /// Processes an inbound disassociation frame.
559    /// This always results in an MLME-DISASSOCIATE.indication message to MLME's SME peer.
560    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    /// Sends an MLME-DEAUTHENTICATE.indication message to MLME's SME peer.
573    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    /// Process every inbound management frame before its being handed off to a more specific
588    /// handler.
589    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    /// Sends PS-POLL requests if the FrameControl's more_data bit is set, and the received frame
598    /// was addressed for this STA. No-op if the controlled port is closed.
599    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        // IEEE Std. 802.11-2016, 9.2.4.1.8
609        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    /// Process and inbound beacon frame.
620    /// Resets LostBssCounter, check buffered frame if available.
621    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        // TODO(b/253637931): Add metrics to track channel switch counts and success rates.
630        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    /// Extracts aggregated and non-aggregated MSDUs from the data frame.
652    /// Handles all data subtypes.
653    /// EAPoL MSDUs are forwarded to SME via an MLME-EAPOL.indication message independent of the
654    /// STA's current controlled port status.
655    /// All other MSDUs are converted into Ethernet II frames and forwarded via the device to
656    /// Fuchsia's Netstack if the STA's controlled port is open.
657    /// NULL-Data frames are interpreted as "Keep Alive" requests and responded with NULL data
658    /// frames if the STA's controlled port is open.
659    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        // Handle NULL data frames independently of the controlled port's status.
676        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        // Handle aggregated and non-aggregated MSDUs.
683        for msdu in data_frame {
684            wtrace::duration_begin!(MSDU_TRACE_NAME);
685
686            match msdu.llc_frame.hdr.protocol_id.to_native() {
687                // Forward EAPoL frames to SME independently of the controlled port's status.
688                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                // Deliver non-EAPoL MSDUs only if the controlled port is open.
706                _ 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                // Drop all non-EAPoL MSDUs if the controlled port is closed.
721                _ => {
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        // TODO(https://fxbug.dev/42104687): Handle BlockAck frames. The following code has been disabled as a
774        //                        fix for https://fxbug.dev/42180615. Without this code, the BlockAck state
775        //                        machine is dormant and, importantly, never transmits BlockAck
776        //                        frames.
777        //self.0.block_ack_state.replace_state(|state| state.on_block_ack_frame(sta, action, body));
778    }
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        // Drop EAPoL frame if it is not a protected network.
807        if !sta.sta.eapol_required() {
808            error!("Unexpected MLME-EAPOL.request message: BSS not protected");
809            return;
810        }
811        // There may be more EAPoL frames (such as key rotation) coming after EAPoL established.
812        // They need to be protected.
813        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    /// Reports average signal strength to SME and check if auto deauthentication is due.
904    /// Returns true if there auto deauthentication is triggered by lack of beacon frames.
905    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            // Always check should_deauthenticate() first since even if Client receives a beacon,
929            // it would still add a full association status check interval to the lost BSS counter.
930            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    /// Client state machine.
940    pub enum States,
941    // Regular successful flow:
942    () => Joined,
943    Joined => Authenticating,
944    Authenticating => Associating,
945    Associating => Associated,
946
947    // Timeout:
948    Authenticating => Joined,
949    Associating => Joined,
950
951    // Deauthentication:
952    Authenticating => Joined,
953    Associating => Joined,
954    Associated => Joined,
955
956    // Disassociation:
957    Associating => Authenticated, // Or failure to (re)associate
958    Associated => Authenticated,
959
960    // Reassociation:
961    Authenticated => Associating,
962);
963
964impl States {
965    /// Returns the STA's initial state.
966    pub fn new_initial() -> States {
967        States::from(State::new(Joined))
968    }
969
970    /// Begin the 802.11 connect operation, starting with authentication.
971    /// This method only has an effect if the initial state is Joined.
972    /// Otherwise it is no-op.
973    pub async fn start_connecting<D: DeviceOps>(self, sta: &mut BoundClient<'_, D>) -> States {
974        match self {
975            States::Joined(state) => {
976                // Setting timeout in term of beacon period allows us to adjust the realtime
977                // timeout in hw-sim, where we set a longer duration in case of a slowbot.
978                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    /// Callback to process arbitrary IEEE 802.11 frames.
995    /// Frames are dropped if:
996    /// - frames are corrupted (too short)
997    /// - frames' frame class is not yet permitted
998    /// - frames are from a foreign BSS
999    /// - frames are unicast but destined for a MAC address that is different from this STA.
1000    // TODO(https://fxbug.dev/42119762): Implement a packet counter and add tests to verify frames are dropped correctly.
1001    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        // Parse mac frame. Drop corrupted ones.
1014        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        // Drop frames which are not permitted in the STA's current state.
1031        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                    // Drop data frames in all other states
1053                    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            // Control frames are not yet supported. Drop them.
1059            _ => {
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    /// Processes inbound management frames.
1068    /// Only frames from the joined BSS are processed. Frames from other STAs are dropped.
1069    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        // Parse management frame. Drop corrupted ones.
1078        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                // This case is highly unlikely and only added to improve interoperability with
1111                // buggy Access Points.
1112                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    /// Callback when a previously scheduled event fired.
1191    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    /// Returns |true| iff a given FrameClass is permitted to be processed in the current state.
1352    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; // Make length shorter
1395        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; // Make VHT operation shorter.
1410        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        // TODO(https://fxbug.dev/327499461): This function is async to ensure MLME functions will
1461        // run in an async context and not call `wlan_common::timer::Timer::now` without an
1462        // executor.
1463        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, //  ID and len
1533                1, 0, //  version
1534                0x00, 0x0f, 0xac, 0x04, //  group data cipher suite
1535                0x01, 0x00, //  pairwise cipher suite count
1536                0x00, 0x0f, 0xac, 0x04, //  pairwise cipher suite list
1537                0x01, 0x00, //  akm suite count
1538                0x00, 0x0f, 0xac, 0x02, //  akm suite list
1539                0xa8, 0x04, //  rsn capabilities
1540            ],
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        // Verify no event was queued up in the timer.
1615        assert!(m.time_stream.try_next().is_err());
1616
1617        // Verify MLME-CONNECT.confirm message was sent.
1618        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            // use an unsupported AuthenticationType
1642            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        // Verify MLME-CONNECT.confirm message was sent.
1656        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        // Verify authentication failed.
1684        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        // Verify MLME-CONNECT.confirm message was sent.
1703        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        // Verify MLME-CONNECT.confirm message was sent.
1737        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        // Verify MLME-CONNECT.confirm message was sent.
1788        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        // Verify association context is set
1842        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        // Verify MLME-CONNECT.confirm message was sent.
1864        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        // Verify authentication was considered successful.
1892        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        // Verify MLME-CONNECT.confirm message was sent.
1909        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        // Verify MLME-CONNECT.confirm message was sent.
1945        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        // Verify MLME-CONNECT.confirm message was sent.
1975        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        // Verify MLME-CONNECT.confirm message was sent.
2000        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                // TODO(https://fxbug.dev/42104687): Handle BlockAck frames. The following code has been
2053                //                        altered as part of a fix for https://fxbug.dev/42180615. This check
2054                //                        should ensure that the state has transitioned to
2055                //                        `Established`, but since the state machine has been
2056                //                        disabled it instead checks that the state has remained
2057                //                        `Closed`.
2058                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        // Association configuration will be cleared when MLME receives deauth frame.
2078        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        // Verify MLME-ASSOCIATE.confirm message was sent.
2096        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        // Verify ethernet port is shut down.
2110        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        // Verify MLME-ASSOCIATE.confirm message was sent.
2143        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        // Verify ethernet port is shut down.
2158        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        // Verify data frame was dropped.
2175        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        // Verify data frame was processed.
2192        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, // dst_addr
2196            4, 4, 4, 4, 4, 4, // src_addr
2197            9, 10, // ether_type
2198            11, 11, 11, // payload
2199        ]);
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        // Verify data frame was discarded.
2216        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        // Verify data frame was not forwarded up.
2240        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        // Verify EAPOL frame was not sent to netstack.
2263        assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2264
2265        // Verify EAPoL frame was sent to SME.
2266        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        // Verify EAPOL frame was not sent to netstack.
2294        assert_eq!(m.fake_device_state.lock().eth_queue.len(), 0);
2295
2296        // Verify EAPoL frame was sent to SME.
2297        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, // dst_addr
2330            0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, // src_addr
2331            0x08, 0x00, // ether_type
2332        ];
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, // dst_addr
2338            0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, // src_addr
2339            0x08, 0x01, // ether_type
2340        ];
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            // Frame Control:
2368            0b10100100, 0b00000000, // FC
2369            42, 0b11_000000, // Id
2370            6, 6, 6, 6, 6, 6, // addr1
2371            3, 3, 3, 3, 3, 3, // addr2
2372        ][..]);
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            // Frame Control:
2407            0b10100100, 0b00000000, // FC
2408            42, 0b11_000000, // Id
2409            6, 6, 6, 6, 6, 6, // addr1
2410            3, 3, 3, 3, 3, 3, // addr2
2411        ][..]);
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        // Closed Controlled port
2422        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        // Foreign management frame
2431        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            // Mgmt Header:
2438            0b1000_00_00, 0b00100000, // Frame Control
2439            0, 0, // Duration
2440            3, 3, 3, 3, 3, 3, // Addr1
2441            7, 7, 7, 7, 7, 7, // Addr2
2442            5, 5, 5, 5, 5, 5, // Addr3
2443            0x10, 0, // Sequence Control
2444            // Omit IEs
2445        ];
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        // Foreign data frame
2459        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        // Send data frame from an address other than the BSSID([6u8; 6]).
2470        let bytes = vec![
2471            // Data Header
2472            fc[0], fc[1], // fc
2473            2, 2, // duration
2474            3, 3, 3, 3, 3, 3, // addr1
2475            4, 4, 4, 4, 4, 4, // addr2
2476            5, 5, 5, 5, 5, 5, // addr3
2477            6, 6, // sequence control
2478            // LLC Header
2479            7, 7, 7, // DSAP, SSAP & control
2480            8, 8, 8, // OUI
2481            9, 10, // eth type
2482            // Trailing bytes
2483            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        // (sme->mlme) Send a reconnect request
2499        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        // Verify MLME-CONNECT.confirm message was sent.
2507        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        // Successful: Joined > Authenticating > Associating
2533        #[rustfmt::skip]
2534        let auth_resp_success = vec![
2535            // Mgmt Header:
2536            0b1011_00_00, 0b00000000, // Frame Control
2537            0, 0, // Duration
2538            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2539            3, 3, 3, 3, 3, 3, // Addr2
2540            6, 6, 6, 6, 6, 6, // Addr3
2541            0x10, 0, // Sequence Control
2542            // Auth Header:
2543            0, 0, // Algorithm Number (Open)
2544            2, 0, // Txn Sequence Number
2545            0, 0, // Status Code
2546        ];
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        // Failure: Joined > Authenticating > Joined
2563        #[rustfmt::skip]
2564        let auth_resp_failure = vec![
2565            // Mgmt Header:
2566            0b1011_00_00, 0b00000000, // Frame Control
2567            0, 0, // Duration
2568            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2569            3, 3, 3, 3, 3, 3, // Addr2
2570            6, 6, 6, 6, 6, 6, // Addr3
2571            0x10, 0, // Sequence Control
2572            // Auth Header:
2573            0, 0, // Algorithm Number (Open)
2574            2, 0, // Txn Sequence Number
2575            42, 0, // Status Code
2576        ];
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        // Deauthenticate: Authenticating > Joined
2594        #[rustfmt::skip]
2595        let deauth = vec![
2596            // Mgmt Header:
2597            0b1100_00_00, 0b00000000, // Frame Control
2598            0, 0, // Duration
2599            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2600            3, 3, 3, 3, 3, 3, // Addr2
2601            6, 6, 6, 6, 6, 6, // Addr3
2602            0x10, 0, // Sequence Control
2603            // Deauth Header:
2604            5, 0, // Algorithm Number (Open)
2605        ];
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        // Send foreign auth response. State should not change.
2622        #[rustfmt::skip]
2623        let auth_resp_success = vec![
2624            // Mgmt Header:
2625            0b1011_00_00, 0b00000000, // Frame Control
2626            0, 0, // Duration
2627            5, 5, 5, 5, 5, 5, // Addr1
2628            3, 3, 3, 3, 3, 3, // Addr2
2629            5, 5, 5, 5, 5, 5, // Addr3
2630            0x10, 0, // Sequence Control
2631            // Auth Header:
2632            0, 0, // Algorithm Number (Open)
2633            2, 0, // Txn Sequence Number
2634            0, 0, // Status Code
2635        ];
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        // Verify that an authentication response from the joined BSS still moves the Client
2641        // forward.
2642        #[rustfmt::skip]
2643        let auth_resp_success = vec![
2644            // Mgmt Header:
2645            0b1011_00_00, 0b00000000, // Frame Control
2646            0, 0, // Duration
2647            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2648            3, 3, 3, 3, 3, 3, // Addr2
2649            6, 6, 6, 6, 6, 6, // Addr3
2650            0x10, 0, // Sequence Control
2651            // Auth Header:
2652            0, 0, // Algorithm Number (Open)
2653            2, 0, // Txn Sequence Number
2654            0, 0, // Status Code
2655        ];
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        // (sme->mlme) Send a reconnect request
2671        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        // Verify MLME-CONNECT.confirm message was sent.
2679        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            // Mgmt Header:
2707            0b1011_00_00, 0b00000000, // Frame Control
2708            0, 0, // Duration
2709            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2710            3, 3, 3, 3, 3, 3, // Addr2
2711            6, 6, 6, 6, 6, 6, // Addr3
2712            0x10, 0, // Sequence Control
2713            // Auth Header:
2714            8, 0, // Algorithm Number (wrong algorithm: SAE)
2715            2, 0, // Txn Sequence Number
2716            0, 0, // Status Code
2717        ];
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        // Successful: Associating > Associated
2733        #[rustfmt::skip]
2734        let assoc_resp_success = vec![
2735            // Mgmt Header:
2736            0b0001_00_00, 0b00000000, // Frame Control
2737            0, 0, // Duration
2738            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2739            3, 3, 3, 3, 3, 3, // Addr2
2740            6, 6, 6, 6, 6, 6, // Addr3
2741            0x10, 0, // Sequence Control
2742            // Assoc Resp Header:
2743            0, 0, // Capabilities
2744            0, 0, // Status Code
2745            0, 0, // AID
2746            // IEs
2747            // Basic Rates
2748            0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
2749            // HT Capabilities
2750            0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
2751            0x17, // A-MPDU parameters
2752            0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2753            // VHT Capabilities
2754            0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
2755            0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
2756        ];
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        // Failure: Associating > Joined
2772        #[rustfmt::skip]
2773        let assoc_resp_failure = vec![
2774            // Mgmt Header:
2775            0b0001_00_00, 0b00000000, // Frame Control
2776            0, 0, // Duration
2777            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2778            3, 3, 3, 3, 3, 3, // Addr2
2779            6, 6, 6, 6, 6, 6, // Addr3
2780            0x10, 0, // Sequence Control
2781            // Assoc Resp Header:
2782            0, 0, // Capabilities
2783            2, 0, // Status Code (Failed)
2784            0, 0, // AID
2785        ];
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        // Deauthentication: Associating > Joined
2802        #[rustfmt::skip]
2803        let deauth = vec![
2804            // Mgmt Header:
2805            0b1100_00_00, 0b00000000, // Frame Control
2806            0, 0, // Duration
2807            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2808            3, 3, 3, 3, 3, 3, // Addr2
2809            6, 6, 6, 6, 6, 6, // Addr3
2810            0x10, 0, // Sequence Control
2811            // Deauth Header:
2812            4, 0, // Reason Code
2813        ];
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        // (sme->mlme) Send a reconnect request
2830        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        // Verify no connect conf is sent
2838        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        // (sme->mlme) Send a reconnect request
2854        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        // Verify a connect conf was sent
2863        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        // Disassociation: Associated > Associating
2890        #[rustfmt::skip]
2891        let disassoc = vec![
2892            // Mgmt Header:
2893            0b1010_00_00, 0b00000000, // Frame Control
2894            0, 0, // Duration
2895            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2896            3, 3, 3, 3, 3, 3, // Addr2
2897            6, 6, 6, 6, 6, 6, // Addr3
2898            0x10, 0, // Sequence Control
2899            // Deauth Header:
2900            4, 0, // Reason Code
2901        ];
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        // Verify a disassoc ind was sent
2908        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        // (sme->mlme) Send a reconnect request
2923        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        // Verify associate request frame was sent
2930        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                // Mgmt header:
2935                0, 0, // FC
2936                0, 0, // Duration
2937                6, 6, 6, 6, 6, 6, // addr1
2938                3, 3, 3, 3, 3, 3, // addr2
2939                6, 6, 6, 6, 6, 6, // addr3
2940            ][..]
2941        );
2942
2943        // Successful: Associating > Associated
2944        #[rustfmt::skip]
2945        let assoc_resp_success = vec![
2946            // Mgmt Header:
2947            0b0001_00_00, 0b00000000, // Frame Control
2948            0, 0, // Duration
2949            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
2950            3, 3, 3, 3, 3, 3, // Addr2
2951            6, 6, 6, 6, 6, 6, // Addr3
2952            0x10, 0, // Sequence Control
2953            // Assoc Resp Header:
2954            0, 0, // Capabilities
2955            0, 0, // Status Code
2956            11, 0, // AID
2957            // IEs
2958            // Basic Rates
2959            0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24,
2960            // HT Capabilities
2961            0x2d, 0x1a, 0xef, 0x09, // HT capabilities info
2962            0x17, // A-MPDU parameters
2963            0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2964            // VHT Capabilities
2965            0xbf, 0x0c, 0x91, 0x59, 0x82, 0x0f, // VHT capabilities info
2966            0xea, 0xff, 0x00, 0x00, 0xea, 0xff, 0x00, 0x00, // VHT supported MCS set
2967        ];
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        // Verify a successful connect conf is sent
2973        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        // Disassociation: Associated > Associating
2994        #[rustfmt::skip]
2995        let disassoc = vec![
2996            // Mgmt Header:
2997            0b1010_00_00, 0b00000000, // Frame Control
2998            0, 0, // Duration
2999            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3000            3, 3, 3, 3, 3, 3, // Addr2
3001            6, 6, 6, 6, 6, 6, // Addr3
3002            0x10, 0, // Sequence Control
3003            // Deauth Header:
3004            4, 0, // Reason Code
3005        ];
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        // Verify a disassoc ind was sent
3012        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        // (sme->mlme) Send a reconnect request
3019        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        // Verify an event was queued up in the timer.
3026        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        // Notify reconnecting timeout
3032        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        // Verify a connect conf was sent
3037        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        // Disassociation: Associated > Associating
3064        #[rustfmt::skip]
3065        let disassoc = vec![
3066            // Mgmt Header:
3067            0b1010_00_00, 0b00000000, // Frame Control
3068            0, 0, // Duration
3069            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3070            3, 3, 3, 3, 3, 3, // Addr2
3071            6, 6, 6, 6, 6, 6, // Addr3
3072            0x10, 0, // Sequence Control
3073            // Deauth Header:
3074            4, 0, // Reason Code
3075        ];
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        // Verify a disassoc ind was sent
3082        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        // (sme->mlme) Send a reconnect request with a different BSSID
3089        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        // Verify a connect conf was sent
3097        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                // Basic Rates
3123                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        // (sme->mlme) Send a reconnect request
3132        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        // Verify a successful connect conf is sent
3140        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        // Deauthentication: Associated > Joined
3161        #[rustfmt::skip]
3162        let deauth = vec![
3163            // Mgmt Header:
3164            0b1100_00_00, 0b00000000, // Frame Control
3165            0, 0, // Duration
3166            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3167            3, 3, 3, 3, 3, 3, // Addr2
3168            6, 6, 6, 6, 6, 6, // Addr3
3169            0x10, 0, // Sequence Control
3170            // Deauth Header:
3171            4, 0, // Reason Code
3172        ];
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, // dst_addr
3214            3, 3, 3, 3, 3, 3, // src_addr == IFACE_MAC
3215            0x0d, 0x05, // ether_type
3216            21, 22, 23, 24, 25, 26, 27, 28, // payload
3217            29, // more payload
3218        ];
3219
3220        state.on_eth_frame(&mut sta, &eth_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                // Data header
3232                0b00001000, fc_byte_2, // Frame Control
3233                0, 0, // Duration
3234                6, 6, 6, 6, 6, 6, // addr1
3235                3, 3, 3, 3, 3, 3, // addr2 (from src_addr above)
3236                1, 2, 3, 4, 5, 6, // addr3 (from dst_addr above)
3237                0x10, 0, // Sequence Control
3238                // LLC header
3239                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
3240                0, 0, 0, // OUI
3241                0x0d, 0x05, // Protocol ID (from ether_type above)
3242                21, 22, 23, 24, 25, 26, 27, 28, // Payload
3243                29, // More payload
3244            ][..]
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]; // An ethernet frame must be at least 14 bytes long.
3267
3268        let error = state
3269            .on_eth_frame(&mut sta, &eth_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]; // Needs at least 14 bytes for header.
3287
3288        let error = state
3289            .on_eth_frame(&mut sta, &eth_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]; // long enough for ethernet header.
3307
3308        let error = state
3309            .on_eth_frame(&mut sta, &eth_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]; // long enough for ethernet header.
3326
3327        let error = state
3328            .on_eth_frame(&mut sta, &eth_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        // No MLME message was sent because MLME already deauthenticated.
3348        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        // No need to notify SME since it already deauthenticated
3369        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        // No need to notify SME since it already deauthenticated
3390        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        // Should accept the deauthentication request and send back confirm.
3423        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        // Verify association context cleared.
3437        assert_eq!(0, m.fake_device_state.lock().assocs.len());
3438        // Verify ethernet link status is down.
3439        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                // Data header (EAPoL frames are data frames)
3503                0b00001000, 0b00000001, // Frame Control
3504                0, 0, // Duration
3505                6, 6, 6, 6, 6, 6, // addr1 - BSSID
3506                3, 3, 3, 3, 3, 3, // addr2 - IFACE_MAC
3507                6, 6, 6, 6, 6, 6, // addr3 - BSSID
3508                0x10, 0, // Sequence Control
3509                // LLC header
3510                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
3511                0, 0, 0, // OUI
3512                0x88, 0x8E, // Protocol ID (EAPoL is 0x888E)
3513                1, 2, 3, 4, // Payload
3514            ][..]
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        // Create a SetKeysReq with one success and one failure.
3601        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            // Data header:
3720            fc[0], fc[1], // FC
3721            0, 0, // Duration
3722            7, 7, 7, 7, 7, 7, // addr1
3723            6, 6, 6, 6, 6, 6, // addr2
3724            42, 42, 42, 42, 42, 42, // addr3
3725            0x10, 0, // Sequence Control
3726            // LLC Header
3727            7, 7, 7, // DSAP, SSAP & control
3728            8, 8, 8, // OUI
3729            9, 10, // eth type
3730            1, 2, 3, 4, 5, 6, 7, 8, // payload
3731        ];
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            // Data header:
3758            fc[0], fc[1], // FC
3759            0, 0, // Duration
3760            7, 7, 7, 7, 7, 7, // addr1
3761            6, 6, 6, 6, 6, 6, // addr2
3762            42, 42, 42, 42, 42, 42, // addr3
3763            0x10, 0, // Sequence Control
3764            // LLC Header
3765            7, 7, 7, // DSAP, SSAP & control
3766            8, 8, 8, // OUI
3767            9, 10, // eth type
3768            1, 2, 3, 4, 5, 6, 7, 8, // payload
3769        ];
3770
3771        let mut rx_info = mock_rx_info(&sta);
3772        // We deliberately ignore the cbw, since it isn't important and not all
3773        // drivers report it consistently.
3774        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            // Mgmt header
3792            0b10000000, 0, // Frame Control
3793            0, 0, // Duration
3794            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3795            7, 7, 7, 7, 7, 7, // Addr2
3796            6, 6, 6, 6, 6, 6, // Addr3
3797            0, 0, // Sequence Control
3798            // Beacon header:
3799            0, 0, 0, 0, 0, 0, 0, 0, // Timestamp
3800            10, 0, // Beacon interval
3801            33, 0, // Capabilities
3802            5, 4, 0, 0, 0, 0b00000010, // Tim IE: bit 1 in the last octet indicates AID 1
3803        ];
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, // Frame control
3813                1, 0b11000000, // ID (2 MSBs are set to 1 from the AID)
3814                6, 6, 6, 6, 6, 6, // BSSID
3815                3, 3, 3, 3, 3, 3, // TA
3816            ][..]
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            // Mgmt header
3833            0b10000000, 0, // Frame Control
3834            0, 0, // Duration
3835            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3836            7, 7, 7, 7, 7, 7, // Addr2
3837            6, 6, 6, 6, 6, 6, // Addr3
3838            0, 0, // Sequence Control
3839            // Beacon header:
3840            0, 0, 0, 0, 0, 0, 0, 0, // Timestamp
3841            10, 0, // Beacon interval
3842            33, 0, // Capabilities
3843            5, 4, 0, 0, 0, 0, // Tim IE: No buffered frame for any client.
3844        ];
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        // -128 is the default value, equivalent to 0 watt.
3882        assert_eq!(signal_ind.rssi_dbm, -128);
3883
3884        let beacon = [
3885            // Mgmt header
3886            0b10000000, 0, // Frame Control
3887            0, 0, // Duration
3888            3, 3, 3, 3, 3, 3, // Addr1 == IFACE_MAC
3889            7, 7, 7, 7, 7, 7, // Addr2
3890            6, 6, 6, 6, 6, 6, // Addr3
3891            0, 0, // Sequence Control
3892            // Beacon header:
3893            0, 0, 0, 0, 0, 0, 0, 0, // Timestamp
3894            10, 0, // Beacon interval
3895            33, 0, // Capabilities
3896        ];
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}