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