wlan_sme/ap/remote_client/
state.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::ap::authenticator::Authenticator;
6use crate::ap::event::*;
7use crate::ap::remote_client::RemoteClient;
8use crate::ap::{Context, RsnCfg, aid};
9use anyhow::{ensure, format_err};
10
11use fuchsia_sync::Mutex;
12use ieee80211::MacAddr;
13use log::error;
14use std::sync::Arc;
15use wlan_common::ie::SupportedRate;
16use wlan_common::ie::rsn::rsne;
17use wlan_common::mac::{Aid, CapabilityInfo};
18use wlan_common::timer::EventHandle;
19use wlan_rsn::gtk::GtkProvider;
20use wlan_rsn::nonce::NonceReader;
21use wlan_rsn::rsna::{SecAssocStatus, SecAssocUpdate, UpdateSink};
22use wlan_rsn::{NegotiatedProtection, ProtectionInfo};
23use wlan_statemachine::*;
24use {fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme};
25
26// This is not specified by 802.11, but we need some way of kicking out clients that authenticate
27// but don't intend to associate.
28const ASSOCIATION_TIMEOUT_SECONDS: i64 = 300;
29
30/// Authenticating is the initial state a client is in when it arrives at the SME.
31///
32/// It may proceed to Authenticated if an appropriate MLME-AUTHENTICATE.indication is received.
33///
34/// If a client had previously been in an authenticated state (i.e. Authenticated or Associated) and
35/// is no longer, it must be forgotten from the SME's known clients.
36pub struct Authenticating;
37
38impl Authenticating {
39    /// Handles MLME-AUTHENTICATE.indication.
40    ///
41    /// Currently, only open system authentication is supported.
42    ///
43    /// If authentication succeeds, an event ID for association timeout is returned and the client
44    /// state machine may proceed to Associated. Otherwise, an error is returned and the client
45    /// should be forgotten from the SME.
46    fn handle_auth_ind(
47        &self,
48        r_sta: &mut RemoteClient,
49        ctx: &mut Context,
50        auth_type: fidl_mlme::AuthenticationTypes,
51    ) -> Result<EventHandle, anyhow::Error> {
52        // We only support open system authentication today.
53        if auth_type != fidl_mlme::AuthenticationTypes::OpenSystem {
54            return Err(format_err!("unsupported authentication type: {:?}", auth_type));
55        }
56
57        let event = ClientEvent::AssociationTimeout;
58        let timeout_event = r_sta.schedule_at(
59            ctx,
60            zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
61                ASSOCIATION_TIMEOUT_SECONDS,
62            )),
63            event,
64        );
65
66        Ok(timeout_event)
67    }
68}
69
70/// Creates a new WPA2-PSK CCMP-128 authenticator.
71fn new_authenticator_from_rsne(
72    device_addr: MacAddr,
73    client_addr: MacAddr,
74    s_rsne_bytes: &[u8],
75    a_rsn: &RsnCfg,
76) -> Result<Box<dyn Authenticator>, anyhow::Error> {
77    let (_, s_rsne) =
78        rsne::from_bytes(s_rsne_bytes).map_err(|e| format_err!("failed to parse RSNE: {:?}", e))?;
79    ensure!(s_rsne.is_valid_subset_of(&a_rsn.rsne)?, "incompatible client RSNE");
80
81    let nonce_reader = NonceReader::new(&device_addr)?;
82
83    // TODO(b/311404887): |key_id| should be based on the current rotation. This
84    // ESSSA implementation does not support GTK key rotation by an
85    // Authenticator.
86    //
87    // TODO(b/311404887): |key_rsc| should be based on the packet number of GTK
88    // encrypted packets. This ESSSA implementation does not support tracking the
89    // packet number of GTK encrypted packets.
90    let gtk_provider =
91        GtkProvider::new(NegotiatedProtection::from_rsne(&s_rsne)?.group_data, 1, 0)?;
92
93    Ok(Box::new(wlan_rsn::Authenticator::new_wpa2psk_ccmp128(
94        // Note: There should be one Reader per device, not per SME.
95        // Follow-up with improving on this.
96        nonce_reader,
97        Arc::new(Mutex::new(gtk_provider)),
98        a_rsn.psk.clone(),
99        client_addr,
100        ProtectionInfo::Rsne(s_rsne),
101        device_addr,
102        ProtectionInfo::Rsne(a_rsn.rsne.clone()),
103    )?))
104}
105
106/// Authenticated is the state a client is in when the SME has successfully accepted an
107/// MLME-AUTHENTICATE.indication.
108///
109/// While the client is Authenticated, a timeout event will fire to transition it back to
110/// Authenticating if it has not associated in time.
111pub struct Authenticated {
112    _timeout_event: EventHandle,
113}
114
115/// AssociationError holds an error to log and the result code to send to the MLME for the
116/// association rejection.
117struct AssociationError {
118    error: anyhow::Error,
119    result_code: fidl_mlme::AssociateResultCode,
120    reason_code: fidl_ieee80211::ReasonCode,
121}
122
123/// Contains information from a successful association.
124struct Association {
125    aid: Aid,
126    capabilities: CapabilityInfo,
127    rates: Vec<SupportedRate>,
128    rsna_link_state: Option<RsnaLinkState>,
129}
130
131impl Authenticated {
132    /// Handles an association indication.
133    ///
134    /// It will:
135    /// - assign an association ID from the provided indication map.
136    /// - find common rates between the client and the AP.
137    /// - if the AP has an RSN configuration, and the client has provided a supplicant RSNE,
138    ///   negotiate an EAPoL controlled port.
139    ///
140    /// If unsuccessful, the resulting error will indicate the MLME result code.
141    #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
142    fn handle_assoc_ind(
143        &self,
144        r_sta: &mut RemoteClient,
145        ctx: &mut Context,
146        aid_map: &mut aid::Map,
147        client_capablities: u16,
148        client_rates: Vec<SupportedRate>,
149        rsn_cfg: &Option<RsnCfg>,
150        s_rsne: Option<Vec<u8>>,
151    ) -> Result<Association, AssociationError> {
152        let rsna_link_state = match (s_rsne.as_ref(), rsn_cfg) {
153            (Some(s_rsne_bytes), Some(a_rsn)) => {
154                let authenticator = new_authenticator_from_rsne(
155                    ctx.device_info.sta_addr.into(),
156                    r_sta.addr,
157                    s_rsne_bytes,
158                    a_rsn,
159                )
160                .map_err(|error| AssociationError {
161                    error,
162                    result_code: fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch,
163                    reason_code: fidl_ieee80211::ReasonCode::Ieee8021XAuthFailed,
164                })?;
165
166                Some(RsnaLinkState::new(authenticator))
167            }
168            (None, None) => None,
169            _ => {
170                return Err(AssociationError {
171                    error: format_err!("unexpected RSN element: {:?}", s_rsne),
172                    result_code: fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch,
173                    reason_code: fidl_ieee80211::ReasonCode::ReasonInvalidElement,
174                });
175            }
176        };
177
178        let aid = aid_map.assign_aid().map_err(|error| AssociationError {
179            error,
180            result_code: fidl_mlme::AssociateResultCode::RefusedReasonUnspecified,
181            reason_code: fidl_ieee80211::ReasonCode::UnspecifiedReason,
182        })?;
183
184        // TODO(https://g-issues.fuchsia.dev/issues/406220225): Intersect client and AP rates for
185        // SoftMAC AP.
186        let (capabilities, rates) = (CapabilityInfo(client_capablities), client_rates);
187        Ok(Association {
188            capabilities: capabilities
189                // IEEE Std 802.11-2016, 9.4.1.4: An AP sets the Privacy subfield to 1 within
190                // transmitted Beacon, Probe Response, (Re)Association Response frames if data
191                // confidentiality is required for all Data frames exchanged within the BSS.
192                .with_privacy(rsna_link_state.is_some()),
193            aid,
194            rates,
195            rsna_link_state,
196        })
197    }
198}
199
200/// RsnaLinkState contains the link state for 802.1X EAP authentication, if RSN configuration is
201/// present.
202#[derive(Debug)]
203struct RsnaLinkState {
204    authenticator: Box<dyn Authenticator>,
205
206    /// The last key frame may be replayed up to RSNA_NEGOTIATION_REQUEST_MAX_ATTEMPTS times, so
207    /// we hold onto it here.
208    last_key_frame: Option<eapol::KeyFrameBuf>,
209
210    request_attempts: usize,
211    request_timeout: Option<EventHandle>,
212    negotiation_timeout: Option<EventHandle>,
213}
214
215pub const RSNA_NEGOTIATION_REQUEST_MAX_ATTEMPTS: usize = 4;
216pub const RSNA_NEGOTIATION_REQUEST_TIMEOUT_SECONDS: i64 = 1;
217pub const RSNA_NEGOTIATION_TIMEOUT_SECONDS: i64 = 5;
218
219impl RsnaLinkState {
220    fn new(authenticator: Box<dyn Authenticator>) -> Self {
221        Self {
222            authenticator,
223            last_key_frame: None,
224            request_attempts: 0,
225            request_timeout: None,
226            negotiation_timeout: None,
227        }
228    }
229
230    /// Initiates a key exchange between the remote client and AP.
231    ///
232    /// It will also set a key exchange timeout.
233    fn initiate_key_exchange(
234        &mut self,
235        r_sta: &mut RemoteClient,
236        ctx: &mut Context,
237    ) -> Result<(), anyhow::Error> {
238        let mut update_sink = vec![];
239        self.authenticator.reset();
240        self.authenticator.initiate(&mut update_sink)?;
241        self.process_authenticator_updates(r_sta, ctx, &update_sink);
242
243        if self.last_key_frame.is_none() {
244            return Err(format_err!("no key frame was produced on authenticator initiation"));
245        }
246
247        self.negotiation_timeout = Some(r_sta.schedule_at(
248            ctx,
249            zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
250                RSNA_NEGOTIATION_TIMEOUT_SECONDS,
251            )),
252            ClientEvent::RsnaTimeout(RsnaTimeout::Negotiation),
253        ));
254
255        self.reschedule_request_timeout(r_sta, ctx);
256        Ok(())
257    }
258
259    fn reschedule_request_timeout(&mut self, r_sta: &mut RemoteClient, ctx: &mut Context) {
260        self.request_timeout = Some(r_sta.schedule_at(
261            ctx,
262            zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
263                RSNA_NEGOTIATION_REQUEST_TIMEOUT_SECONDS,
264            )),
265            ClientEvent::RsnaTimeout(RsnaTimeout::Request),
266        ));
267    }
268
269    fn handle_rsna_timeout(
270        &mut self,
271        r_sta: &mut RemoteClient,
272        ctx: &mut Context,
273        timeout_type: RsnaTimeout,
274    ) -> Result<(), RsnaNegotiationError> {
275        match timeout_type {
276            RsnaTimeout::Request => self.handle_rsna_request_timeout(r_sta, ctx),
277            RsnaTimeout::Negotiation => self.handle_rsna_negotiation_timeout(),
278        }
279    }
280
281    fn handle_rsna_request_timeout(
282        &mut self,
283        r_sta: &mut RemoteClient,
284        ctx: &mut Context,
285    ) -> Result<(), RsnaNegotiationError> {
286        self.request_timeout = None;
287
288        self.request_attempts += 1;
289        if self.request_attempts >= RSNA_NEGOTIATION_REQUEST_MAX_ATTEMPTS {
290            return Err(RsnaNegotiationError::Timeout);
291        }
292
293        let frame = self
294            .last_key_frame
295            .as_ref()
296            .ok_or(RsnaNegotiationError::Error(format_err!("no key frame available to resend?")))?;
297
298        r_sta.send_eapol_req(ctx, frame.clone());
299        self.reschedule_request_timeout(r_sta, ctx);
300        Ok(())
301    }
302
303    fn handle_rsna_negotiation_timeout(&mut self) -> Result<(), RsnaNegotiationError> {
304        self.negotiation_timeout = None;
305        Err(RsnaNegotiationError::Timeout)
306    }
307
308    /// Processes updates from the authenticator.
309    fn process_authenticator_updates(
310        &mut self,
311        r_sta: &mut RemoteClient,
312        ctx: &mut Context,
313        update_sink: &UpdateSink,
314    ) {
315        for update in update_sink {
316            match update {
317                SecAssocUpdate::TxEapolKeyFrame { frame, .. } => {
318                    r_sta.send_eapol_req(ctx, frame.clone());
319                    self.last_key_frame = Some(frame.clone());
320                }
321                SecAssocUpdate::Key(key) => r_sta.send_key(ctx, key),
322                SecAssocUpdate::Status(status) => {
323                    if *status == SecAssocStatus::EssSaEstablished {
324                        r_sta.send_set_controlled_port_req(
325                            ctx,
326                            fidl_mlme::ControlledPortState::Open,
327                        );
328
329                        // Negotiation is complete, clear the timeout and stop storing the last key
330                        // frame.
331                        self.last_key_frame = None;
332                        self.request_timeout = None;
333                        self.negotiation_timeout = None;
334                    }
335                }
336                update => error!("Unhandled association update: {:?}", update),
337            }
338        }
339    }
340
341    /// Passes EAPoL frames into the underlying authenticator.
342    fn handle_eapol_frame(
343        &mut self,
344        r_sta: &mut RemoteClient,
345        ctx: &mut Context,
346        data: &[u8],
347    ) -> Result<(), anyhow::Error> {
348        self.request_attempts = 0;
349
350        let authenticator = self.authenticator.as_mut();
351        let key_frame = eapol::KeyFrameRx::parse(
352            authenticator.get_negotiated_protection().mic_size as usize,
353            data,
354        )?;
355
356        let mut update_sink = vec![];
357        // TODO(b/311379419): The AP doesn't currently track whether a client is responsive.
358        authenticator.on_eapol_frame(&mut update_sink, eapol::Frame::Key(key_frame))?;
359        self.process_authenticator_updates(r_sta, ctx, &update_sink);
360        Ok(())
361    }
362
363    fn handle_eapol_conf(
364        &mut self,
365        r_sta: &mut RemoteClient,
366        ctx: &mut Context,
367        result: fidl_mlme::EapolResultCode,
368    ) -> Result<(), anyhow::Error> {
369        let authenticator = self.authenticator.as_mut();
370
371        let mut update_sink = vec![];
372        authenticator.on_eapol_conf(&mut update_sink, result)?;
373        self.process_authenticator_updates(r_sta, ctx, &update_sink);
374        Ok(())
375    }
376}
377
378/// Authenticated is the state a client is in when the SME has successfully accepted an
379/// MLME-ASSOCIATE.indication.
380pub struct Associated {
381    aid: Aid,
382    rsna_link_state: Option<RsnaLinkState>,
383}
384
385enum RsnaNegotiationError {
386    Error(anyhow::Error),
387    Timeout,
388}
389
390impl Associated {
391    fn aid(&self) -> Aid {
392        self.aid
393    }
394
395    /// If RSNA configuration is present, handles per-request (i.e. key frame resend) or negotiation
396    /// timeouts.
397    fn handle_rsna_timeout(
398        &mut self,
399        r_sta: &mut RemoteClient,
400        ctx: &mut Context,
401        timeout_type: RsnaTimeout,
402    ) -> Result<(), RsnaNegotiationError> {
403        match self.rsna_link_state.as_mut() {
404            Some(rsna_link_state) => rsna_link_state.handle_rsna_timeout(r_sta, ctx, timeout_type),
405            None => Ok(()),
406        }
407    }
408
409    /// If RSNA configuration is present, forwards EAPoL frames to the authenticator. Otherwise,
410    /// returns an error.
411    fn handle_eapol_ind(
412        &mut self,
413        r_sta: &mut RemoteClient,
414        ctx: &mut Context,
415        data: &[u8],
416    ) -> Result<(), anyhow::Error> {
417        match self.rsna_link_state.as_mut() {
418            Some(rsna_link_state) => rsna_link_state.handle_eapol_frame(r_sta, ctx, data),
419            None => Err(format_err!("received EAPoL indication without RSNA link state")),
420        }
421    }
422
423    /// If RSNA configuration is present, forwards EAPoL frames to the authenticator. Otherwise,
424    /// returns an error.
425    fn handle_eapol_conf(
426        &mut self,
427        r_sta: &mut RemoteClient,
428        ctx: &mut Context,
429        result: fidl_mlme::EapolResultCode,
430    ) -> Result<(), anyhow::Error> {
431        match self.rsna_link_state.as_mut() {
432            Some(rsna_link_state) => rsna_link_state.handle_eapol_conf(r_sta, ctx, result),
433            None => Err(format_err!("received EAPoL confirm without RSNA link state")),
434        }
435    }
436
437    /// Handles an incoming disassociation from the client. An event ID for association timeout is
438    /// returned and the client state machine may proceed to Associated.
439    fn handle_disassoc_ind(
440        &self,
441        r_sta: &mut RemoteClient,
442        ctx: &mut Context,
443        aid_map: &mut aid::Map,
444    ) -> EventHandle {
445        aid_map.release_aid(self.aid);
446        let event = ClientEvent::AssociationTimeout;
447        r_sta.schedule_at(
448            ctx,
449            zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(
450                ASSOCIATION_TIMEOUT_SECONDS,
451            )),
452            event,
453        )
454    }
455}
456
457statemachine!(
458    pub enum States,
459
460    () => Authenticating,
461    Authenticating => Authenticated,
462    Authenticated => Associated,
463
464    Associated => Authenticated,
465    Authenticated => Authenticating,
466
467    // Allow associated to go directly to authenticating, if we fail RSN authentication.
468    Associated => Authenticating,
469);
470
471/// The external representation of the state machine for the client.
472impl States {
473    pub fn new_initial() -> States {
474        States::from(State::new(Authenticating))
475    }
476
477    /// Retrieves the association ID of the remote client.
478    ///
479    /// aid() != None iff the client is associated.
480    pub fn aid(&self) -> Option<Aid> {
481        match self {
482            States::Associated(state) => Some(state.aid()),
483            _ => None,
484        }
485    }
486
487    /// Returns if the client is (at least) authenticated (i.e. authenticated or associated).
488    pub fn authenticated(&self) -> bool {
489        !matches!(self, States::Authenticating(..))
490    }
491
492    /// Handles an incoming MLME-AUTHENTICATE.indication.
493    ///
494    /// On success, sends a successful MLME-AUTHENTICATE.response and transitions the client to
495    /// Authenticated.
496    ///
497    /// Otherwise, sends a refused MLME-AUTHENTICATE.response and leaves the client in
498    /// Authenticating. The caller should forget this client from its internal state.
499    pub fn handle_auth_ind(
500        self,
501        r_sta: &mut RemoteClient,
502        ctx: &mut Context,
503        auth_type: fidl_mlme::AuthenticationTypes,
504    ) -> States {
505        match self {
506            States::Authenticating(state) => match state.handle_auth_ind(r_sta, ctx, auth_type) {
507                Ok(timeout_event) => {
508                    r_sta.send_authenticate_resp(ctx, fidl_mlme::AuthenticateResultCode::Success);
509                    state.transition_to(Authenticated { _timeout_event: timeout_event }).into()
510                }
511                Err(e) => {
512                    error!("client {:02X?} MLME-AUTHENTICATE.indication: {}", r_sta.addr, e);
513                    r_sta.send_authenticate_resp(ctx, fidl_mlme::AuthenticateResultCode::Refused);
514                    state.into()
515                }
516            },
517            _ => {
518                r_sta.send_authenticate_resp(ctx, fidl_mlme::AuthenticateResultCode::Refused);
519                self
520            }
521        }
522    }
523
524    /// Handles an incoming MLME-ASSOCIATE.indication.
525    ///
526    /// On success, sends a successful MLME-ASSOCIATE.response and transitions the client to
527    /// Authenticated.
528    ///
529    /// Otherwise, sends an unsuccessful MLME-ASSOCIATE.response AND a MLME-DEAUTHENTICATE-request,
530    /// and transitions the client to Authenticating. The caller should forget this client from its
531    /// internal state.
532    #[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
533    pub fn handle_assoc_ind(
534        self,
535        r_sta: &mut RemoteClient,
536        ctx: &mut Context,
537        aid_map: &mut aid::Map,
538        client_capablities: u16,
539        client_rates: Vec<SupportedRate>,
540        rsn_cfg: &Option<RsnCfg>,
541        s_rsne: Option<Vec<u8>>,
542    ) -> States {
543        match self {
544            States::Authenticated(state) => {
545                match state.handle_assoc_ind(
546                    r_sta,
547                    ctx,
548                    aid_map,
549                    client_capablities,
550                    client_rates,
551                    rsn_cfg,
552                    s_rsne,
553                ) {
554                    Ok(Association { aid, capabilities, rates, mut rsna_link_state }) => {
555                        r_sta.send_associate_resp(
556                            ctx,
557                            fidl_mlme::AssociateResultCode::Success,
558                            aid,
559                            capabilities,
560                            rates,
561                        );
562
563                        // RSNA authentication needs to be handled after association.
564                        if let Some(rsna_link_state) = rsna_link_state.as_mut()
565                            && let Err(error) = rsna_link_state.initiate_key_exchange(r_sta, ctx)
566                        {
567                            error!(
568                                "client {:02X?} MLME-ASSOCIATE.indication (key exchange): {}",
569                                r_sta.addr, error
570                            );
571                            r_sta.send_deauthenticate_req(
572                                ctx,
573                                fidl_ieee80211::ReasonCode::Ieee8021XAuthFailed,
574                            );
575                            return state.transition_to(Authenticating).into();
576                        }
577
578                        state.transition_to(Associated { aid, rsna_link_state }).into()
579                    }
580                    Err(AssociationError { error, result_code, reason_code }) => {
581                        error!("client {:02X?} MLME-ASSOCIATE.indication: {}", r_sta.addr, error);
582                        r_sta.send_associate_resp(ctx, result_code, 0, CapabilityInfo(0), vec![]);
583                        r_sta.send_deauthenticate_req(ctx, reason_code);
584                        state.transition_to(Authenticating).into()
585                    }
586                }
587            }
588            _ => {
589                r_sta.send_associate_resp(
590                    ctx,
591                    fidl_mlme::AssociateResultCode::RefusedReasonUnspecified,
592                    0,
593                    CapabilityInfo(0),
594                    vec![],
595                );
596                self
597            }
598        }
599    }
600
601    /// Handles an incoming MLME-DISASSOCIATE.indication.
602    ///
603    /// Unconditionally transitions the client to Authenticated.
604    pub fn handle_disassoc_ind(
605        self,
606        r_sta: &mut RemoteClient,
607        ctx: &mut Context,
608        aid_map: &mut aid::Map,
609    ) -> States {
610        match self {
611            States::Associated(state) => {
612                let timeout_event = state.handle_disassoc_ind(r_sta, ctx, aid_map);
613                state.transition_to(Authenticated { _timeout_event: timeout_event }).into()
614            }
615            _ => self,
616        }
617    }
618
619    /// Handles an incoming EAPOL.indication.
620    ///
621    /// This may update the client's RSNA link state. This will not transition the client.
622    pub fn handle_eapol_ind(
623        self,
624        r_sta: &mut RemoteClient,
625        ctx: &mut Context,
626        data: &[u8],
627    ) -> States {
628        match self {
629            States::Associated(mut state) => {
630                if let Err(e) = state.handle_eapol_ind(r_sta, ctx, data) {
631                    error!("client {:02X?} EAPOL.indication: {}", r_sta.addr, e);
632                }
633                state.into()
634            }
635            _ => self,
636        }
637    }
638
639    /// Handles an incoming EAPOL.confirm.
640    ///
641    /// This may update the client's RSNA link state. This will not transition the client.
642    pub fn handle_eapol_conf(
643        self,
644        r_sta: &mut RemoteClient,
645        ctx: &mut Context,
646        result: fidl_mlme::EapolResultCode,
647    ) -> States {
648        match self {
649            States::Associated(mut state) => {
650                if let Err(e) = state.handle_eapol_conf(r_sta, ctx, result) {
651                    error!("client {:02X?} EAPOL.confirm: {}", r_sta.addr, e);
652                }
653                state.into()
654            }
655            _ => self,
656        }
657    }
658
659    /// Handles a timeout.
660    ///
661    /// If the timeout is not being handled by the underlying state (e.g. if an association timeout
662    /// fires but the client has transitioned to Associated), the timeout is ignored.
663    ///
664    /// If the timeout is a handled association timeout, MLME-DEAUTHENTICATE.request is sent to the
665    /// client the client is transitioned to Authenticating. The caller should forget this client
666    /// from its internal state.
667    ///
668    /// If the timeout is a key exchange timeout, the client may either reattempt its key exchange
669    /// or otherwise exceed the maximum number of key exchange attempts:
670    ///
671    /// - If key exchange initiation is successful, no transition occurs.
672    ///
673    /// - If key exchange fails, MLME-DEAUTHENTICATE.request is sent to the client and the client
674    ///   is transitioned to Authenticating. The caller should forget this client from its internal
675    ///   state.
676    ///
677    /// - If the client is out key exchange attempts, MLME-DEAUTHENTICATE.request is sent to the
678    ///   client and the client is transitioned to Authenticating. The caller should forget this
679    ///   client from its internal state.
680    pub fn handle_timeout(
681        self,
682        r_sta: &mut RemoteClient,
683        ctx: &mut Context,
684        event: ClientEvent,
685    ) -> States {
686        match event {
687            ClientEvent::AssociationTimeout => match self {
688                States::Authenticated(state) => {
689                    r_sta.send_deauthenticate_req(
690                        ctx,
691                        // Not sure if this is the correct reason code.
692                        fidl_ieee80211::ReasonCode::InvalidAuthentication,
693                    );
694                    state.transition_to(Authenticating).into()
695                }
696                States::Associated(state) => {
697                    // If the client is already associated, we can't time it out.
698                    state.into()
699                }
700                _ => {
701                    error!(
702                        "client {:02X?} received AssociationTimeout in unexpected state; \
703                         ignoring timeout",
704                        r_sta.addr,
705                    );
706                    self
707                }
708            },
709            ClientEvent::RsnaTimeout(timeout_type) => match self {
710                States::Associated(state) => {
711                    let (transition, mut state) = state.release_data();
712                    match state.handle_rsna_timeout(r_sta, ctx, timeout_type) {
713                        Ok(()) => transition.to(state).into(),
714                        Err(e) => {
715                            let reason_code = match e {
716                                RsnaNegotiationError::Error(e) => {
717                                    error!(
718                                        "client {:02X?} RSNA negotiation error: {}",
719                                        r_sta.addr, e
720                                    );
721                                    fidl_ieee80211::ReasonCode::UnspecifiedReason
722                                }
723                                RsnaNegotiationError::Timeout => {
724                                    fidl_ieee80211::ReasonCode::FourwayHandshakeTimeout
725                                }
726                            };
727                            r_sta.send_deauthenticate_req(ctx, reason_code);
728                            transition.to(Authenticating).into()
729                        }
730                    }
731                }
732                _ => {
733                    error!(
734                        "client {:02X?} received RsnaTimeout in unexpected state; \
735                         ignoring timeout",
736                        r_sta.addr,
737                    );
738                    self
739                }
740            },
741        }
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use super::*;
748    use crate::ap::create_rsn_cfg;
749    use crate::ap::test_utils::MockAuthenticator;
750    use crate::{MlmeRequest, MlmeSink, MlmeStream, test_utils};
751    use assert_matches::assert_matches;
752    use futures::channel::mpsc;
753    use ieee80211::{MacAddr, MacAddrBytes, Ssid};
754    use std::sync::LazyLock;
755    use wlan_common::ie::rsn::akm::AKM_PSK;
756    use wlan_common::ie::rsn::cipher::{CIPHER_CCMP_128, CIPHER_GCMP_256};
757    use wlan_common::ie::rsn::rsne::Rsne;
758    use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
759    use wlan_common::timer;
760    use wlan_rsn::key::exchange::Key;
761
762    static AP_ADDR: LazyLock<MacAddr> = LazyLock::new(|| [6u8; 6].into());
763    static CLIENT_ADDR: LazyLock<MacAddr> = LazyLock::new(|| [7u8; 6].into());
764
765    fn make_remote_client() -> RemoteClient {
766        RemoteClient::new(*CLIENT_ADDR)
767    }
768
769    fn make_env() -> (Context, MlmeStream, timer::EventStream<Event>) {
770        let device_info = test_utils::fake_device_info(*AP_ADDR);
771        let (mlme_sink, mlme_stream) = mpsc::unbounded();
772        let (timer, time_stream) = timer::create_timer();
773        let ctx = Context {
774            device_info,
775            spectrum_management_support: fake_spectrum_management_support_empty(),
776            mlme_sink: MlmeSink::new(mlme_sink),
777            timer,
778        };
779        (ctx, mlme_stream, time_stream)
780    }
781
782    #[test]
783    fn authenticating_goes_to_authenticated() {
784        let mut r_sta = make_remote_client();
785        let (mut ctx, mut mlme_stream, mut time_stream) = make_env();
786
787        let state = States::from(State::new(Authenticating));
788        let state =
789            state.handle_auth_ind(&mut r_sta, &mut ctx, fidl_mlme::AuthenticationTypes::OpenSystem);
790
791        let (_, Authenticated { _timeout_event }) = match state {
792            States::Authenticated(state) => state.release_data(),
793            _ => panic!("unexpected state"),
794        };
795
796        let (_, timed_event, _) = time_stream.try_next().unwrap().expect("expected timed event");
797        assert_eq!(timed_event.id, _timeout_event.id());
798        assert_matches!(timed_event.event, Event::Client { addr, event } => {
799            assert_eq!(addr, *CLIENT_ADDR);
800            assert_matches!(event, ClientEvent::AssociationTimeout);
801        });
802
803        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
804        assert_matches!(mlme_event, MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
805            peer_sta_address,
806            result_code,
807        }) => {
808            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
809            assert_eq!(result_code, fidl_mlme::AuthenticateResultCode::Success);
810        });
811    }
812
813    #[test]
814    fn authenticating_stays_authenticating_with_unsupported_authentication_type() {
815        let mut r_sta = make_remote_client();
816        let (mut ctx, mut mlme_stream, _) = make_env();
817
818        let state = States::from(State::new(Authenticating));
819        let state =
820            state.handle_auth_ind(&mut r_sta, &mut ctx, fidl_mlme::AuthenticationTypes::SharedKey);
821
822        let (_, Authenticating) = match state {
823            States::Authenticating(state) => state.release_data(),
824            _ => panic!("unexpected state"),
825        };
826
827        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
828        assert_matches!(mlme_event, MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
829            peer_sta_address,
830            result_code,
831        }) => {
832            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
833            assert_eq!(result_code, fidl_mlme::AuthenticateResultCode::Refused);
834        });
835    }
836
837    #[test]
838    fn authenticating_refuses_association() {
839        let mut r_sta = make_remote_client();
840        let (mut ctx, mut mlme_stream, _) = make_env();
841
842        let state = States::from(State::new(Authenticating));
843
844        let mut aid_map = aid::Map::default();
845        let state = state.handle_assoc_ind(
846            &mut r_sta,
847            &mut ctx,
848            &mut aid_map,
849            CapabilityInfo(0).with_short_preamble(true).raw(),
850            vec![SupportedRate(0b11111000)],
851            &None,
852            None,
853        );
854
855        let (_, Authenticating) = match state {
856            States::Authenticating(state) => state.release_data(),
857            _ => panic!("unexpected state"),
858        };
859
860        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
861        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
862            peer_sta_address,
863            association_id,
864            result_code,
865            capability_info,
866            rates,
867        }) => {
868            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
869            assert_eq!(association_id, 0);
870            assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedReasonUnspecified);
871            assert_eq!(capability_info, 0);
872            assert_eq!(rates, Vec::<u8>::new());
873        });
874    }
875
876    #[test]
877    fn authenticated_refuses_authentication() {
878        let mut r_sta = make_remote_client();
879        let (mut ctx, mut mlme_stream, _) = make_env();
880
881        let state: States = State::new(Authenticating)
882            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
883            .into();
884        let state =
885            state.handle_auth_ind(&mut r_sta, &mut ctx, fidl_mlme::AuthenticationTypes::SharedKey);
886
887        let (_, Authenticated { .. }) = match state {
888            States::Authenticated(state) => state.release_data(),
889            _ => panic!("unexpected state"),
890        };
891
892        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
893        assert_matches!(mlme_event, MlmeRequest::AuthResponse(fidl_mlme::AuthenticateResponse {
894            peer_sta_address,
895            result_code,
896        }) => {
897            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
898            assert_eq!(result_code, fidl_mlme::AuthenticateResultCode::Refused);
899        });
900    }
901
902    #[test]
903    fn authenticated_deauthenticates_on_timeout() {
904        let mut r_sta = make_remote_client();
905        let (mut ctx, mut mlme_stream, _) = make_env();
906
907        let state: States = State::new(Authenticating)
908            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
909            .into();
910        let state = state.handle_timeout(&mut r_sta, &mut ctx, ClientEvent::AssociationTimeout);
911
912        let (_, Authenticating) = match state {
913            States::Authenticating(state) => state.release_data(),
914            _ => panic!("unexpected state"),
915        };
916
917        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
918        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
919            peer_sta_address,
920            reason_code,
921        }) => {
922            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
923            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::InvalidAuthentication);
924        });
925    }
926
927    #[test]
928    fn authenticated_goes_to_associated_no_rsn() {
929        let mut r_sta = make_remote_client();
930        let (mut ctx, mut mlme_stream, _) = make_env();
931
932        let state: States = State::new(Authenticating)
933            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
934            .into();
935
936        let mut aid_map = aid::Map::default();
937        let state = state.handle_assoc_ind(
938            &mut r_sta,
939            &mut ctx,
940            &mut aid_map,
941            CapabilityInfo(0).with_short_preamble(true).raw(),
942            vec![SupportedRate(0b11111000)],
943            &None,
944            None,
945        );
946
947        let (_, Associated { rsna_link_state, aid }) = match state {
948            States::Associated(state) => state.release_data(),
949            _ => panic!("unexpected_state"),
950        };
951
952        assert_matches!(rsna_link_state, None);
953        assert_eq!(aid, 1);
954
955        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
956        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
957            peer_sta_address,
958            result_code,
959            capability_info,
960            rates,
961            ..
962        }) => {
963            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
964            assert_eq!(result_code, fidl_mlme::AssociateResultCode::Success);
965            assert_eq!(capability_info, CapabilityInfo(0).with_short_preamble(true).raw());
966            assert_eq!(rates, vec![0b11111000]);
967        });
968    }
969
970    #[test]
971    fn authenticated_goes_to_associated() {
972        let mut r_sta = make_remote_client();
973        let (mut ctx, mut mlme_stream, _) = make_env();
974
975        let state: States = State::new(Authenticating)
976            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
977            .into();
978
979        let mut aid_map = aid::Map::default();
980        let _next_state = state.handle_assoc_ind(
981            &mut r_sta,
982            &mut ctx,
983            &mut aid_map,
984            CapabilityInfo(0).with_short_preamble(true).raw(),
985            vec![SupportedRate(0b11111000), SupportedRate(0b01111010)],
986            &None,
987            None,
988        );
989
990        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
991        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
992            capability_info,
993            rates,
994            ..
995        }) => {
996            assert_eq!(capability_info, CapabilityInfo(0).with_short_preamble(true).raw());
997            assert_eq!(rates, vec![0b11111000, 0b01111010]);
998        });
999    }
1000
1001    #[test]
1002    fn authenticated_goes_to_authenticating_out_of_aids() {
1003        let mut r_sta = make_remote_client();
1004        let (mut ctx, mut mlme_stream, _) = make_env();
1005
1006        let state: States = State::new(Authenticating)
1007            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1008            .into();
1009
1010        let mut aid_map = aid::Map::default();
1011        while aid_map.assign_aid().is_ok() {
1012            // Keep assigning AIDs until we run out of them.
1013        }
1014
1015        let state = state.handle_assoc_ind(
1016            &mut r_sta,
1017            &mut ctx,
1018            &mut aid_map,
1019            CapabilityInfo(0).with_short_preamble(true).raw(),
1020            vec![SupportedRate(0b11111000)],
1021            &None,
1022            None,
1023        );
1024
1025        let (_, Authenticating) = match state {
1026            States::Authenticating(state) => state.release_data(),
1027            _ => panic!("unexpected state"),
1028        };
1029
1030        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1031        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
1032            peer_sta_address,
1033            result_code,
1034            ..
1035        }) => {
1036            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1037            assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedReasonUnspecified);
1038        });
1039
1040        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1041        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1042            peer_sta_address,
1043            reason_code,
1044            ..
1045        }) => {
1046            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1047            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::UnspecifiedReason);
1048        });
1049    }
1050
1051    #[test]
1052    fn authenticated_goes_to_authenticating_with_bogus_rsn_ind() {
1053        let mut r_sta = make_remote_client();
1054        let (mut ctx, mut mlme_stream, _) = make_env();
1055
1056        let state: States = State::new(Authenticating)
1057            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1058            .into();
1059
1060        let s_rsne = Rsne::wpa2_rsne();
1061        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1062        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1063
1064        let mut aid_map = aid::Map::default();
1065        let state = state.handle_assoc_ind(
1066            &mut r_sta,
1067            &mut ctx,
1068            &mut aid_map,
1069            CapabilityInfo(0).with_short_preamble(true).raw(),
1070            vec![SupportedRate(0b11111000)],
1071            &None,
1072            Some(s_rsne_vec),
1073        );
1074
1075        let (_, Authenticating) = match state {
1076            States::Authenticating(state) => state.release_data(),
1077            _ => panic!("unexpected state"),
1078        };
1079
1080        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1081        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
1082            peer_sta_address,
1083            result_code,
1084            ..
1085        }) => {
1086            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1087            assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch);
1088        });
1089
1090        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1091        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1092            peer_sta_address,
1093            reason_code,
1094            ..
1095        }) => {
1096            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1097            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::ReasonInvalidElement);
1098        });
1099    }
1100
1101    #[test]
1102    fn authenticated_goes_to_authenticating_with_incompatible_rsn() {
1103        let mut r_sta = make_remote_client();
1104        let (mut ctx, mut mlme_stream, _) = make_env();
1105
1106        let state: States = State::new(Authenticating)
1107            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1108            .into();
1109
1110        let mut rsn_cfg =
1111            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1112        rsn_cfg.rsne = Rsne {
1113            group_data_cipher_suite: Some(CIPHER_GCMP_256),
1114            pairwise_cipher_suites: vec![CIPHER_CCMP_128],
1115            akm_suites: vec![AKM_PSK],
1116            ..Default::default()
1117        };
1118
1119        let s_rsne = Rsne {
1120            group_data_cipher_suite: Some(CIPHER_CCMP_128),
1121            pairwise_cipher_suites: vec![CIPHER_CCMP_128],
1122            akm_suites: vec![AKM_PSK],
1123            ..Default::default()
1124        };
1125        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1126        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1127
1128        let mut aid_map = aid::Map::default();
1129        let state = state.handle_assoc_ind(
1130            &mut r_sta,
1131            &mut ctx,
1132            &mut aid_map,
1133            CapabilityInfo(0).with_short_preamble(true).raw(),
1134            vec![SupportedRate(0b11111000)],
1135            &Some(rsn_cfg),
1136            Some(s_rsne_vec),
1137        );
1138
1139        let (_, Authenticating) = match state {
1140            States::Authenticating(state) => state.release_data(),
1141            _ => panic!("unexpected state"),
1142        };
1143
1144        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1145        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
1146            peer_sta_address,
1147            result_code,
1148            ..
1149        }) => {
1150            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1151            assert_eq!(result_code, fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch);
1152        });
1153
1154        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1155        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1156            peer_sta_address,
1157            reason_code,
1158            ..
1159        }) => {
1160            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1161            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::Ieee8021XAuthFailed);
1162        });
1163    }
1164
1165    #[test]
1166    fn authenticated_goes_to_associated_rsn() {
1167        let mut r_sta = make_remote_client();
1168        let (mut ctx, mut mlme_stream, _) = make_env();
1169
1170        let state: States = State::new(Authenticating)
1171            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1172            .into();
1173
1174        let rsn_cfg =
1175            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1176
1177        let mut s_rsne_vec = Vec::with_capacity(rsn_cfg.rsne.len());
1178        rsn_cfg.rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1179
1180        let mut aid_map = aid::Map::default();
1181        let state = state.handle_assoc_ind(
1182            &mut r_sta,
1183            &mut ctx,
1184            &mut aid_map,
1185            CapabilityInfo(0).with_short_preamble(true).raw(),
1186            vec![SupportedRate(0b11111000)],
1187            &Some(rsn_cfg),
1188            Some(s_rsne_vec),
1189        );
1190
1191        let (_, Associated { rsna_link_state, aid }) = match state {
1192            States::Associated(state) => state.release_data(),
1193            _ => panic!("unexpected_state"),
1194        };
1195
1196        assert_eq!(aid, 1);
1197        assert_matches!(rsna_link_state, Some(_));
1198
1199        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1200        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
1201            peer_sta_address,
1202            result_code,
1203            capability_info,
1204            rates,
1205            ..
1206        }) => {
1207            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1208            assert_eq!(result_code, fidl_mlme::AssociateResultCode::Success);
1209            assert_eq!(capability_info, CapabilityInfo(0).with_short_preamble(true).with_privacy(true).raw());
1210            assert_eq!(rates, vec![0b11111000]);
1211        });
1212
1213        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1214        assert_matches!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest { .. }));
1215    }
1216
1217    #[test]
1218    fn authenticated_goes_to_associated_rsn_different_cap() {
1219        let mut r_sta = make_remote_client();
1220        let (mut ctx, mut mlme_stream, _) = make_env();
1221
1222        let state: States = State::new(Authenticating)
1223            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1224            .into();
1225
1226        let rsn_cfg =
1227            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1228
1229        let mut s_rsne_vec = Vec::with_capacity(rsn_cfg.rsne.len());
1230        rsn_cfg.rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1231
1232        let mut aid_map = aid::Map::default();
1233        let state = state.handle_assoc_ind(
1234            &mut r_sta,
1235            &mut ctx,
1236            &mut aid_map,
1237            CapabilityInfo(0).with_short_preamble(true).with_spectrum_mgmt(true).raw(),
1238            vec![SupportedRate(0b11111000)],
1239            &Some(rsn_cfg),
1240            Some(s_rsne_vec),
1241        );
1242
1243        let (_, Associated { rsna_link_state, aid }) = match state {
1244            States::Associated(state) => state.release_data(),
1245            _ => panic!("unexpected_state"),
1246        };
1247
1248        assert_eq!(aid, 1);
1249        assert_matches!(rsna_link_state, Some(_));
1250
1251        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1252        assert_matches!(mlme_event, MlmeRequest::AssocResponse(fidl_mlme::AssociateResponse {
1253            peer_sta_address,
1254            result_code,
1255            capability_info,
1256            rates,
1257            ..
1258        }) => {
1259            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1260            assert_eq!(result_code, fidl_mlme::AssociateResultCode::Success);
1261            assert_eq!(
1262                capability_info,
1263                CapabilityInfo(0)
1264                    .with_short_preamble(true)
1265                    .with_spectrum_mgmt(true)
1266                    .with_privacy(true)
1267                    .raw());
1268            assert_eq!(rates, vec![0b11111000]);
1269        });
1270
1271        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1272        assert_matches!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest { .. }));
1273    }
1274
1275    #[test]
1276    fn associated_goes_to_authenticated() {
1277        let mut r_sta = make_remote_client();
1278        let (mut ctx, _, mut time_stream) = make_env();
1279        let mut aid_map = aid::Map::default();
1280
1281        let aid = aid_map.assign_aid().unwrap();
1282
1283        let state: States = State::new(Authenticating)
1284            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1285            .transition_to(Associated { aid, rsna_link_state: None })
1286            .into();
1287
1288        let state = state.handle_disassoc_ind(&mut r_sta, &mut ctx, &mut aid_map);
1289
1290        let (_, Authenticated { _timeout_event }) = match state {
1291            States::Authenticated(state) => state.release_data(),
1292            _ => panic!("unexpected state"),
1293        };
1294
1295        assert_eq!(aid, aid_map.assign_aid().unwrap());
1296
1297        let (_, timed_event, _) = time_stream.try_next().unwrap().expect("expected timed event");
1298        assert_eq!(timed_event.id, _timeout_event.id());
1299        assert_matches!(timed_event.event, Event::Client { addr, event } => {
1300            assert_eq!(addr, *CLIENT_ADDR);
1301            assert_matches!(event, ClientEvent::AssociationTimeout);
1302        });
1303    }
1304
1305    #[test]
1306    fn associated_ignores_rsna_negotiation_timeout_without_rsna_link_state() {
1307        let mut r_sta = make_remote_client();
1308        let (mut ctx, _, mut time_stream) = make_env();
1309
1310        let state: States = State::new(Authenticating)
1311            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1312            .transition_to(Associated { aid: 1, rsna_link_state: None })
1313            .into();
1314
1315        let state = state.handle_timeout(
1316            &mut r_sta,
1317            &mut ctx,
1318            ClientEvent::RsnaTimeout(RsnaTimeout::Negotiation),
1319        );
1320
1321        let (_, Associated { .. }) = match state {
1322            States::Associated(state) => state.release_data(),
1323            _ => panic!("unexpected_state"),
1324        };
1325
1326        assert_matches!(time_stream.try_next(), Err(_));
1327    }
1328
1329    #[test]
1330    fn associated_ignores_rsna_request_timeout_without_rsna_link_state() {
1331        let mut r_sta = make_remote_client();
1332        let (mut ctx, _, mut time_stream) = make_env();
1333
1334        let state: States = State::new(Authenticating)
1335            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1336            .transition_to(Associated { aid: 1, rsna_link_state: None })
1337            .into();
1338
1339        let state = state.handle_timeout(
1340            &mut r_sta,
1341            &mut ctx,
1342            ClientEvent::RsnaTimeout(RsnaTimeout::Request),
1343        );
1344
1345        let (_, Associated { .. }) = match state {
1346            States::Associated(state) => state.release_data(),
1347            _ => panic!("unexpected_state"),
1348        };
1349
1350        assert_matches!(time_stream.try_next(), Err(_));
1351    }
1352
1353    #[test]
1354    fn associated_handles_rsna_request_timeout() {
1355        let mut r_sta = make_remote_client();
1356        let (mut ctx, mut mlme_stream, mut time_stream) = make_env();
1357
1358        let rsn_cfg =
1359            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1360
1361        let s_rsne = Rsne::wpa2_rsne();
1362        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1363        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1364
1365        let state: States = State::new(Authenticating)
1366            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1367            .transition_to(Associated {
1368                aid: 1,
1369                rsna_link_state: Some(RsnaLinkState {
1370                    request_attempts: 0,
1371                    last_key_frame: Some(test_utils::eapol_key_frame()),
1372                    request_timeout: Some(EventHandle::new_test(2)),
1373                    negotiation_timeout: Some(EventHandle::new_test(3)),
1374                    authenticator: new_authenticator_from_rsne(
1375                        *AP_ADDR,
1376                        *CLIENT_ADDR,
1377                        &s_rsne_vec[..],
1378                        &rsn_cfg,
1379                    )
1380                    .unwrap(),
1381                }),
1382            })
1383            .into();
1384
1385        let state = state.handle_timeout(
1386            &mut r_sta,
1387            &mut ctx,
1388            ClientEvent::RsnaTimeout(RsnaTimeout::Request),
1389        );
1390
1391        let (_, Associated { rsna_link_state, .. }) = match state {
1392            States::Associated(state) => state.release_data(),
1393            _ => panic!("unexpected_state"),
1394        };
1395
1396        assert_eq!(rsna_link_state.as_ref().unwrap().request_attempts, 1);
1397
1398        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1399        assert_matches!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest { .. }));
1400
1401        let (_, timed_event, _) = time_stream.try_next().unwrap().expect("expected timed event");
1402        assert_eq!(
1403            timed_event.id,
1404            rsna_link_state.as_ref().unwrap().request_timeout.as_ref().unwrap().id()
1405        );
1406        assert_matches!(timed_event.event, Event::Client { addr, event } => {
1407            assert_eq!(addr, *CLIENT_ADDR);
1408            assert_matches!(event, ClientEvent::RsnaTimeout(RsnaTimeout::Request));
1409        });
1410    }
1411
1412    #[test]
1413    fn associated_handles_rsna_negotiation_timeout() {
1414        let mut r_sta = make_remote_client();
1415        let (mut ctx, mut mlme_stream, _) = make_env();
1416
1417        let rsn_cfg =
1418            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1419
1420        let s_rsne = Rsne::wpa2_rsne();
1421        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1422        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1423
1424        let state: States = State::new(Authenticating)
1425            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1426            .transition_to(Associated {
1427                aid: 1,
1428                rsna_link_state: Some(RsnaLinkState {
1429                    request_attempts: 3,
1430                    last_key_frame: Some(test_utils::eapol_key_frame()),
1431                    request_timeout: Some(EventHandle::new_test(2)),
1432                    negotiation_timeout: Some(EventHandle::new_test(3)),
1433                    authenticator: new_authenticator_from_rsne(
1434                        *AP_ADDR,
1435                        *CLIENT_ADDR,
1436                        &s_rsne_vec[..],
1437                        &rsn_cfg,
1438                    )
1439                    .unwrap(),
1440                }),
1441            })
1442            .into();
1443
1444        let state = state.handle_timeout(
1445            &mut r_sta,
1446            &mut ctx,
1447            ClientEvent::RsnaTimeout(RsnaTimeout::Negotiation),
1448        );
1449
1450        let (_, Authenticating) = match state {
1451            States::Authenticating(state) => state.release_data(),
1452            _ => panic!("unexpected_state"),
1453        };
1454
1455        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1456        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1457            peer_sta_address,
1458            reason_code,
1459        }) => {
1460            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1461            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::FourwayHandshakeTimeout);
1462        });
1463    }
1464
1465    #[test]
1466    fn associated_handles_rsna_key_frame_resets_request_attempts() {
1467        let mut r_sta = make_remote_client();
1468        let (mut ctx, _, _) = make_env();
1469
1470        let rsn_cfg =
1471            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1472
1473        let s_rsne = Rsne::wpa2_rsne();
1474        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1475        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1476
1477        let state: States = State::new(Authenticating)
1478            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1479            .transition_to(Associated {
1480                aid: 1,
1481                rsna_link_state: Some(RsnaLinkState {
1482                    request_attempts: 3,
1483                    last_key_frame: Some(test_utils::eapol_key_frame()),
1484                    request_timeout: Some(EventHandle::new_test(1)),
1485                    negotiation_timeout: Some(EventHandle::new_test(2)),
1486                    authenticator: new_authenticator_from_rsne(
1487                        *AP_ADDR,
1488                        *CLIENT_ADDR,
1489                        &s_rsne_vec[..],
1490                        &rsn_cfg,
1491                    )
1492                    .unwrap(),
1493                }),
1494            })
1495            .into();
1496
1497        let state = state.handle_eapol_ind(
1498            &mut r_sta,
1499            &mut ctx,
1500            &Vec::<u8>::from(test_utils::eapol_key_frame())[..],
1501        );
1502
1503        let (_, Associated { rsna_link_state, .. }) = match state {
1504            States::Associated(state) => state.release_data(),
1505            _ => panic!("unexpected_state"),
1506        };
1507
1508        assert_eq!(rsna_link_state.as_ref().unwrap().request_attempts, 0);
1509    }
1510
1511    #[test]
1512    fn associated_handles_rsna_request_timeout_last_attempt() {
1513        let mut r_sta = make_remote_client();
1514        let (mut ctx, mut mlme_stream, _) = make_env();
1515
1516        let rsn_cfg =
1517            create_rsn_cfg(&Ssid::try_from("coolnet").unwrap(), b"password").unwrap().unwrap();
1518
1519        let s_rsne = Rsne::wpa2_rsne();
1520        let mut s_rsne_vec = Vec::with_capacity(s_rsne.len());
1521        s_rsne.write_into(&mut s_rsne_vec).expect("error writing RSNE");
1522
1523        let state: States = State::new(Authenticating)
1524            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1525            .transition_to(Associated {
1526                aid: 1,
1527                rsna_link_state: Some(RsnaLinkState {
1528                    request_attempts: 3,
1529                    last_key_frame: Some(test_utils::eapol_key_frame()),
1530                    request_timeout: Some(EventHandle::new_test(2)),
1531                    negotiation_timeout: Some(EventHandle::new_test(3)),
1532                    authenticator: new_authenticator_from_rsne(
1533                        *AP_ADDR,
1534                        *CLIENT_ADDR,
1535                        &s_rsne_vec[..],
1536                        &rsn_cfg,
1537                    )
1538                    .unwrap(),
1539                }),
1540            })
1541            .into();
1542
1543        let state = state.handle_timeout(
1544            &mut r_sta,
1545            &mut ctx,
1546            ClientEvent::RsnaTimeout(RsnaTimeout::Request),
1547        );
1548
1549        let (_, Authenticating) = match state {
1550            States::Authenticating(state) => state.release_data(),
1551            _ => panic!("unexpected state"),
1552        };
1553
1554        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1555        assert_matches!(mlme_event, MlmeRequest::Deauthenticate(fidl_mlme::DeauthenticateRequest {
1556            peer_sta_address,
1557            reason_code,
1558        }) => {
1559            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1560            assert_eq!(reason_code, fidl_ieee80211::ReasonCode::FourwayHandshakeTimeout);
1561        });
1562    }
1563
1564    #[test]
1565    fn associated_handles_eapol_key_frame() {
1566        let mut r_sta = make_remote_client();
1567        let (mut ctx, mut mlme_stream, _) = make_env();
1568
1569        let state: States = State::new(Authenticating)
1570            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1571            .transition_to(Associated {
1572                aid: 1,
1573                rsna_link_state: Some(RsnaLinkState {
1574                    request_attempts: 0,
1575                    last_key_frame: Some(test_utils::eapol_key_frame()),
1576                    request_timeout: Some(EventHandle::new_test(2)),
1577                    negotiation_timeout: Some(EventHandle::new_test(3)),
1578                    authenticator: Box::new(MockAuthenticator::new(
1579                        Arc::new(Mutex::new(vec![])),
1580                        Arc::new(Mutex::new(vec![SecAssocUpdate::TxEapolKeyFrame {
1581                            frame: test_utils::eapol_key_frame(),
1582                            expect_response: false,
1583                        }])),
1584                    )),
1585                }),
1586            })
1587            .into();
1588
1589        let _next_state = state.handle_eapol_ind(
1590            &mut r_sta,
1591            &mut ctx,
1592            &Vec::<u8>::from(test_utils::eapol_key_frame())[..],
1593        );
1594
1595        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1596        assert_matches!(mlme_event, MlmeRequest::Eapol(fidl_mlme::EapolRequest {
1597            src_addr,
1598            dst_addr,
1599            data,
1600        }) => {
1601            assert_eq!(&src_addr, AP_ADDR.as_array());
1602            assert_eq!(&dst_addr, CLIENT_ADDR.as_array());
1603            assert_eq!(data, Vec::<u8>::from(test_utils::eapol_key_frame()));
1604        });
1605    }
1606
1607    #[test]
1608    fn associated_handles_eapol_conf() {
1609        let mut r_sta = make_remote_client();
1610        let (mut ctx, _mlme_stream, _) = make_env();
1611
1612        let state: States = State::new(Authenticating)
1613            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1614            .transition_to(Associated {
1615                aid: 1,
1616                rsna_link_state: Some(RsnaLinkState {
1617                    request_attempts: 0,
1618                    last_key_frame: Some(test_utils::eapol_key_frame()),
1619                    request_timeout: Some(EventHandle::new_test(2)),
1620                    negotiation_timeout: Some(EventHandle::new_test(3)),
1621                    authenticator: Box::new(MockAuthenticator::new(
1622                        Arc::new(Mutex::new(vec![])),
1623                        Arc::new(Mutex::new(vec![SecAssocUpdate::TxEapolKeyFrame {
1624                            frame: test_utils::eapol_key_frame(),
1625                            expect_response: false,
1626                        }])),
1627                    )),
1628                }),
1629            })
1630            .into();
1631
1632        let state =
1633            state.handle_eapol_conf(&mut r_sta, &mut ctx, fidl_mlme::EapolResultCode::Success);
1634        match state {
1635            States::Associated(_) => (),
1636            _ => panic!("Eapol conf should leave us in Associated"),
1637        }
1638
1639        // TODO(https://fxbug.dev/42147479): Populate this test once eapol conf is handled.
1640    }
1641
1642    #[test]
1643    fn associated_handles_eapol_key() {
1644        let mut r_sta = make_remote_client();
1645        let (mut ctx, mut mlme_stream, _) = make_env();
1646
1647        let state: States = State::new(Authenticating)
1648            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1649            .transition_to(Associated {
1650                aid: 1,
1651                rsna_link_state: Some(RsnaLinkState {
1652                    request_attempts: 0,
1653                    last_key_frame: Some(test_utils::eapol_key_frame()),
1654                    request_timeout: Some(EventHandle::new_test(2)),
1655                    negotiation_timeout: Some(EventHandle::new_test(3)),
1656                    authenticator: Box::new(MockAuthenticator::new(
1657                        Arc::new(Mutex::new(vec![])),
1658                        Arc::new(Mutex::new(vec![SecAssocUpdate::Key(
1659                            Key::Ptk(test_utils::ptk()),
1660                        )])),
1661                    )),
1662                }),
1663            })
1664            .into();
1665
1666        let _next_state = state.handle_eapol_ind(
1667            &mut r_sta,
1668            &mut ctx,
1669            &Vec::<u8>::from(test_utils::eapol_key_frame())[..],
1670        );
1671
1672        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1673        assert_matches!(mlme_event, MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist }) => {
1674            assert_eq!(keylist.len(), 1);
1675            let k = keylist.first().expect("expect key descriptor");
1676            assert_eq!(k.key, vec![0xCCu8; test_utils::cipher().tk_bytes().unwrap() as usize]);
1677            assert_eq!(k.key_id, 0);
1678            assert_eq!(k.key_type, fidl_mlme::KeyType::Pairwise);
1679            assert_eq!(&k.address, CLIENT_ADDR.as_array());
1680            assert_eq!(k.rsc, 0);
1681            assert_eq!(k.cipher_suite_oui, [0x00, 0x0F, 0xAC]);
1682            assert_eq!(k.cipher_suite_type, fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(4));
1683        });
1684    }
1685
1686    #[test]
1687    fn associated_handles_esssa_established() {
1688        let mut r_sta = make_remote_client();
1689        let (mut ctx, mut mlme_stream, _) = make_env();
1690
1691        let state: States = State::new(Authenticating)
1692            .transition_to(Authenticated { _timeout_event: EventHandle::new_test(1) })
1693            .transition_to(Associated {
1694                aid: 1,
1695                rsna_link_state: Some(RsnaLinkState {
1696                    request_attempts: 0,
1697                    last_key_frame: Some(test_utils::eapol_key_frame()),
1698                    request_timeout: Some(EventHandle::new_test(2)),
1699                    negotiation_timeout: Some(EventHandle::new_test(3)),
1700                    authenticator: Box::new(MockAuthenticator::new(
1701                        Arc::new(Mutex::new(vec![])),
1702                        Arc::new(Mutex::new(vec![SecAssocUpdate::Status(
1703                            SecAssocStatus::EssSaEstablished,
1704                        )])),
1705                    )),
1706                }),
1707            })
1708            .into();
1709
1710        let state = state.handle_eapol_ind(
1711            &mut r_sta,
1712            &mut ctx,
1713            &Vec::<u8>::from(test_utils::eapol_key_frame())[..],
1714        );
1715
1716        let (_, Associated { rsna_link_state, .. }) = match state {
1717            States::Associated(state) => state.release_data(),
1718            _ => panic!("unexpected_state"),
1719        };
1720
1721        assert_matches!(&rsna_link_state.as_ref().unwrap().last_key_frame, None);
1722        assert_matches!(&rsna_link_state.as_ref().unwrap().request_timeout, None);
1723        assert_matches!(&rsna_link_state.as_ref().unwrap().negotiation_timeout, None);
1724
1725        let mlme_event = mlme_stream.try_next().unwrap().expect("expected mlme event");
1726        assert_matches!(mlme_event, MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
1727            peer_sta_address,
1728            state,
1729        }) => {
1730            assert_eq!(&peer_sta_address, CLIENT_ADDR.as_array());
1731            assert_eq!(state, fidl_mlme::ControlledPortState::Open);
1732        });
1733    }
1734}