wlan_sme/client/state/
link_state.rs

1// Copyright 2019 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 super::{now, Protection, StateChangeContext, StateChangeContextExt};
6use crate::client::event::{self, Event, RsnaCompletionTimeout, RsnaResponseTimeout};
7use crate::client::internal::Context;
8use crate::client::rsn::Rsna;
9use crate::client::EstablishRsnaFailureReason;
10use crate::{MlmeRequest, MlmeSink};
11use fuchsia_inspect_contrib::inspect_log;
12use fuchsia_inspect_contrib::log::InspectBytes;
13use ieee80211::{Bssid, MacAddr, MacAddrBytes, WILDCARD_BSSID};
14use log::{error, warn};
15use wlan_common::bss::BssDescription;
16use wlan_common::timer::EventHandle;
17use wlan_rsn::key::exchange::Key;
18use wlan_rsn::key::Tk;
19use wlan_rsn::rsna::{self, SecAssocStatus, SecAssocUpdate};
20use wlan_statemachine::*;
21use {fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_mlme as fidl_mlme};
22
23#[derive(Debug)]
24pub struct Init;
25
26#[derive(Debug)]
27pub struct EstablishingRsna {
28    pub rsna: Rsna,
29    // Timeout for the total duration RSNA may take to complete. This timeout is
30    // never rescheduled.
31    pub rsna_completion_timeout: Option<EventHandle>,
32    // Timeout for the duration RSNA will await a response after transmitting a frame.
33    // This timeout will be rescheduled upon receiving each valid frame.
34    pub rsna_response_timeout: Option<EventHandle>,
35    // Timeout for the duration RSNA will await a response after transmitting a frame
36    // before possibly retransmitting the same frame. This timeout will be rescheduled
37    // upon transmitting each frame.
38    pub rsna_retransmission_timeout: Option<EventHandle>,
39
40    // The following conditions must all be satisfied to consider an RSNA established. They
41    // may be satisfied in multiple orders, so we represent them as a threshold rather than
42    // as additional link states.
43    // Indicates that all handshake frames have been sent or received.
44    pub handshake_complete: bool,
45    // If empty, indicates that we have received confirms for all keys we've installed.
46    pub pending_key_ids: std::collections::HashSet<u16>,
47}
48
49#[derive(Debug)]
50pub struct LinkUp {
51    pub protection: Protection,
52    pub since: zx::MonotonicInstant,
53}
54
55statemachine!(
56    #[derive(Debug)]
57    pub enum LinkState,
58    () => Init,
59    // If the association does not use an Rsna, we move directly from Init to LinkUp.
60    Init => [EstablishingRsna, LinkUp],
61    EstablishingRsna => LinkUp,
62);
63
64#[derive(Debug)]
65enum RsnaStatus {
66    Failed(EstablishRsnaFailureReason),
67    Unchanged,
68    Progressed {
69        ap_responsive: Option<EventHandle>,
70        new_retransmission_timeout: Option<EventHandle>,
71        handshake_complete: bool,
72        sent_keys: Vec<u16>,
73    },
74}
75
76#[derive(Debug)]
77enum RsnaProgressed {
78    Complete(LinkUp),
79    InProgress(EstablishingRsna),
80}
81
82impl EstablishingRsna {
83    fn on_rsna_progressed(
84        mut self,
85        ap_responsive: Option<EventHandle>,
86        rsna_retransmission_timeout: Option<EventHandle>,
87        handshake_complete: bool,
88        sent_keys: Vec<u16>,
89    ) -> Self {
90        sent_keys.into_iter().for_each(|key| {
91            let _ = self.pending_key_ids.insert(key);
92        });
93        self.handshake_complete |= handshake_complete;
94
95        // Always cancel the retransmission timeout if RSNA progresssed since,
96        // once transmitting frames, all meaningful progression implies the last
97        // tranmsmitted frame resulted in progress.
98        self.rsna_retransmission_timeout = rsna_retransmission_timeout;
99        // If the AP is responsive, then reset the response timeout.
100        self.rsna_response_timeout = ap_responsive.or(self.rsna_response_timeout);
101        self
102    }
103
104    /// Establish the RSNA if all conditions are met.
105    fn try_establish(self, bss: &BssDescription, context: &mut Context) -> RsnaProgressed {
106        if self.handshake_complete && self.pending_key_ids.is_empty() {
107            context.mlme_sink.send(MlmeRequest::SetCtrlPort(fidl_mlme::SetControlledPortRequest {
108                peer_sta_address: bss.bssid.to_array(),
109                state: fidl_mlme::ControlledPortState::Open,
110            }));
111
112            let now = now();
113            RsnaProgressed::Complete(LinkUp { protection: Protection::Rsna(self.rsna), since: now })
114        } else {
115            RsnaProgressed::InProgress(self)
116        }
117    }
118
119    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
120    fn handle_rsna_response_timeout(mut self) -> Result<Self, EstablishRsnaFailureReason> {
121        warn!("RSNA response timeout expired: {}ms", event::RSNA_RESPONSE_TIMEOUT_MILLIS);
122        self.rsna_retransmission_timeout = None;
123        self.rsna_response_timeout = None;
124        self.rsna_completion_timeout = None;
125        Err(self.rsna.supplicant.on_rsna_response_timeout())
126    }
127
128    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
129    fn handle_rsna_completion_timeout(mut self) -> Result<Self, EstablishRsnaFailureReason> {
130        warn!("RSNA completion timeout expired: {}ms", event::RSNA_COMPLETION_TIMEOUT_MILLIS);
131        self.rsna_retransmission_timeout = None;
132        self.rsna_response_timeout = None;
133        self.rsna_completion_timeout = None;
134        Err(self.rsna.supplicant.on_rsna_completion_timeout())
135    }
136}
137
138impl LinkState {
139    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
140    pub fn new(
141        protection: Protection,
142        context: &mut Context,
143    ) -> Result<Self, EstablishRsnaFailureReason> {
144        match protection {
145            Protection::Open | Protection::Wep(_) => {
146                let now = now();
147                Ok(State::new(Init)
148                    .transition_to(LinkUp { protection: Protection::Open, since: now })
149                    .into())
150            }
151            Protection::Rsna(mut rsna) | Protection::LegacyWpa(mut rsna) => {
152                let mut update_sink = rsna::UpdateSink::default();
153                rsna.supplicant.start(&mut update_sink).map_err(|e| {
154                    error!("could not start Supplicant: {}", e);
155                    EstablishRsnaFailureReason::StartSupplicantFailed
156                })?;
157                let state = State::new(Init).transition_to(EstablishingRsna {
158                    rsna,
159                    rsna_completion_timeout: Some(
160                        context.timer.schedule(event::RsnaCompletionTimeout),
161                    ),
162                    rsna_response_timeout: Some(context.timer.schedule(event::RsnaResponseTimeout)),
163                    rsna_retransmission_timeout: None,
164                    handshake_complete: false,
165                    pending_key_ids: Default::default(),
166                });
167                let (transition, state) = state.release_data();
168                match process_rsna_updates(context, None, update_sink, None) {
169                    RsnaStatus::Unchanged => Ok(transition.to(state).into()),
170                    // RSNA progress during start() should only be trivial.
171                    RsnaStatus::Progressed {
172                        ap_responsive: None,
173                        new_retransmission_timeout: None,
174                        handshake_complete: false,
175                        sent_keys,
176                    } if sent_keys.is_empty() => {
177                        // Normally, we call both on_rsna_progressed() and
178                        // try_establish(). Here, we omit try_establish() since it is not
179                        // possible to establish an RSNA without any EAPOL frames exchanged.
180                        let state = state.on_rsna_progressed(None, None, false, sent_keys);
181                        Ok(transition.to(state).into())
182                    }
183                    RsnaStatus::Failed(reason) => Err(reason),
184                    rsna_status => {
185                        error!("Unexpected RsnaStatus upon Supplicant::start(): {:?}", rsna_status);
186                        Err(EstablishRsnaFailureReason::StartSupplicantFailed)
187                    }
188                }
189            }
190        }
191    }
192
193    pub fn disconnect(self) -> (Protection, Option<zx::MonotonicDuration>) {
194        match self {
195            Self::EstablishingRsna(state) => {
196                let (_, state) = state.release_data();
197                (Protection::Rsna(state.rsna), None)
198            }
199            Self::LinkUp(state) => {
200                let (_, state) = state.release_data();
201                let connected_duration = now() - state.since;
202                (state.protection, Some(connected_duration))
203            }
204            // We always transition to EstablishingRsna or LinkUp on initialization
205            // and never transition back
206            #[expect(clippy::unreachable)]
207            _ => unreachable!(),
208        }
209    }
210
211    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
212    fn on_eapol_event<T, H>(
213        self,
214        eapol_event: T,
215        process_eapol_event: H,
216        bss: &BssDescription,
217        state_change_msg: &mut Option<StateChangeContext>,
218        context: &mut Context,
219    ) -> Result<Self, EstablishRsnaFailureReason>
220    where
221        H: Fn(&mut Context, &mut Rsna, &T) -> RsnaStatus,
222    {
223        match self {
224            Self::EstablishingRsna(state) => {
225                let (transition, mut state) = state.release_data();
226                match process_eapol_event(context, &mut state.rsna, &eapol_event) {
227                    RsnaStatus::Failed(failure_reason) => {
228                        state_change_msg.set_msg(format!("{:?}", failure_reason));
229                        Err(failure_reason)
230                    }
231                    RsnaStatus::Progressed {
232                        ap_responsive,
233                        new_retransmission_timeout,
234                        sent_keys,
235                        handshake_complete,
236                    } => {
237                        match state
238                            .on_rsna_progressed(
239                                ap_responsive,
240                                new_retransmission_timeout,
241                                handshake_complete,
242                                sent_keys,
243                            )
244                            .try_establish(bss, context)
245                        {
246                            RsnaProgressed::Complete(link_up) => {
247                                state_change_msg.set_msg("RSNA established".to_string());
248                                Ok(transition.to(link_up).into())
249                            }
250                            RsnaProgressed::InProgress(still_establishing_rsna) => {
251                                Ok(transition.to(still_establishing_rsna).into())
252                            }
253                        }
254                    }
255                    RsnaStatus::Unchanged => Ok(transition.to(state).into()),
256                }
257            }
258            Self::LinkUp(state) => {
259                let (transition, mut state) = state.release_data();
260                // Drop EAPOL frames if the BSS is not an RSN.
261                if let Protection::Rsna(rsna) = &mut state.protection {
262                    match process_eapol_event(context, rsna, &eapol_event) {
263                        RsnaStatus::Unchanged => {}
264                        // This can happen when there's a GTK rotation.
265                        // Timeout is ignored because only one RX frame is
266                        // needed in the exchange, so we are not waiting for
267                        // another one.
268                        // sent_keys is ignored because we're not waiting for key installations
269                        // to complete the RSNA.
270                        RsnaStatus::Progressed {
271                            ap_responsive: _,
272                            new_retransmission_timeout: _,
273                            handshake_complete: _,
274                            sent_keys: _,
275                        } => {}
276                        // Once re-keying is supported, the RSNA can fail in
277                        // LinkUp as well and cause deauthentication.
278                        s => error!("unexpected RsnaStatus in LinkUp state: {:?}", s),
279                    };
280                }
281                Ok(transition.to(state).into())
282            }
283            // We always transition to EstablishingRsna or LinkUp on initialization
284            // and never transition back
285            #[expect(clippy::unreachable)]
286            _ => unreachable!(),
287        }
288    }
289
290    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
291    pub fn on_eapol_ind(
292        self,
293        eapol_ind: fidl_mlme::EapolIndication,
294        bss: &BssDescription,
295        state_change_msg: &mut Option<StateChangeContext>,
296        context: &mut Context,
297    ) -> Result<Self, EstablishRsnaFailureReason> {
298        self.on_eapol_event(eapol_ind, process_eapol_ind, bss, state_change_msg, context)
299    }
300
301    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
302    pub fn on_eapol_conf(
303        self,
304        eapol_conf: fidl_mlme::EapolConfirm,
305        bss: &BssDescription,
306        state_change_msg: &mut Option<StateChangeContext>,
307        context: &mut Context,
308    ) -> Result<Self, EstablishRsnaFailureReason> {
309        self.on_eapol_event(eapol_conf, process_eapol_conf, bss, state_change_msg, context)
310    }
311
312    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
313    pub fn on_set_keys_conf(
314        self,
315        set_keys_conf: fidl_mlme::SetKeysConfirm,
316        bss: &BssDescription,
317        state_change_msg: &mut Option<StateChangeContext>,
318        context: &mut Context,
319    ) -> Result<Self, EstablishRsnaFailureReason> {
320        for key_result in &set_keys_conf.results {
321            if key_result.status != zx::Status::OK.into_raw() {
322                state_change_msg.set_msg("Failed to set key in driver".to_string());
323                return Err(EstablishRsnaFailureReason::InternalError);
324            }
325        }
326
327        match self {
328            Self::EstablishingRsna(state) => {
329                let (transition, mut state) = state.release_data();
330                for key_result in set_keys_conf.results {
331                    let _ = state.pending_key_ids.remove(&key_result.key_id);
332                }
333                match state.try_establish(bss, context) {
334                    RsnaProgressed::Complete(link_up) => {
335                        state_change_msg.set_msg("RSNA established".to_string());
336                        Ok(transition.to(link_up).into())
337                    }
338                    RsnaProgressed::InProgress(still_establishing_rsna) => {
339                        Ok(transition.to(still_establishing_rsna).into())
340                    }
341                }
342            }
343            _ => Ok(self),
344        }
345    }
346
347    #[allow(clippy::result_large_err)] // TODO(https://fxbug.dev/401255153)
348    pub fn handle_timeout(
349        self,
350        event: Event,
351        state_change_msg: &mut Option<StateChangeContext>,
352        context: &mut Context,
353    ) -> Result<Self, EstablishRsnaFailureReason> {
354        match self {
355            Self::EstablishingRsna(state) => match event {
356                Event::RsnaResponseTimeout(RsnaResponseTimeout {}) => {
357                    let (transition, state) = state.release_data();
358                    match state.handle_rsna_response_timeout() {
359                        Ok(still_establishing_rsna) => {
360                            Ok(transition.to(still_establishing_rsna).into())
361                        }
362                        Err(failure) => {
363                            state_change_msg.set_msg("RSNA response timeout".to_string());
364                            Err(failure)
365                        }
366                    }
367                }
368                Event::RsnaCompletionTimeout(RsnaCompletionTimeout {}) => {
369                    let (transition, state) = state.release_data();
370                    match state.handle_rsna_completion_timeout() {
371                        Ok(still_establishing_rsna) => {
372                            Ok(transition.to(still_establishing_rsna).into())
373                        }
374                        Err(failure) => {
375                            state_change_msg.set_msg("RSNA completion timeout".to_string());
376                            Err(failure)
377                        }
378                    }
379                }
380                Event::RsnaRetransmissionTimeout(timeout) => {
381                    let (transition, mut state) = state.release_data();
382                    match process_rsna_retransmission_timeout(context, timeout, &mut state.rsna) {
383                        RsnaStatus::Failed(failure_reason) => Err(failure_reason),
384                        RsnaStatus::Unchanged => Ok(transition.to(state).into()),
385                        RsnaStatus::Progressed {
386                            ap_responsive,
387                            new_retransmission_timeout,
388                            sent_keys,
389                            handshake_complete,
390                        } => {
391                            let still_establishing_rsna = state.on_rsna_progressed(
392                                ap_responsive,
393                                new_retransmission_timeout,
394                                handshake_complete,
395                                sent_keys,
396                            );
397                            Ok(transition.to(still_establishing_rsna).into())
398                        }
399                    }
400                }
401                _ => Ok(state.into()),
402            },
403            Self::LinkUp(state) => Ok(state.into()),
404            // We always transition to EstablishingRsna or LinkUp on initialization
405            // and never transition back
406            #[expect(clippy::unreachable)]
407            _ => unreachable!(),
408        }
409    }
410}
411
412fn inspect_log_key(context: &mut Context, key: &Key) {
413    let (cipher, key_index) = match key {
414        Key::Ptk(ptk) => (Some(&ptk.cipher), None),
415        Key::Gtk(gtk) => (Some(gtk.cipher()), Some(gtk.key_id())),
416        _ => (None, None),
417    };
418    inspect_log!(context.inspect.rsn_events.lock(), {
419        derived_key: key.name(),
420        cipher?: cipher.map(|c| format!("{:?}", c)),
421        key_index?: key_index,
422    });
423}
424
425fn send_keys(mlme_sink: &MlmeSink, bssid: Bssid, key: Key) -> Option<u16> {
426    let key_descriptor = match key {
427        Key::Ptk(ptk) => fidl_mlme::SetKeyDescriptor {
428            key_type: fidl_mlme::KeyType::Pairwise,
429            key: ptk.tk().to_vec(),
430            key_id: 0,
431            address: bssid.to_array(),
432            cipher_suite_oui: eapol::to_array(&ptk.cipher.oui[..]),
433            cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
434                ptk.cipher.suite_type.into(),
435            ),
436            rsc: 0,
437        },
438        Key::Gtk(gtk) => fidl_mlme::SetKeyDescriptor {
439            key_type: fidl_mlme::KeyType::Group,
440            key: gtk.tk().to_vec(),
441            key_id: gtk.key_id() as u16,
442            address: WILDCARD_BSSID.to_array(),
443            cipher_suite_oui: eapol::to_array(&gtk.cipher().oui[..]),
444            cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
445                gtk.cipher().suite_type.into(),
446            ),
447            rsc: gtk.key_rsc(),
448        },
449        Key::Igtk(igtk) => {
450            let mut rsc = [0u8; 8];
451            rsc[2..].copy_from_slice(&igtk.ipn[..]);
452            fidl_mlme::SetKeyDescriptor {
453                key_type: fidl_mlme::KeyType::Igtk,
454                key: igtk.igtk,
455                key_id: igtk.key_id,
456                address: [0xFFu8; 6],
457                cipher_suite_oui: eapol::to_array(&igtk.cipher.oui[..]),
458                cipher_suite_type: fidl_ieee80211::CipherSuiteType::from_primitive_allow_unknown(
459                    igtk.cipher.suite_type.into(),
460                ),
461                rsc: u64::from_be_bytes(rsc),
462            }
463        }
464        _ => {
465            error!("derived unexpected key");
466            return None;
467        }
468    };
469    let key_id = key_descriptor.key_id;
470    mlme_sink
471        .send(MlmeRequest::SetKeys(fidl_mlme::SetKeysRequest { keylist: vec![key_descriptor] }));
472    Some(key_id)
473}
474
475/// Sends an eapol frame, and optionally schedules a timeout for the response.
476/// If schedule_timeout is true, we should expect our peer to send us an eapol
477/// frame in response to this one, and schedule a timeout as well.
478fn send_eapol_frame(
479    context: &mut Context,
480    bssid: Bssid,
481    sta_addr: MacAddr,
482    frame: eapol::KeyFrameBuf,
483    schedule_timeout: bool,
484) -> Option<EventHandle> {
485    let resp_timeout = if schedule_timeout {
486        Some(context.timer.schedule(event::RsnaRetransmissionTimeout { bssid, sta_addr }))
487    } else {
488        None
489    };
490    inspect_log!(context.inspect.rsn_events.lock(), tx_eapol_frame: InspectBytes(&frame[..]));
491    context.mlme_sink.send(MlmeRequest::Eapol(fidl_mlme::EapolRequest {
492        src_addr: sta_addr.to_array(),
493        dst_addr: bssid.to_array(),
494        data: frame.into(),
495    }));
496    resp_timeout
497}
498
499fn process_eapol_conf(
500    context: &mut Context,
501    rsna: &mut Rsna,
502    eapol_conf: &fidl_mlme::EapolConfirm,
503) -> RsnaStatus {
504    let mut update_sink = rsna::UpdateSink::default();
505    match rsna.supplicant.on_eapol_conf(&mut update_sink, eapol_conf.result_code) {
506        Err(e) => {
507            error!("error handling EAPOL confirm: {}", e);
508            RsnaStatus::Unchanged
509        }
510        Ok(()) => {
511            process_rsna_updates(context, Some(Bssid::from(eapol_conf.dst_addr)), update_sink, None)
512        }
513    }
514}
515
516fn process_rsna_retransmission_timeout(
517    context: &mut Context,
518    timeout: event::RsnaRetransmissionTimeout,
519    rsna: &mut Rsna,
520) -> RsnaStatus {
521    let mut update_sink = rsna::UpdateSink::default();
522    match rsna.supplicant.on_rsna_retransmission_timeout(&mut update_sink) {
523        Err(e) => {
524            error!("{:?}", e);
525            RsnaStatus::Failed(EstablishRsnaFailureReason::InternalError)
526        }
527        Ok(()) => process_rsna_updates(context, Some(timeout.bssid), update_sink, None),
528    }
529}
530
531fn process_eapol_ind(
532    context: &mut Context,
533    rsna: &mut Rsna,
534    ind: &fidl_mlme::EapolIndication,
535) -> RsnaStatus {
536    let mic_size = rsna.negotiated_protection.mic_size;
537    let eapol_pdu = &ind.data[..];
538    let eapol_frame = match eapol::KeyFrameRx::parse(mic_size as usize, eapol_pdu) {
539        Ok(key_frame) => eapol::Frame::Key(key_frame),
540        Err(e) => {
541            error!("received invalid EAPOL Key frame: {:?}", e);
542            inspect_log!(context.inspect.rsn_events.lock(), {
543                rx_eapol_frame: InspectBytes(&eapol_pdu),
544                status: format!("rejected (parse error): {:?}", e)
545            });
546            return RsnaStatus::Unchanged;
547        }
548    };
549
550    let mut update_sink = rsna::UpdateSink::default();
551    if let Err(e) = rsna.supplicant.on_eapol_frame(&mut update_sink, eapol_frame) {
552        error!("error processing EAPOL key frame: {}", e);
553        inspect_log!(context.inspect.rsn_events.lock(), {
554            rx_eapol_frame: InspectBytes(&eapol_pdu),
555            status: format!("rejected (processing error): {}", e)
556        });
557        return RsnaStatus::Unchanged;
558    }
559
560    inspect_log!(context.inspect.rsn_events.lock(), {
561        rx_eapol_frame: InspectBytes(&eapol_pdu),
562        status: "processed"
563    });
564    let ap_responsive =
565        (!update_sink.is_empty()).then(|| context.timer.schedule(event::RsnaResponseTimeout {}));
566    process_rsna_updates(context, Some(Bssid::from(ind.src_addr)), update_sink, ap_responsive)
567}
568
569fn process_rsna_updates(
570    context: &mut Context,
571    bssid: Option<Bssid>,
572    updates: rsna::UpdateSink,
573    ap_responsive: Option<EventHandle>,
574) -> RsnaStatus {
575    if updates.is_empty() {
576        return RsnaStatus::Unchanged;
577    }
578
579    let sta_addr = MacAddr::from(context.device_info.sta_addr);
580    let mut new_retransmission_timeout = None;
581    let mut handshake_complete = false;
582    let mut sent_keys = vec![];
583    for update in updates {
584        match update {
585            // ESS Security Association requests to send an EAPOL frame.
586            // Forward EAPOL frame to MLME.
587            SecAssocUpdate::TxEapolKeyFrame { frame, expect_response } => {
588                new_retransmission_timeout = match bssid {
589                    None => {
590                        error!("No BSSID set to handle SecAssocUpdate::TxEapolKeyFrame");
591                        return RsnaStatus::Failed(EstablishRsnaFailureReason::InternalError);
592                    }
593                    Some(bssid) => {
594                        send_eapol_frame(context, bssid, sta_addr, frame, expect_response)
595                    }
596                }
597            }
598            // ESS Security Association derived a new key.
599            // Configure key in MLME.
600            SecAssocUpdate::Key(key) => match bssid {
601                None => {
602                    error!("No BSSID set to handle SecAssocUpdate::Key");
603                    return RsnaStatus::Failed(EstablishRsnaFailureReason::InternalError);
604                }
605                Some(bssid) => {
606                    inspect_log_key(context, &key);
607                    if let Some(key_id) = send_keys(&context.mlme_sink, bssid, key) {
608                        sent_keys.push(key_id);
609                    }
610                }
611            },
612            // Received a status update.
613            SecAssocUpdate::Status(status) => {
614                inspect_log!(
615                    context.inspect.rsn_events.lock(),
616                    rsna_status: format!("{:?}", status)
617                );
618                match status {
619                    // ESS Security Association was successfully established. Link is now up.
620                    SecAssocStatus::EssSaEstablished => {
621                        handshake_complete = true;
622                    }
623                    SecAssocStatus::WrongPassword => {
624                        return RsnaStatus::Failed(EstablishRsnaFailureReason::InternalError);
625                    }
626                    SecAssocStatus::PmkSaEstablished => (),
627                }
628            }
629            // TODO(https://fxbug.dev/42103820): We must handle SAE here for FullMAC devices.
630            update => warn!("Unhandled association update: {:?}", update),
631        }
632    }
633
634    RsnaStatus::Progressed {
635        ap_responsive,
636        new_retransmission_timeout,
637        handshake_complete,
638        sent_keys,
639    }
640}