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_common as fidl_common;
18use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
19use fidl_fuchsia_wlan_internal as fidl_internal;
20use fidl_fuchsia_wlan_mlme::{self as fidl_mlme, DeviceInfo, MlmeEvent};
21use fidl_fuchsia_wlan_sme as fidl_sme;
22use futures::channel::{mpsc, oneshot};
23use ieee80211::{MacAddr, MacAddrBytes, Ssid};
24use log::{debug, error, info, warn};
25use std::collections::HashMap;
26use wlan_common::capabilities::get_band_cap_for_channel;
27use wlan_common::channel::{Bandwidth, Channel};
28use wlan_common::ie::rsn::rsne::{RsnCapabilities, Rsne};
29use wlan_common::ie::{ChanWidthSet, SupportedRate, parse_ht_capabilities};
30use wlan_common::timer::{self, EventHandle, Timer};
31use wlan_common::{RadioConfig, mac};
32use wlan_rsn::psk;
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_ieee80211::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.band == fidl_ieee80211::WlanBand::FiveGhz
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_ieee80211::WlanPhyType::Dsss
447        | fidl_ieee80211::WlanPhyType::Hr
448        | fidl_ieee80211::WlanPhyType::Ofdm
449        | fidl_ieee80211::WlanPhyType::Erp => match channel.bandwidth {
450            Bandwidth::Cbw20 => (),
451            _ => {
452                return Err(StartResult::InvalidArguments(format!(
453                    "PHY type {phy:?} not supported on channel {channel}"
454                )));
455            }
456        },
457        fidl_ieee80211::WlanPhyType::Ht => {
458            match channel.bandwidth {
459                Bandwidth::Cbw20 | Bandwidth::Cbw40 | Bandwidth::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.bandwidth != Bandwidth::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_ieee80211::WlanPhyType::Vht => {
490            match channel.bandwidth {
491                Bandwidth::Cbw160 | Bandwidth::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.band != fidl_ieee80211::WlanBand::FiveGhz {
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_ieee80211::WlanPhyType::Dmg
512        | fidl_ieee80211::WlanPhyType::Tvht
513        | fidl_ieee80211::WlanPhyType::S1G
514        | fidl_ieee80211::WlanPhyType::Cdmg
515        | fidl_ieee80211::WlanPhyType::Cmmg
516        | fidl_ieee80211::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_primary_channel.number;
589        self.op_radio_cfg.channel.band = info.new_primary_channel.band;
590
591        match Bandwidth::from_fidl(info.bandwidth, info.vht_secondary_80_channel.number) {
592            Ok(cbw) => self.op_radio_cfg.channel.bandwidth = cbw,
593            Err(e) => warn!("Invalid CBW: {}", e),
594        }
595    }
596
597    fn handle_auth_ind(&mut self, ind: fidl_mlme::AuthenticateIndication) {
598        let peer_addr: MacAddr = ind.peer_sta_address.into();
599        if self.remove_client(&peer_addr) {
600            // This may occur if an already authenticated client on the SME receives a fresh
601            // MLME-AUTHENTICATE.indication from the MLME.
602            //
603            // This is safe, as we will make a fresh the client state and return an appropriate
604            // MLME-AUTHENTICATE.response to the MLME, indicating whether it should deauthenticate
605            // the client or not.
606            warn!(
607                "client {} is trying to reauthenticate; removing client and starting again",
608                peer_addr
609            );
610        }
611        let mut client = RemoteClient::new(peer_addr);
612        client.handle_auth_ind(&mut self.ctx, ind.auth_type);
613        if !client.authenticated() {
614            info!("client {} was not authenticated", peer_addr);
615            return;
616        }
617
618        info!("client {} authenticated", peer_addr);
619        let _ = self.clients.insert(peer_addr, client);
620    }
621
622    fn handle_deauth(&mut self, peer_addr: &MacAddr) {
623        if !self.remove_client(peer_addr) {
624            warn!("client {} never authenticated, ignoring deauthentication request", peer_addr);
625            return;
626        }
627
628        info!("client {} deauthenticated", peer_addr);
629    }
630
631    fn handle_assoc_ind(&mut self, ind: fidl_mlme::AssociateIndication) {
632        let peer_addr: MacAddr = ind.peer_sta_address.into();
633
634        let client = match self.clients.get_mut(&peer_addr) {
635            None => {
636                warn!("client {} never authenticated, ignoring association indication", peer_addr);
637                return;
638            }
639            Some(client) => client,
640        };
641
642        client.handle_assoc_ind(
643            &mut self.ctx,
644            &mut self.aid_map,
645            ind.capability_info,
646            ind.rates.into_iter().map(SupportedRate).collect::<Vec<_>>(),
647            &self.rsn_cfg,
648            ind.rsne,
649        );
650        if !client.authenticated() {
651            warn!("client {} failed to associate and was deauthenticated", peer_addr);
652            let _ = self.remove_client(&peer_addr);
653        } else if !client.associated() {
654            warn!("client {} failed to associate but did not deauthenticate", peer_addr);
655        } else {
656            info!("client {} associated", peer_addr);
657        }
658    }
659
660    fn handle_disassoc_ind(&mut self, ind: fidl_mlme::DisassociateIndication) {
661        let peer_addr: MacAddr = ind.peer_sta_address.into();
662
663        let client = match self.clients.get_mut(&peer_addr) {
664            None => {
665                warn!(
666                    "client {} never authenticated, ignoring disassociation indication",
667                    peer_addr
668                );
669                return;
670            }
671            Some(client) => client,
672        };
673
674        client.handle_disassoc_ind(&mut self.ctx, &mut self.aid_map);
675        if client.associated() {
676            panic!("client {peer_addr} didn't disassociate? this should never happen!")
677        } else {
678            info!("client {} disassociated", peer_addr);
679        }
680    }
681
682    fn handle_timeout(&mut self, timed_event: timer::Event<Event>) {
683        match timed_event.event {
684            Event::Sme { .. } => (),
685            Event::Client { addr, event } => {
686                let client = match self.clients.get_mut(&addr) {
687                    None => {
688                        return;
689                    }
690                    Some(client) => client,
691                };
692
693                client.handle_timeout(&mut self.ctx, event);
694                if !client.authenticated() {
695                    if !self.remove_client(&addr) {
696                        error!("failed to remove client {} from AID map", addr);
697                    }
698                    info!("client {} lost authentication", addr);
699                }
700            }
701        }
702    }
703
704    fn handle_eapol_ind(&mut self, ind: fidl_mlme::EapolIndication) {
705        let peer_addr: MacAddr = ind.src_addr.into();
706        let client = match self.clients.get_mut(&peer_addr) {
707            None => {
708                warn!("client {} never authenticated, ignoring EAPoL indication", peer_addr);
709                return;
710            }
711            Some(client) => client,
712        };
713
714        client.handle_eapol_ind(&mut self.ctx, &ind.data[..]);
715    }
716
717    fn handle_eapol_conf(&mut self, resp: fidl_mlme::EapolConfirm) {
718        let dst_addr: MacAddr = resp.dst_addr.into();
719        let client = match self.clients.get_mut(&dst_addr) {
720            None => {
721                warn!("never sent EAPOL frame to client {}, ignoring confirm", dst_addr);
722                return;
723            }
724            Some(client) => client,
725        };
726
727        client.handle_eapol_conf(&mut self.ctx, resp.result_code);
728    }
729}
730
731fn create_rsn_cfg(ssid: &Ssid, password: &[u8]) -> Result<Option<RsnCfg>, StartResult> {
732    if password.is_empty() {
733        Ok(None)
734    } else {
735        let psk_result = psk::compute(password, ssid);
736        let psk = match psk_result {
737            Err(e) => {
738                return Err(StartResult::InvalidArguments(e.to_string()));
739            }
740            Ok(o) => o,
741        };
742
743        // Note: TKIP is legacy and considered insecure. Only allow CCMP usage
744        // for group and pairwise ciphers.
745        Ok(Some(RsnCfg { psk, rsne: Rsne::wpa2_rsne_with_caps(RsnCapabilities(0)) }))
746    }
747}
748
749fn create_start_request(
750    op_radio_cfg: &OpRadioConfig,
751    ssid: &Ssid,
752    ap_rsn: Option<&RsnCfg>,
753    capabilities: mac::CapabilityInfo,
754) -> Result<fidl_mlme::StartRequest, StartResult> {
755    let rsne_bytes = ap_rsn.as_ref().map(|RsnCfg { rsne, .. }| {
756        let mut buf = Vec::with_capacity(rsne.len());
757        if let Err(e) = rsne.write_into(&mut buf) {
758            error!("error writing RSNE into MLME-START.request: {}", e);
759        }
760        buf
761    });
762
763    let (channel_bandwidth, _vht_secondary_80_channel) = op_radio_cfg.channel.bandwidth.to_fidl();
764
765    if op_radio_cfg.basic_rates.len() > fidl_internal::MAX_ASSOC_BASIC_RATES as usize {
766        error!(
767            "Too many basic rates ({}). Max is {}.",
768            op_radio_cfg.basic_rates.len(),
769            fidl_internal::MAX_ASSOC_BASIC_RATES
770        );
771        return Err(StartResult::InternalError);
772    }
773
774    Ok(fidl_mlme::StartRequest {
775        ssid: ssid.to_vec(),
776        bss_type: fidl_ieee80211::BssType::Infrastructure,
777        beacon_period: DEFAULT_BEACON_PERIOD,
778        dtim_period: DEFAULT_DTIM_PERIOD,
779        primary: op_radio_cfg.channel.into(),
780        capability_info: capabilities.raw(),
781        rates: op_radio_cfg.basic_rates.clone(),
782        country: fidl_mlme::Country {
783            // TODO(https://fxbug.dev/42104247): Get config from wlancfg
784            alpha2: *b"US",
785            suffix: fidl_mlme::COUNTRY_ENVIRON_ALL,
786        },
787        rsne: rsne_bytes,
788        mesh_id: vec![],
789        phy: op_radio_cfg.phy,
790        bandwidth: channel_bandwidth,
791    })
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797    use crate::test_utils::*;
798    use crate::{MlmeStream, Station};
799    use assert_matches::assert_matches;
800    use fidl_fuchsia_wlan_mlme as fidl_mlme;
801    use fidl_ieee80211::WlanBand::{FiveGhz, TwoGhz};
802    use std::sync::LazyLock;
803    use test_case::test_case;
804    use wlan_common::channel::Bandwidth;
805    use wlan_common::mac::Aid;
806    use wlan_common::test_utils::fake_capabilities::{
807        fake_2ghz_band_capability_ht, fake_5ghz_band_capability, fake_5ghz_band_capability_ht,
808        fake_5ghz_band_capability_vht,
809    };
810    use wlan_common::test_utils::fake_features::{
811        fake_dfs_supported, fake_spectrum_management_support_empty,
812    };
813
814    static AP_ADDR: LazyLock<MacAddr> =
815        LazyLock::new(|| [0x11, 0x22, 0x33, 0x44, 0x55, 0x66].into());
816    static CLIENT_ADDR: LazyLock<MacAddr> =
817        LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
818    static CLIENT_ADDR2: LazyLock<MacAddr> =
819        LazyLock::new(|| [0x22, 0x22, 0x22, 0x22, 0x22, 0x22].into());
820    static SSID: LazyLock<Ssid> =
821        LazyLock::new(|| Ssid::try_from([0x46, 0x55, 0x43, 0x48, 0x53, 0x49, 0x41]).unwrap());
822
823    const RSNE: &[u8] = &[
824        0x30, // element id
825        0x2A, // length
826        0x01, 0x00, // version
827        0x00, 0x0f, 0xac, 0x04, // group data cipher suite -- CCMP-128
828        0x01, 0x00, // pairwise cipher suite count
829        0x00, 0x0f, 0xac, 0x04, // pairwise cipher suite list -- CCMP-128
830        0x01, 0x00, // akm suite count
831        0x00, 0x0f, 0xac, 0x02, // akm suite list -- PSK
832        0xa8, 0x04, // rsn capabilities
833        0x01, 0x00, // pmk id count
834        // pmk id list
835        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
836        0x11, 0x00, 0x0f, 0xac, 0x04, // group management cipher suite -- CCMP-128
837    ];
838
839    fn unprotected_config() -> Config {
840        Config {
841            ssid: SSID.clone(),
842            password: vec![],
843            radio_cfg: RadioConfig::new(
844                fidl_ieee80211::WlanPhyType::Ht,
845                Bandwidth::Cbw20,
846                11,
847                TwoGhz,
848            ),
849        }
850    }
851
852    fn protected_config() -> Config {
853        Config {
854            ssid: SSID.clone(),
855            password: vec![0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68],
856            radio_cfg: RadioConfig::new(
857                fidl_ieee80211::WlanPhyType::Ht,
858                Bandwidth::Cbw20,
859                11,
860                TwoGhz,
861            ),
862        }
863    }
864
865    fn create_channel_switch_ind(channel: u8, band: fidl_ieee80211::WlanBand) -> MlmeEvent {
866        MlmeEvent::OnChannelSwitched {
867            info: fidl_internal::ChannelSwitchInfo {
868                new_primary_channel: fidl_ieee80211::ChannelNumber { band, number: channel },
869                bandwidth: fidl_ieee80211::ChannelBandwidth::Cbw20,
870                vht_secondary_80_channel: fidl_ieee80211::ChannelNumber { band, number: 0 },
871            },
872        }
873    }
874
875    #[derive(Clone, Debug)]
876    struct ValidateRadioConfigArgs {
877        bands: Vec<fidl_mlme::BandCapability>,
878        radio_cfg: RadioConfig,
879        spectrum_management_support: fidl_common::SpectrumManagementSupport,
880    }
881
882    #[test_case(false, ValidateRadioConfigArgs {
883        bands: vec![fake_2ghz_band_capability_ht()],
884        radio_cfg: RadioConfig {
885            phy: fidl_ieee80211::WlanPhyType::Ht,
886            channel: Channel::new(15, Bandwidth::Cbw20, FiveGhz),
887        },
888        spectrum_management_support: fake_spectrum_management_support_empty(),
889    }; "invalid US channel")]
890    #[test_case(false, ValidateRadioConfigArgs {
891        bands: vec![fake_5ghz_band_capability()],
892        radio_cfg: RadioConfig {
893            phy: fidl_ieee80211::WlanPhyType::Ht,
894            channel: Channel::new(36, Bandwidth::Cbw20, FiveGhz),
895        },
896        spectrum_management_support: fake_spectrum_management_support_empty(),
897    }; "5 GHz channel and no DFS support")]
898    #[test_case(false, ValidateRadioConfigArgs {
899        bands: vec![fake_2ghz_band_capability_ht()],
900        radio_cfg: RadioConfig {
901            phy: fidl_ieee80211::WlanPhyType::Dmg,
902            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
903        },
904        spectrum_management_support: fake_spectrum_management_support_empty(),
905    }; "DMG not supported")]
906    #[test_case(false, ValidateRadioConfigArgs {
907        bands: vec![fake_2ghz_band_capability_ht()],
908        radio_cfg: RadioConfig {
909            phy: fidl_ieee80211::WlanPhyType::Tvht,
910            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
911        },
912        spectrum_management_support: fake_spectrum_management_support_empty(),
913    }; "TVHT not supported")]
914    #[test_case(false, ValidateRadioConfigArgs {
915        bands: vec![fake_2ghz_band_capability_ht()],
916        radio_cfg: RadioConfig {
917            phy: fidl_ieee80211::WlanPhyType::S1G,
918            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
919        },
920        spectrum_management_support: fake_spectrum_management_support_empty(),
921    }; "S1G not supported")]
922    #[test_case(false, ValidateRadioConfigArgs {
923        bands: vec![fake_2ghz_band_capability_ht()],
924        radio_cfg: RadioConfig {
925            phy: fidl_ieee80211::WlanPhyType::Cdmg,
926            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
927        },
928        spectrum_management_support: fake_spectrum_management_support_empty(),
929    }; "CDMG not supported")]
930    #[test_case(false, ValidateRadioConfigArgs {
931        bands: vec![fake_2ghz_band_capability_ht()],
932        radio_cfg: RadioConfig {
933            phy: fidl_ieee80211::WlanPhyType::Cmmg,
934            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
935        },
936        spectrum_management_support: fake_spectrum_management_support_empty(),
937    }; "CMMG not supported")]
938    #[test_case(false, ValidateRadioConfigArgs {
939        bands: vec![fake_2ghz_band_capability_ht()],
940        radio_cfg: RadioConfig {
941            phy: fidl_ieee80211::WlanPhyType::He,
942            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
943        },
944        spectrum_management_support: fake_spectrum_management_support_empty(),
945    }; "HE not supported")]
946    #[test_case(false, ValidateRadioConfigArgs {
947        bands: vec![fake_2ghz_band_capability_ht()],
948        radio_cfg: RadioConfig {
949            phy: fidl_ieee80211::WlanPhyType::Ht,
950            channel: Channel::new(36, Bandwidth::Cbw80, FiveGhz),
951        },
952        spectrum_management_support: fake_dfs_supported(),
953    }; "invalid HT width")]
954    #[test_case(false, ValidateRadioConfigArgs {
955        bands: vec![fake_2ghz_band_capability_ht()],
956        radio_cfg: RadioConfig {
957            phy: fidl_ieee80211::WlanPhyType::Erp,
958            channel: Channel::new(1, Bandwidth::Cbw40, TwoGhz),
959        },
960        spectrum_management_support: fake_spectrum_management_support_empty(),
961    }; "non-HT greater than 20 MHz")]
962    #[test_case(false, ValidateRadioConfigArgs {
963        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
964        radio_cfg: RadioConfig {
965            phy: fidl_ieee80211::WlanPhyType::Ht,
966            channel: Channel::new(36, Bandwidth::Cbw80, FiveGhz),
967        },
968        spectrum_management_support: fake_dfs_supported(),
969    }; "HT greater than 40 MHz")]
970    #[test_case(false, ValidateRadioConfigArgs {
971        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
972        radio_cfg: RadioConfig {
973            phy: fidl_ieee80211::WlanPhyType::unknown(),
974            channel: Channel::new(36, Bandwidth::Cbw40, FiveGhz),
975        },
976        spectrum_management_support: fake_dfs_supported(),
977    }; "Unknown PHY type")]
978    #[test_case(false, ValidateRadioConfigArgs {
979        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_ONLY)],
980        radio_cfg: RadioConfig {
981            phy: fidl_ieee80211::WlanPhyType::Ht,
982            channel: Channel::new(44, Bandwidth::Cbw40, FiveGhz),
983        },
984        spectrum_management_support: fake_dfs_supported(),
985    }; "HT 20 MHz only")]
986    #[test_case(false, ValidateRadioConfigArgs {
987        bands: vec![fake_5ghz_band_capability()],
988        radio_cfg: RadioConfig {
989            phy: fidl_ieee80211::WlanPhyType::Ht,
990            channel: Channel::new(48, Bandwidth::Cbw40, FiveGhz),
991        },
992        spectrum_management_support: fake_dfs_supported(),
993    }; "No HT capabilities")]
994    #[test_case(false, ValidateRadioConfigArgs {
995        bands: vec![fake_5ghz_band_capability_vht()],
996        radio_cfg: RadioConfig {
997            phy: fidl_ieee80211::WlanPhyType::Vht,
998            channel: Channel::new(36, Bandwidth::Cbw160, FiveGhz),
999        },
1000        spectrum_management_support: fake_dfs_supported(),
1001    }; "160 MHz not supported")]
1002    #[test_case(false, ValidateRadioConfigArgs {
1003        bands: vec![fake_5ghz_band_capability_vht()],
1004        radio_cfg: RadioConfig {
1005            phy: fidl_ieee80211::WlanPhyType::Vht,
1006            channel: Channel::new(36, Bandwidth::Cbw80P80 { vht_secondary_80_channel: 106 }, FiveGhz),
1007        },
1008        spectrum_management_support: fake_dfs_supported(),
1009    }; "80+80 MHz not supported")]
1010    #[test_case(false, ValidateRadioConfigArgs {
1011        bands: vec![fake_2ghz_band_capability_ht()],
1012        radio_cfg: RadioConfig {
1013            phy: fidl_ieee80211::WlanPhyType::Vht,
1014            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
1015        },
1016        spectrum_management_support: fake_spectrum_management_support_empty(),
1017    }; "VHT 2.4 GHz not supported")]
1018    #[test_case(false, ValidateRadioConfigArgs {
1019        bands: vec![fake_5ghz_band_capability()],
1020        radio_cfg: RadioConfig {
1021            phy: fidl_ieee80211::WlanPhyType::Vht,
1022            channel: Channel::new(149, Bandwidth::Cbw80, FiveGhz),
1023        },
1024        spectrum_management_support: fake_dfs_supported(),
1025    }; "no VHT capabilities")]
1026    #[test_case(false, ValidateRadioConfigArgs {
1027        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1028        radio_cfg: RadioConfig {
1029            phy: fidl_ieee80211::WlanPhyType::Vht,
1030            channel: Channel::new(1, Bandwidth::Cbw40, TwoGhz),
1031        },
1032        spectrum_management_support: fake_spectrum_management_support_empty(),
1033    }; "no VHT capabilities on 2.4 GHz event when 5 GHz band capabilities provided")]
1034    #[test_case(false, ValidateRadioConfigArgs {
1035        bands: vec![fidl_mlme::BandCapability {
1036            primary_channels: vec![fidl_ieee80211::ChannelNumber {
1037                number: 2,
1038                band: fidl_ieee80211::WlanBand::TwoGhz,
1039            }],
1040            ..fake_2ghz_band_capability_ht()
1041        }],
1042        radio_cfg: RadioConfig {
1043            phy: fidl_ieee80211::WlanPhyType::Hr,
1044            channel: Channel::new(1, Bandwidth::Cbw40, TwoGhz),
1045        },
1046        spectrum_management_support: fake_spectrum_management_support_empty(),
1047    }; "disallow non-operating 2.4 GHz channel")]
1048    #[test_case(false, ValidateRadioConfigArgs {
1049        bands: vec![fidl_mlme::BandCapability {
1050            primary_channels: vec![fidl_ieee80211::ChannelNumber {
1051                number: 40,
1052                band: fidl_ieee80211::WlanBand::FiveGhz,
1053            }],
1054            ..fake_5ghz_band_capability_vht()
1055        }],
1056        radio_cfg: RadioConfig {
1057            phy: fidl_ieee80211::WlanPhyType::Vht,
1058            channel: Channel::new(36, Bandwidth::Cbw80, FiveGhz),
1059        },
1060        spectrum_management_support: fake_spectrum_management_support_empty(),
1061    }; "disallow non-operating 5 GHz channel")]
1062    #[test_case(true, ValidateRadioConfigArgs {
1063        bands: vec![fake_2ghz_band_capability_ht()],
1064        radio_cfg: RadioConfig {
1065            phy: fidl_ieee80211::WlanPhyType::Hr,
1066            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
1067        },
1068        spectrum_management_support: fake_spectrum_management_support_empty(),
1069    })]
1070    #[test_case(true, ValidateRadioConfigArgs {
1071        bands: vec![fake_2ghz_band_capability_ht()],
1072        radio_cfg: RadioConfig {
1073            phy: fidl_ieee80211::WlanPhyType::Erp,
1074            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
1075        },
1076        spectrum_management_support: fake_spectrum_management_support_empty(),
1077    })]
1078    #[test_case(true, ValidateRadioConfigArgs {
1079        bands: vec![fake_2ghz_band_capability_ht()],
1080        radio_cfg: RadioConfig {
1081            phy: fidl_ieee80211::WlanPhyType::Ht,
1082            channel: Channel::new(1, Bandwidth::Cbw20, TwoGhz),
1083        },
1084        spectrum_management_support: fake_spectrum_management_support_empty(),
1085    })]
1086    #[test_case(true, ValidateRadioConfigArgs {
1087        bands: vec![fake_2ghz_band_capability_ht()],
1088        radio_cfg: RadioConfig {
1089            phy: fidl_ieee80211::WlanPhyType::Ht,
1090            channel: Channel::new(1, Bandwidth::Cbw40, TwoGhz),
1091        },
1092        spectrum_management_support: fake_spectrum_management_support_empty(),
1093    })]
1094    #[test_case(true, ValidateRadioConfigArgs {
1095        bands: vec![fake_2ghz_band_capability_ht()],
1096        radio_cfg: RadioConfig {
1097            phy: fidl_ieee80211::WlanPhyType::Ht,
1098            channel: Channel::new(11, Bandwidth::Cbw40Below, TwoGhz),
1099        },
1100        spectrum_management_support: fake_spectrum_management_support_empty(),
1101    })]
1102    #[test_case(true, ValidateRadioConfigArgs {
1103        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_ONLY)],
1104        radio_cfg: RadioConfig {
1105            phy: fidl_ieee80211::WlanPhyType::Ht,
1106            channel: Channel::new(36, Bandwidth::Cbw20, FiveGhz),
1107        },
1108        spectrum_management_support: fake_dfs_supported(),
1109    })]
1110    #[test_case(true, ValidateRadioConfigArgs {
1111        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1112        radio_cfg: RadioConfig {
1113            phy: fidl_ieee80211::WlanPhyType::Ht,
1114            channel: Channel::new(36, Bandwidth::Cbw40, FiveGhz),
1115        },
1116        spectrum_management_support: fake_dfs_supported(),
1117    })]
1118    #[test_case(true, ValidateRadioConfigArgs {
1119        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1120        radio_cfg: RadioConfig {
1121            phy: fidl_ieee80211::WlanPhyType::Ht,
1122            channel: Channel::new(40, Bandwidth::Cbw40Below, FiveGhz),
1123        },
1124        spectrum_management_support: fake_dfs_supported(),
1125    })]
1126    #[test_case(true, ValidateRadioConfigArgs {
1127        bands: vec![fake_5ghz_band_capability_ht(ChanWidthSet::TWENTY_FORTY)],
1128        radio_cfg: RadioConfig {
1129            phy: fidl_ieee80211::WlanPhyType::Ht,
1130            channel: Channel::new(36, Bandwidth::Cbw20, FiveGhz),
1131        },
1132        spectrum_management_support: fake_dfs_supported(),
1133    })]
1134    #[test_case(true, ValidateRadioConfigArgs {
1135        bands: vec![fake_5ghz_band_capability_vht()],
1136        radio_cfg: RadioConfig {
1137            phy: fidl_ieee80211::WlanPhyType::Ht,
1138            channel: Channel::new(36, Bandwidth::Cbw40, FiveGhz),
1139        },
1140        spectrum_management_support: fake_dfs_supported(),
1141    })]
1142    #[test_case(true, ValidateRadioConfigArgs {
1143        bands: vec![fake_5ghz_band_capability_vht()],
1144        radio_cfg: RadioConfig {
1145            phy: fidl_ieee80211::WlanPhyType::Ht,
1146            channel: Channel::new(40, Bandwidth::Cbw40Below, FiveGhz),
1147        },
1148        spectrum_management_support: fake_dfs_supported(),
1149    })]
1150    #[test_case(true, ValidateRadioConfigArgs {
1151        bands: vec![fake_5ghz_band_capability_vht()],
1152        radio_cfg: RadioConfig {
1153            phy: fidl_ieee80211::WlanPhyType::Vht,
1154            channel: Channel::new(36, Bandwidth::Cbw80, FiveGhz),
1155        },
1156        spectrum_management_support: fake_dfs_supported(),
1157    })]
1158    #[test_case(true, ValidateRadioConfigArgs {
1159        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1160        radio_cfg: RadioConfig {
1161            phy: fidl_ieee80211::WlanPhyType::Ht,
1162            channel: Channel::new(1, Bandwidth::Cbw40, TwoGhz),
1163        },
1164        spectrum_management_support: fake_spectrum_management_support_empty(),
1165    })]
1166    #[test_case(true, ValidateRadioConfigArgs {
1167        bands: vec![fake_2ghz_band_capability_ht(), fake_5ghz_band_capability_vht()],
1168        radio_cfg: RadioConfig {
1169            phy: fidl_ieee80211::WlanPhyType::Vht,
1170            channel: Channel::new(36, Bandwidth::Cbw80, FiveGhz),
1171        },
1172        spectrum_management_support: fake_dfs_supported(),
1173    })]
1174    fn test_validate_radio_cfg(expect_ok: bool, fn_args: ValidateRadioConfigArgs) {
1175        match validate_radio_cfg(
1176            &fn_args.bands[..],
1177            &fn_args.radio_cfg,
1178            fn_args.spectrum_management_support.clone(),
1179        ) {
1180            Ok(op_radio_cfg) => {
1181                if !expect_ok {
1182                    panic!("Unexpected successful validation: {0:?}, {op_radio_cfg:?}", fn_args);
1183                }
1184                assert_matches!(
1185                    op_radio_cfg,
1186                    OpRadioConfig {
1187                        phy,
1188                        channel,
1189                        basic_rates: _,
1190                    } => {
1191                        assert_eq!(phy, fn_args.radio_cfg.phy);
1192                        assert_eq!(channel, fn_args.radio_cfg.channel);
1193                    }
1194                )
1195            }
1196            Err(e @ StartResult::InvalidArguments { .. }) => {
1197                if expect_ok {
1198                    panic!("Unexpected failure to validate: {0:?}, {e:?}", fn_args)
1199                }
1200            }
1201            Err(e) => panic!("Unexpected StartResult value: {0:?}, {e:?}", fn_args),
1202        }
1203    }
1204
1205    #[fuchsia::test(allow_stalls = false)]
1206    async fn authenticate_while_sme_is_idle() {
1207        let (mut sme, mut mlme_stream, _) = create_sme().await;
1208        let client = Client::default();
1209        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1210
1211        assert_matches!(mlme_stream.try_next(), Err(e) => {
1212            assert_eq!(e.to_string(), "receiver channel is empty");
1213        });
1214    }
1215
1216    // Check status when sme is idle
1217    #[fuchsia::test(allow_stalls = false)]
1218    async fn status_when_sme_is_idle() {
1219        let (sme, _, _) = create_sme().await;
1220        assert_eq!(None, sme.get_running_ap());
1221    }
1222
1223    #[fuchsia::test(allow_stalls = false)]
1224    async fn ap_starts_success() {
1225        let (mut sme, mut mlme_stream, _) = create_sme().await;
1226        let mut receiver = sme.on_start_command(unprotected_config());
1227
1228        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(start_req))) => {
1229            assert_eq!(start_req.ssid, SSID.to_vec());
1230            assert_eq!(
1231                start_req.capability_info,
1232                mac::CapabilityInfo(0).with_short_preamble(true).with_ess(true).raw(),
1233            );
1234            assert_eq!(start_req.bss_type, fidl_ieee80211::BssType::Infrastructure);
1235            assert_ne!(start_req.beacon_period, 0);
1236            assert_eq!(start_req.dtim_period, DEFAULT_DTIM_PERIOD);
1237            assert_eq!(
1238                start_req.primary,
1239                unprotected_config().radio_cfg.channel.into(),
1240            );
1241            assert!(start_req.rsne.is_none());
1242        });
1243
1244        assert_eq!(Ok(None), receiver.try_recv());
1245        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1246        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1247    }
1248
1249    // Check status when Ap starting and started
1250    #[fuchsia::test(allow_stalls = false)]
1251    async fn ap_starts_success_get_running_ap() {
1252        let (mut sme, mut mlme_stream, _) = create_sme().await;
1253        let mut receiver = sme.on_start_command(unprotected_config());
1254        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(_start_req))) => {});
1255        // status should be Starting
1256        assert_eq!(None, sme.get_running_ap());
1257        assert_eq!(Ok(None), receiver.try_recv());
1258        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1259        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1260        assert_eq!(
1261            Some(fidl_sme::Ap {
1262                ssid: SSID.to_vec(),
1263                channel: unprotected_config().radio_cfg.channel.primary,
1264                num_clients: 0,
1265            }),
1266            sme.get_running_ap()
1267        );
1268    }
1269
1270    // Check status after channel change
1271    #[fuchsia::test(allow_stalls = false)]
1272    async fn ap_check_status_after_channel_change() {
1273        let (mut sme, _, _) = start_unprotected_ap().await;
1274        // Check status
1275        assert_eq!(
1276            Some(fidl_sme::Ap {
1277                ssid: SSID.to_vec(),
1278                channel: unprotected_config().radio_cfg.channel.primary,
1279                num_clients: 0,
1280            }),
1281            sme.get_running_ap()
1282        );
1283        sme.on_mlme_event(create_channel_switch_ind(6, TwoGhz));
1284        // Check status
1285        assert_eq!(
1286            Some(fidl_sme::Ap { ssid: SSID.to_vec(), channel: 6, num_clients: 0 }),
1287            sme.get_running_ap()
1288        );
1289    }
1290
1291    #[fuchsia::test(allow_stalls = false)]
1292    async fn ap_starts_timeout() {
1293        let (mut sme, _, mut time_stream) = create_sme().await;
1294        let mut receiver = sme.on_start_command(unprotected_config());
1295
1296        let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1297        sme.on_timeout(event);
1298
1299        assert_eq!(Ok(Some(StartResult::TimedOut)), receiver.try_recv());
1300        // Check status
1301        assert_eq!(None, sme.get_running_ap());
1302    }
1303
1304    // Disable logging to prevent failure from emitted error logs.
1305    #[fuchsia::test(allow_stalls = false, logging = false)]
1306    async fn ap_starts_fails() {
1307        let (mut sme, _, _) = create_sme().await;
1308        let mut receiver = sme.on_start_command(unprotected_config());
1309
1310        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::NotSupported));
1311        assert_eq!(Ok(Some(StartResult::InternalError)), receiver.try_recv());
1312        // Check status
1313        assert_eq!(None, sme.get_running_ap());
1314    }
1315
1316    #[fuchsia::test(allow_stalls = false)]
1317    async fn start_req_while_ap_is_starting() {
1318        let (mut sme, _, _) = create_sme().await;
1319        let mut receiver_one = sme.on_start_command(unprotected_config());
1320
1321        // While SME is starting, any start request receives an error immediately
1322        let mut receiver_two = sme.on_start_command(unprotected_config());
1323        assert_eq!(Ok(Some(StartResult::PreviousStartInProgress)), receiver_two.try_recv());
1324
1325        // Start confirmation for first request should still have an affect
1326        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1327        assert_eq!(Ok(Some(StartResult::Success)), receiver_one.try_recv());
1328    }
1329
1330    #[fuchsia::test(allow_stalls = false)]
1331    async fn start_req_while_ap_is_stopping() {
1332        let (mut sme, _, _) = start_unprotected_ap().await;
1333        let mut stop_receiver = sme.on_stop_command();
1334        let mut start_receiver = sme.on_start_command(unprotected_config());
1335        assert_eq!(Ok(None), stop_receiver.try_recv());
1336        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1337    }
1338
1339    #[fuchsia::test(allow_stalls = false)]
1340    async fn ap_stops_while_idle() {
1341        let (mut sme, mut mlme_stream, _) = create_sme().await;
1342        let mut receiver = sme.on_stop_command();
1343        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1344            assert!(stop_req.ssid.is_empty());
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)), receiver.try_recv());
1350    }
1351
1352    #[fuchsia::test(allow_stalls = false)]
1353    async fn stop_req_while_ap_is_starting_then_succeeds() {
1354        let (mut sme, mut mlme_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        // Once start confirmation is finished, then stop request is sent out
1367        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1368        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1369        assert_eq!(Ok(None), stop_receiver.try_recv());
1370        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1371            assert_eq!(stop_req.ssid, SSID.to_vec());
1372        });
1373
1374        // Respond with a successful stop result code
1375        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1376        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver.try_recv());
1377    }
1378
1379    #[fuchsia::test(allow_stalls = false)]
1380    async fn stop_req_while_ap_is_starting_then_times_out() {
1381        let (mut sme, mut mlme_stream, mut time_stream) = create_sme().await;
1382        let mut start_receiver = sme.on_start_command(unprotected_config());
1383        let mut stop_receiver = sme.on_stop_command();
1384        assert_eq!(Ok(None), start_receiver.try_recv());
1385        assert_eq!(Ok(None), stop_receiver.try_recv());
1386
1387        // Verify start request is sent to MLME but not stop request yet
1388        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(_))));
1389        assert_matches!(mlme_stream.try_next(), Err(e) => {
1390            assert_eq!(e.to_string(), "receiver channel is empty");
1391        });
1392
1393        // Time out the start request. Then stop request is sent out
1394        let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1395        sme.on_timeout(event);
1396        assert_eq!(Ok(Some(StartResult::TimedOut)), start_receiver.try_recv());
1397        assert_eq!(Ok(None), stop_receiver.try_recv());
1398        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1399            assert_eq!(stop_req.ssid, SSID.to_vec());
1400        });
1401
1402        // Respond with a successful stop result code
1403        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1404        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver.try_recv());
1405    }
1406
1407    #[fuchsia::test(allow_stalls = false)]
1408    async fn ap_stops_after_started() {
1409        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1410        let mut receiver = sme.on_stop_command();
1411
1412        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1413            assert_eq!(stop_req.ssid, SSID.to_vec());
1414        });
1415        assert_eq!(Ok(None), receiver.try_recv());
1416        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::BssAlreadyStopped));
1417        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver.try_recv());
1418    }
1419
1420    #[fuchsia::test(allow_stalls = false)]
1421    async fn ap_stops_after_started_and_deauths_all_clients() {
1422        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1423        let client = Client::default();
1424        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1425        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1426
1427        // Check status
1428        assert_eq!(
1429            Some(fidl_sme::Ap {
1430                ssid: SSID.to_vec(),
1431                channel: unprotected_config().radio_cfg.channel.primary,
1432                num_clients: 1,
1433            }),
1434            sme.get_running_ap()
1435        );
1436        let mut receiver = sme.on_stop_command();
1437        assert_matches!(
1438        mlme_stream.try_next(),
1439        Ok(Some(MlmeRequest::Deauthenticate(deauth_req))) => {
1440            assert_eq!(&deauth_req.peer_sta_address, client.addr.as_array());
1441            assert_eq!(deauth_req.reason_code, fidl_ieee80211::ReasonCode::StaLeaving);
1442        });
1443
1444        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1445            assert_eq!(stop_req.ssid, SSID.to_vec());
1446        });
1447        assert_eq!(Ok(None), receiver.try_recv());
1448        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1449        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver.try_recv());
1450
1451        // Check status
1452        assert_eq!(None, sme.get_running_ap());
1453    }
1454
1455    #[fuchsia::test(allow_stalls = false)]
1456    async fn ap_queues_concurrent_stop_requests() {
1457        let (mut sme, _, _) = start_unprotected_ap().await;
1458        let mut receiver1 = sme.on_stop_command();
1459        let mut receiver2 = sme.on_stop_command();
1460
1461        assert_eq!(Ok(None), receiver1.try_recv());
1462        assert_eq!(Ok(None), receiver2.try_recv());
1463
1464        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1465        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver1.try_recv());
1466        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), receiver2.try_recv());
1467    }
1468
1469    #[fuchsia::test(allow_stalls = false)]
1470    async fn uncleaned_stopping_state() {
1471        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1472        let mut stop_receiver1 = sme.on_stop_command();
1473        // Clear out the stop request
1474        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1475            assert_eq!(stop_req.ssid, SSID.to_vec());
1476        });
1477
1478        assert_eq!(Ok(None), stop_receiver1.try_recv());
1479        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::InternalError));
1480        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::InternalError)), stop_receiver1.try_recv());
1481
1482        // While in unclean stopping state, no start request can be made
1483        let mut start_receiver = sme.on_start_command(unprotected_config());
1484        assert_eq!(Ok(Some(StartResult::Canceled)), start_receiver.try_recv());
1485        assert_matches!(mlme_stream.try_next(), Err(e) => {
1486            assert_eq!(e.to_string(), "receiver channel is empty");
1487        });
1488
1489        // SME will forward another stop request to lower layer
1490        let mut stop_receiver2 = sme.on_stop_command();
1491        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Stop(stop_req))) => {
1492            assert_eq!(stop_req.ssid, SSID.to_vec());
1493        });
1494
1495        // Respond successful this time
1496        assert_eq!(Ok(None), stop_receiver2.try_recv());
1497        sme.on_mlme_event(create_stop_conf(fidl_mlme::StopResultCode::Success));
1498        assert_eq!(Ok(Some(fidl_sme::StopApResultCode::Success)), stop_receiver2.try_recv());
1499    }
1500
1501    #[fuchsia::test(allow_stalls = false)]
1502    async fn client_authenticates_supported_authentication_type() {
1503        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1504        let client = Client::default();
1505        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1506        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1507    }
1508
1509    // Disable logging to prevent failure from emitted error logs.
1510    #[fuchsia::test(allow_stalls = false, logging = false)]
1511    async fn client_authenticates_unsupported_authentication_type() {
1512        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1513        let client = Client::default();
1514        let auth_ind = client.create_auth_ind(fidl_mlme::AuthenticationTypes::FastBssTransition);
1515        sme.on_mlme_event(auth_ind);
1516        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Refused);
1517    }
1518
1519    #[fuchsia::test(allow_stalls = false)]
1520    async fn client_associates_unprotected_network() {
1521        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1522        let client = Client::default();
1523        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1524        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1525
1526        sme.on_mlme_event(client.create_assoc_ind(None));
1527        client.verify_assoc_resp(
1528            &mut mlme_stream,
1529            1,
1530            fidl_mlme::AssociateResultCode::Success,
1531            false,
1532        );
1533    }
1534
1535    #[fuchsia::test(allow_stalls = false)]
1536    async fn client_associates_valid_rsne() {
1537        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1538        let client = Client::default();
1539        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1540
1541        sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
1542        client.verify_assoc_resp(
1543            &mut mlme_stream,
1544            1,
1545            fidl_mlme::AssociateResultCode::Success,
1546            true,
1547        );
1548        client.verify_eapol_req(&mut mlme_stream);
1549    }
1550
1551    // Disable logging to prevent failure from emitted error logs.
1552    #[fuchsia::test(allow_stalls = false, logging = false)]
1553    async fn client_associates_invalid_rsne() {
1554        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1555        let client = Client::default();
1556        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1557
1558        sme.on_mlme_event(client.create_assoc_ind(None));
1559        client.verify_refused_assoc_resp(
1560            &mut mlme_stream,
1561            fidl_mlme::AssociateResultCode::RefusedCapabilitiesMismatch,
1562        );
1563    }
1564
1565    #[fuchsia::test(allow_stalls = false)]
1566    async fn rsn_handshake_timeout() {
1567        let (mut sme, mut mlme_stream, mut time_stream) = start_protected_ap().await;
1568        let client = Client::default();
1569        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1570
1571        // Drain the association timeout message.
1572        assert_matches!(time_stream.try_next(), Ok(Some(_)));
1573
1574        sme.on_mlme_event(client.create_assoc_ind(Some(RSNE.to_vec())));
1575        client.verify_assoc_resp(
1576            &mut mlme_stream,
1577            1,
1578            fidl_mlme::AssociateResultCode::Success,
1579            true,
1580        );
1581
1582        // Drain the RSNA negotiation timeout message.
1583        assert_matches!(time_stream.try_next(), Ok(Some(_)));
1584
1585        for _i in 0..4 {
1586            client.verify_eapol_req(&mut mlme_stream);
1587            let (_, event, _) = time_stream.try_next().unwrap().expect("expect timer message");
1588            sme.on_timeout(event);
1589        }
1590
1591        client.verify_deauth_req(
1592            &mut mlme_stream,
1593            fidl_ieee80211::ReasonCode::FourwayHandshakeTimeout,
1594        );
1595    }
1596
1597    #[fuchsia::test(allow_stalls = false)]
1598    async fn client_restarts_authentication_flow() {
1599        let (mut sme, mut mlme_stream, _) = start_unprotected_ap().await;
1600        let client = Client::default();
1601        client.authenticate_and_drain_mlme(&mut sme, &mut mlme_stream);
1602        client.associate_and_drain_mlme(&mut sme, &mut mlme_stream, None);
1603
1604        sme.on_mlme_event(client.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1605        client.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1606
1607        sme.on_mlme_event(client.create_assoc_ind(None));
1608        client.verify_assoc_resp(
1609            &mut mlme_stream,
1610            1,
1611            fidl_mlme::AssociateResultCode::Success,
1612            false,
1613        );
1614    }
1615
1616    #[fuchsia::test(allow_stalls = false)]
1617    async fn multiple_clients_associate() {
1618        let (mut sme, mut mlme_stream, _) = start_protected_ap().await;
1619        let client1 = Client::default();
1620        let client2 = Client { addr: *CLIENT_ADDR2 };
1621
1622        sme.on_mlme_event(client1.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1623        client1.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1624
1625        sme.on_mlme_event(client2.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1626        client2.verify_auth_resp(&mut mlme_stream, fidl_mlme::AuthenticateResultCode::Success);
1627
1628        sme.on_mlme_event(client1.create_assoc_ind(Some(RSNE.to_vec())));
1629        client1.verify_assoc_resp(
1630            &mut mlme_stream,
1631            1,
1632            fidl_mlme::AssociateResultCode::Success,
1633            true,
1634        );
1635        client1.verify_eapol_req(&mut mlme_stream);
1636
1637        sme.on_mlme_event(client2.create_assoc_ind(Some(RSNE.to_vec())));
1638        client2.verify_assoc_resp(
1639            &mut mlme_stream,
1640            2,
1641            fidl_mlme::AssociateResultCode::Success,
1642            true,
1643        );
1644        client2.verify_eapol_req(&mut mlme_stream);
1645    }
1646
1647    fn create_start_conf(result_code: fidl_mlme::StartResultCode) -> MlmeEvent {
1648        MlmeEvent::StartConf { resp: fidl_mlme::StartConfirm { result_code } }
1649    }
1650
1651    fn create_stop_conf(result_code: fidl_mlme::StopResultCode) -> MlmeEvent {
1652        MlmeEvent::StopConf { resp: fidl_mlme::StopConfirm { result_code } }
1653    }
1654
1655    struct Client {
1656        addr: MacAddr,
1657    }
1658
1659    impl Client {
1660        fn default() -> Self {
1661            Client { addr: *CLIENT_ADDR }
1662        }
1663
1664        fn authenticate_and_drain_mlme(
1665            &self,
1666            sme: &mut ApSme,
1667            mlme_stream: &mut crate::MlmeStream,
1668        ) {
1669            sme.on_mlme_event(self.create_auth_ind(fidl_mlme::AuthenticationTypes::OpenSystem));
1670            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::AuthResponse(..))));
1671        }
1672
1673        fn associate_and_drain_mlme(
1674            &self,
1675            sme: &mut ApSme,
1676            mlme_stream: &mut crate::MlmeStream,
1677            rsne: Option<Vec<u8>>,
1678        ) {
1679            sme.on_mlme_event(self.create_assoc_ind(rsne));
1680            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::AssocResponse(..))));
1681        }
1682
1683        fn create_auth_ind(&self, auth_type: fidl_mlme::AuthenticationTypes) -> MlmeEvent {
1684            MlmeEvent::AuthenticateInd {
1685                ind: fidl_mlme::AuthenticateIndication {
1686                    peer_sta_address: self.addr.to_array(),
1687                    auth_type,
1688                },
1689            }
1690        }
1691
1692        fn create_assoc_ind(&self, rsne: Option<Vec<u8>>) -> MlmeEvent {
1693            MlmeEvent::AssociateInd {
1694                ind: fidl_mlme::AssociateIndication {
1695                    peer_sta_address: self.addr.to_array(),
1696                    listen_interval: 100,
1697                    ssid: Some(SSID.to_vec()),
1698                    rsne,
1699                    capability_info: mac::CapabilityInfo(0).with_short_preamble(true).raw(),
1700                    rates: vec![
1701                        0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, 0x30, 0x48, 0x60, 0x6c,
1702                    ],
1703                },
1704            }
1705        }
1706
1707        fn verify_auth_resp(
1708            &self,
1709            mlme_stream: &mut MlmeStream,
1710            result_code: fidl_mlme::AuthenticateResultCode,
1711        ) {
1712            let msg = mlme_stream.try_next();
1713            assert_matches!(msg, Ok(Some(MlmeRequest::AuthResponse(auth_resp))) => {
1714                assert_eq!(&auth_resp.peer_sta_address, self.addr.as_array());
1715                assert_eq!(auth_resp.result_code, result_code);
1716            });
1717        }
1718
1719        fn verify_assoc_resp(
1720            &self,
1721            mlme_stream: &mut MlmeStream,
1722            aid: Aid,
1723            result_code: fidl_mlme::AssociateResultCode,
1724            privacy: bool,
1725        ) {
1726            let msg = mlme_stream.try_next();
1727            assert_matches!(msg, Ok(Some(MlmeRequest::AssocResponse(assoc_resp))) => {
1728                assert_eq!(&assoc_resp.peer_sta_address, self.addr.as_array());
1729                assert_eq!(assoc_resp.association_id, aid);
1730                assert_eq!(assoc_resp.result_code, result_code);
1731                assert_eq!(
1732                    assoc_resp.capability_info,
1733                    mac::CapabilityInfo(0).with_short_preamble(true).with_privacy(privacy).raw(),
1734                );
1735            });
1736        }
1737
1738        fn verify_refused_assoc_resp(
1739            &self,
1740            mlme_stream: &mut MlmeStream,
1741            result_code: fidl_mlme::AssociateResultCode,
1742        ) {
1743            let msg = mlme_stream.try_next();
1744            assert_matches!(msg, Ok(Some(MlmeRequest::AssocResponse(assoc_resp))) => {
1745                assert_eq!(&assoc_resp.peer_sta_address, self.addr.as_array());
1746                assert_eq!(assoc_resp.association_id, 0);
1747                assert_eq!(assoc_resp.result_code, result_code);
1748                assert_eq!(assoc_resp.capability_info, 0);
1749            });
1750        }
1751
1752        fn verify_eapol_req(&self, mlme_stream: &mut MlmeStream) {
1753            assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Eapol(eapol_req))) => {
1754                assert_eq!(&eapol_req.src_addr, AP_ADDR.as_array());
1755                assert_eq!(&eapol_req.dst_addr, self.addr.as_array());
1756                assert!(!eapol_req.data.is_empty());
1757            });
1758        }
1759
1760        fn verify_deauth_req(
1761            &self,
1762            mlme_stream: &mut MlmeStream,
1763            reason_code: fidl_ieee80211::ReasonCode,
1764        ) {
1765            let msg = mlme_stream.try_next();
1766            assert_matches!(msg, Ok(Some(MlmeRequest::Deauthenticate(deauth_req))) => {
1767                assert_eq!(&deauth_req.peer_sta_address, self.addr.as_array());
1768                assert_eq!(deauth_req.reason_code, reason_code);
1769            });
1770        }
1771    }
1772
1773    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1774    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1775    // executor.
1776    async fn start_protected_ap() -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1777        start_ap(true).await
1778    }
1779
1780    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1781    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1782    // executor.
1783    async fn start_unprotected_ap() -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1784        start_ap(false).await
1785    }
1786
1787    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1788    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1789    // executor.
1790    async fn start_ap(protected: bool) -> (ApSme, crate::MlmeStream, timer::EventStream<Event>) {
1791        let (mut sme, mut mlme_stream, mut time_stream) = create_sme().await;
1792        let config = if protected { protected_config() } else { unprotected_config() };
1793        let mut receiver = sme.on_start_command(config);
1794        assert_eq!(Ok(None), receiver.try_recv());
1795        assert_matches!(mlme_stream.try_next(), Ok(Some(MlmeRequest::Start(..))));
1796        // drain time stream
1797        while time_stream.try_next().is_ok() {}
1798        sme.on_mlme_event(create_start_conf(fidl_mlme::StartResultCode::Success));
1799
1800        assert_eq!(Ok(Some(StartResult::Success)), receiver.try_recv());
1801        (sme, mlme_stream, time_stream)
1802    }
1803
1804    // TODO(https://fxbug.dev/327499461): This function is async to ensure SME functions will
1805    // run in an async context and not call `wlan_common::timer::Timer::now` without an
1806    // executor.
1807    async fn create_sme() -> (ApSme, MlmeStream, timer::EventStream<Event>) {
1808        let (ap_sme, _mlme_sink, mlme_stream, time_stream) =
1809            ApSme::new(fake_device_info(*AP_ADDR), fake_spectrum_management_support_empty());
1810        (ap_sme, mlme_stream, time_stream)
1811    }
1812}