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