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