wlan_mlme/ap/
remote_client.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
5use crate::ap::{frame_writer, BufferedFrame, Context, TimedEvent};
6use crate::device::DeviceOps;
7use crate::disconnect::LocallyInitiated;
8use crate::error::Error;
9use fdf::ArenaStaticBox;
10use ieee80211::{MacAddr, MacAddrBytes, Ssid};
11use log::warn;
12use std::collections::VecDeque;
13use wlan_common::append::Append;
14use wlan_common::buffer_writer::BufferWriter;
15use wlan_common::mac::{self, Aid, AuthAlgorithmNumber, FrameClass, ReasonCode};
16use wlan_common::timer::EventHandle;
17use wlan_common::{ie, TimeUnit};
18use wlan_statemachine::StateMachine;
19use zerocopy::SplitByteSlice;
20use {
21    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
22    fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_softmac as fidl_softmac,
23    fuchsia_trace as trace, wlan_trace as wtrace,
24};
25
26/// dot11BssMaxIdlePeriod (IEEE Std 802.11-2016, 11.24.13 and Annex C.3): This attribute indicates
27/// that the number of 1000 TUs that pass before an AP disassociates an inactive non-AP STA. This
28/// value is transmitted via the BSS Max Idle Period element (IEEE Std 802.11-2016, 9.4.2.79) in
29/// Association Response and Reassociation Response frames, which contains a 16-bit integer.
30// TODO(https://fxbug.dev/42113580): Move this setting into the SME.
31const BSS_MAX_IDLE_PERIOD: u16 = 90;
32
33#[derive(Debug)]
34enum PowerSaveState {
35    /// The device is awake.
36    Awake,
37
38    /// The device is dozing.
39    Dozing {
40        /// Buffered frames that will be sent once the device wakes up.
41        buffered: VecDeque<BufferedFrame>,
42    },
43}
44
45/// The MLME state machine. The actual state machine transitions are managed and validated in the
46/// SME: we only use these states to determine when packets can be sent and received.
47#[derive(Debug)]
48enum State {
49    /// An unknown client is initially placed in the |Authenticating| state. A client may remain in
50    /// this state until an MLME-AUTHENTICATE.indication is received, at which point it may either
51    /// move to Authenticated or Deauthenticated.
52    Authenticating,
53
54    /// The client has successfully authenticated.
55    Authenticated,
56
57    /// The client has successfully associated.
58    Associated {
59        /// The association ID.
60        aid: Aid,
61
62        /// The EAPoL controlled port can be in three states:
63        /// - Some(Closed): The EAPoL controlled port is closed. Only unprotected EAPoL frames can
64        ///   be sent.
65        /// - Some(Open): The EAPoL controlled port is open. All frames can be sent, and will be
66        ///   protected.
67        /// - None: There is no EAPoL authentication required, i.e. the network is not an RSN. All
68        ///   frames can be sent, and will NOT be protected.
69        eapol_controlled_port: Option<fidl_mlme::ControlledPortState>,
70
71        /// The current active timeout. Should never be None, except during initialization.
72        active_timeout: Option<EventHandle>,
73
74        /// Power-saving state of the client.
75        ps_state: PowerSaveState,
76    },
77
78    /// This is a terminal state indicating the client cannot progress any further, and should be
79    /// forgotten from the MLME state.
80    Deauthenticated,
81}
82
83impl State {
84    fn max_frame_class(&self) -> FrameClass {
85        match self {
86            State::Deauthenticated | State::Authenticating => FrameClass::Class1,
87            State::Authenticated => FrameClass::Class2,
88            State::Associated { .. } => FrameClass::Class3,
89        }
90    }
91}
92
93pub struct RemoteClient {
94    pub addr: MacAddr,
95    state: StateMachine<State>,
96}
97
98#[derive(Debug)]
99pub enum ClientRejection {
100    /// The frame was not permitted in the client's current state.
101    NotPermitted,
102
103    /// The frame does not have a corresponding handler.
104    Unsupported,
105
106    /// The client is not authenticated.
107    NotAuthenticated,
108
109    /// The client is not associated.
110    NotAssociated,
111
112    /// The EAPoL controlled port is closed.
113    ControlledPortClosed,
114
115    /// The frame could not be parsed.
116    ParseFailed,
117
118    /// A request could not be sent to the SME.
119    SmeSendError(Error),
120
121    /// A request could not be sent to the PHY.
122    WlanSendError(Error),
123
124    /// A request could not be sent to the netstack.
125    EthSendError(Error),
126
127    /// An error occurred on the device.
128    DeviceError(Error),
129}
130
131impl ClientRejection {
132    pub fn log_level(&self) -> log::Level {
133        match self {
134            Self::ParseFailed
135            | Self::SmeSendError(..)
136            | Self::WlanSendError(..)
137            | Self::EthSendError(..) => log::Level::Error,
138            Self::ControlledPortClosed | Self::Unsupported => log::Level::Warn,
139            _ => log::Level::Trace,
140        }
141    }
142}
143
144#[derive(Debug)]
145pub enum ClientEvent {
146    /// This is the timeout that fires after dot11BssMaxIdlePeriod (IEEE Std 802.11-2016, 11.24.13
147    /// and Annex C.3) elapses and no activity was detected, at which point the client is
148    /// disassociated.
149    BssIdleTimeout,
150}
151
152// TODO(https://fxbug.dev/42113580): Implement capability negotiation in MLME-ASSOCIATE.response.
153// TODO(https://fxbug.dev/42113580): Implement action frame handling.
154impl RemoteClient {
155    pub fn new(addr: MacAddr) -> Self {
156        Self { addr, state: StateMachine::new(State::Authenticating) }
157    }
158
159    /// Returns if the client is deauthenticated. The caller should use this to check if the client
160    /// needs to be forgotten from its state.
161    pub fn deauthenticated(&self) -> bool {
162        match self.state.as_ref() {
163            State::Deauthenticated => true,
164            _ => false,
165        }
166    }
167
168    /// Returns the association ID of the client, or None if it is not associated.
169    pub fn aid(&self) -> Option<Aid> {
170        match self.state.as_ref() {
171            State::Associated { aid, .. } => Some(*aid),
172            _ => None,
173        }
174    }
175
176    /// Returns if the client has buffered frames (i.e. dozing and the queue is not empty).
177    pub fn has_buffered_frames(&self) -> bool {
178        match self.state.as_ref() {
179            State::Associated { ps_state: PowerSaveState::Dozing { buffered }, .. } => {
180                !buffered.is_empty()
181            }
182            _ => false,
183        }
184    }
185
186    pub fn dozing(&self) -> bool {
187        match self.state.as_ref() {
188            State::Associated { ps_state: PowerSaveState::Dozing { .. }, .. } => true,
189            _ => false,
190        }
191    }
192
193    async fn change_state<D: DeviceOps>(
194        &mut self,
195        ctx: &mut Context<D>,
196        next_state: State,
197    ) -> Result<(), Error> {
198        match self.state.as_mut() {
199            State::Associated { .. } => {
200                ctx.device
201                    .clear_association(&fidl_softmac::WlanSoftmacBaseClearAssociationRequest {
202                        peer_addr: Some(self.addr.to_array()),
203                        ..Default::default()
204                    })
205                    .await
206                    .map_err(|s| Error::Status(format!("failed to clear association"), s))?;
207            }
208            _ => (),
209        }
210        self.state.replace_state_with(next_state);
211        Ok(())
212    }
213
214    fn schedule_after<D>(
215        &self,
216        ctx: &mut Context<D>,
217        duration: zx::MonotonicDuration,
218        event: ClientEvent,
219    ) -> EventHandle {
220        ctx.schedule_after(duration, TimedEvent::ClientEvent(self.addr, event))
221    }
222
223    fn schedule_bss_idle_timeout<D>(&self, ctx: &mut Context<D>) -> EventHandle {
224        self.schedule_after(
225            ctx,
226            // dot11BssMaxIdlePeriod (IEEE Std 802.11-2016, 11.24.13 and Annex C.3) is measured in
227            // increments of 1000 TUs, with a range from 1-65535. We therefore need do this
228            // conversion to zx::MonotonicDuration in a 64-bit number space to avoid any overflow that might
229            // occur, as 65535 * 1000 > 2^sizeof(TimeUnit).
230            zx::MonotonicDuration::from(TimeUnit(1000)) * (BSS_MAX_IDLE_PERIOD as i64),
231            ClientEvent::BssIdleTimeout,
232        )
233    }
234
235    async fn handle_bss_idle_timeout<D: DeviceOps>(
236        &mut self,
237        ctx: &mut Context<D>,
238    ) -> Result<(), ClientRejection> {
239        match self.state.as_ref() {
240            State::Associated { .. } => {}
241            _ => {
242                // This is not the right state.
243                return Ok(());
244            }
245        }
246
247        self.change_state(ctx, State::Authenticated).await.map_err(ClientRejection::DeviceError)?;
248
249        // On BSS idle timeout, we need to tell the client that they've been disassociated, and the
250        // SME to transition the client to Authenticated.
251        let buffer = ctx
252            .make_disassoc_frame(
253                self.addr.clone(),
254                fidl_ieee80211::ReasonCode::ReasonInactivity.into(),
255            )
256            .map_err(ClientRejection::WlanSendError)?;
257        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None).map_err(
258            |s| {
259                ClientRejection::WlanSendError(Error::Status(
260                    format!("error sending disassoc frame on BSS idle timeout"),
261                    s,
262                ))
263            },
264        )?;
265        ctx.send_mlme_disassoc_ind(
266            self.addr.clone(),
267            fidl_ieee80211::ReasonCode::ReasonInactivity,
268            LocallyInitiated(true),
269        )
270        .map_err(ClientRejection::SmeSendError)?;
271        Ok(())
272    }
273
274    /// Resets the BSS max idle timeout.
275    ///
276    /// If we receive a WLAN frame, we need to reset the clock on disassociating the client after
277    /// timeout.
278    fn reset_bss_max_idle_timeout<D>(&mut self, ctx: &mut Context<D>) {
279        // TODO(https://fxbug.dev/42113580): IEEE Std 802.11-2016, 9.4.2.79 specifies a "Protected Keep-Alive Required"
280        // option that indicates that only a protected frame indicates activity. It is unclear how
281        // this interacts with open networks.
282
283        // We need to do this in two parts: we can't schedule the timeout while also borrowing the
284        // state, because it results in two simultaneous mutable borrows.
285        let new_active_timeout = match self.state.as_ref() {
286            State::Associated { .. } => Some(self.schedule_bss_idle_timeout(ctx)),
287            _ => None,
288        };
289
290        match self.state.as_mut() {
291            State::Associated { active_timeout, .. } => {
292                *active_timeout = new_active_timeout;
293            }
294            _ => (),
295        }
296    }
297
298    fn is_frame_class_permitted(&self, frame_class: FrameClass) -> bool {
299        frame_class <= self.state.as_ref().max_frame_class()
300    }
301
302    pub async fn handle_event<D: DeviceOps>(
303        &mut self,
304        ctx: &mut Context<D>,
305        event: ClientEvent,
306    ) -> Result<(), ClientRejection> {
307        match event {
308            ClientEvent::BssIdleTimeout => self.handle_bss_idle_timeout(ctx).await,
309        }
310    }
311
312    // MLME SAP handlers.
313
314    /// Handles MLME-AUTHENTICATE.response (IEEE Std 802.11-2016, 6.3.5.5) from the SME.
315    ///
316    /// If result_code is Success, the SME will have authenticated this client.
317    ///
318    /// Otherwise, the MLME should forget about this client.
319    pub async fn handle_mlme_auth_resp<D: DeviceOps>(
320        &mut self,
321        ctx: &mut Context<D>,
322        result_code: fidl_mlme::AuthenticateResultCode,
323    ) -> Result<(), Error> {
324        // TODO(https://fxbug.dev/42172646) - Added to help investigate hw-sim test. Remove later
325        log::info!("enter handle_mlme_auth_resp");
326        self.change_state(
327            ctx,
328            if result_code == fidl_mlme::AuthenticateResultCode::Success {
329                State::Authenticated
330            } else {
331                State::Deauthenticated
332            },
333        )
334        .await?;
335
336        // TODO(https://fxbug.dev/42172646) - Added to help investigate hw-sim test. Remove later
337        log::info!("creating auth frame");
338
339        // We only support open system auth in the SME.
340        // IEEE Std 802.11-2016, 12.3.3.2.3 & Table 9-36: Sequence number 2 indicates the response
341        // and final part of Open System authentication.
342        let buffer = ctx.make_auth_frame(
343            self.addr.clone(),
344            AuthAlgorithmNumber::OPEN,
345            2,
346            match result_code {
347                fidl_mlme::AuthenticateResultCode::Success => {
348                    fidl_ieee80211::StatusCode::Success.into()
349                }
350                fidl_mlme::AuthenticateResultCode::Refused => {
351                    fidl_ieee80211::StatusCode::RefusedReasonUnspecified.into()
352                }
353                fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired => {
354                    fidl_ieee80211::StatusCode::AntiCloggingTokenRequired.into()
355                }
356                fidl_mlme::AuthenticateResultCode::FiniteCyclicGroupNotSupported => {
357                    fidl_ieee80211::StatusCode::UnsupportedFiniteCyclicGroup.into()
358                }
359                fidl_mlme::AuthenticateResultCode::AuthenticationRejected => {
360                    fidl_ieee80211::StatusCode::ChallengeFailure.into()
361                }
362                fidl_mlme::AuthenticateResultCode::AuthFailureTimeout => {
363                    fidl_ieee80211::StatusCode::RejectedSequenceTimeout.into()
364                }
365            },
366        )?;
367        // TODO(https://fxbug.dev/42172646) - Added to help investigate hw-sim test. Remove later
368        log::info!("Sending auth frame to driver: {} bytes", buffer.len());
369        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
370            .map_err(|s| Error::Status(format!("error sending auth frame"), s))
371    }
372
373    /// Handles MLME-DEAUTHENTICATE.request (IEEE Std 802.11-2016, 6.3.6.2) from the SME.
374    ///
375    /// The SME has already deauthenticated this client.
376    ///
377    /// After this function is called, the MLME must forget about this client.
378    pub async fn handle_mlme_deauth_req<D: DeviceOps>(
379        &mut self,
380        ctx: &mut Context<D>,
381        reason_code: fidl_ieee80211::ReasonCode,
382    ) -> Result<(), Error> {
383        self.change_state(ctx, State::Deauthenticated).await?;
384
385        // IEEE Std 802.11-2016, 6.3.6.3.3 states that we should send MLME-DEAUTHENTICATE.confirm
386        // to the SME on success. However, our SME only sends MLME-DEAUTHENTICATE.request when it
387        // has already forgotten about the client on its side, so sending
388        // MLME-DEAUTHENTICATE.confirm is redundant.
389
390        let buffer = ctx.make_deauth_frame(self.addr.clone(), reason_code.into())?;
391        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
392            .map_err(|s| Error::Status(format!("error sending deauth frame"), s))
393    }
394
395    /// Handles MLME-ASSOCIATE.response (IEEE Std 802.11-2016, 6.3.7.5) from the SME.
396    ///
397    /// If the result code is Success, the SME will have associated this client.
398    ///
399    /// Otherwise, the SME has not associated this client. However, the SME has not forgotten about
400    /// the client either until MLME-DEAUTHENTICATE.request is received.
401    pub async fn handle_mlme_assoc_resp<D: DeviceOps>(
402        &mut self,
403        ctx: &mut Context<D>,
404        is_rsn: bool,
405        channel: u8,
406        capabilities: mac::CapabilityInfo,
407        result_code: fidl_mlme::AssociateResultCode,
408        aid: Aid,
409        rates: &[u8],
410    ) -> Result<(), Error> {
411        self.change_state(
412            ctx,
413            if result_code == fidl_mlme::AssociateResultCode::Success {
414                State::Associated {
415                    aid,
416                    eapol_controlled_port: if is_rsn {
417                        Some(fidl_mlme::ControlledPortState::Closed)
418                    } else {
419                        None
420                    },
421                    active_timeout: None,
422                    ps_state: PowerSaveState::Awake,
423                }
424            } else {
425                State::Authenticated
426            },
427        )
428        .await?;
429
430        if let State::Associated { .. } = self.state.as_ref() {
431            // Reset the client's activeness as soon as it is associated, kicking off the BSS max
432            // idle timer.
433            self.reset_bss_max_idle_timeout(ctx);
434            ctx.device
435                .notify_association_complete(fidl_softmac::WlanAssociationConfig {
436                    bssid: Some(self.addr.to_array()),
437                    aid: Some(aid),
438                    listen_interval: None, // This field is not used for AP.
439                    channel: Some(fidl_common::WlanChannel {
440                        primary: channel,
441                        // TODO(https://fxbug.dev/42116942): Correctly support this.
442                        cbw: fidl_common::ChannelBandwidth::Cbw20,
443                        secondary80: 0,
444                    }),
445
446                    qos: Some(false),
447                    wmm_params: None,
448
449                    rates: Some(rates.to_vec()),
450                    capability_info: Some(capabilities.raw()),
451
452                    // TODO(https://fxbug.dev/42116942): Correctly support all of this.
453                    ht_cap: None,
454                    ht_op: None,
455                    vht_cap: None,
456                    vht_op: None,
457                    ..Default::default()
458                })
459                .await
460                .map_err(|s| Error::Status(format!("failed to configure association"), s))?;
461        }
462
463        let buffer = match result_code {
464            fidl_mlme::AssociateResultCode::Success => ctx.make_assoc_resp_frame(
465                self.addr,
466                capabilities,
467                aid,
468                rates,
469                Some(BSS_MAX_IDLE_PERIOD),
470            ),
471            _ => ctx.make_assoc_resp_frame_error(
472                self.addr,
473                capabilities,
474                match result_code {
475                    fidl_mlme::AssociateResultCode::Success => {
476                        panic!("Success should have already been handled");
477                    }
478                    fidl_mlme::AssociateResultCode::RefusedReasonUnspecified => {
479                        fidl_ieee80211::StatusCode::RefusedReasonUnspecified.into()
480                    }
481                    fidl_mlme::AssociateResultCode::RefusedNotAuthenticated => {
482                        fidl_ieee80211::StatusCode::RefusedUnauthenticatedAccessNotSupported.into()
483                    }
484                    fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch => {
485                        fidl_ieee80211::StatusCode::RefusedCapabilitiesMismatch.into()
486                    }
487                    fidl_mlme::AssociateResultCode::RefusedExternalReason => {
488                        fidl_ieee80211::StatusCode::RefusedExternalReason.into()
489                    }
490                    fidl_mlme::AssociateResultCode::RefusedApOutOfMemory => {
491                        fidl_ieee80211::StatusCode::RefusedApOutOfMemory.into()
492                    }
493                    fidl_mlme::AssociateResultCode::RefusedBasicRatesMismatch => {
494                        fidl_ieee80211::StatusCode::RefusedBasicRatesMismatch.into()
495                    }
496                    fidl_mlme::AssociateResultCode::RejectedEmergencyServicesNotSupported => {
497                        fidl_ieee80211::StatusCode::RejectedEmergencyServicesNotSupported.into()
498                    }
499                    fidl_mlme::AssociateResultCode::RefusedTemporarily => {
500                        fidl_ieee80211::StatusCode::RefusedTemporarily.into()
501                    }
502                },
503            ),
504        }?;
505        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
506            .map_err(|s| Error::Status(format!("error sending assoc frame"), s))
507    }
508
509    /// Handles MLME-DISASSOCIATE.request (IEEE Std 802.11-2016, 6.3.9.1) from the SME.
510    ///
511    /// The SME has already disassociated this client.
512    ///
513    /// The MLME doesn't have to do anything other than change its state to acknowledge the
514    /// disassociation.
515    pub async fn handle_mlme_disassoc_req<D: DeviceOps>(
516        &mut self,
517        ctx: &mut Context<D>,
518        reason_code: u16,
519    ) -> Result<(), Error> {
520        self.change_state(ctx, State::Authenticated).await?;
521
522        // IEEE Std 802.11-2016, 6.3.9.2.3 states that we should send MLME-DISASSOCIATE.confirm
523        // to the SME on success. Like MLME-DEAUTHENTICATE.confirm, our SME has already forgotten
524        // about this client, so sending MLME-DISASSOCIATE.confirm is redundant.
525
526        let buffer = ctx.make_disassoc_frame(self.addr.clone(), ReasonCode(reason_code))?;
527        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
528            .map_err(|s| Error::Status(format!("error sending disassoc frame"), s))
529    }
530
531    /// Handles SET_CONTROLLED_PORT.request (fuchsia.wlan.mlme.SetControlledPortRequest) from the
532    /// SME.
533    pub fn handle_mlme_set_controlled_port_req(
534        &mut self,
535        state: fidl_mlme::ControlledPortState,
536    ) -> Result<(), Error> {
537        match self.state.as_mut() {
538            State::Associated {
539                eapol_controlled_port: eapol_controlled_port @ Some(_), ..
540            } => {
541                eapol_controlled_port.replace(state);
542                Ok(())
543            }
544            State::Associated { eapol_controlled_port: None, .. } => {
545                Err(Error::Status(format!("client is not in an RSN"), zx::Status::BAD_STATE))
546            }
547            _ => Err(Error::Status(format!("client is not associated"), zx::Status::BAD_STATE)),
548        }
549    }
550
551    /// Handles MLME-EAPOL.request (IEEE Std 802.11-2016, 6.3.22.1) from the SME.
552    ///
553    /// The MLME should forward these frames to the PHY layer.
554    pub fn handle_mlme_eapol_req<D: DeviceOps>(
555        &mut self,
556        ctx: &mut Context<D>,
557        src_addr: MacAddr,
558        data: &[u8],
559    ) -> Result<(), Error> {
560        // IEEE Std 802.11-2016, 6.3.22.2.3 states that we should send MLME-EAPOL.confirm to the
561        // SME on success. Our SME employs a timeout for EAPoL negotiation, so MLME-EAPOL.confirm is
562        // redundant.
563        let buffer = ctx.make_eapol_frame(self.addr, src_addr, false, data)?;
564        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::FAVOR_RELIABILITY, None)
565            .map_err(|s| Error::Status(format!("error sending eapol frame"), s))
566    }
567
568    // WLAN frame handlers.
569
570    /// Handles disassociation frames (IEEE Std 802.11-2016, 9.3.3.5) from the PHY.
571    ///
572    /// self is mutable here as receiving a disassociation immediately disassociates us.
573    async fn handle_disassoc_frame<D: DeviceOps>(
574        &mut self,
575        ctx: &mut Context<D>,
576        reason_code: ReasonCode,
577    ) -> Result<(), ClientRejection> {
578        self.change_state(ctx, State::Authenticated).await.map_err(ClientRejection::DeviceError)?;
579        ctx.send_mlme_disassoc_ind(
580            self.addr.clone(),
581            Option::<fidl_ieee80211::ReasonCode>::from(reason_code)
582                .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason),
583            LocallyInitiated(false),
584        )
585        .map_err(ClientRejection::SmeSendError)
586    }
587
588    /// Handles association request frames (IEEE Std 802.11-2016, 9.3.3.6) from the PHY.
589    fn handle_assoc_req_frame<D: DeviceOps>(
590        &self,
591        ctx: &mut Context<D>,
592        capabilities: mac::CapabilityInfo,
593        listen_interval: u16,
594        ssid: Option<Ssid>,
595        rates: Vec<ie::SupportedRate>,
596        rsne: Option<Vec<u8>>,
597    ) -> Result<(), ClientRejection> {
598        ctx.send_mlme_assoc_ind(self.addr.clone(), listen_interval, ssid, capabilities, rates, rsne)
599            .map_err(ClientRejection::SmeSendError)
600    }
601
602    /// Handles authentication frames (IEEE Std 802.11-2016, 9.3.3.12) from the PHY.
603    ///
604    /// self is mutable here as we may deauthenticate without even getting to the SME if we don't
605    /// recognize the authentication algorithm.
606    async fn handle_auth_frame<D: DeviceOps>(
607        &mut self,
608        ctx: &mut Context<D>,
609        auth_alg_num: AuthAlgorithmNumber,
610    ) -> Result<(), ClientRejection> {
611        ctx.send_mlme_auth_ind(
612            self.addr.clone(),
613            match auth_alg_num {
614                AuthAlgorithmNumber::OPEN => fidl_mlme::AuthenticationTypes::OpenSystem,
615                AuthAlgorithmNumber::SHARED_KEY => fidl_mlme::AuthenticationTypes::SharedKey,
616                AuthAlgorithmNumber::FAST_BSS_TRANSITION => {
617                    fidl_mlme::AuthenticationTypes::FastBssTransition
618                }
619                AuthAlgorithmNumber::SAE => fidl_mlme::AuthenticationTypes::Sae,
620                _ => {
621                    self.change_state(ctx, State::Deauthenticated)
622                        .await
623                        .map_err(ClientRejection::DeviceError)?;
624
625                    // Don't even bother sending this to the SME if we don't understand the auth
626                    // algorithm.
627                    let buffer = ctx
628                        .make_auth_frame(
629                            self.addr.clone(),
630                            auth_alg_num,
631                            2,
632                            fidl_ieee80211::StatusCode::UnsupportedAuthAlgorithm.into(),
633                        )
634                        .map_err(ClientRejection::WlanSendError)?;
635                    return self
636                        .send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
637                        .map_err(|s| {
638                            ClientRejection::WlanSendError(Error::Status(
639                                format!("failed to send auth frame"),
640                                s,
641                            ))
642                        });
643                }
644            },
645        )
646        .map_err(ClientRejection::SmeSendError)
647    }
648
649    /// Handles deauthentication frames (IEEE Std 802.11-2016, 9.3.3.13) from the PHY.
650    ///
651    /// self is mutable here as receiving a deauthentication immediately deauthenticates us.
652    async fn handle_deauth_frame<D: DeviceOps>(
653        &mut self,
654        ctx: &mut Context<D>,
655        reason_code: ReasonCode,
656    ) -> Result<(), ClientRejection> {
657        self.change_state(ctx, State::Deauthenticated)
658            .await
659            .map_err(ClientRejection::DeviceError)?;
660        ctx.send_mlme_deauth_ind(
661            self.addr.clone(),
662            Option::<fidl_ieee80211::ReasonCode>::from(reason_code)
663                .unwrap_or(fidl_ieee80211::ReasonCode::UnspecifiedReason),
664            LocallyInitiated(false),
665        )
666        .map_err(ClientRejection::SmeSendError)
667    }
668
669    /// Handles action frames (IEEE Std 802.11-2016, 9.3.3.14) from the PHY.
670    fn handle_action_frame<D>(&self, _ctx: &mut Context<D>) -> Result<(), ClientRejection> {
671        // TODO(https://fxbug.dev/42113580): Implement me!
672        Ok(())
673    }
674
675    /// Handles PS-Poll (IEEE Std 802.11-2016, 9.3.1.5) from the PHY.
676    pub fn handle_ps_poll<D: DeviceOps>(
677        &mut self,
678        ctx: &mut Context<D>,
679        aid: Aid,
680    ) -> Result<(), ClientRejection> {
681        // All PS-Poll frames are Class 3.
682        self.reject_frame_class_if_not_permitted(ctx, mac::FrameClass::Class3)?;
683
684        match self.state.as_mut() {
685            State::Associated { aid: current_aid, ps_state, .. } => {
686                if aid != *current_aid {
687                    return Err(ClientRejection::NotPermitted);
688                }
689
690                match ps_state {
691                    PowerSaveState::Dozing { buffered } => {
692                        let BufferedFrame { mut buffer, tx_flags, async_id } =
693                            match buffered.pop_front() {
694                                Some(buffered) => buffered,
695                                None => {
696                                    // No frames available for the client to PS-Poll, just return
697                                    // OK.
698                                    return Ok(());
699                                }
700                            };
701                        if !buffered.is_empty() {
702                            frame_writer::set_more_data(&mut buffer[..]).map_err(|e| {
703                                wtrace::async_end_wlansoftmac_tx(async_id, zx::Status::INTERNAL);
704                                ClientRejection::WlanSendError(e)
705                            })?;
706                        }
707                        ctx.device.send_wlan_frame(buffer, tx_flags, None).map_err(|s| {
708                            wtrace::async_end_wlansoftmac_tx(async_id, s);
709                            ClientRejection::WlanSendError(Error::Status(
710                                format!("error sending buffered frame on PS-Poll"),
711                                s,
712                            ))
713                        })?;
714                    }
715                    _ => {
716                        return Err(ClientRejection::NotPermitted);
717                    }
718                }
719            }
720            _ => {
721                return Err(ClientRejection::NotAssociated);
722            }
723        };
724        Ok(())
725    }
726
727    /// Moves an associated remote client's power saving state into Dozing.
728    fn doze(&mut self) -> Result<(), ClientRejection> {
729        match self.state.as_mut() {
730            State::Associated { ps_state, .. } => match ps_state {
731                PowerSaveState::Awake => {
732                    *ps_state = PowerSaveState::Dozing {
733                        // TODO(https://fxbug.dev/42117877): Impose some kind of limit on this.
734                        buffered: VecDeque::new(),
735                    }
736                }
737                PowerSaveState::Dozing { .. } => {}
738            },
739            _ => {
740                // Unassociated clients are never allowed to doze.
741                return Err(ClientRejection::NotAssociated);
742            }
743        };
744        Ok(())
745    }
746
747    /// Moves an associated remote client's power saving state into Awake.
748    ///
749    /// This will also send all buffered frames.
750    fn wake<D: DeviceOps>(&mut self, ctx: &mut Context<D>) -> Result<(), ClientRejection> {
751        match self.state.as_mut() {
752            State::Associated { ps_state, .. } => {
753                let mut old_ps_state = PowerSaveState::Awake;
754                std::mem::swap(ps_state, &mut old_ps_state);
755
756                let mut buffered = match old_ps_state {
757                    PowerSaveState::Awake => {
758                        // It is not an error to go from awake to awake.
759                        return Ok(());
760                    }
761                    PowerSaveState::Dozing { buffered } => buffered.into_iter().peekable(),
762                };
763
764                while let Some(BufferedFrame { mut buffer, tx_flags, async_id }) = buffered.next() {
765                    if buffered.peek().is_some() {
766                        // We need to mark all except the last of these frames' frame control fields
767                        // with More Data, as per IEEE Std 802.11-2016, 11.2.3.2: The Power
768                        // Management subfield(s) in the Frame Control field of the frame(s) sent by
769                        // the STA in this exchange indicates the power management mode that the STA
770                        // shall adopt upon successful completion of the entire frame exchange.
771                        //
772                        // As the client does not complete the entire frame exchange until all
773                        // buffered frames are sent, we consider the client to be dozing until we
774                        // finish sending it all its frames. As per IEEE Std 802.11-2016, 9.2.4.1.8,
775                        // we need to mark all frames except the last frame with More Data.
776                        frame_writer::set_more_data(&mut buffer[..])
777                            .map_err(ClientRejection::WlanSendError)?;
778                    }
779                    ctx.device.send_wlan_frame(buffer, tx_flags, Some(async_id)).map_err(|s| {
780                        ClientRejection::WlanSendError(Error::Status(
781                            format!("error sending buffered frame on wake"),
782                            s,
783                        ))
784                    })?;
785                }
786            }
787            _ => {
788                // Unassociated clients are always awake.
789                return Ok(());
790            }
791        };
792        Ok(())
793    }
794
795    pub fn set_power_state<D: DeviceOps>(
796        &mut self,
797        ctx: &mut Context<D>,
798        power_state: mac::PowerState,
799    ) -> Result<(), ClientRejection> {
800        match power_state {
801            mac::PowerState::AWAKE => self.wake(ctx),
802            mac::PowerState::DOZE => self.doze(),
803        }
804    }
805
806    /// Handles EAPoL requests (IEEE Std 802.1X-2010, 11.3) from PHY data frames.
807    fn handle_eapol_llc_frame<D: DeviceOps>(
808        &self,
809        ctx: &mut Context<D>,
810        dst_addr: MacAddr,
811        src_addr: MacAddr,
812        body: &[u8],
813    ) -> Result<(), ClientRejection> {
814        ctx.send_mlme_eapol_ind(dst_addr, src_addr, &body).map_err(ClientRejection::SmeSendError)
815    }
816
817    // Handles LLC frames from PHY data frames.
818    fn handle_llc_frame<D: DeviceOps>(
819        &self,
820        ctx: &mut Context<D>,
821        dst_addr: MacAddr,
822        src_addr: MacAddr,
823        ether_type: u16,
824        body: &[u8],
825    ) -> Result<(), ClientRejection> {
826        ctx.deliver_eth_frame(dst_addr, src_addr, ether_type, body)
827            .map_err(ClientRejection::EthSendError)
828    }
829
830    /// Checks if a given frame class is permitted, and sends an appropriate deauthentication or
831    /// disassociation frame if it is not.
832    ///
833    /// If a frame is sent, the client's state is not in sync with the AP's, e.g. the AP may have
834    /// been restarted and the client needs to reset its state.
835    fn reject_frame_class_if_not_permitted<D: DeviceOps>(
836        &mut self,
837        ctx: &mut Context<D>,
838        frame_class: FrameClass,
839    ) -> Result<(), ClientRejection> {
840        if self.is_frame_class_permitted(frame_class) {
841            return Ok(());
842        }
843
844        let reason_code = match frame_class {
845            FrameClass::Class1 => panic!("class 1 frames should always be permitted"),
846            FrameClass::Class2 => fidl_ieee80211::ReasonCode::InvalidClass2Frame,
847            FrameClass::Class3 => fidl_ieee80211::ReasonCode::InvalidClass3Frame,
848        };
849
850        // Safe: |state| is never None and always replaced with Some(..).
851        match self.state.as_ref() {
852            State::Deauthenticated | State::Authenticating => {
853                let buffer = ctx
854                    .make_deauth_frame(self.addr, reason_code.into())
855                    .map_err(ClientRejection::WlanSendError)?;
856                self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
857                    .map_err(|s| {
858                        ClientRejection::WlanSendError(Error::Status(
859                            format!("failed to send deauth frame"),
860                            s,
861                        ))
862                    })?;
863
864                ctx.send_mlme_deauth_ind(self.addr, reason_code, LocallyInitiated(true))
865                    .map_err(ClientRejection::SmeSendError)?;
866            }
867            State::Authenticated => {
868                let buffer = ctx
869                    .make_disassoc_frame(self.addr, reason_code.into())
870                    .map_err(ClientRejection::WlanSendError)?;
871                self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), None)
872                    .map_err(|s| {
873                        ClientRejection::WlanSendError(Error::Status(
874                            format!("failed to send disassoc frame"),
875                            s,
876                        ))
877                    })?;
878
879                ctx.send_mlme_disassoc_ind(self.addr, reason_code, LocallyInitiated(true))
880                    .map_err(ClientRejection::SmeSendError)?;
881            }
882            State::Associated { .. } => {
883                panic!("all frames should be permitted for an associated client")
884            }
885        };
886
887        return Err(ClientRejection::NotPermitted);
888    }
889
890    // Public handler functions.
891
892    /// Handles management frames (IEEE Std 802.11-2016, 9.3.3) from the PHY.
893    pub async fn handle_mgmt_frame<B: SplitByteSlice, D: DeviceOps>(
894        &mut self,
895        ctx: &mut Context<D>,
896        capabilities: mac::CapabilityInfo,
897        ssid: Option<Ssid>,
898        mgmt_frame: mac::MgmtFrame<B>,
899    ) -> Result<(), ClientRejection> {
900        self.reject_frame_class_if_not_permitted(ctx, mac::frame_class(&mgmt_frame.frame_ctrl()))?;
901
902        self.reset_bss_max_idle_timeout(ctx);
903
904        match mgmt_frame.try_into_mgmt_body().1.ok_or(ClientRejection::ParseFailed)? {
905            mac::MgmtBody::Authentication(mac::AuthFrame { auth_hdr, .. }) => {
906                self.handle_auth_frame(ctx, auth_hdr.auth_alg_num).await
907            }
908            mac::MgmtBody::AssociationReq(assoc_req_frame) => {
909                let mut rates = vec![];
910                let mut rsne = None;
911
912                // TODO(https://fxbug.dev/42164332): This should probably use IeSummaryIter instead.
913                for (id, ie_body) in assoc_req_frame.ies() {
914                    match id {
915                        // We don't try too hard to verify the supported rates and extended
916                        // supported rates provided. A warning is logged if parsing of either
917                        // did not succeed, but otherwise whatever rates are parsed, even if
918                        // none, are passed on to SME.
919                        ie::Id::SUPPORTED_RATES => {
920                            match ie::parse_supported_rates(ie_body) {
921                                Ok(supported_rates) => rates.extend(&*supported_rates),
922                                Err(e) => warn!("{:?}", e),
923                            };
924                        }
925                        ie::Id::EXTENDED_SUPPORTED_RATES => {
926                            match ie::parse_extended_supported_rates(ie_body) {
927                                Ok(extended_supported_rates) => {
928                                    rates.extend(&*extended_supported_rates)
929                                }
930                                Err(e) => warn!("{:?}", e),
931                            };
932                        }
933                        ie::Id::RSNE => {
934                            rsne = Some({
935                                // TODO(https://fxbug.dev/42117156): Stop passing RSNEs around like this.
936                                let mut buffer =
937                                    vec![0; std::mem::size_of::<ie::Header>() + ie_body.len()];
938                                let mut w = BufferWriter::new(&mut buffer[..]);
939                                w.append_value(&ie::Header {
940                                    id: ie::Id::RSNE,
941                                    body_len: ie_body.len() as u8,
942                                })
943                                .expect("expected enough room in buffer for IE header");
944                                w.append_bytes(ie_body)
945                                    .expect("expected enough room in buffer for IE body");
946                                buffer
947                            });
948                        }
949                        _ => {}
950                    }
951                }
952
953                self.handle_assoc_req_frame(
954                    ctx,
955                    capabilities,
956                    assoc_req_frame.assoc_req_hdr.listen_interval,
957                    ssid,
958                    rates,
959                    rsne,
960                )
961            }
962            mac::MgmtBody::Deauthentication { deauth_hdr, .. } => {
963                self.handle_deauth_frame(ctx, deauth_hdr.reason_code).await
964            }
965            mac::MgmtBody::Disassociation { disassoc_hdr, .. } => {
966                self.handle_disassoc_frame(ctx, disassoc_hdr.reason_code).await
967            }
968            mac::MgmtBody::Action(_) => self.handle_action_frame(ctx),
969            _ => Err(ClientRejection::Unsupported),
970        }
971    }
972
973    /// Handles data frames (IEEE Std 802.11-2016, 9.3.2) from the PHY.
974    ///
975    /// These data frames may be in A-MSDU format (IEEE Std 802.11-2016, 9.3.2.2). However, the
976    /// individual frames will be passed to |handle_msdu| and we don't need to care what format
977    /// they're in.
978    pub fn handle_data_frame<B: SplitByteSlice, D: DeviceOps>(
979        &mut self,
980        ctx: &mut Context<D>,
981        data_frame: mac::DataFrame<B>,
982    ) -> Result<(), ClientRejection> {
983        self.reject_frame_class_if_not_permitted(ctx, mac::frame_class(&data_frame.frame_ctrl()))?;
984
985        self.reset_bss_max_idle_timeout(ctx);
986
987        for msdu in data_frame {
988            let mac::Msdu { dst_addr, src_addr, llc_frame } = msdu;
989            match llc_frame.hdr.protocol_id.to_native() {
990                // Handle EAPOL LLC frames.
991                mac::ETHER_TYPE_EAPOL => {
992                    self.handle_eapol_llc_frame(ctx, dst_addr, src_addr, &llc_frame.body)?
993                }
994                // Non-EAPOL frames...
995                _ => match self.state.as_ref() {
996                    // Drop all non-EAPoL MSDUs if the controlled port is closed.
997                    State::Associated {
998                        eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Closed),
999                        ..
1000                    } => (),
1001                    // Handle LLC frames only if the controlled port is not closed and the frame type
1002                    // is not EAPOL. If there is no controlled port, sending frames is OK.
1003                    _ => self.handle_llc_frame(
1004                        ctx,
1005                        dst_addr,
1006                        src_addr,
1007                        llc_frame.hdr.protocol_id.to_native(),
1008                        &llc_frame.body,
1009                    )?,
1010                },
1011            }
1012        }
1013        Ok(())
1014    }
1015
1016    /// Handles Ethernet II frames from the netstack.
1017    pub fn handle_eth_frame<D: DeviceOps>(
1018        &mut self,
1019        ctx: &mut Context<D>,
1020        dst_addr: MacAddr,
1021        src_addr: MacAddr,
1022        ether_type: u16,
1023        body: &[u8],
1024        async_id: trace::Id,
1025    ) -> Result<(), ClientRejection> {
1026        let eapol_controlled_port = match self.state.as_ref() {
1027            State::Associated { eapol_controlled_port, .. } => eapol_controlled_port,
1028            _ => {
1029                return Err(ClientRejection::NotAssociated);
1030            }
1031        };
1032
1033        let protection = match eapol_controlled_port {
1034            None => false,
1035            Some(fidl_mlme::ControlledPortState::Open) => true,
1036            Some(fidl_mlme::ControlledPortState::Closed) => {
1037                return Err(ClientRejection::ControlledPortClosed);
1038            }
1039        };
1040
1041        let buffer = ctx
1042            .make_data_frame(
1043                dst_addr, src_addr, protection,
1044                false, // TODO(https://fxbug.dev/42113580): Support QoS.
1045                ether_type, body,
1046            )
1047            .map_err(ClientRejection::WlanSendError)?;
1048
1049        self.send_wlan_frame(ctx, buffer, fidl_softmac::WlanTxInfoFlags::empty(), Some(async_id))
1050            .map_err(move |s| {
1051                ClientRejection::WlanSendError(Error::Status(
1052                    format!("error sending eapol frame"),
1053                    s,
1054                ))
1055            })
1056    }
1057
1058    pub fn send_wlan_frame<D: DeviceOps>(
1059        &mut self,
1060        ctx: &mut Context<D>,
1061        buffer: ArenaStaticBox<[u8]>,
1062        tx_flags: fidl_softmac::WlanTxInfoFlags,
1063        async_id: Option<trace::Id>,
1064    ) -> Result<(), zx::Status> {
1065        let async_id = async_id.unwrap_or_else(|| {
1066            let async_id = trace::Id::new();
1067            wtrace::async_begin_wlansoftmac_tx(async_id, "mlme");
1068            async_id
1069        });
1070
1071        match self.state.as_mut() {
1072            State::Associated { ps_state, .. } => match ps_state {
1073                PowerSaveState::Awake => {
1074                    ctx.device.send_wlan_frame(buffer, tx_flags, Some(async_id))
1075                }
1076                PowerSaveState::Dozing { buffered } => {
1077                    buffered.push_back(BufferedFrame { buffer, tx_flags, async_id });
1078                    Ok(())
1079                }
1080            },
1081            _ => ctx.device.send_wlan_frame(buffer, tx_flags, Some(async_id)),
1082        }
1083    }
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088    use super::*;
1089    use crate::device::FakeDevice;
1090    use ieee80211::Bssid;
1091    use lazy_static::lazy_static;
1092    use test_case::test_case;
1093    use wlan_common::assert_variant;
1094    use wlan_common::mac::{CapabilityInfo, IntoBytesExt as _};
1095    use wlan_common::test_utils::fake_frames::*;
1096    use wlan_common::timer::{self, create_timer};
1097    use zerocopy::Unalign;
1098
1099    lazy_static! {
1100        static ref CLIENT_ADDR: MacAddr = [1; 6].into();
1101        static ref AP_ADDR: Bssid = [2; 6].into();
1102        static ref CLIENT_ADDR2: MacAddr = [3; 6].into();
1103    }
1104    fn make_remote_client() -> RemoteClient {
1105        RemoteClient::new(*CLIENT_ADDR)
1106    }
1107
1108    fn make_context(
1109        fake_device: FakeDevice,
1110    ) -> (Context<FakeDevice>, timer::EventStream<TimedEvent>) {
1111        let (timer, time_stream) = create_timer();
1112        (Context::new(fake_device, timer, *AP_ADDR), time_stream)
1113    }
1114
1115    #[fuchsia::test(allow_stalls = false)]
1116    async fn handle_mlme_auth_resp() {
1117        let (fake_device, fake_device_state) = FakeDevice::new().await;
1118        let mut r_sta = make_remote_client();
1119        let (mut ctx, _) = make_context(fake_device);
1120        r_sta
1121            .handle_mlme_auth_resp(&mut ctx, fidl_mlme::AuthenticateResultCode::Success)
1122            .await
1123            .expect("expected OK");
1124        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
1125        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1126        #[rustfmt::skip]
1127        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1128            // Mgmt header
1129            0b10110000, 0, // Frame Control
1130            0, 0, // Duration
1131            1, 1, 1, 1, 1, 1, // addr1
1132            2, 2, 2, 2, 2, 2, // addr2
1133            2, 2, 2, 2, 2, 2, // addr3
1134            0x10, 0, // Sequence Control
1135            // Auth header:
1136            0, 0, // auth algorithm
1137            2, 0, // auth txn seq num
1138            0, 0, // status code
1139        ][..]);
1140    }
1141
1142    #[fuchsia::test(allow_stalls = false)]
1143    async fn handle_mlme_auth_resp_failure() {
1144        let (fake_device, fake_device_state) = FakeDevice::new().await;
1145        let mut r_sta = make_remote_client();
1146        let (mut ctx, _) = make_context(fake_device);
1147        r_sta
1148            .handle_mlme_auth_resp(
1149                &mut ctx,
1150                fidl_mlme::AuthenticateResultCode::AntiCloggingTokenRequired,
1151            )
1152            .await
1153            .expect("expected OK");
1154        assert_variant!(r_sta.state.as_ref(), State::Deauthenticated);
1155        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1156        #[rustfmt::skip]
1157        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1158            // Mgmt header
1159            0b10110000, 0, // Frame Control
1160            0, 0, // Duration
1161            1, 1, 1, 1, 1, 1, // addr1
1162            2, 2, 2, 2, 2, 2, // addr2
1163            2, 2, 2, 2, 2, 2, // addr3
1164            0x10, 0, // Sequence Control
1165            // Auth header:
1166            0, 0, // auth algorithm
1167            2, 0, // auth txn seq num
1168            76, 0, // status code
1169        ][..]);
1170    }
1171
1172    #[test_case(State::Authenticating; "in authenticating state")]
1173    #[test_case(State::Authenticated; "in authenticated state")]
1174    #[test_case(State::Associated {
1175            aid: 1,
1176            eapol_controlled_port: None,
1177            active_timeout: None,
1178            ps_state: PowerSaveState::Awake,
1179        }; "in associated state")]
1180    #[fuchsia::test(allow_stalls = false)]
1181    async fn handle_mlme_deauth_req(init_state: State) {
1182        let (fake_device, fake_device_state) = FakeDevice::new().await;
1183        let mut r_sta = make_remote_client();
1184        r_sta.state = StateMachine::new(init_state);
1185        let (mut ctx, _) = make_context(fake_device);
1186        r_sta
1187            .handle_mlme_deauth_req(&mut ctx, fidl_ieee80211::ReasonCode::LeavingNetworkDeauth)
1188            .await
1189            .expect("expected OK");
1190        assert_variant!(r_sta.state.as_ref(), State::Deauthenticated);
1191        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1192        #[rustfmt::skip]
1193        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1194            // Mgmt header
1195            0b11000000, 0, // Frame Control
1196            0, 0, // Duration
1197            1, 1, 1, 1, 1, 1, // addr1
1198            2, 2, 2, 2, 2, 2, // addr2
1199            2, 2, 2, 2, 2, 2, // addr3
1200            0x10, 0, // Sequence Control
1201            // Deauth header:
1202            3, 0, // reason code
1203        ][..]);
1204    }
1205
1206    #[fuchsia::test(allow_stalls = false)]
1207    async fn handle_mlme_assoc_resp() {
1208        let (fake_device, fake_device_state) = FakeDevice::new().await;
1209        let mut r_sta = make_remote_client();
1210        let (mut ctx, mut time_stream) = make_context(fake_device);
1211        r_sta
1212            .handle_mlme_assoc_resp(
1213                &mut ctx,
1214                true,
1215                1,
1216                CapabilityInfo(0),
1217                fidl_mlme::AssociateResultCode::Success,
1218                1,
1219                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
1220            )
1221            .await
1222            .expect("expected OK");
1223
1224        assert_variant!(
1225            r_sta.state.as_ref(),
1226            State::Associated {
1227                eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Closed),
1228                ..
1229            }
1230        );
1231
1232        assert_variant!(r_sta.aid(), Some(aid) => {
1233            assert_eq!(aid, 1);
1234        });
1235
1236        let active_timeout = match r_sta.state.as_ref() {
1237            State::Associated { active_timeout: Some(active_timeout), .. } => active_timeout,
1238            _ => panic!("no active timeout?"),
1239        };
1240
1241        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1242        #[rustfmt::skip]
1243        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1244            // Mgmt header
1245            0b00010000, 0, // Frame Control
1246            0, 0, // Duration
1247            1, 1, 1, 1, 1, 1, // addr1
1248            2, 2, 2, 2, 2, 2, // addr2
1249            2, 2, 2, 2, 2, 2, // addr3
1250            0x10, 0, // Sequence Control
1251            // Association response header:
1252            0, 0, // Capabilities
1253            0, 0, // status code
1254            1, 0, // AID
1255            // IEs
1256            1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
1257            50, 2, 9, 10, // Extended rates
1258            90, 3, 90, 0, 0, // BSS max idle period
1259        ][..]);
1260        let (_, timed_event, _) =
1261            time_stream.try_next().unwrap().expect("Should have scheduled a timeout");
1262        assert_eq!(timed_event.id, active_timeout.id());
1263
1264        assert!(fake_device_state.lock().assocs.contains_key(&CLIENT_ADDR));
1265    }
1266
1267    #[fuchsia::test(allow_stalls = false)]
1268    async fn handle_mlme_assoc_resp_then_handle_mlme_disassoc_req() {
1269        let (fake_device, fake_device_state) = FakeDevice::new().await;
1270        let mut r_sta = make_remote_client();
1271        let (mut ctx, _) = make_context(fake_device);
1272
1273        r_sta
1274            .handle_mlme_assoc_resp(
1275                &mut ctx,
1276                true,
1277                1,
1278                CapabilityInfo(0),
1279                fidl_mlme::AssociateResultCode::Success,
1280                1,
1281                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
1282            )
1283            .await
1284            .expect("expected OK");
1285        assert!(fake_device_state.lock().assocs.contains_key(&CLIENT_ADDR));
1286
1287        r_sta
1288            .handle_mlme_disassoc_req(
1289                &mut ctx,
1290                fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc.into_primitive(),
1291            )
1292            .await
1293            .expect("expected OK");
1294        assert!(!fake_device_state.lock().assocs.contains_key(&CLIENT_ADDR));
1295    }
1296
1297    #[fuchsia::test(allow_stalls = false)]
1298    async fn handle_mlme_assoc_resp_then_handle_mlme_deauth_req() {
1299        let (fake_device, fake_device_state) = FakeDevice::new().await;
1300        let mut r_sta = make_remote_client();
1301        let (mut ctx, _) = make_context(fake_device);
1302
1303        r_sta
1304            .handle_mlme_assoc_resp(
1305                &mut ctx,
1306                true,
1307                1,
1308                CapabilityInfo(0),
1309                fidl_mlme::AssociateResultCode::Success,
1310                1,
1311                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
1312            )
1313            .await
1314            .expect("expected OK");
1315        assert!(fake_device_state.lock().assocs.contains_key(&CLIENT_ADDR));
1316
1317        r_sta
1318            .handle_mlme_deauth_req(&mut ctx, fidl_ieee80211::ReasonCode::LeavingNetworkDeauth)
1319            .await
1320            .expect("expected OK");
1321        assert!(!fake_device_state.lock().assocs.contains_key(&CLIENT_ADDR));
1322    }
1323
1324    #[fuchsia::test(allow_stalls = false)]
1325    async fn handle_mlme_assoc_resp_no_rsn() {
1326        let (fake_device, _) = FakeDevice::new().await;
1327        let mut r_sta = make_remote_client();
1328        let (mut ctx, _) = make_context(fake_device);
1329        r_sta
1330            .handle_mlme_assoc_resp(
1331                &mut ctx,
1332                false,
1333                1,
1334                CapabilityInfo(0),
1335                fidl_mlme::AssociateResultCode::Success,
1336                1,
1337                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..],
1338            )
1339            .await
1340            .expect("expected OK");
1341        assert_variant!(
1342            r_sta.state.as_ref(),
1343            State::Associated { eapol_controlled_port: None, active_timeout: Some(_), .. }
1344        );
1345    }
1346
1347    #[fuchsia::test(allow_stalls = false)]
1348    async fn handle_mlme_assoc_resp_failure_reason_unspecified() {
1349        let (fake_device, fake_device_state) = FakeDevice::new().await;
1350        let mut r_sta = make_remote_client();
1351        let (mut ctx, _) = make_context(fake_device);
1352        r_sta
1353            .handle_mlme_assoc_resp(
1354                &mut ctx,
1355                false,
1356                1,
1357                CapabilityInfo(0),
1358                fidl_mlme::AssociateResultCode::RefusedReasonUnspecified,
1359                1, // This AID is ignored in the case of an error.
1360                &[][..],
1361            )
1362            .await
1363            .expect("expected OK");
1364        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
1365        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1366        #[rustfmt::skip]
1367        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1368            // Mgmt header
1369            0b00010000, 0, // Frame Control
1370            0, 0, // Duration
1371            1, 1, 1, 1, 1, 1, // addr1
1372            2, 2, 2, 2, 2, 2, // addr2
1373            2, 2, 2, 2, 2, 2, // addr3
1374            0x10, 0, // Sequence Control
1375            // Association response header:
1376            0, 0, // Capabilities
1377            1, 0, // status code
1378            0, 0, // AID
1379        ][..]);
1380    }
1381
1382    #[fuchsia::test(allow_stalls = false)]
1383    async fn handle_mlme_assoc_resp_failure_emergency_services_not_supported() {
1384        let (fake_device, fake_device_state) = FakeDevice::new().await;
1385        let mut r_sta = make_remote_client();
1386        let (mut ctx, _) = make_context(fake_device);
1387        r_sta
1388            .handle_mlme_assoc_resp(
1389                &mut ctx,
1390                false,
1391                1,
1392                CapabilityInfo(0),
1393                fidl_mlme::AssociateResultCode::RejectedEmergencyServicesNotSupported,
1394                1, // This AID is ignored in the case of an error.
1395                &[][..],
1396            )
1397            .await
1398            .expect("expected OK");
1399        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
1400        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1401        #[rustfmt::skip]
1402        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1403            // Mgmt header
1404            0b00010000, 0, // Frame Control
1405            0, 0, // Duration
1406            1, 1, 1, 1, 1, 1, // addr1
1407            2, 2, 2, 2, 2, 2, // addr2
1408            2, 2, 2, 2, 2, 2, // addr3
1409            0x10, 0, // Sequence Control
1410            // Association response header:
1411            0, 0, // Capabilities
1412            94, 0, // status code
1413            0, 0, // AID
1414        ][..]);
1415    }
1416
1417    #[fuchsia::test(allow_stalls = false)]
1418    async fn handle_mlme_disassoc_req() {
1419        let (fake_device, fake_device_state) = FakeDevice::new().await;
1420        let mut r_sta = make_remote_client();
1421        let (mut ctx, _) = make_context(fake_device);
1422        r_sta
1423            .handle_mlme_disassoc_req(
1424                &mut ctx,
1425                fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc.into_primitive(),
1426            )
1427            .await
1428            .expect("expected OK");
1429        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
1430        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1431        #[rustfmt::skip]
1432        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1433            // Mgmt header
1434            0b10100000, 0, // Frame Control
1435            0, 0, // Duration
1436            1, 1, 1, 1, 1, 1, // addr1
1437            2, 2, 2, 2, 2, 2, // addr2
1438            2, 2, 2, 2, 2, 2, // addr3
1439            0x10, 0, // Sequence Control
1440            // Disassoc header:
1441            8, 0, // reason code
1442        ][..]);
1443    }
1444
1445    #[test]
1446    fn handle_mlme_set_controlled_port_req() {
1447        let mut r_sta = make_remote_client();
1448        r_sta.state = StateMachine::new(State::Associated {
1449            aid: 1,
1450            eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Closed),
1451            active_timeout: None,
1452            ps_state: PowerSaveState::Awake,
1453        });
1454        r_sta
1455            .handle_mlme_set_controlled_port_req(fidl_mlme::ControlledPortState::Open)
1456            .expect("expected OK");
1457        assert_variant!(
1458            r_sta.state.as_ref(),
1459            State::Associated {
1460                eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Open),
1461                ..
1462            }
1463        );
1464    }
1465
1466    #[test]
1467    fn handle_mlme_set_controlled_port_req_closed() {
1468        let mut r_sta = make_remote_client();
1469        r_sta.state = StateMachine::new(State::Associated {
1470            aid: 1,
1471            eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Open),
1472            active_timeout: None,
1473            ps_state: PowerSaveState::Awake,
1474        });
1475        r_sta
1476            .handle_mlme_set_controlled_port_req(fidl_mlme::ControlledPortState::Closed)
1477            .expect("expected OK");
1478        assert_variant!(
1479            r_sta.state.as_ref(),
1480            State::Associated {
1481                eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Closed),
1482                ..
1483            }
1484        );
1485    }
1486
1487    #[test]
1488    fn handle_mlme_set_controlled_port_req_no_rsn() {
1489        let mut r_sta = make_remote_client();
1490        r_sta.state = StateMachine::new(State::Associated {
1491            aid: 1,
1492            eapol_controlled_port: None,
1493            active_timeout: None,
1494            ps_state: PowerSaveState::Awake,
1495        });
1496        assert_eq!(
1497            zx::Status::from(
1498                r_sta
1499                    .handle_mlme_set_controlled_port_req(fidl_mlme::ControlledPortState::Open)
1500                    .expect_err("expected err")
1501            ),
1502            zx::Status::BAD_STATE
1503        );
1504        assert_variant!(
1505            r_sta.state.as_ref(),
1506            State::Associated { eapol_controlled_port: None, .. }
1507        );
1508    }
1509
1510    #[test]
1511    fn handle_mlme_set_controlled_port_req_wrong_state() {
1512        let mut r_sta = make_remote_client();
1513        r_sta.state = StateMachine::new(State::Authenticating);
1514        assert_eq!(
1515            zx::Status::from(
1516                r_sta
1517                    .handle_mlme_set_controlled_port_req(fidl_mlme::ControlledPortState::Open)
1518                    .expect_err("expected err")
1519            ),
1520            zx::Status::BAD_STATE
1521        );
1522    }
1523
1524    #[fuchsia::test(allow_stalls = false)]
1525    async fn handle_mlme_eapol_req() {
1526        let (fake_device, fake_device_state) = FakeDevice::new().await;
1527        let mut r_sta = make_remote_client();
1528        let (mut ctx, _) = make_context(fake_device);
1529        r_sta.handle_mlme_eapol_req(&mut ctx, *CLIENT_ADDR2, &[1, 2, 3][..]).expect("expected OK");
1530        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1531        #[rustfmt::skip]
1532        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1533            // Mgmt header
1534            0b00001000, 0b00000010, // Frame Control
1535            0, 0, // Duration
1536            1, 1, 1, 1, 1, 1, // addr1
1537            2, 2, 2, 2, 2, 2, // addr2
1538            3, 3, 3, 3, 3, 3, // addr3
1539            0x10, 0, // Sequence Control
1540            0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1541            0, 0, 0, // OUI
1542            0x88, 0x8E, // EAPOL protocol ID
1543            // Data
1544            1, 2, 3,
1545        ][..]);
1546    }
1547
1548    #[fuchsia::test(allow_stalls = false)]
1549    async fn handle_disassoc_frame() {
1550        let (fake_device, fake_device_state) = FakeDevice::new().await;
1551        let mut r_sta = make_remote_client();
1552        let (mut ctx, _) = make_context(fake_device);
1553        r_sta
1554            .handle_disassoc_frame(
1555                &mut ctx,
1556                ReasonCode(fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc.into_primitive()),
1557            )
1558            .await
1559            .expect("expected OK");
1560
1561        let msg = fake_device_state
1562            .lock()
1563            .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
1564            .expect("expected MLME message");
1565        assert_eq!(
1566            msg,
1567            fidl_mlme::DisassociateIndication {
1568                peer_sta_address: CLIENT_ADDR.to_array(),
1569                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDisassoc,
1570                locally_initiated: false,
1571            },
1572        );
1573        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
1574    }
1575
1576    #[test_case(State::Authenticating; "in authenticating state")]
1577    #[test_case(State::Authenticated; "in authenticated state")]
1578    #[test_case(State::Associated {
1579            aid: 1,
1580            eapol_controlled_port: None,
1581            active_timeout: None,
1582            ps_state: PowerSaveState::Awake,
1583        }; "in associated state")]
1584    #[fuchsia::test(allow_stalls = false)]
1585    async fn handle_assoc_req_frame(init_state: State) {
1586        let (fake_device, fake_device_state) = FakeDevice::new().await;
1587        let mut r_sta = make_remote_client();
1588        r_sta.state = StateMachine::new(init_state);
1589        let (mut ctx, _) = make_context(fake_device);
1590        r_sta
1591            .handle_assoc_req_frame(
1592                &mut ctx,
1593                CapabilityInfo(0).with_short_preamble(true),
1594                1,
1595                Some(Ssid::try_from("coolnet").unwrap()),
1596                vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10].iter().map(|r| ie::SupportedRate(*r)).collect(),
1597                None,
1598            )
1599            .expect("expected OK");
1600
1601        let msg = fake_device_state
1602            .lock()
1603            .next_mlme_msg::<fidl_mlme::AssociateIndication>()
1604            .expect("expected MLME message");
1605        assert_eq!(
1606            msg,
1607            fidl_mlme::AssociateIndication {
1608                peer_sta_address: CLIENT_ADDR.to_array(),
1609                listen_interval: 1,
1610                ssid: Some(Ssid::try_from("coolnet").unwrap().into()),
1611                capability_info: CapabilityInfo(0).with_short_preamble(true).raw(),
1612                rates: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
1613                rsne: None,
1614            },
1615        );
1616    }
1617
1618    #[test_case(State::Authenticating; "in authenticating state")]
1619    #[test_case(State::Authenticated; "in authenticated state")]
1620    #[test_case(State::Associated {
1621            aid: 1,
1622            eapol_controlled_port: None,
1623            active_timeout: None,
1624            ps_state: PowerSaveState::Awake,
1625        }; "in associated state")]
1626    #[fuchsia::test(allow_stalls = false)]
1627    async fn handle_auth_frame(init_state: State) {
1628        let (fake_device, fake_device_state) = FakeDevice::new().await;
1629        let mut r_sta = make_remote_client();
1630        r_sta.state = StateMachine::new(init_state);
1631        let (mut ctx, _) = make_context(fake_device);
1632
1633        r_sta
1634            .handle_auth_frame(&mut ctx, AuthAlgorithmNumber::SHARED_KEY)
1635            .await
1636            .expect("expected OK");
1637        let msg = fake_device_state
1638            .lock()
1639            .next_mlme_msg::<fidl_mlme::AuthenticateIndication>()
1640            .expect("expected MLME message");
1641        assert_eq!(
1642            msg,
1643            fidl_mlme::AuthenticateIndication {
1644                peer_sta_address: CLIENT_ADDR.to_array(),
1645                auth_type: fidl_mlme::AuthenticationTypes::SharedKey,
1646            },
1647        );
1648    }
1649
1650    #[fuchsia::test(allow_stalls = false)]
1651    async fn handle_auth_frame_unknown_algorithm() {
1652        let (fake_device, fake_device_state) = FakeDevice::new().await;
1653        let mut r_sta = make_remote_client();
1654        let (mut ctx, _) = make_context(fake_device);
1655
1656        r_sta.handle_auth_frame(&mut ctx, AuthAlgorithmNumber(0xffff)).await.expect("expected OK");
1657        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1658        #[rustfmt::skip]
1659        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1660            // Mgmt header
1661            0b10110000, 0, // Frame Control
1662            0, 0, // Duration
1663            1, 1, 1, 1, 1, 1, // addr1
1664            2, 2, 2, 2, 2, 2, // addr2
1665            2, 2, 2, 2, 2, 2, // addr3
1666            0x10, 0, // Sequence Control
1667            // Auth header:
1668            0xff, 0xff, // auth algorithm
1669            2, 0, // auth txn seq num
1670            13, 0, // status code
1671        ][..]);
1672        assert_variant!(r_sta.state.as_ref(), State::Deauthenticated);
1673    }
1674
1675    #[test_case(false; "from idle state")]
1676    #[test_case(true; "while already authenticated")]
1677    #[fuchsia::test(allow_stalls = false)]
1678    async fn handle_deauth_frame(already_authenticated: bool) {
1679        let (fake_device, fake_device_state) = FakeDevice::new().await;
1680        let mut r_sta = make_remote_client();
1681        if already_authenticated {
1682            r_sta.state = StateMachine::new(State::Authenticated);
1683        }
1684        let (mut ctx, _) = make_context(fake_device);
1685
1686        r_sta
1687            .handle_deauth_frame(
1688                &mut ctx,
1689                ReasonCode(fidl_ieee80211::ReasonCode::LeavingNetworkDeauth.into_primitive()),
1690            )
1691            .await
1692            .expect("expected OK");
1693        let msg = fake_device_state
1694            .lock()
1695            .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
1696            .expect("expected MLME message");
1697        assert_eq!(
1698            msg,
1699            fidl_mlme::DeauthenticateIndication {
1700                peer_sta_address: CLIENT_ADDR.to_array(),
1701                reason_code: fidl_ieee80211::ReasonCode::LeavingNetworkDeauth,
1702                locally_initiated: false,
1703            }
1704        );
1705        assert_variant!(r_sta.state.as_ref(), State::Deauthenticated);
1706    }
1707
1708    #[test]
1709    fn handle_action_frame() {
1710        // TODO(https://fxbug.dev/42113580): Implement me!
1711    }
1712
1713    #[fuchsia::test(allow_stalls = false)]
1714    async fn handle_ps_poll() {
1715        let (fake_device, fake_device_state) = FakeDevice::new().await;
1716        let (mut ctx, _) = make_context(fake_device);
1717
1718        let mut r_sta = make_remote_client();
1719        r_sta.state = StateMachine::new(State::Associated {
1720            aid: 1,
1721            eapol_controlled_port: None,
1722            active_timeout: None,
1723            ps_state: PowerSaveState::Awake,
1724        });
1725
1726        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
1727
1728        // Send a bunch of Ethernet frames.
1729        r_sta
1730            .handle_eth_frame(
1731                &mut ctx,
1732                *CLIENT_ADDR2,
1733                *CLIENT_ADDR,
1734                0x1234,
1735                &[1, 2, 3, 4, 5][..],
1736                0.into(),
1737            )
1738            .expect("expected OK");
1739        r_sta
1740            .handle_eth_frame(
1741                &mut ctx,
1742                *CLIENT_ADDR2,
1743                *CLIENT_ADDR,
1744                0x1234,
1745                &[6, 7, 8, 9, 0][..],
1746                0.into(),
1747            )
1748            .expect("expected OK");
1749
1750        // Make sure nothing has been actually sent to the WLAN queue.
1751        assert_eq!(fake_device_state.lock().wlan_queue.len(), 0);
1752
1753        r_sta.handle_ps_poll(&mut ctx, 1).expect("expected handle_ps_poll OK");
1754        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1755        assert_eq!(
1756            &fake_device_state.lock().wlan_queue[0].0[..],
1757            &[
1758                // Mgmt header
1759                0b00001000, 0b00100010, // Frame Control
1760                0, 0, // Duration
1761                3, 3, 3, 3, 3, 3, // addr1
1762                2, 2, 2, 2, 2, 2, // addr2
1763                1, 1, 1, 1, 1, 1, // addr3
1764                0x10, 0, // Sequence Control
1765                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1766                0, 0, 0, // OUI
1767                0x12, 0x34, // Protocol ID
1768                // Data
1769                1, 2, 3, 4, 5,
1770            ][..]
1771        );
1772
1773        r_sta.handle_ps_poll(&mut ctx, 1).expect("expected handle_ps_poll OK");
1774        assert_eq!(fake_device_state.lock().wlan_queue.len(), 2);
1775        assert_eq!(
1776            &fake_device_state.lock().wlan_queue[1].0[..],
1777            &[
1778                // Mgmt header
1779                0b00001000, 0b00000010, // Frame Control
1780                0, 0, // Duration
1781                3, 3, 3, 3, 3, 3, // addr1
1782                2, 2, 2, 2, 2, 2, // addr2
1783                1, 1, 1, 1, 1, 1, // addr3
1784                0x20, 0, // Sequence Control
1785                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1786                0, 0, 0, // OUI
1787                0x12, 0x34, // Protocol ID
1788                // Data
1789                6, 7, 8, 9, 0,
1790            ][..]
1791        );
1792
1793        r_sta.handle_ps_poll(&mut ctx, 1).expect("expected handle_ps_poll OK");
1794        assert_eq!(fake_device_state.lock().wlan_queue.len(), 2);
1795    }
1796
1797    #[fuchsia::test(allow_stalls = false)]
1798    async fn handle_ps_poll_not_buffered() {
1799        let (fake_device, _) = FakeDevice::new().await;
1800        let (mut ctx, _) = make_context(fake_device);
1801
1802        let mut r_sta = make_remote_client();
1803        r_sta.state = StateMachine::new(State::Associated {
1804            aid: 1,
1805            eapol_controlled_port: None,
1806            active_timeout: None,
1807            ps_state: PowerSaveState::Awake,
1808        });
1809
1810        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
1811
1812        r_sta.handle_ps_poll(&mut ctx, 1).expect("expected handle_ps_poll OK");
1813    }
1814
1815    #[fuchsia::test(allow_stalls = false)]
1816    async fn handle_ps_poll_wrong_aid() {
1817        let (fake_device, _) = FakeDevice::new().await;
1818        let (mut ctx, _) = make_context(fake_device);
1819
1820        let mut r_sta = make_remote_client();
1821        r_sta.state = StateMachine::new(State::Associated {
1822            aid: 1,
1823            eapol_controlled_port: None,
1824            active_timeout: None,
1825            ps_state: PowerSaveState::Awake,
1826        });
1827
1828        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
1829
1830        assert_variant!(
1831            r_sta.handle_ps_poll(&mut ctx, 2).expect_err("expected handle_ps_poll error"),
1832            ClientRejection::NotPermitted
1833        );
1834    }
1835
1836    #[fuchsia::test(allow_stalls = false)]
1837    async fn handle_ps_poll_not_dozing() {
1838        let (fake_device, _) = FakeDevice::new().await;
1839        let (mut ctx, _) = make_context(fake_device);
1840
1841        let mut r_sta = make_remote_client();
1842        r_sta.state = StateMachine::new(State::Associated {
1843            aid: 1,
1844            eapol_controlled_port: None,
1845            active_timeout: None,
1846            ps_state: PowerSaveState::Awake,
1847        });
1848
1849        assert_variant!(
1850            r_sta.handle_ps_poll(&mut ctx, 1).expect_err("expected handle_ps_poll error"),
1851            ClientRejection::NotPermitted
1852        );
1853    }
1854
1855    #[fuchsia::test(allow_stalls = false)]
1856    async fn handle_eapol_llc_frame() {
1857        let (fake_device, fake_device_state) = FakeDevice::new().await;
1858        let mut r_sta = make_remote_client();
1859        let (mut ctx, _) = make_context(fake_device);
1860
1861        r_sta.state = StateMachine::new(State::Associated {
1862            aid: 1,
1863            eapol_controlled_port: None,
1864            active_timeout: None,
1865            ps_state: PowerSaveState::Awake,
1866        });
1867        r_sta
1868            .handle_eapol_llc_frame(&mut ctx, *CLIENT_ADDR2, *CLIENT_ADDR, &[1, 2, 3, 4, 5][..])
1869            .expect("expected OK");
1870        let msg = fake_device_state
1871            .lock()
1872            .next_mlme_msg::<fidl_mlme::EapolIndication>()
1873            .expect("expected MLME message");
1874        assert_eq!(
1875            msg,
1876            fidl_mlme::EapolIndication {
1877                dst_addr: CLIENT_ADDR2.to_array(),
1878                src_addr: CLIENT_ADDR.to_array(),
1879                data: vec![1, 2, 3, 4, 5],
1880            },
1881        );
1882    }
1883
1884    #[fuchsia::test(allow_stalls = false)]
1885    async fn handle_llc_frame() {
1886        let (fake_device, fake_device_state) = FakeDevice::new().await;
1887        let mut r_sta = make_remote_client();
1888        let (mut ctx, _) = make_context(fake_device);
1889
1890        r_sta.state = StateMachine::new(State::Associated {
1891            aid: 1,
1892            eapol_controlled_port: None,
1893            active_timeout: None,
1894            ps_state: PowerSaveState::Awake,
1895        });
1896        r_sta
1897            .handle_llc_frame(&mut ctx, *CLIENT_ADDR2, *CLIENT_ADDR, 0x1234, &[1, 2, 3, 4, 5][..])
1898            .expect("expected OK");
1899        assert_eq!(fake_device_state.lock().eth_queue.len(), 1);
1900        #[rustfmt::skip]
1901        assert_eq!(&fake_device_state.lock().eth_queue[0][..], &[
1902            3, 3, 3, 3, 3, 3,  // dest
1903            1, 1, 1, 1, 1, 1,  // src
1904            0x12, 0x34,        // ether_type
1905            // Data
1906            1, 2, 3, 4, 5,
1907        ][..]);
1908    }
1909
1910    #[fuchsia::test(allow_stalls = false)]
1911    async fn handle_eth_frame_no_eapol_controlled_port() {
1912        let (fake_device, fake_device_state) = FakeDevice::new().await;
1913        let mut r_sta = make_remote_client();
1914        let (mut ctx, _) = make_context(fake_device);
1915
1916        r_sta.state = StateMachine::new(State::Associated {
1917            aid: 1,
1918            eapol_controlled_port: None,
1919            active_timeout: None,
1920            ps_state: PowerSaveState::Awake,
1921        });
1922        r_sta
1923            .handle_eth_frame(
1924                &mut ctx,
1925                *CLIENT_ADDR2,
1926                *CLIENT_ADDR,
1927                0x1234,
1928                &[1, 2, 3, 4, 5][..],
1929                0.into(),
1930            )
1931            .expect("expected OK");
1932        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
1933        #[rustfmt::skip]
1934        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
1935            // Mgmt header
1936            0b00001000, 0b00000010, // Frame Control
1937            0, 0, // Duration
1938            3, 3, 3, 3, 3, 3, // addr1
1939            2, 2, 2, 2, 2, 2, // addr2
1940            1, 1, 1, 1, 1, 1, // addr3
1941            0x10, 0, // Sequence Control
1942            0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
1943            0, 0, 0, // OUI
1944            0x12, 0x34, // Protocol ID
1945            // Data
1946            1, 2, 3, 4, 5,
1947        ][..]);
1948    }
1949
1950    #[fuchsia::test(allow_stalls = false)]
1951    async fn handle_eth_frame_not_associated() {
1952        let (fake_device, _) = FakeDevice::new().await;
1953        let mut r_sta = make_remote_client();
1954        let (mut ctx, _) = make_context(fake_device);
1955
1956        r_sta.state = StateMachine::new(State::Authenticated);
1957        assert_variant!(
1958            r_sta
1959                .handle_eth_frame(
1960                    &mut ctx,
1961                    *CLIENT_ADDR2,
1962                    *CLIENT_ADDR,
1963                    0x1234,
1964                    &[1, 2, 3, 4, 5][..],
1965                    0.into()
1966                )
1967                .expect_err("expected error"),
1968            ClientRejection::NotAssociated
1969        );
1970    }
1971
1972    #[fuchsia::test(allow_stalls = false)]
1973    async fn handle_eth_frame_eapol_controlled_port_closed() {
1974        let (fake_device, _) = FakeDevice::new().await;
1975        let mut r_sta = make_remote_client();
1976        let (mut ctx, _) = make_context(fake_device);
1977
1978        r_sta.state = StateMachine::new(State::Associated {
1979            aid: 1,
1980            eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Closed),
1981            active_timeout: None,
1982            ps_state: PowerSaveState::Awake,
1983        });
1984        assert_variant!(
1985            r_sta
1986                .handle_eth_frame(
1987                    &mut ctx,
1988                    *CLIENT_ADDR2,
1989                    *CLIENT_ADDR,
1990                    0x1234,
1991                    &[1, 2, 3, 4, 5][..],
1992                    0.into()
1993                )
1994                .expect_err("expected error"),
1995            ClientRejection::ControlledPortClosed
1996        );
1997    }
1998
1999    #[fuchsia::test(allow_stalls = false)]
2000    async fn handle_eth_frame_eapol_controlled_port_open() {
2001        let (fake_device, fake_device_state) = FakeDevice::new().await;
2002        let mut r_sta = make_remote_client();
2003        let (mut ctx, _) = make_context(fake_device);
2004
2005        r_sta.state = StateMachine::new(State::Associated {
2006            aid: 1,
2007            eapol_controlled_port: Some(fidl_mlme::ControlledPortState::Open),
2008            active_timeout: None,
2009            ps_state: PowerSaveState::Awake,
2010        });
2011        r_sta
2012            .handle_eth_frame(
2013                &mut ctx,
2014                *CLIENT_ADDR2,
2015                *CLIENT_ADDR,
2016                0x1234,
2017                &[1, 2, 3, 4, 5][..],
2018                0.into(),
2019            )
2020            .expect("expected OK");
2021        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
2022        #[rustfmt::skip]
2023        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
2024            // Mgmt header
2025            0b00001000, 0b01000010, // Frame Control
2026            0, 0, // Duration
2027            3, 3, 3, 3, 3, 3, // addr1
2028            2, 2, 2, 2, 2, 2, // addr2
2029            1, 1, 1, 1, 1, 1, // addr3
2030            0x10, 0, // Sequence Control
2031            0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
2032            0, 0, 0, // OUI
2033            0x12, 0x34, // Protocol ID
2034            // Data
2035            1, 2, 3, 4, 5,
2036        ][..]);
2037    }
2038
2039    #[fuchsia::test(allow_stalls = false)]
2040    async fn handle_data_frame_not_permitted() {
2041        let (fake_device, fake_device_state) = FakeDevice::new().await;
2042        let mut r_sta = make_remote_client();
2043        r_sta.state = StateMachine::new(State::Authenticating);
2044        let (mut ctx, _) = make_context(fake_device);
2045
2046        assert_variant!(
2047            r_sta
2048                .handle_data_frame(
2049                    &mut ctx,
2050                    mac::DataFrame {
2051                        fixed_fields: mac::FixedDataHdrFields {
2052                            frame_ctrl: mac::FrameControl(0b000000010_00001000),
2053                            duration: 0,
2054                            addr1: *CLIENT_ADDR,
2055                            addr2: (*AP_ADDR).into(),
2056                            addr3: *CLIENT_ADDR2,
2057                            seq_ctrl: mac::SequenceControl(10),
2058                        }
2059                        .as_bytes_ref(),
2060                        addr4: None,
2061                        qos_ctrl: None,
2062                        ht_ctrl: None,
2063                        body: &[
2064                            7, 7, 7, // DSAP, SSAP & control
2065                            8, 8, 8, // OUI
2066                            9, 10, // eth type
2067                            // Trailing bytes
2068                            11, 11, 11,
2069                        ][..],
2070                    },
2071                )
2072                .expect_err("expected err"),
2073            ClientRejection::NotPermitted
2074        );
2075
2076        let msg = fake_device_state
2077            .lock()
2078            .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
2079            .expect("expected MLME message");
2080        assert_eq!(
2081            msg,
2082            fidl_mlme::DeauthenticateIndication {
2083                peer_sta_address: CLIENT_ADDR.to_array(),
2084                reason_code: fidl_ieee80211::ReasonCode::InvalidClass3Frame,
2085                locally_initiated: true,
2086            },
2087        );
2088
2089        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
2090        assert_eq!(
2091            fake_device_state.lock().wlan_queue[0].0,
2092            &[
2093                // Mgmt header
2094                0b11000000, 0b00000000, // Frame Control
2095                0, 0, // Duration
2096                1, 1, 1, 1, 1, 1, // addr1
2097                2, 2, 2, 2, 2, 2, // addr2
2098                2, 2, 2, 2, 2, 2, // addr3
2099                0x10, 0, // Sequence Control
2100                // Deauth header:
2101                7, 0, // reason code
2102            ][..]
2103        );
2104    }
2105
2106    #[fuchsia::test(allow_stalls = false)]
2107    async fn handle_data_frame_not_permitted_disassoc() {
2108        let (fake_device, fake_device_state) = FakeDevice::new().await;
2109        let mut r_sta = make_remote_client();
2110        r_sta.state = StateMachine::new(State::Authenticated);
2111        let (mut ctx, _) = make_context(fake_device);
2112
2113        assert_variant!(
2114            r_sta
2115                .handle_data_frame(
2116                    &mut ctx,
2117                    mac::DataFrame {
2118                        fixed_fields: mac::FixedDataHdrFields {
2119                            frame_ctrl: mac::FrameControl(0b000000010_00001000),
2120                            duration: 0,
2121                            addr1: *CLIENT_ADDR,
2122                            addr2: (*AP_ADDR).into(),
2123                            addr3: *CLIENT_ADDR2,
2124                            seq_ctrl: mac::SequenceControl(10),
2125                        }
2126                        .as_bytes_ref(),
2127                        addr4: None,
2128                        qos_ctrl: None,
2129                        ht_ctrl: None,
2130                        body: &[
2131                            7, 7, 7, // DSAP, SSAP & control
2132                            8, 8, 8, // OUI
2133                            9, 10, // eth type
2134                            // Trailing bytes
2135                            11, 11, 11,
2136                        ][..],
2137                    },
2138                )
2139                .expect_err("expected err"),
2140            ClientRejection::NotPermitted
2141        );
2142
2143        let msg = fake_device_state
2144            .lock()
2145            .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2146            .expect("expected MLME message");
2147        assert_eq!(
2148            msg,
2149            fidl_mlme::DisassociateIndication {
2150                peer_sta_address: CLIENT_ADDR.to_array(),
2151                reason_code: fidl_ieee80211::ReasonCode::InvalidClass3Frame,
2152                locally_initiated: true,
2153            },
2154        );
2155
2156        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
2157        assert_eq!(
2158            fake_device_state.lock().wlan_queue[0].0,
2159            &[
2160                // Mgmt header
2161                0b10100000, 0b00000000, // Frame Control
2162                0, 0, // Duration
2163                1, 1, 1, 1, 1, 1, // addr1
2164                2, 2, 2, 2, 2, 2, // addr2
2165                2, 2, 2, 2, 2, 2, // addr3
2166                0x10, 0, // Sequence Control
2167                // Deauth header:
2168                7, 0, // reason code
2169            ][..]
2170        );
2171    }
2172
2173    #[fuchsia::test(allow_stalls = false)]
2174    async fn handle_data_frame_single_llc() {
2175        let (fake_device, fake_device_state) = FakeDevice::new().await;
2176        let mut r_sta = make_remote_client();
2177        r_sta.state = StateMachine::new(State::Associated {
2178            aid: 1,
2179            eapol_controlled_port: None,
2180            active_timeout: None,
2181            ps_state: PowerSaveState::Awake,
2182        });
2183        let (mut ctx, _) = make_context(fake_device);
2184
2185        r_sta
2186            .handle_data_frame(
2187                &mut ctx,
2188                mac::DataFrame {
2189                    fixed_fields: mac::FixedDataHdrFields {
2190                        frame_ctrl: mac::FrameControl(0b000000010_00001000),
2191                        duration: 0,
2192                        addr1: *CLIENT_ADDR,
2193                        addr2: (*AP_ADDR).into(),
2194                        addr3: *CLIENT_ADDR2,
2195                        seq_ctrl: mac::SequenceControl(10),
2196                    }
2197                    .as_bytes_ref(),
2198                    addr4: None,
2199                    qos_ctrl: None,
2200                    ht_ctrl: None,
2201                    body: &[
2202                        7, 7, 7, // DSAP, SSAP & control
2203                        8, 8, 8, // OUI
2204                        9, 10, // eth type
2205                        // Trailing bytes
2206                        11, 11, 11,
2207                    ][..],
2208                },
2209            )
2210            .expect("expected OK");
2211
2212        assert_eq!(fake_device_state.lock().eth_queue.len(), 1);
2213        match r_sta.state.as_ref() {
2214            State::Associated { active_timeout, .. } => assert!(active_timeout.is_some()),
2215            _ => panic!("expected Associated"),
2216        }
2217    }
2218
2219    #[fuchsia::test(allow_stalls = false)]
2220    async fn handle_data_frame_amsdu() {
2221        let (fake_device, fake_device_state) = FakeDevice::new().await;
2222        let mut r_sta = make_remote_client();
2223        r_sta.state = StateMachine::new(State::Associated {
2224            aid: 1,
2225            eapol_controlled_port: None,
2226            active_timeout: None,
2227            ps_state: PowerSaveState::Awake,
2228        });
2229        let (mut ctx, _) = make_context(fake_device);
2230
2231        let mut amsdu_data_frame_body = vec![];
2232        amsdu_data_frame_body.extend(&[
2233            // A-MSDU Subframe #1
2234            0x78, 0x8a, 0x20, 0x0d, 0x67, 0x03, // dst_addr
2235            0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xab, // src_addr
2236            0x00, 0x74, // MSDU length
2237        ]);
2238        amsdu_data_frame_body.extend(MSDU_1_LLC_HDR);
2239        amsdu_data_frame_body.extend(MSDU_1_PAYLOAD);
2240        amsdu_data_frame_body.extend(&[
2241            // Padding
2242            0x00, 0x00, // A-MSDU Subframe #2
2243            0x78, 0x8a, 0x20, 0x0d, 0x67, 0x04, // dst_addr
2244            0xb4, 0xf7, 0xa1, 0xbe, 0xb9, 0xac, // src_addr
2245            0x00, 0x66, // MSDU length
2246        ]);
2247        amsdu_data_frame_body.extend(MSDU_2_LLC_HDR);
2248        amsdu_data_frame_body.extend(MSDU_2_PAYLOAD);
2249
2250        r_sta
2251            .handle_data_frame(
2252                &mut ctx,
2253                mac::DataFrame {
2254                    fixed_fields: mac::FixedDataHdrFields {
2255                        frame_ctrl: mac::FrameControl(0b000000010_00001000),
2256                        duration: 0,
2257                        addr1: *CLIENT_ADDR,
2258                        addr2: (*AP_ADDR).into(),
2259                        addr3: *CLIENT_ADDR2,
2260                        seq_ctrl: mac::SequenceControl(10),
2261                    }
2262                    .as_bytes_ref(),
2263                    addr4: None,
2264                    qos_ctrl: Some(
2265                        Unalign::new(mac::QosControl(0).with_amsdu_present(true)).as_bytes_ref(),
2266                    ),
2267                    ht_ctrl: None,
2268                    body: &amsdu_data_frame_body[..],
2269                },
2270            )
2271            .expect("expected OK");
2272
2273        assert_eq!(fake_device_state.lock().eth_queue.len(), 2);
2274        match r_sta.state.as_ref() {
2275            State::Associated { active_timeout, .. } => assert!(active_timeout.is_some()),
2276            _ => panic!("expected Associated"),
2277        }
2278    }
2279
2280    #[fuchsia::test(allow_stalls = false)]
2281    async fn handle_mgmt_frame() {
2282        let (fake_device, _) = FakeDevice::new().await;
2283        let mut r_sta = make_remote_client();
2284        r_sta.state = StateMachine::new(State::Authenticating);
2285        let (mut ctx, _) = make_context(fake_device);
2286
2287        r_sta
2288            .handle_mgmt_frame(
2289                &mut ctx,
2290                mac::CapabilityInfo(0),
2291                None,
2292                mac::MgmtFrame {
2293                    mgmt_hdr: mac::MgmtHdr {
2294                        frame_ctrl: mac::FrameControl(0b00000000_10110000), // Auth frame
2295                        duration: 0,
2296                        addr1: [1; 6].into(),
2297                        addr2: [2; 6].into(),
2298                        addr3: [3; 6].into(),
2299                        seq_ctrl: mac::SequenceControl(10),
2300                    }
2301                    .as_bytes_ref(),
2302                    ht_ctrl: None,
2303                    body: &[
2304                        0, 0, // Auth algorithm number
2305                        1, 0, // Auth txn seq number
2306                        0, 0, // Status code
2307                    ][..],
2308                },
2309            )
2310            .await
2311            .expect("expected OK");
2312    }
2313
2314    #[test_case(Ssid::try_from("coolnet").unwrap(), true; "with ssid and rsne")]
2315    #[test_case(Ssid::try_from("").unwrap(), true; "with empty ssid")]
2316    #[test_case(Ssid::try_from("coolnet").unwrap(), false; "without rsne")]
2317    #[fuchsia::test(allow_stalls = false)]
2318    async fn handle_mgmt_frame_assoc_req(ssid: Ssid, has_rsne: bool) {
2319        let (fake_device, fake_device_state) = FakeDevice::new().await;
2320        let mut r_sta = make_remote_client();
2321        r_sta.state = StateMachine::new(State::Authenticated);
2322        let (mut ctx, _) = make_context(fake_device);
2323
2324        let mut assoc_frame_body = vec![
2325            0, 0, // Capability info
2326            10, 0, // Listen interval
2327            // IEs
2328            1, 8, 1, 2, 3, 4, 5, 6, 7, 8, // Rates
2329            50, 2, 9, 10, // Extended rates
2330        ];
2331        if has_rsne {
2332            assoc_frame_body.extend(&[48, 2, 77, 88][..]); // RSNE
2333        }
2334
2335        r_sta
2336            .handle_mgmt_frame(
2337                &mut ctx,
2338                mac::CapabilityInfo(0),
2339                Some(ssid.clone()),
2340                mac::MgmtFrame {
2341                    mgmt_hdr: mac::MgmtHdr {
2342                        frame_ctrl: mac::FrameControl(0b00000000_00000000), // Assoc req frame
2343                        duration: 0,
2344                        addr1: [1; 6].into(),
2345                        addr2: [2; 6].into(),
2346                        addr3: [3; 6].into(),
2347                        seq_ctrl: mac::SequenceControl(10),
2348                    }
2349                    .as_bytes_ref(),
2350                    ht_ctrl: None,
2351                    body: &assoc_frame_body[..],
2352                },
2353            )
2354            .await
2355            .expect("expected OK");
2356
2357        let msg = fake_device_state
2358            .lock()
2359            .next_mlme_msg::<fidl_mlme::AssociateIndication>()
2360            .expect("expected MLME message");
2361        let expected_rsne = if has_rsne { Some(vec![48, 2, 77, 88]) } else { None };
2362        assert_eq!(
2363            msg,
2364            fidl_mlme::AssociateIndication {
2365                peer_sta_address: CLIENT_ADDR.to_array(),
2366                listen_interval: 10,
2367                ssid: Some(ssid.into()),
2368                capability_info: CapabilityInfo(0).raw(),
2369                rates: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
2370                rsne: expected_rsne,
2371            },
2372        );
2373    }
2374
2375    #[test_case(vec![1, 0],
2376                vec![50, 2, 9, 10],
2377                vec![9, 10] ; "when no supported rates")]
2378    #[test_case(vec![1, 8, 1, 2, 3, 4, 5, 6, 7, 8],
2379                vec![50, 0],
2380                vec![1, 2, 3, 4, 5, 6, 7, 8] ; "when no extended supported rates")]
2381    #[test_case(vec![1, 0],
2382                vec![50, 0],
2383                vec![] ; "when no rates")]
2384    // This case expects the Supported Rates to reach SME successfully despite the number of rates
2385    // exceeding the limit of eight specified in IEEE Std 802.11-2016 9.2.4.3. This limit is
2386    // ignored while parsing rates to improve interoperability with devices that overload the IE.
2387    #[test_case(vec![1, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9],
2388                vec![50, 9, 10],
2389                vec![1, 2, 3, 4, 5, 6, 7, 8, 9] ; "when too many supported rates")]
2390    #[fuchsia::test(allow_stalls = false)]
2391    async fn assoc_req_with_bad_rates_still_passed_to_sme(
2392        supported_rates_ie: Vec<u8>,
2393        extended_supported_rates_ie: Vec<u8>,
2394        expected_rates: Vec<u8>,
2395    ) {
2396        let (fake_device, fake_device_state) = FakeDevice::new().await;
2397        let mut r_sta = make_remote_client();
2398        r_sta.state = StateMachine::new(State::Authenticated);
2399        let (mut ctx, _) = make_context(fake_device);
2400        let mut ies = vec![
2401            0, 0, // Capability info
2402            10, 0, // Listen interval
2403        ];
2404        ies.extend(supported_rates_ie);
2405        ies.extend(extended_supported_rates_ie);
2406
2407        r_sta
2408            .handle_mgmt_frame(
2409                &mut ctx,
2410                mac::CapabilityInfo(0),
2411                Some(Ssid::try_from("coolnet").unwrap()),
2412                mac::MgmtFrame {
2413                    mgmt_hdr: mac::MgmtHdr {
2414                        frame_ctrl: mac::FrameControl(0b00000000_00000000), // Assoc req frame
2415                        duration: 0,
2416                        addr1: [1; 6].into(),
2417                        addr2: [2; 6].into(),
2418                        addr3: [3; 6].into(),
2419                        seq_ctrl: mac::SequenceControl(10),
2420                    }
2421                    .as_bytes_ref(),
2422                    ht_ctrl: None,
2423                    body: &ies[..],
2424                },
2425            )
2426            .await
2427            .expect("parsing should not fail");
2428
2429        let msg = fake_device_state
2430            .lock()
2431            .next_mlme_msg::<fidl_mlme::AssociateIndication>()
2432            .expect("expected MLME message");
2433        assert_eq!(
2434            msg,
2435            fidl_mlme::AssociateIndication {
2436                peer_sta_address: CLIENT_ADDR.to_array(),
2437                listen_interval: 10,
2438                ssid: Some(Ssid::try_from("coolnet").unwrap().into()),
2439                capability_info: CapabilityInfo(0).raw(),
2440                rates: expected_rates,
2441                rsne: None,
2442            },
2443        );
2444    }
2445
2446    #[fuchsia::test(allow_stalls = false)]
2447    async fn handle_mgmt_frame_not_permitted() {
2448        let (fake_device, fake_device_state) = FakeDevice::new().await;
2449        let mut r_sta = make_remote_client();
2450        r_sta.state = StateMachine::new(State::Authenticating);
2451        let (mut ctx, _) = make_context(fake_device);
2452
2453        assert_variant!(
2454            r_sta
2455                .handle_mgmt_frame(
2456                    &mut ctx,
2457                    mac::CapabilityInfo(0),
2458                    None,
2459                    mac::MgmtFrame {
2460                        mgmt_hdr: mac::MgmtHdr {
2461                            frame_ctrl: mac::FrameControl(0b00000000_00000000), // Assoc req frame
2462                            duration: 0,
2463                            addr1: [1; 6].into(),
2464                            addr2: [2; 6].into(),
2465                            addr3: [3; 6].into(),
2466                            seq_ctrl: mac::SequenceControl(10),
2467                        }
2468                        .as_bytes_ref(),
2469                        ht_ctrl: None,
2470                        body: &[
2471                            0, 0, // Capability info
2472                            10, 0, // Listen interval
2473                        ][..],
2474                    },
2475                )
2476                .await
2477                .expect_err("expected error"),
2478            ClientRejection::NotPermitted
2479        );
2480
2481        let msg = fake_device_state
2482            .lock()
2483            .next_mlme_msg::<fidl_mlme::DeauthenticateIndication>()
2484            .expect("expected MLME message");
2485        assert_eq!(
2486            msg,
2487            fidl_mlme::DeauthenticateIndication {
2488                peer_sta_address: CLIENT_ADDR.to_array(),
2489                reason_code: fidl_ieee80211::ReasonCode::InvalidClass2Frame,
2490                locally_initiated: true,
2491            },
2492        );
2493
2494        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
2495        assert_eq!(
2496            fake_device_state.lock().wlan_queue[0].0,
2497            &[
2498                // Mgmt header
2499                0b11000000, 0b00000000, // Frame Control
2500                0, 0, // Duration
2501                1, 1, 1, 1, 1, 1, // addr1
2502                2, 2, 2, 2, 2, 2, // addr2
2503                2, 2, 2, 2, 2, 2, // addr3
2504                0x10, 0, // Sequence Control
2505                // Deauth header:
2506                6, 0, // reason code
2507            ][..]
2508        );
2509    }
2510
2511    #[fuchsia::test(allow_stalls = false)]
2512    async fn handle_mgmt_frame_not_handled() {
2513        let (fake_device, _) = FakeDevice::new().await;
2514        let mut r_sta = make_remote_client();
2515        r_sta.state = StateMachine::new(State::Associated {
2516            aid: 1,
2517            eapol_controlled_port: None,
2518            active_timeout: None,
2519            ps_state: PowerSaveState::Awake,
2520        });
2521        let (mut ctx, _) = make_context(fake_device);
2522
2523        assert_variant!(
2524            r_sta
2525                .handle_mgmt_frame(
2526                    &mut ctx,
2527                    mac::CapabilityInfo(0),
2528                    None,
2529                    mac::MgmtFrame {
2530                        mgmt_hdr: mac::MgmtHdr {
2531                            frame_ctrl: mac::FrameControl(0b00000000_00010000), // Assoc resp frame
2532                            duration: 0,
2533                            addr1: [1; 6].into(),
2534                            addr2: [2; 6].into(),
2535                            addr3: [3; 6].into(),
2536                            seq_ctrl: mac::SequenceControl(10),
2537                        }
2538                        .as_bytes_ref(),
2539                        ht_ctrl: None,
2540                        body: &[
2541                            0, 0, // Capability info
2542                            0, 0, // Status code
2543                            1, 0, // AID
2544                        ][..],
2545                    },
2546                )
2547                .await
2548                .expect_err("expected error"),
2549            ClientRejection::Unsupported
2550        );
2551    }
2552
2553    #[fuchsia::test(allow_stalls = false)]
2554    async fn handle_mgmt_frame_resets_active_timer() {
2555        let (fake_device, _) = FakeDevice::new().await;
2556        let mut r_sta = make_remote_client();
2557        r_sta.state = StateMachine::new(State::Associated {
2558            aid: 1,
2559            eapol_controlled_port: None,
2560            active_timeout: None,
2561            ps_state: PowerSaveState::Awake,
2562        });
2563        let (mut ctx, _) = make_context(fake_device);
2564
2565        r_sta
2566            .handle_mgmt_frame(
2567                &mut ctx,
2568                mac::CapabilityInfo(0),
2569                None,
2570                mac::MgmtFrame {
2571                    mgmt_hdr: mac::MgmtHdr {
2572                        frame_ctrl: mac::FrameControl(0b00000000_00000000), // Assoc req frame
2573                        duration: 0,
2574                        addr1: [1; 6].into(),
2575                        addr2: [2; 6].into(),
2576                        addr3: [3; 6].into(),
2577                        seq_ctrl: mac::SequenceControl(10),
2578                    }
2579                    .as_bytes_ref(),
2580                    ht_ctrl: None,
2581                    body: &[
2582                        0, 0, // Capability info
2583                        10, 0, // Listen interval
2584                    ][..],
2585                },
2586            )
2587            .await
2588            .expect("expected OK");
2589        match r_sta.state.as_ref() {
2590            State::Associated { active_timeout, .. } => assert!(active_timeout.is_some()),
2591            _ => panic!("expected Associated"),
2592        }
2593    }
2594
2595    #[fuchsia::test(allow_stalls = false)]
2596    async fn handle_bss_idle_timeout() {
2597        let (fake_device, fake_device_state) = FakeDevice::new().await;
2598        let (mut ctx, _) = make_context(fake_device);
2599
2600        let mut r_sta = make_remote_client();
2601        let event_handle = r_sta.schedule_bss_idle_timeout(&mut ctx);
2602        r_sta.state = StateMachine::new(State::Associated {
2603            aid: 1,
2604            eapol_controlled_port: None,
2605            active_timeout: Some(event_handle),
2606            ps_state: PowerSaveState::Awake,
2607        });
2608
2609        r_sta.handle_bss_idle_timeout(&mut ctx).await.expect("expected OK");
2610        assert_variant!(r_sta.state.as_ref(), State::Authenticated);
2611        assert_eq!(fake_device_state.lock().wlan_queue.len(), 1);
2612        #[rustfmt::skip]
2613        assert_eq!(&fake_device_state.lock().wlan_queue[0].0[..], &[
2614            // Mgmt header
2615            0b10100000, 0, // Frame Control
2616            0, 0, // Duration
2617            1, 1, 1, 1, 1, 1, // addr1
2618            2, 2, 2, 2, 2, 2, // addr2
2619            2, 2, 2, 2, 2, 2, // addr3
2620            0x10, 0, // Sequence Control
2621            // Disassoc header:
2622            4, 0, // reason code
2623        ][..]);
2624        let msg = fake_device_state
2625            .lock()
2626            .next_mlme_msg::<fidl_mlme::DisassociateIndication>()
2627            .expect("expected MLME message");
2628        assert_eq!(
2629            msg,
2630            fidl_mlme::DisassociateIndication {
2631                peer_sta_address: CLIENT_ADDR.to_array(),
2632                reason_code: fidl_ieee80211::ReasonCode::ReasonInactivity,
2633                locally_initiated: true,
2634            },
2635        );
2636    }
2637
2638    #[fuchsia::test(allow_stalls = false)]
2639    async fn doze_then_wake() {
2640        let (fake_device, fake_device_state) = FakeDevice::new().await;
2641        let (mut ctx, _) = make_context(fake_device);
2642
2643        let mut r_sta = make_remote_client();
2644        r_sta.state = StateMachine::new(State::Associated {
2645            aid: 1,
2646            eapol_controlled_port: None,
2647            active_timeout: None,
2648            ps_state: PowerSaveState::Awake,
2649        });
2650
2651        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
2652
2653        // Send a bunch of Ethernet frames.
2654        r_sta
2655            .handle_eth_frame(
2656                &mut ctx,
2657                *CLIENT_ADDR2,
2658                *CLIENT_ADDR,
2659                0x1234,
2660                &[1, 2, 3, 4, 5][..],
2661                0.into(),
2662            )
2663            .expect("expected OK");
2664        r_sta
2665            .handle_eth_frame(
2666                &mut ctx,
2667                *CLIENT_ADDR2,
2668                *CLIENT_ADDR,
2669                0x1234,
2670                &[6, 7, 8, 9, 0][..],
2671                0.into(),
2672            )
2673            .expect("expected OK");
2674
2675        assert!(r_sta.has_buffered_frames());
2676
2677        // Make sure nothing has been actually sent to the WLAN queue.
2678        assert_eq!(fake_device_state.lock().wlan_queue.len(), 0);
2679
2680        r_sta.set_power_state(&mut ctx, mac::PowerState::AWAKE).expect("expected wake OK");
2681        assert!(!r_sta.has_buffered_frames());
2682        assert_eq!(fake_device_state.lock().wlan_queue.len(), 2);
2683
2684        assert_eq!(
2685            &fake_device_state.lock().wlan_queue[0].0[..],
2686            &[
2687                // Mgmt header
2688                0b00001000, 0b00100010, // Frame Control
2689                0, 0, // Duration
2690                3, 3, 3, 3, 3, 3, // addr1
2691                2, 2, 2, 2, 2, 2, // addr2
2692                1, 1, 1, 1, 1, 1, // addr3
2693                0x10, 0, // Sequence Control
2694                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
2695                0, 0, 0, // OUI
2696                0x12, 0x34, // Protocol ID
2697                // Data
2698                1, 2, 3, 4, 5,
2699            ][..]
2700        );
2701        assert_eq!(
2702            &fake_device_state.lock().wlan_queue[1].0[..],
2703            &[
2704                // Mgmt header
2705                0b00001000, 0b00000010, // Frame Control
2706                0, 0, // Duration
2707                3, 3, 3, 3, 3, 3, // addr1
2708                2, 2, 2, 2, 2, 2, // addr2
2709                1, 1, 1, 1, 1, 1, // addr3
2710                0x20, 0, // Sequence Control
2711                0xAA, 0xAA, 0x03, // DSAP, SSAP, Control, OUI
2712                0, 0, 0, // OUI
2713                0x12, 0x34, // Protocol ID
2714                // Data
2715                6, 7, 8, 9, 0,
2716            ][..]
2717        );
2718    }
2719
2720    #[fuchsia::test(allow_stalls = false)]
2721    async fn doze_then_doze() {
2722        let (fake_device, _) = FakeDevice::new().await;
2723        let (mut ctx, _) = make_context(fake_device);
2724
2725        let mut r_sta = make_remote_client();
2726        r_sta.state = StateMachine::new(State::Associated {
2727            aid: 1,
2728            eapol_controlled_port: None,
2729            active_timeout: None,
2730            ps_state: PowerSaveState::Awake,
2731        });
2732
2733        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
2734        r_sta.set_power_state(&mut ctx, mac::PowerState::DOZE).expect("expected doze OK");
2735    }
2736
2737    #[fuchsia::test(allow_stalls = false)]
2738    async fn wake_then_wake() {
2739        let (fake_device, _) = FakeDevice::new().await;
2740        let (mut ctx, _) = make_context(fake_device);
2741
2742        let mut r_sta = make_remote_client();
2743        r_sta.state = StateMachine::new(State::Associated {
2744            aid: 1,
2745            eapol_controlled_port: None,
2746            active_timeout: None,
2747            ps_state: PowerSaveState::Awake,
2748        });
2749
2750        r_sta.set_power_state(&mut ctx, mac::PowerState::AWAKE).expect("expected wake OK");
2751        r_sta.set_power_state(&mut ctx, mac::PowerState::AWAKE).expect("expected wake OK");
2752    }
2753
2754    #[fuchsia::test(allow_stalls = false)]
2755    async fn doze_not_associated() {
2756        let (fake_device, _) = FakeDevice::new().await;
2757        let (mut ctx, _) = make_context(fake_device);
2758
2759        let mut r_sta = make_remote_client();
2760        r_sta.state = StateMachine::new(State::Authenticating);
2761
2762        assert_variant!(
2763            r_sta
2764                .set_power_state(&mut ctx, mac::PowerState::DOZE)
2765                .expect_err("expected doze error"),
2766            ClientRejection::NotAssociated
2767        );
2768    }
2769
2770    #[fuchsia::test(allow_stalls = false)]
2771    async fn wake_not_associated() {
2772        let (fake_device, _) = FakeDevice::new().await;
2773        let (mut ctx, _) = make_context(fake_device);
2774
2775        let mut r_sta = make_remote_client();
2776        r_sta.state = StateMachine::new(State::Authenticating);
2777
2778        r_sta.set_power_state(&mut ctx, mac::PowerState::AWAKE).expect("expected wake OK");
2779    }
2780}