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