Skip to main content

wlan_sme/ap/
mod.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
5mod aid;
6mod authenticator;
7mod event;
8mod remote_client;
9#[cfg(test)]
10pub mod test_utils;
11
12use event::*;
13use remote_client::*;
14
15use crate::responder::Responder;
16use crate::{MlmeRequest, MlmeSink, mlme_event_name};
17use fidl_fuchsia_wlan_mlme::{self as fidl_mlme, DeviceInfo, MlmeEvent};
18use futures::channel::{mpsc, oneshot};
19use ieee80211::{MacAddr, MacAddrBytes, Ssid};
20use log::{debug, error, info, warn};
21use std::collections::HashMap;
22use wlan_common::capabilities::get_band_cap_for_channel;
23use wlan_common::channel::{Cbw, Channel};
24use wlan_common::ie::rsn::rsne::{RsnCapabilities, Rsne};
25use wlan_common::ie::{ChanWidthSet, SupportedRate, parse_ht_capabilities};
26use wlan_common::timer::{self, EventHandle, Timer};
27use wlan_common::{RadioConfig, mac};
28use wlan_rsn::psk;
29use {
30    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
31    fidl_fuchsia_wlan_internal as fidl_internal, fidl_fuchsia_wlan_sme as fidl_sme,
32};
33
34const DEFAULT_BEACON_PERIOD: u16 = 100;
35const DEFAULT_DTIM_PERIOD: u8 = 2;
36
37#[derive(Clone, Debug, PartialEq)]
38pub struct Config {
39    pub ssid: Ssid,
40    pub password: Vec<u8>,
41    pub radio_cfg: RadioConfig,
42}
43
44// OpRadioConfig keeps admitted configuration and operation state
45#[derive(Clone, Debug, PartialEq)]
46pub struct OpRadioConfig {
47    phy: fidl_common::WlanPhyType,
48    channel: Channel,
49    basic_rates: Vec<u8>,
50}
51
52struct StartingState {
53    ctx: Context,
54    ssid: Ssid,
55    rsn_cfg: Option<RsnCfg>,
56    _capabilities: mac::CapabilityInfo,
57    _rates: Vec<SupportedRate>,
58    start_responder: Responder<StartResult>,
59    stop_responders: Vec<Responder<fidl_sme::StopApResultCode>>,
60    _start_timeout: EventHandle,
61    op_radio_cfg: OpRadioConfig,
62}
63
64enum State {
65    Idle(Box<IdleState>),
66    Started(Box<StartedState>),
67    Starting(Box<StartingState>),
68    Stopping(Box<StoppingState>),
69}
70
71struct StoppingState {
72    ctx: Context,
73    stop_req: fidl_mlme::StopRequest,
74    responders: Vec<Responder<fidl_sme::StopApResultCode>>,
75    stop_timeout: Option<EventHandle>,
76}
77
78#[derive(Clone)]
79pub struct RsnCfg {
80    psk: psk::Psk,
81    rsne: Rsne,
82}
83
84struct StartedState {
85    ssid: Ssid,
86    rsn_cfg: Option<RsnCfg>,
87    clients: HashMap<MacAddr, RemoteClient>,
88    aid_map: aid::Map,
89    op_radio_cfg: OpRadioConfig,
90    ctx: Context,
91}
92
93pub struct Context {
94    device_info: DeviceInfo,
95    spectrum_management_support: fidl_common::SpectrumManagementSupport,
96    mlme_sink: MlmeSink,
97    timer: Timer<Event>,
98}
99
100pub struct ApSme {
101    state: Option<State>,
102}
103
104struct IdleState {
105    ctx: Context,
106}
107
108#[derive(Debug, PartialEq)]
109pub enum StartResult {
110    Success,
111    Canceled,
112    TimedOut,
113    InvalidArguments(String),
114    PreviousStartInProgress,
115    AlreadyStarted,
116    InternalError,
117}
118
119impl ApSme {
120    pub fn new(
121        device_info: DeviceInfo,
122        spectrum_management_support: fidl_common::SpectrumManagementSupport,
123    ) -> (Self, crate::MlmeSink, crate::MlmeStream, timer::EventStream<Event>) {
124        let (mlme_sink, mlme_stream) = mpsc::unbounded();
125        let (timer, time_stream) = timer::create_timer();
126        let sme = ApSme {
127            state: Some(State::Idle(Box::new(IdleState {
128                ctx: Context {
129                    device_info,
130                    spectrum_management_support,
131                    mlme_sink: MlmeSink::new(mlme_sink.clone()),
132                    timer,
133                },
134            }))),
135        };
136        (sme, MlmeSink::new(mlme_sink), mlme_stream, time_stream)
137    }
138
139    pub fn on_start_command(&mut self, config: Config) -> oneshot::Receiver<StartResult> {
140        let (responder, receiver) = Responder::new();
141        self.state = self.state.take().map(|state| match state {
142            State::Idle(idle_state) => {
143                let mut ctx = idle_state.ctx;
144                let op_radio_cfg = match validate_radio_cfg(
145                    &ctx.device_info.bands[..],
146                    &config.radio_cfg,
147                    ctx.spectrum_management_support.clone(),
148                ) {
149                    Err(result) => {
150                        responder.respond(result);
151                        return State::Idle(Box::new(IdleState { ctx }));
152                    }
153                    Ok(op_radio_cfg) => op_radio_cfg,
154                };
155
156                let rsn_cfg_result = create_rsn_cfg(&config.ssid, &config.password[..]);
157                let rsn_cfg = match rsn_cfg_result {
158                    Err(e) => {
159                        responder.respond(e);
160                        return State::Idle(Box::new(IdleState { ctx }));
161                    }
162                    Ok(rsn_cfg) => rsn_cfg,
163                };
164
165                let capabilities =
166                    mac::CapabilityInfo(ctx.device_info.softmac_hardware_capability as u16)
167                        // IEEE Std 802.11-2016, 9.4.1.4: An AP sets the ESS subfield to 1 and the IBSS
168                        // subfield to 0 within transmitted Beacon or Probe Response frames.
169                        .with_ess(true)
170                        .with_ibss(false)
171                        // IEEE Std 802.11-2016, 9.4.1.4: An AP sets the Privacy subfield to 1 within
172                        // transmitted Beacon, Probe Response, (Re)Association Response frames if data
173                        // confidentiality is required for all Data frames exchanged within the BSS.
174                        .with_privacy(rsn_cfg.is_some());
175
176                let req = match create_start_request(
177                    &op_radio_cfg,
178                    &config.ssid,
179                    rsn_cfg.as_ref(),
180                    capabilities,
181                ) {
182                    Ok(req) => req,
183                    Err(result) => {
184                        responder.respond(result);
185                        return State::Idle(Box::new(IdleState { ctx }));
186                    }
187                };
188
189                // TODO(https://fxbug.dev/42103581): Select which rates are mandatory here.
190                let rates = op_radio_cfg.basic_rates.iter().map(|r| SupportedRate(*r)).collect();
191
192                ctx.mlme_sink.send(MlmeRequest::Start(req));
193                let event = Event::Sme { event: SmeEvent::StartTimeout };
194                let start_timeout = ctx.timer.schedule(event);
195
196                State::Starting(Box::new(StartingState {
197                    ctx,
198                    ssid: config.ssid,
199                    rsn_cfg,
200                    _capabilities: capabilities,
201                    _rates: rates,
202                    start_responder: responder,
203                    stop_responders: vec![],
204                    _start_timeout: start_timeout,
205                    op_radio_cfg,
206                }))
207            }
208            s @ State::Starting(_) => {
209                responder.respond(StartResult::PreviousStartInProgress);
210                s
211            }
212            s @ State::Stopping(_) => {
213                responder.respond(StartResult::Canceled);
214                s
215            }
216            s @ State::Started(_) => {
217                responder.respond(StartResult::AlreadyStarted);
218                s
219            }
220        });
221        receiver
222    }
223
224    pub fn on_stop_command(&mut self) -> oneshot::Receiver<fidl_sme::StopApResultCode> {
225        let (responder, receiver) = Responder::new();
226        self.state = self.state.take().map(|mut state| match state {
227            State::Idle(idle_state) => {
228                let mut ctx = idle_state.ctx;
229                // We don't have an SSID, so just do a best-effort StopAP request with no SSID
230                // filled in
231                let stop_req = fidl_mlme::StopRequest { ssid: Ssid::empty().into() };
232                let timeout = send_stop_req(&mut ctx, stop_req.clone());
233                State::Stopping(Box::new(StoppingState {
234                    ctx,
235                    stop_req,
236                    responders: vec![responder],
237                    stop_timeout: Some(timeout),
238                }))
239            }
240            State::Starting(ref mut starting_state) => {
241                starting_state.stop_responders.push(responder);
242                state
243            }
244            State::Stopping(mut state) => {
245                state.responders.push(responder);
246                // No stop request is ongoing, so forward this stop request.
247                // The previous stop request may have timed out or failed and we are in an
248                // unclean state where we don't know whether the AP has stopped or not.
249                state.stop_timeout = state
250                    .stop_timeout
251                    .or_else(|| Some(send_stop_req(&mut state.ctx, state.stop_req.clone())));
252                State::Stopping(state)
253            }
254            State::Started(mut bss) => {
255                // IEEE Std 802.11-2016, 6.3.12.2.3: The SME should notify associated non-AP STAs of
256                // imminent infrastructure BSS termination before issuing the MLME-STOP.request
257                // primitive.
258                for client_addr in bss.clients.keys() {
259                    bss.ctx.mlme_sink.send(MlmeRequest::Deauthenticate(
260                        fidl_mlme::DeauthenticateRequest {
261                            peer_sta_address: client_addr.to_array(),
262                            // This seems to be the most appropriate reason code (IEEE Std
263                            // 802.11-2016, Table 9-45): Requesting STA is leaving the BSS (or
264                            // resetting). The spec doesn't seem to mandate a choice of reason code
265                            // here, so Fuchsia picks STA_LEAVING.
266                            reason_code: fidl_ieee80211::ReasonCode::StaLeaving,
267                        },
268                    ));
269                }
270
271                let stop_req = fidl_mlme::StopRequest { ssid: bss.ssid.to_vec() };
272                let timeout = send_stop_req(&mut bss.ctx, stop_req.clone());
273                State::Stopping(Box::new(StoppingState {
274                    ctx: bss.ctx,
275                    stop_req,
276                    responders: vec![responder],
277                    stop_timeout: Some(timeout),
278                }))
279            }
280        });
281        receiver
282    }
283
284    pub fn get_running_ap(&self) -> Option<fidl_sme::Ap> {
285        match self.state.as_ref() {
286            Some(State::Started(bss)) => Some(fidl_sme::Ap {
287                ssid: bss.ssid.to_vec(),
288                channel: bss.op_radio_cfg.channel.primary,
289                num_clients: bss.clients.len() as u16,
290            }),
291            _ => None,
292        }
293    }
294}
295
296fn send_stop_req(ctx: &mut Context, stop_req: fidl_mlme::StopRequest) -> EventHandle {
297    let event = Event::Sme { event: SmeEvent::StopTimeout };
298    let stop_timeout = ctx.timer.schedule(event);
299    ctx.mlme_sink.send(MlmeRequest::Stop(stop_req));
300    stop_timeout
301}
302
303impl super::Station for ApSme {
304    type Event = Event;
305
306    fn on_mlme_event(&mut self, event: MlmeEvent) {
307        debug!("received MLME event: {:?}", &event);
308        self.state = self.state.take().map(|state| match state {
309            State::Idle(_) => {
310                warn!("received MlmeEvent while ApSme is idle {:?}", mlme_event_name(&event));
311                state
312            }
313            State::Starting(state) => match event {
314                MlmeEvent::StartConf { resp } => handle_start_conf(resp, *state),
315                _ => {
316                    warn!(
317                        "received MlmeEvent while ApSme is starting {:?}",
318                        mlme_event_name(&event)
319                    );
320                    State::Starting(state)
321                }
322            },
323            State::Stopping(mut state) => match event {
324                MlmeEvent::StopConf { resp } => match resp.result_code {
325                    fidl_mlme::StopResultCode::Success
326                    | fidl_mlme::StopResultCode::BssAlreadyStopped => {
327                        for responder in state.responders.drain(..) {
328                            responder.respond(fidl_sme::StopApResultCode::Success);
329                        }
330                        State::Idle(Box::new(IdleState { ctx: state.ctx }))
331                    }
332                    fidl_mlme::StopResultCode::InternalError => {
333                        for responder in state.responders.drain(..) {
334                            responder.respond(fidl_sme::StopApResultCode::InternalError);
335                        }
336                        state.stop_timeout = None;
337                        State::Stopping(state)
338                    }
339                },
340                _ => {
341                    warn!(
342                        "received MlmeEvent while ApSme is stopping {:?}",
343                        mlme_event_name(&event)
344                    );
345                    State::Stopping(state)
346                }
347            },
348            State::Started(mut bss) => {
349                match event {
350                    MlmeEvent::OnChannelSwitched { info } => bss.handle_channel_switch(info),
351                    MlmeEvent::AuthenticateInd { ind } => bss.handle_auth_ind(ind),
352                    MlmeEvent::DeauthenticateInd { ind } => {
353                        bss.handle_deauth(&ind.peer_sta_address.into())
354                    }
355                    // TODO(https://fxbug.dev/42113580): This path should never be taken, as the MLME will never send
356                    // this. Make sure this is the case.
357                    MlmeEvent::DeauthenticateConf { resp } => {
358                        bss.handle_deauth(&resp.peer_sta_address.into())
359                    }
360                    MlmeEvent::AssociateInd { ind } => bss.handle_assoc_ind(ind),
361                    MlmeEvent::DisassociateInd { ind } => bss.handle_disassoc_ind(ind),
362                    MlmeEvent::EapolInd { ind } => bss.handle_eapol_ind(ind),
363                    MlmeEvent::EapolConf { resp } => bss.handle_eapol_conf(resp),
364                    _ => {
365                        warn!("unsupported MlmeEvent type {:?}; ignoring", mlme_event_name(&event))
366                    }
367                }
368                State::Started(bss)
369            }
370        });
371    }
372
373    fn on_timeout(&mut self, timed_event: timer::Event<Event>) {
374        self.state = self.state.take().map(|state| match state {
375            State::Idle(_) => state,
376            State::Starting(state) => match timed_event.event {
377                Event::Sme { event: SmeEvent::StartTimeout } => {
378                    let StartingState { mut ctx, start_responder, stop_responders, ssid, .. } =
379                        *state;
380                    warn!("Timed out waiting for MLME to start");
381                    start_responder.respond(StartResult::TimedOut);
382                    if stop_responders.is_empty() {
383                        State::Idle(Box::new(IdleState { ctx }))
384                    } else {
385                        let stop_req = fidl_mlme::StopRequest { ssid: ssid.to_vec() };
386                        let timeout = send_stop_req(&mut ctx, stop_req.clone());
387                        State::Stopping(Box::new(StoppingState {
388                            ctx,
389                            stop_req,
390                            responders: stop_responders,
391                            stop_timeout: Some(timeout),
392                        }))
393                    }
394                }
395                _ => State::Starting(state),
396            },
397            State::Stopping(mut state) => {
398                if let Event::Sme { event: SmeEvent::StopTimeout } = timed_event.event {
399                    for responder in state.responders.drain(..) {
400                        responder.respond(fidl_sme::StopApResultCode::TimedOut);
401                    }
402                    state.stop_timeout = None;
403                }
404                // If timeout triggered, then the responders and the timeout are cleared, and
405                // we are left in an unclean stopping state
406                State::Stopping(state)
407            }
408            State::Started(mut bss) => {
409                bss.handle_timeout(timed_event);
410                State::Started(bss)
411            }
412        });
413    }
414}
415
416/// Validate the channel, PHY type, bandwidth, and band capabilities, in that order.
417fn validate_radio_cfg(
418    bands: &[fidl_mlme::BandCapability],
419    radio_cfg: &RadioConfig,
420    spectrum_management_support: fidl_common::SpectrumManagementSupport,
421) -> Result<OpRadioConfig, StartResult> {
422    let band_cap = get_band_cap_for_channel(bands, radio_cfg.channel).map_err(|e| {
423        let e = e.context(format!(
424            "No band capabilities for channel {}: {bands:?}",
425            radio_cfg.channel.primary
426        ));
427        StartResult::InvalidArguments(format!("{e:?}"))
428    })?;
429    let channel = radio_cfg.channel;
430
431    // Avoid hosting an AP on a 5 GHz channel on a non-DFS devices. There is no 5 GHz
432    // channel that is valid in all regulatory domains.
433    if channel.is_5ghz()
434        && !spectrum_management_support
435            .dfs
436            .as_ref()
437            .is_some_and(|dfs| dfs.supported.unwrap_or(false))
438    {
439        return Err(StartResult::InvalidArguments(format!(
440            "5 GHz channels not supported: {channel}"
441        )));
442    }
443
444    let phy = radio_cfg.phy;
445    match phy {
446        fidl_common::WlanPhyType::Dsss
447        | fidl_common::WlanPhyType::Hr
448        | fidl_common::WlanPhyType::Ofdm
449        | fidl_common::WlanPhyType::Erp => match channel.cbw {
450            Cbw::Cbw20 => (),
451            _ => {
452                return Err(StartResult::InvalidArguments(format!(
453                    "PHY type {phy:?} not supported on channel {channel}"
454                )));
455            }
456        },
457        fidl_common::WlanPhyType::Ht => {
458            match channel.cbw {
459                Cbw::Cbw20 | Cbw::Cbw40 | Cbw::Cbw40Below => (),
460                _ => {
461                    return Err(StartResult::InvalidArguments(format!(
462                        "HT-mode not supported for channel {channel}"
463                    )));
464                }
465            }
466
467            match band_cap.ht_cap.as_ref() {
468                None => {
469                    return Err(StartResult::InvalidArguments(format!(
470                        "No HT capabilities: {channel}"
471                    )));
472                }
473                Some(ht_cap) => {
474                    let ht_cap = parse_ht_capabilities(&ht_cap.bytes[..]).map_err(|e| {
475                        error!("failed to parse HT capability bytes: {:?}", e);
476                        StartResult::InternalError
477                    })?;
478                    let ht_cap_info = ht_cap.ht_cap_info;
479                    if ht_cap_info.chan_width_set() == ChanWidthSet::TWENTY_ONLY
480                        && channel.cbw != Cbw::Cbw20
481                    {
482                        return Err(StartResult::InvalidArguments(format!(
483                            "20 MHz band capabilities does not support channel {channel}"
484                        )));
485                    }
486                }
487            }
488        }
489        fidl_common::WlanPhyType::Vht => {
490            match channel.cbw {
491                Cbw::Cbw160 | Cbw::Cbw80P80 { .. } => {
492                    return Err(StartResult::InvalidArguments(format!(
493                        "Supported for channel {channel} in VHT mode not available"
494                    )));
495                }
496                _ => (),
497            }
498
499            if !channel.is_5ghz() {
500                return Err(StartResult::InvalidArguments(format!(
501                    "VHT only supported on 5 GHz channels: {channel}"
502                )));
503            }
504
505            if band_cap.vht_cap.is_none() {
506                return Err(StartResult::InvalidArguments(format!(
507                    "No VHT capabilities: {channel}"
508                )));
509            }
510        }
511        fidl_common::WlanPhyType::Dmg
512        | fidl_common::WlanPhyType::Tvht
513        | fidl_common::WlanPhyType::S1G
514        | fidl_common::WlanPhyType::Cdmg
515        | fidl_common::WlanPhyType::Cmmg
516        | fidl_common::WlanPhyType::He => {
517            return Err(StartResult::InvalidArguments(format!("Unsupported PHY type: {phy:?}")));
518        }
519        fidl_common::WlanPhyTypeUnknown!() => {
520            return Err(StartResult::InvalidArguments(format!("Unknown PHY type: {phy:?}")));
521        }
522    }
523
524    Ok(OpRadioConfig { phy, channel, basic_rates: band_cap.basic_rates.clone() })
525}
526
527#[allow(clippy::too_many_arguments, reason = "mass allow for https://fxbug.dev/381896734")]
528fn handle_start_conf(conf: fidl_mlme::StartConfirm, mut state: StartingState) -> State {
529    if state.stop_responders.is_empty() {
530        match conf.result_code {
531            fidl_mlme::StartResultCode::Success => {
532                state.start_responder.respond(StartResult::Success);
533                State::Started(Box::new(StartedState {
534                    ssid: state.ssid,
535                    rsn_cfg: state.rsn_cfg,
536                    clients: HashMap::new(),
537                    aid_map: aid::Map::default(),
538                    op_radio_cfg: state.op_radio_cfg,
539                    ctx: state.ctx,
540                }))
541            }
542            result_code => {
543                error!("failed to start BSS: {:?}", result_code);
544                state.start_responder.respond(StartResult::InternalError);
545                State::Idle(Box::new(IdleState { ctx: state.ctx }))
546            }
547        }
548    } else {
549        state.start_responder.respond(StartResult::Canceled);
550        let stop_req = fidl_mlme::StopRequest { ssid: state.ssid.to_vec() };
551        let timeout = send_stop_req(&mut state.ctx, stop_req.clone());
552        State::Stopping(Box::new(StoppingState {
553            ctx: state.ctx,
554            stop_req,
555            responders: state.stop_responders,
556            stop_timeout: Some(timeout),
557        }))
558    }
559}
560
561impl StartedState {
562    /// Removes a client from the map.
563    ///
564    /// A client may only be removed via |remove_client| if:
565    ///
566    /// - MLME-DEAUTHENTICATE.request has been issued for the client, or,
567    /// - MLME-DEAUTHENTICATE.indication or MLME-DEAUTHENTICATE.confirm has been received for the
568    ///   client, or,
569    /// - MLME-AUTHENTICATE.indication is being handled (see comment in |handle_auth_ind| for
570    ///   details).
571    ///
572    /// If the client has an AID, its AID will be released from the AID map.
573    ///
574    /// Returns true if a client was removed, otherwise false.
575    fn remove_client(&mut self, addr: &MacAddr) -> bool {
576        if let Some(client) = self.clients.remove(addr) {
577            if let Some(aid) = client.aid() {
578                self.aid_map.release_aid(aid);
579            }
580            true
581        } else {
582            false
583        }
584    }
585
586    fn handle_channel_switch(&mut self, info: fidl_internal::ChannelSwitchInfo) {
587        info!("Channel switch for AP {:?}", info);
588        self.op_radio_cfg.channel.primary = info.new_channel;
589    }
590
591    fn handle_auth_ind(&mut self, ind: fidl_mlme::AuthenticateIndication) {
592        let peer_addr: MacAddr = ind.peer_sta_address.into();
593        if self.remove_client(&peer_addr) {
594            // This may occur if an already authenticated client on the SME receives a fresh
595            // MLME-AUTHENTICATE.indication from the MLME.
596            //
597            // This is safe, as we will make a fresh the client state and return an appropriate
598            // MLME-AUTHENTICATE.response to the MLME, indicating whether it should deauthenticate
599            // the client or not.
600            warn!(
601                "client {} is trying to reauthenticate; removing client and starting again",
602                peer_addr
603            );
604        }
605        let mut client = RemoteClient::new(peer_addr);
606        client.handle_auth_ind(&mut self.ctx, ind.auth_type);
607        if !client.authenticated() {
608            info!("client {} was not authenticated", peer_addr);
609            return;
610        }
611
612        info!("client {} authenticated", peer_addr);
613        let _ = self.clients.insert(peer_addr, client);
614    }
615
616    fn handle_deauth(&mut self, peer_addr: &MacAddr) {
617        if !self.remove_client(peer_addr) {
618            warn!("client {} never authenticated, ignoring deauthentication request", peer_addr);
619            return;
620        }
621
622        info!("client {} deauthenticated", peer_addr);
623    }
624
625    fn handle_assoc_ind(&mut self, ind: fidl_mlme::AssociateIndication) {
626        let peer_addr: MacAddr = ind.peer_sta_address.into();
627
628        let client = match self.clients.get_mut(&peer_addr) {
629            None => {
630                warn!("client {} never authenticated, ignoring association indication", peer_addr);
631                return;
632            }
633            Some(client) => client,
634        };
635
636        client.handle_assoc_ind(
637            &mut self.ctx,
638            &mut self.aid_map,
639            ind.capability_info,
640            ind.rates.into_iter().map(SupportedRate).collect::<Vec<_>>(),
641            &self.rsn_cfg,
642            ind.rsne,
643        );
644        if !client.authenticated() {
645            warn!("client {} failed to associate and was deauthenticated", peer_addr);
646            let _ = self.remove_client(&peer_addr);
647        } else if !client.associated() {
648            warn!("client {} failed to associate but did not deauthenticate", peer_addr);
649        } else {
650            info!("client {} associated", peer_addr);
651        }
652    }
653
654    fn handle_disassoc_ind(&mut self, ind: fidl_mlme::DisassociateIndication) {
655        let peer_addr: MacAddr = ind.peer_sta_address.into();
656
657        let client = match self.clients.get_mut(&peer_addr) {
658            None => {
659                warn!(
660                    "client {} never authenticated, ignoring disassociation indication",
661                    peer_addr
662                );
663                return;
664            }
665            Some(client) => client,
666        };
667
668        client.handle_disassoc_ind(&mut self.ctx, &mut self.aid_map);
669        if client.associated() {
670            panic!("client {peer_addr} didn't disassociate? this should never happen!")
671        } else {
672            info!("client {} disassociated", peer_addr);
673        }
674    }
675
676    fn handle_timeout(&mut self, timed_event: timer::Event<Event>) {
677        match timed_event.event {
678            Event::Sme { .. } => (),
679            Event::Client { addr, event } => {
680                let client = match self.clients.get_mut(&addr) {
681                    None => {
682                        return;
683                    }
684                    Some(client) => client,
685                };
686
687                client.handle_timeout(&mut self.ctx, event);
688                if !client.authenticated() {
689                    if !self.remove_client(&addr) {
690                        error!("failed to remove client {} from AID map", addr);
691                    }
692                    info!("client {} lost authentication", addr);
693                }
694            }
695        }
696    }
697
698    fn handle_eapol_ind(&mut self, ind: fidl_mlme::EapolIndication) {
699        let peer_addr: MacAddr = ind.src_addr.into();
700        let client = match self.clients.get_mut(&peer_addr) {
701            None => {
702                warn!("client {} never authenticated, ignoring EAPoL indication", peer_addr);
703                return;
704            }
705            Some(client) => client,
706        };
707
708        client.handle_eapol_ind(&mut self.ctx, &ind.data[..]);
709    }
710
711    fn handle_eapol_conf(&mut self, resp: fidl_mlme::EapolConfirm) {
712        let dst_addr: MacAddr = resp.dst_addr.into();
713        let client = match self.clients.get_mut(&dst_addr) {
714            None => {
715                warn!("never sent EAPOL frame to client {}, ignoring confirm", dst_addr);
716                return;
717            }
718            Some(client) => client,
719        };
720
721        client.handle_eapol_conf(&mut self.ctx, resp.result_code);
722    }
723}
724
725fn create_rsn_cfg(ssid: &Ssid, password: &[u8]) -> Result<Option<RsnCfg>, StartResult> {
726    if password.is_empty() {
727        Ok(None)
728    } else {
729        let psk_result = psk::compute(password, ssid);
730        let psk = match psk_result {
731            Err(e) => {
732                return Err(StartResult::InvalidArguments(e.to_string()));
733            }
734            Ok(o) => o,
735        };
736
737        // Note: TKIP is legacy and considered insecure. Only allow CCMP usage
738        // for group and pairwise ciphers.
739        Ok(Some(RsnCfg { psk, rsne: Rsne::wpa2_rsne_with_caps(RsnCapabilities(0)) }))
740    }
741}
742
743fn create_start_request(
744    op_radio_cfg: &OpRadioConfig,
745    ssid: &Ssid,
746    ap_rsn: Option<&RsnCfg>,
747    capabilities: mac::CapabilityInfo,
748) -> Result<fidl_mlme::StartRequest, StartResult> {
749    let rsne_bytes = ap_rsn.as_ref().map(|RsnCfg { rsne, .. }| {
750        let mut buf = Vec::with_capacity(rsne.len());
751        if let Err(e) = rsne.write_into(&mut buf) {
752            error!("error writing RSNE into MLME-START.request: {}", e);
753        }
754        buf
755    });
756
757    let (channel_bandwidth, _secondary80) = op_radio_cfg.channel.cbw.to_fidl();
758
759    if op_radio_cfg.basic_rates.len() > fidl_internal::MAX_ASSOC_BASIC_RATES as usize {
760        error!(
761            "Too many basic rates ({}). Max is {}.",
762            op_radio_cfg.basic_rates.len(),
763            fidl_internal::MAX_ASSOC_BASIC_RATES
764        );
765        return Err(StartResult::InternalError);
766    }
767
768    Ok(fidl_mlme::StartRequest {
769        ssid: ssid.to_vec(),
770        bss_type: fidl_common::BssType::Infrastructure,
771        beacon_period: DEFAULT_BEACON_PERIOD,
772        dtim_period: DEFAULT_DTIM_PERIOD,
773        channel: op_radio_cfg.channel.primary,
774        capability_info: capabilities.raw(),
775        rates: op_radio_cfg.basic_rates.clone(),
776        country: fidl_mlme::Country {
777            // TODO(https://fxbug.dev/42104247): Get config from wlancfg
778            alpha2: [b'U', b'S'],
779            suffix: fidl_mlme::COUNTRY_ENVIRON_ALL,
780        },
781        rsne: rsne_bytes,
782        mesh_id: vec![],
783        phy: op_radio_cfg.phy,
784        channel_bandwidth,
785    })
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791    use crate::test_utils::*;
792    use crate::{MlmeStream, Station};
793    use assert_matches::assert_matches;
794    use fidl_fuchsia_wlan_mlme as fidl_mlme;
795    use std::sync::LazyLock;
796    use test_case::test_case;
797    use wlan_common::channel::Cbw;
798    use wlan_common::mac::Aid;
799    use wlan_common::test_utils::fake_capabilities::{
800        fake_2ghz_band_capability_ht, fake_5ghz_band_capability, fake_5ghz_band_capability_ht,
801        fake_5ghz_band_capability_vht,
802    };
803    use wlan_common::test_utils::fake_features::{
804        fake_dfs_supported, fake_spectrum_management_support_empty,
805    };
806
807    static AP_ADDR: LazyLock<MacAddr> =
808        LazyLock::new(|| [0x11, 0x22, 0x33, 0x44, 0x55, 0x66].into());
809    static CLIENT_ADDR: LazyLock<MacAddr> =
810        LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
811    static CLIENT_ADDR2: LazyLock<MacAddr> =
812        LazyLock::new(|| [0x22, 0x22, 0x22, 0x22, 0x22, 0x22].into());
813    static SSID: LazyLock<Ssid> =
814        LazyLock::new(|| Ssid::try_from([0x46, 0x55, 0x43, 0x48, 0x53, 0x49, 0x41]).unwrap());
815
816    const RSNE: &[u8] = &[
817        0x30, // element id
818        0x2A, // length
819        0x01, 0x00, // version
820        0x00, 0x0f, 0xac, 0x04, // group data cipher suite -- CCMP-128
821        0x01, 0x00, // pairwise cipher suite count
822        0x00, 0x0f, 0xac, 0x04, // pairwise cipher suite list -- CCMP-128
823        0x01, 0x00, // akm suite count
824        0x00, 0x0f, 0xac, 0x02, // akm suite list -- PSK
825        0xa8, 0x04, // rsn capabilities
826        0x01, 0x00, // pmk id count
827        // pmk id list
828        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
829        0x11, 0x00, 0x0f, 0xac, 0x04, // group management cipher suite -- CCMP-128
830    ];
831
832    fn radio_cfg(primary_channel: u8) -> RadioConfig {
833        RadioConfig::new(fidl_common::WlanPhyType::Ht, Cbw::Cbw20, primary_channel)
834    }
835
836    fn unprotected_config() -> Config {
837        Config { ssid: SSID.clone(), password: vec![], radio_cfg: radio_cfg(11) }
838    }
839
840    fn protected_config() -> Config {
841        Config {
842            ssid: SSID.clone(),
843            password: vec![0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68],
844            radio_cfg: radio_cfg(11),
845        }
846    }
847
848    fn create_channel_switch_ind(channel: u8) -> MlmeEvent {
849        MlmeEvent::OnChannelSwitched {
850            info: fidl_internal::ChannelSwitchInfo { new_channel: channel },
851        }
852    }
853
854    #[derive(Clone, Debug)]
855    struct ValidateRadioConfigArgs {
856        bands: Vec<fidl_mlme::BandCapability>,
857        radio_cfg: RadioConfig,
858        spectrum_management_support: fidl_common::SpectrumManagementSupport,
859    }
860
861    #[test_case(false, ValidateRadioConfigArgs {
862        bands: vec![fake_2ghz_band_capability_ht()],
863        radio_cfg: RadioConfig {
864            phy: fidl_common::WlanPhyType::Ht,
865            channel: Channel::new(15, Cbw::Cbw20),
866        },
867        spectrum_management_support: fake_spectrum_management_support_empty(),
868    }; "invalid US channel")]
869    #[test_case(false, ValidateRadioConfigArgs {
870        bands: vec![fake_5ghz_band_capability()],
871        radio_cfg: RadioConfig {
872            phy: fidl_common::WlanPhyType::Ht,
873            channel: Channel::new(36, Cbw::Cbw20),
874        },
875        spectrum_management_support: fake_spectrum_management_support_empty(),
876    }; "5 GHz channel and no DFS support")]
877    #[test_case(false, ValidateRadioConfigArgs {
878        bands: vec![fake_2ghz_band_capability_ht()],
879        radio_cfg: RadioConfig {
880            phy: fidl_common::WlanPhyType::Dmg,
881            channel: Channel::new(1, Cbw::Cbw20),
882        },
883        spectrum_management_support: fake_spectrum_management_support_empty(),
884    }; "DMG not supported")]
885    #[test_case(false, ValidateRadioConfigArgs {
886        bands: vec![fake_2ghz_band_capability_ht()],
887        radio_cfg: RadioConfig {
888            phy: fidl_common::WlanPhyType::Tvht,
889            channel: Channel::new(1, Cbw::Cbw20),
890        },
891        spectrum_management_support: fake_spectrum_management_support_empty(),
892    }; "TVHT not supported")]
893    #[test_case(false, ValidateRadioConfigArgs {
894        bands: vec![fake_2ghz_band_capability_ht()],
895        radio_cfg: RadioConfig {
896            phy: fidl_common::WlanPhyType::S1G,
897            channel: Channel::new(1, Cbw::Cbw20),
898        },
899        spectrum_management_support: fake_spectrum_management_support_empty(),
900    }; "S1G not supported")]
901    #[test_case(false, ValidateRadioConfigArgs {
902        bands: vec![fake_2ghz_band_capability_ht()],
903        radio_cfg: RadioConfig {
904            phy: fidl_common::WlanPhyType::Cdmg,
905            channel: Channel::new(1, Cbw::Cbw20),
906        },
907        spectrum_management_support: fake_spectrum_management_support_empty(),
908    }; "CDMG not supported")]
909    #[test_case(false, ValidateRadioConfigArgs {
910        bands: vec![fake_2ghz_band_capability_ht()],
911        radio_cfg: RadioConfig {
912            phy: fidl_common::WlanPhyType::Cmmg,
913            channel: Channel::new(1, Cbw::Cbw20),
914        },
915        spectrum_management_support: fake_spectrum_management_support_empty(),
916    }; "CMMG not supported")]
917    #[test_case(false, ValidateRadioConfigArgs {
918        bands: vec![fake_2ghz_band_capability_ht()],
919        radio_cfg: RadioConfig {
920            phy: fidl_common::WlanPhyType::He,
921            channel: Channel::new(1, Cbw::Cbw20),
922        },
923        spectrum_management_support: fake_spectrum_management_support_empty(),
924    }; "HE not supported")]
925    #[test_case(false, ValidateRadioConfigArgs {
926        bands: vec![fake_2ghz_band_capability_ht()],
927        radio_cfg: RadioConfig {
928            phy: fidl_common::WlanPhyType::Ht,
929            channel: Channel::new(36, Cbw::Cbw80),
930        },
931        spectrum_management_support: fake_dfs_supported(),
932    }; "invalid HT width")]
933    #[test_case(false, ValidateRadioConfigArgs {
934        bands: vec![fake_2ghz_band_capability_ht()],
935        radio_cfg: RadioConfig {
936            phy: fidl_common::WlanPhyType::Erp,
937            channel: Channel::new(1, Cbw::Cbw40),
938        },
939        spectrum_management_support: fake_spectrum_management_support_empty(),
940    }; "non-HT greater than 20 MHz")]
941    #[test_case(false, ValidateRadioConfigArgs {
942        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
943        radio_cfg: RadioConfig {
944            phy: fidl_common::WlanPhyType::Ht,
945            channel: Channel::new(36, Cbw::Cbw80),
946        },
947        spectrum_management_support: fake_dfs_supported(),
948    }; "HT greater than 40 MHz")]
949    #[test_case(false, ValidateRadioConfigArgs {
950        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
951        radio_cfg: RadioConfig {
952            phy: fidl_common::WlanPhyType::unknown(),
953            channel: Channel::new(36, Cbw::Cbw40),
954        },
955        spectrum_management_support: fake_dfs_supported(),
956    }; "Unknown PHY type")]
957    #[test_case(false, ValidateRadioConfigArgs {
958        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_ONLY)],
959        radio_cfg: RadioConfig {
960            phy: fidl_common::WlanPhyType::Ht,
961            channel: Channel::new(44, Cbw::Cbw40),
962        },
963        spectrum_management_support: fake_dfs_supported(),
964    }; "HT 20 MHz only")]
965    #[test_case(false, ValidateRadioConfigArgs {
966        bands: vec![fake_5ghz_band_capability()],
967        radio_cfg: RadioConfig {
968            phy: fidl_common::WlanPhyType::Ht,
969            channel: Channel::new(48, Cbw::Cbw40),
970        },
971        spectrum_management_support: fake_dfs_supported(),
972    }; "No HT capabilities")]
973    #[test_case(false, ValidateRadioConfigArgs {
974        bands: vec![fake_5ghz_band_capability_vht()],
975        radio_cfg: RadioConfig {
976            phy: fidl_common::WlanPhyType::Vht,
977            channel: Channel::new(36, Cbw::Cbw160),
978        },
979        spectrum_management_support: fake_dfs_supported(),
980    }; "160 MHz not supported")]
981    #[test_case(false, ValidateRadioConfigArgs {
982        bands: vec![fake_5ghz_band_capability_vht()],
983        radio_cfg: RadioConfig {
984            phy: fidl_common::WlanPhyType::Vht,
985            channel: Channel::new(36, Cbw::Cbw80P80 { secondary80: 106 }),
986        },
987        spectrum_management_support: fake_dfs_supported(),
988    }; "80+80 MHz not supported")]
989    #[test_case(false, ValidateRadioConfigArgs {
990        bands: vec![fake_2ghz_band_capability_ht()],
991        radio_cfg: RadioConfig {
992            phy: fidl_common::WlanPhyType::Vht,
993            channel: Channel::new(1, Cbw::Cbw20),
994        },
995        spectrum_management_support: fake_spectrum_management_support_empty(),
996    }; "VHT 2.4 GHz not supported")]
997    #[test_case(false, ValidateRadioConfigArgs {
998        bands: vec![fake_5ghz_band_capability()],
999        radio_cfg: RadioConfig {
1000            phy: fidl_common::WlanPhyType::Vht,
1001            channel: Channel::new(149, Cbw::Cbw80),
1002        },
1003        spectrum_management_support: fake_dfs_supported(),
1004    }; "no VHT capabilities")]
1005    #[test_case(false, ValidateRadioConfigArgs {
1006        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1007        radio_cfg: RadioConfig {
1008            phy: fidl_common::WlanPhyType::Vht,
1009            channel: Channel::new(1, Cbw::Cbw40),
1010        },
1011        spectrum_management_support: fake_spectrum_management_support_empty(),
1012    }; "no VHT capabilities on 2.4 GHz event when 5 GHz band capabilities provided")]
1013    #[test_case(false, ValidateRadioConfigArgs {
1014        bands: vec![fidl_mlme::BandCapability {
1015            operating_channels: vec![2],
1016            ..fake_2ghz_band_capability_ht()
1017        }],
1018        radio_cfg: RadioConfig {
1019            phy: fidl_common::WlanPhyType::Hr,
1020            channel: Channel::new(1, Cbw::Cbw40),
1021        },
1022        spectrum_management_support: fake_spectrum_management_support_empty(),
1023    }; "disallow non-operating 2.4 GHz channel")]
1024    #[test_case(false, ValidateRadioConfigArgs {
1025        bands: vec![fidl_mlme::BandCapability {
1026            operating_channels: vec![40],
1027            ..fake_5ghz_band_capability_vht()
1028        }],
1029        radio_cfg: RadioConfig {
1030            phy: fidl_common::WlanPhyType::Vht,
1031            channel: Channel::new(36, Cbw::Cbw80),
1032        },
1033        spectrum_management_support: fake_spectrum_management_support_empty(),
1034    }; "disallow non-operating 5 GHz channel")]
1035    #[test_case(true, ValidateRadioConfigArgs {
1036        bands: vec![fake_2ghz_band_capability_ht()],
1037        radio_cfg: RadioConfig {
1038            phy: fidl_common::WlanPhyType::Hr,
1039            channel: Channel::new(1, Cbw::Cbw20),
1040        },
1041        spectrum_management_support: fake_spectrum_management_support_empty(),
1042    })]
1043    #[test_case(true, ValidateRadioConfigArgs {
1044        bands: vec![fake_2ghz_band_capability_ht()],
1045        radio_cfg: RadioConfig {
1046            phy: fidl_common::WlanPhyType::Erp,
1047            channel: Channel::new(1, Cbw::Cbw20),
1048        },
1049        spectrum_management_support: fake_spectrum_management_support_empty(),
1050    })]
1051    #[test_case(true, ValidateRadioConfigArgs {
1052        bands: vec![fake_2ghz_band_capability_ht()],
1053        radio_cfg: RadioConfig {
1054            phy: fidl_common::WlanPhyType::Ht,
1055            channel: Channel::new(1, Cbw::Cbw20),
1056        },
1057        spectrum_management_support: fake_spectrum_management_support_empty(),
1058    })]
1059    #[test_case(true, ValidateRadioConfigArgs {
1060        bands: vec![fake_2ghz_band_capability_ht()],
1061        radio_cfg: RadioConfig {
1062            phy: fidl_common::WlanPhyType::Ht,
1063            channel: Channel::new(1, Cbw::Cbw40),
1064        },
1065        spectrum_management_support: fake_spectrum_management_support_empty(),
1066    })]
1067    #[test_case(true, ValidateRadioConfigArgs {
1068        bands: vec![fake_2ghz_band_capability_ht()],
1069        radio_cfg: RadioConfig {
1070            phy: fidl_common::WlanPhyType::Ht,
1071            channel: Channel::new(11, Cbw::Cbw40Below),
1072        },
1073        spectrum_management_support: fake_spectrum_management_support_empty(),
1074    })]
1075    #[test_case(true, ValidateRadioConfigArgs {
1076        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_ONLY)],
1077        radio_cfg: RadioConfig {
1078            phy: fidl_common::WlanPhyType::Ht,
1079            channel: Channel::new(36, Cbw::Cbw20),
1080        },
1081        spectrum_management_support: fake_dfs_supported(),
1082    })]
1083    #[test_case(true, ValidateRadioConfigArgs {
1084        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1085        radio_cfg: RadioConfig {
1086            phy: fidl_common::WlanPhyType::Ht,
1087            channel: Channel::new(36, Cbw::Cbw40),
1088        },
1089        spectrum_management_support: fake_dfs_supported(),
1090    })]
1091    #[test_case(true, ValidateRadioConfigArgs {
1092        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1093        radio_cfg: RadioConfig {
1094            phy: fidl_common::WlanPhyType::Ht,
1095            channel: Channel::new(40, Cbw::Cbw40Below),
1096        },
1097        spectrum_management_support: fake_dfs_supported(),
1098    })]
1099    #[test_case(true, ValidateRadioConfigArgs {
1100        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1101        radio_cfg: RadioConfig {
1102            phy: fidl_common::WlanPhyType::Ht,
1103            channel: Channel::new(36, Cbw::Cbw20),
1104        },
1105        spectrum_management_support: fake_dfs_supported(),
1106    })]
1107    #[test_case(true, ValidateRadioConfigArgs {
1108        bands: vec![fake_5ghz_band_capability_vht()],
1109        radio_cfg: RadioConfig {
1110            phy: fidl_common::WlanPhyType::Ht,
1111            channel: Channel::new(36, Cbw::Cbw40),
1112        },
1113        spectrum_management_support: fake_dfs_supported(),
1114    })]
1115    #[test_case(true, ValidateRadioConfigArgs {
1116        bands: vec![fake_5ghz_band_capability_vht()],
1117        radio_cfg: RadioConfig {
1118            phy: fidl_common::WlanPhyType::Ht,
1119            channel: Channel::new(40, Cbw::Cbw40Below),
1120        },
1121        spectrum_management_support: fake_dfs_supported(),
1122    })]
1123    #[test_case(true, ValidateRadioConfigArgs {
1124        bands: vec![fake_5ghz_band_capability_vht()],
1125        radio_cfg: RadioConfig {
1126            phy: fidl_common::WlanPhyType::Vht,
1127            channel: Channel::new(36, Cbw::Cbw80),
1128        },
1129        spectrum_management_support: fake_dfs_supported(),
1130    })]
1131    #[test_case(true, ValidateRadioConfigArgs {
1132        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1133        radio_cfg: RadioConfig {
1134            phy: fidl_common::WlanPhyType::Ht,
1135            channel: Channel::new(1, Cbw::Cbw40),
1136        },
1137        spectrum_management_support: fake_spectrum_management_support_empty(),
1138    })]
1139    #[test_case(true, ValidateRadioConfigArgs {
1140        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1141        radio_cfg: RadioConfig {
1142            phy: fidl_common::WlanPhyType::Vht,
1143            channel: Channel::new(36, Cbw::Cbw80),
1144        },
1145        spectrum_management_support: fake_dfs_supported(),
1146    })]
1147    fn test_validate_radio_cfg(expect_ok: bool, fn_args: ValidateRadioConfigArgs) {
1148        match validate_radio_cfg(
1149            &fn_args.bands[..],
1150            &fn_args.radio_cfg,
1151            fn_args.spectrum_management_support.clone(),
1152        ) {
1153            Ok(op_radio_cfg) => {
1154                if !expect_ok {
1155                    panic!("Unexpected successful validation: {0:?}, {op_radio_cfg:?}", fn_args);
1156                }
1157                assert_matches!(
1158                    op_radio_cfg,
1159                    OpRadioConfig {
1160                        phy,
1161                        channel,
1162                        basic_rates: _,
1163                    } => {
1164                        assert_eq!(phy, fn_args.radio_cfg.phy);
1165                        assert_eq!(channel, fn_args.radio_cfg.channel);
1166                    }
1167                )
1168            }
1169            Err(e @ StartResult::InvalidArguments { .. }) => {
1170                if expect_ok {
1171                    panic!("Unexpected failure to validate: {0:?}, {e:?}", fn_args)
1172                }
1173            }
1174            Err(e) => panic!("Unexpected StartResult value: {0:?}, {e:?}", fn_args),
1175        }
1176    }
1177
1178    #[fuchsia::test(allow_stalls = false)]
1179    async fn authenticate_while_sme_is_idle() {
1180        let (mut sme, mut mlme_stream, _) = create_sme().await;
1181        let client = Client::default();
1182        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1183
1184        assert_matches!(mlme_stream.try_next(), Err(e) => {
1185            assert_eq!(e.to_string(), "receiver channel is empty");
1186        });
1187    }
1188
1189    // Check status when sme is idle
1190    #[fuchsia::test(allow_stalls = false)]
1191    async fn status_when_sme_is_idle() {
1192        let (sme, _, _) = create_sme().await;
1193        assert_eq!(None, sme.get_running_ap());
1194    }
1195
1196    #[fuchsia::test(allow_stalls = false)]
1197    async fn ap_starts_success() {
1198        let (mut sme, mut mlme_stream, _) = create_sme().await;
1199        let mut receiver = sme.on_start_command(unprotected_config());
1200
1201        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(start_req))) => {
1202            assert_eq!(start_req.ssid, SSID.to_vec());
1203            assert_eq!(
1204                start_req.capability_info,
1205                mac::CapabilityInfo(0).with_short_preamble(true).with_ess(true).raw(),
1206            );
1207            assert_eq!(start_req.bss_type, fidl_common::BssType::Infrastructure);
1208            assert_ne!(start_req.beacon_period, 0);
1209            assert_eq!(start_req.dtim_period, DEFAULT_DTIM_PERIOD);
1210            assert_eq!(
1211                start_req.channel,
1212                unprotected_config().radio_cfg.channel.primary,
1213            );
1214            assert!(start_req.rsne.is_none());
1215        });
1216
1217        assert_eq!(Ok(None), receiver.try_recv());
1218        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1219        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1220    }
1221
1222    // Check status when Ap starting and started
1223    #[fuchsia::test(allow_stalls = false)]
1224    async fn ap_starts_success_get_running_ap() {
1225        let (mut sme, mut mlme_stream, _) = create_sme().await;
1226        let mut receiver = sme.on_start_command(unprotected_config());
1227        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(_start_req))) => {});
1228        // status should be Starting
1229        assert_eq!(None, sme.get_running_ap());
1230        assert_eq!(Ok(None), receiver.try_recv());
1231        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1232        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1233        assert_eq!(
1234            Some(fidl_sme::Ap {
1235                ssid: SSID.to_vec(),
1236                channel: unprotected_config().radio_cfg.channel.primary,
1237                num_clients: 0,
1238            }),
1239            sme.get_running_ap()
1240        );
1241    }
1242
1243    // Check status after channel change
1244    #[fuchsia::test(allow_stalls = false)]
1245    async fn ap_check_status_after_channel_change() {
1246        let (mut sme, _, _) = start_unprotected_ap().await;
1247        // Check status
1248        assert_eq!(
1249            Some(fidl_sme::Ap {
1250                ssid: SSID.to_vec(),
1251                channel: unprotected_config().radio_cfg.channel.primary,
1252                num_clients: 0,
1253            }),
1254            sme.get_running_ap()
1255        );
1256        sme.on_mlme_event(create_channel_switch_ind(6));
1257        // Check status
1258        assert_eq!(
1259            Some(fidl_sme::Ap { ssid: SSID.to_vec(), channel: 6, num_clients: 0 }),
1260            sme.get_running_ap()
1261        );
1262    }
1263
1264    #[fuchsia::test(allow_stalls = false)]
1265    async fn ap_starts_timeout() {
1266        let (mut sme, _, mut time_stream) = create_sme().await;
1267        let mut receiver = sme.on_start_command(unprotected_config());
1268
1269        let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1270        sme.on_timeout(event);
1271
1272        assert_eq!(Ok(Some(StartResult::TimedOut)), receiver.try_recv());
1273        // Check status
1274        assert_eq!(None, sme.get_running_ap());
1275    }
1276
1277    // Disable logging to prevent failure from emitted error logs.
1278    #[fuchsia::test(allow_stalls = false, logging = false)]
1279    async fn ap_starts_fails() {
1280        let (mut sme, _, _) = create_sme().await;
1281        let mut receiver = sme.on_start_command(unprotected_config());
1282
1283        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::NotSupported));
1284        assert_eq!(Ok(Some(StartResult::InternalError)), receiver.try_recv());
1285        // Check status
1286        assert_eq!(None, sme.get_running_ap());
1287    }
1288
1289    #[fuchsia::test(allow_stalls = false)]
1290    async fn start_req_while_ap_is_starting() {
1291        let (mut sme, _, _) = create_sme().await;
1292        let mut receiver_one = sme.on_start_command(unprotected_config());
1293
1294        // While SME is starting, any start request receives an error immediately
1295        let mut receiver_two = sme.on_start_command(unprotected_config());
1296        assert_eq!(Ok(Some(StartResult::PreviousStartInProgress)), receiver_two.try_recv());
1297
1298        // Start confirmation for first request should still have an affect
1299        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1300        assert_eq!(Ok(Some(StartResult::Success)), receiver_one.try_recv());
1301    }
1302
1303    #[fuchsia::test(allow_stalls = false)]
1304    async fn start_req_while_ap_is_stopping() {
1305        let (mut sme, _, _) = start_unprotected_ap().await;
1306        let mut stop_receiver = sme.on_stop_command();
1307        let mut start_receiver = sme.on_start_command(unprotected_config());
1308        assert_eq!(Ok(None), stop_receiver.try_recv());
1309        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1310    }
1311
1312    #[fuchsia::test(allow_stalls = false)]
1313    async fn ap_stops_while_idle() {
1314        let (mut sme, mut mlme_stream, _) = create_sme().await;
1315        let mut receiver = sme.on_stop_command();
1316        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1317            assert!(stop_req.ssid.is_empty());
1318        });
1319
1320        // Respond with a successful stop result code
1321        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1322        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver.try_recv());
1323    }
1324
1325    #[fuchsia::test(allow_stalls = false)]
1326    async fn stop_req_while_ap_is_starting_then_succeeds() {
1327        let (mut sme, mut mlme_stream, _) = create_sme().await;
1328        let mut start_receiver = sme.on_start_command(unprotected_config());
1329        let mut stop_receiver = sme.on_stop_command();
1330        assert_eq!(Ok(None), start_receiver.try_recv());
1331        assert_eq!(Ok(None), stop_receiver.try_recv());
1332
1333        // Verify start request is sent to MLME but not stop request yet
1334        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(_))));
1335        assert_matches!(mlme_stream.try_next(), Err(e) => {
1336            assert_eq!(e.to_string(), "receiver channel is empty");
1337        });
1338
1339        // Once start confirmation is finished, then stop request is sent out
1340        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1341        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1342        assert_eq!(Ok(None), stop_receiver.try_recv());
1343        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1344            assert_eq!(stop_req.ssid, SSID.to_vec());
1345        });
1346
1347        // Respond with a successful stop result code
1348        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1349        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver.try_recv());
1350    }
1351
1352    #[fuchsia::test(allow_stalls = false)]
1353    async fn stop_req_while_ap_is_starting_then_times_out() {
1354        let (mut sme, mut mlme_stream, mut time_stream) = create_sme().await;
1355        let mut start_receiver = sme.on_start_command(unprotected_config());
1356        let mut stop_receiver = sme.on_stop_command();
1357        assert_eq!(Ok(None), start_receiver.try_recv());
1358        assert_eq!(Ok(None), stop_receiver.try_recv());
1359
1360        // Verify start request is sent to MLME but not stop request yet
1361        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(_))));
1362        assert_matches!(mlme_stream.try_next(), Err(e) => {
1363            assert_eq!(e.to_string(), "receiver channel is empty");
1364        });
1365
1366        // Time out the start request. Then stop request is sent out
1367        let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1368        sme.on_timeout(event);
1369        assert_eq!(Ok(Some(StartResult::TimedOut)), start_receiver.try_recv());
1370        assert_eq!(Ok(None), stop_receiver.try_recv());
1371        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1372            assert_eq!(stop_req.ssid, SSID.to_vec());
1373        });
1374
1375        // Respond with a successful stop result code
1376        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1377        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver.try_recv());
1378    }
1379
1380    #[fuchsia::test(allow_stalls = false)]
1381    async fn ap_stops_after_started() {
1382        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1383        let mut receiver = sme.on_stop_command();
1384
1385        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1386            assert_eq!(stop_req.ssid, SSID.to_vec());
1387        });
1388        assert_eq!(Ok(None), receiver.try_recv());
1389        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::BssAlreadyStopped));
1390        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver.try_recv());
1391    }
1392
1393    #[fuchsia::test(allow_stalls = false)]
1394    async fn ap_stops_after_started_and_deauths_all_clients() {
1395        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1396        let client = Client::default();
1397        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1398        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1399
1400        // Check status
1401        assert_eq!(
1402            Some(fidl_sme::Ap {
1403                ssid: SSID.to_vec(),
1404                channel: unprotected_config().radio_cfg.channel.primary,
1405                num_clients: 1,
1406            }),
1407            sme.get_running_ap()
1408        );
1409        let mut receiver = sme.on_stop_command();
1410        assert_matches!(
1411        mlme_stream.try_next(),
1412        Ok(Some(MlmeRequest::Deauthenticate(deauth_req))) => {
1413            assert_eq!(&deauth_req.peer_sta_address, client.addr.as_array());
1414            assert_eq!(deauth_req.reason_code, fidl_ieee80211::ReasonCode::StaLeaving);
1415        });
1416
1417        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1418            assert_eq!(stop_req.ssid, SSID.to_vec());
1419        });
1420        assert_eq!(Ok(None), receiver.try_recv());
1421        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1422        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver.try_recv());
1423
1424        // Check status
1425        assert_eq!(None, sme.get_running_ap());
1426    }
1427
1428    #[fuchsia::test(allow_stalls = false)]
1429    async fn ap_queues_concurrent_stop_requests() {
1430        let (mut sme, _, _) = start_unprotected_ap().await;
1431        let mut receiver1 = sme.on_stop_command();
1432        let mut receiver2 = sme.on_stop_command();
1433
1434        assert_eq!(Ok(None), receiver1.try_recv());
1435        assert_eq!(Ok(None), receiver2.try_recv());
1436
1437        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1438        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver1.try_recv());
1439        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver2.try_recv());
1440    }
1441
1442    #[fuchsia::test(allow_stalls = false)]
1443    async fn uncleaned_stopping_state() {
1444        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1445        let mut stop_receiver1 = sme.on_stop_command();
1446        // Clear out the stop request
1447        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1448            assert_eq!(stop_req.ssid, SSID.to_vec());
1449        });
1450
1451        assert_eq!(Ok(None), stop_receiver1.try_recv());
1452        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::InternalError));
1453        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::InternalError)), stop_receiver1.try_recv());
1454
1455        // While in unclean stopping state, no start request can be made
1456        let mut start_receiver = sme.on_start_command(unprotected_config());
1457        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1458        assert_matches!(mlme_stream.try_next(), Err(e) => {
1459            assert_eq!(e.to_string(), "receiver channel is empty");
1460        });
1461
1462        // SME will forward another stop request to lower layer
1463        let mut stop_receiver2 = sme.on_stop_command();
1464        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1465            assert_eq!(stop_req.ssid, SSID.to_vec());
1466        });
1467
1468        // Respond successful this time
1469        assert_eq!(Ok(None), stop_receiver2.try_recv());
1470        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1471        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver2.try_recv());
1472    }
1473
1474    #[fuchsia::test(allow_stalls = false)]
1475    async fn client_authenticates_supported_authentication_type() {
1476        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1477        let client = Client::default();
1478        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1479        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1480    }
1481
1482    // Disable logging to prevent failure from emitted error logs.
1483    #[fuchsia::test(allow_stalls = false, logging = false)]
1484    async fn client_authenticates_unsupported_authentication_type() {
1485        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1486        let client = Client::default();
1487        let auth_ind = client.create_auth_ind(fidl_mlme::AuthenticationTypes::FastBssTransition);
1488        sme.on_mlme_event(auth_ind);
1489        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Refused);
1490    }
1491
1492    #[fuchsia::test(allow_stalls = false)]
1493    async fn client_associates_unprotected_network() {
1494        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1495        let client = Client::default();
1496        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1497        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1498
1499        sme.on_mlme_event(client.create_assoc_ind(None));
1500        client.verify_assoc_resp(
1501            &mut mlme_stream,
1502            1,
1503            fidl_mlme::AssociateResultCode::Success,
1504            false,
1505        );
1506    }
1507
1508    #[fuchsia::test(allow_stalls = false)]
1509    async fn client_associates_valid_rsne() {
1510        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1511        let client = Client::default();
1512        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1513
1514        sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
1515        client.verify_assoc_resp(
1516            &mut mlme_stream,
1517            1,
1518            fidl_mlme::AssociateResultCode::Success,
1519            true,
1520        );
1521        client.verify_eapol_req(&mut mlme_stream);
1522    }
1523
1524    // Disable logging to prevent failure from emitted error logs.
1525    #[fuchsia::test(allow_stalls = false, logging = false)]
1526    async fn client_associates_invalid_rsne() {
1527        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1528        let client = Client::default();
1529        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1530
1531        sme.on_mlme_event(client.create_assoc_ind(None));
1532        client.verify_refused_assoc_resp(
1533            &mut mlme_stream,
1534            fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch,
1535        );
1536    }
1537
1538    #[fuchsia::test(allow_stalls = false)]
1539    async fn rsn_handshake_timeout() {
1540        let (mut sme, mut mlme_stream, mut time_stream) = start_protected_ap().await;
1541        let client = Client::default();
1542        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1543
1544        // Drain the association timeout message.
1545        assert_matches!(time_stream.try_next(), Ok(Some(_)));
1546
1547        sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
1548        client.verify_assoc_resp(
1549            &mut mlme_stream,
1550            1,
1551            fidl_mlme::AssociateResultCode::Success,
1552            true,
1553        );
1554
1555        // Drain the RSNA negotiation timeout message.
1556        assert_matches!(time_stream.try_next(), Ok(Some(_)));
1557
1558        for _i in 0..4 {
1559            client.verify_eapol_req(&mut mlme_stream);
1560            let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1561            sme.on_timeout(event);
1562        }
1563
1564        client.verify_deauth_req(
1565            &mut mlme_stream,
1566            fidl_ieee80211::ReasonCode::FourwayHandshakeTimeout,
1567        );
1568    }
1569
1570    #[fuchsia::test(allow_stalls = false)]
1571    async fn client_restarts_authentication_flow() {
1572        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1573        let client = Client::default();
1574        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1575        client.associate_and_drain_mlme(&mut sme, &mut mlme_stream, None);
1576
1577        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1578        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1579
1580        sme.on_mlme_event(client.create_assoc_ind(None));
1581        client.verify_assoc_resp(
1582            &mut mlme_stream,
1583            1,
1584            fidl_mlme::AssociateResultCode::Success,
1585            false,
1586        );
1587    }
1588
1589    #[fuchsia::test(allow_stalls = false)]
1590    async fn multiple_clients_associate() {
1591        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1592        let client1 = Client::default();
1593        let client2 = Client { addr: *CLIENT_ADDR2 };
1594
1595        sme.on_mlme_event(client1.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1596        client1.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1597
1598        sme.on_mlme_event(client2.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1599        client2.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1600
1601        sme.on_mlme_event(client1.create_assoc_ind(Some(RSNE.to_vec())));
1602        client1.verify_assoc_resp(
1603            &mut mlme_stream,
1604            1,
1605            fidl_mlme::AssociateResultCode::Success,
1606            true,
1607        );
1608        client1.verify_eapol_req(&mut mlme_stream);
1609
1610        sme.on_mlme_event(client2.create_assoc_ind(Some(RSNE.to_vec())));
1611        client2.verify_assoc_resp(
1612            &mut mlme_stream,
1613            2,
1614            fidl_mlme::AssociateResultCode::Success,
1615            true,
1616        );
1617        client2.verify_eapol_req(&mut mlme_stream);
1618    }
1619
1620    fn create_start_conf(result_code: fidl_mlme::StartResultCode) -> MlmeEvent {
1621        MlmeEvent::StartConf { resp: fidl_mlme::StartConfirm { result_code } }
1622    }
1623
1624    fn create_stop_conf(result_code: fidl_mlme::StopResultCode) -> MlmeEvent {
1625        MlmeEvent::StopConf { resp: fidl_mlme::StopConfirm { result_code } }
1626    }
1627
1628    struct Client {
1629        addr: MacAddr,
1630    }
1631
1632    impl Client {
1633        fn default() -> Self {
1634            Client { addr: *CLIENT_ADDR }
1635        }
1636
1637        fn authenticate_and_drain_mlme(
1638            &self,
1639            sme: &mut ApSme,
1640            mlme_stream: &mut crate::MlmeStream,
1641        ) {
1642            sme.on_mlme_event(self.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1643            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::AuthResponse(..))));
1644        }
1645
1646        fn associate_and_drain_mlme(
1647            &self,
1648            sme: &mut ApSme,
1649            mlme_stream: &mut crate::MlmeStream,
1650            rsne: Option<Vec<u8>>,
1651        ) {
1652            sme.on_mlme_event(self.create_assoc_ind(rsne));
1653            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::AssocResponse(..))));
1654        }
1655
1656        fn create_auth_ind(&self, auth_type: fidl_mlme::AuthenticationTypes) -> MlmeEvent {
1657            MlmeEvent::AuthenticateInd {
1658                ind: fidl_mlme::AuthenticateIndication {
1659                    peer_sta_address: self.addr.to_array(),
1660                    auth_type,
1661                },
1662            }
1663        }
1664
1665        fn create_assoc_ind(&self, rsne: Option<Vec<u8>>) -> MlmeEvent {
1666            MlmeEvent::AssociateInd {
1667                ind: fidl_mlme::AssociateIndication {
1668                    peer_sta_address: self.addr.to_array(),
1669                    listen_interval: 100,
1670                    ssid: Some(SSID.to_vec()),
1671                    rsne,
1672                    capability_info: mac::CapabilityInfo(0).with_short_preamble(true).raw(),
1673                    rates: vec![
1674                        0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c,
1675                    ],
1676                },
1677            }
1678        }
1679
1680        fn verify_auth_resp(
1681            &self,
1682            mlme_stream: &mut MlmeStream,
1683            result_code: fidl_mlme::AuthenticateResultCode,
1684        ) {
1685            let msg = mlme_stream.try_next();
1686            assert_matches!(msg, Ok(Some(MlmeRequest::AuthResponse(auth_resp))) => {
1687                assert_eq!(&auth_resp.peer_sta_address, self.addr.as_array());
1688                assert_eq!(auth_resp.result_code, result_code);
1689            });
1690        }
1691
1692        fn verify_assoc_resp(
1693            &self,
1694            mlme_stream: &mut MlmeStream,
1695            aid: Aid,
1696            result_code: fidl_mlme::AssociateResultCode,
1697            privacy: bool,
1698        ) {
1699            let msg = mlme_stream.try_next();
1700            assert_matches!(msg, Ok(Some(MlmeRequest::AssocResponse(assoc_resp))) => {
1701                assert_eq!(&assoc_resp.peer_sta_address, self.addr.as_array());
1702                assert_eq!(assoc_resp.association_id, aid);
1703                assert_eq!(assoc_resp.result_code, result_code);
1704                assert_eq!(
1705                    assoc_resp.capability_info,
1706                    mac::CapabilityInfo(0).with_short_preamble(true).with_privacy(privacy).raw(),
1707                );
1708            });
1709        }
1710
1711        fn verify_refused_assoc_resp(
1712            &self,
1713            mlme_stream: &mut MlmeStream,
1714            result_code: fidl_mlme::AssociateResultCode,
1715        ) {
1716            let msg = mlme_stream.try_next();
1717            assert_matches!(msg, Ok(Some(MlmeRequest::AssocResponse(assoc_resp))) => {
1718                assert_eq!(&assoc_resp.peer_sta_address, self.addr.as_array());
1719                assert_eq!(assoc_resp.association_id, 0);
1720                assert_eq!(assoc_resp.result_code, result_code);
1721                assert_eq!(assoc_resp.capability_info, 0);
1722            });
1723        }
1724
1725        fn verify_eapol_req(&self, mlme_stream: &mut MlmeStream) {
1726            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Eapol(eapol_req))) => {
1727                assert_eq!(&eapol_req.src_addr, AP_ADDR.as_array());
1728                assert_eq!(&eapol_req.dst_addr, self.addr.as_array());
1729                assert!(!eapol_req.data.is_empty());
1730            });
1731        }
1732
1733        fn verify_deauth_req(
1734            &self,
1735            mlme_stream: &mut MlmeStream,
1736            reason_code: fidl_ieee80211::ReasonCode,
1737        ) {
1738            let msg = mlme_stream.try_next();
1739            assert_matches!(msg, Ok(Some(MlmeRequest::Deauthenticate(deauth_req))) => {
1740                assert_eq!(&deauth_req.peer_sta_address, self.addr.as_array());
1741                assert_eq!(deauth_req.reason_code, reason_code);
1742            });
1743        }
1744    }
1745
1746    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1747    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1748    // executor.
1749    async fn start_protected_ap() -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1750        start_ap(true).await
1751    }
1752
1753    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1754    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1755    // executor.
1756    async fn start_unprotected_ap() -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1757        start_ap(false).await
1758    }
1759
1760    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1761    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1762    // executor.
1763    async fn start_ap(protected: bool) -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1764        let (mut sme, mut mlme_stream, mut time_stream) = create_sme().await;
1765        let config = if protected { protected_config() } else { unprotected_config() };
1766        let mut receiver = sme.on_start_command(config);
1767        assert_eq!(Ok(None), receiver.try_recv());
1768        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(..))));
1769        // drain time stream
1770        while time_stream.try_next().is_ok() {}
1771        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1772
1773        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1774        (sme, mlme_stream, time_stream)
1775    }
1776
1777    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1778    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1779    // executor.
1780    async fn create_sme() -> (ApSme, MlmeStream, timer::EventStream<Event>) {
1781        let (ap_sme, _mlme_sink, mlme_stream, time_stream) =
1782            ApSme::new(fake_device_info(*AP_ADDR), fake_spectrum_management_support_empty());
1783        (ap_sme, mlme_stream, time_stream)
1784    }
1785}