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