wlan_mlme/client/
scanner.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
5use crate::WlanSoftmacBandCapabilityExt as _;
6use crate::client::Context;
7use crate::client::convert_beacon::construct_bss_description;
8use crate::ddk_converter::cssid_from_ssid_unchecked;
9use crate::device::{self, DeviceOps};
10use crate::error::Error;
11use anyhow::format_err;
12use ieee80211::{Bssid, MacAddr};
13use log::{error, warn};
14use thiserror::Error;
15use wlan_common::mac::{self, CapabilityInfo};
16use wlan_common::mgmt_writer;
17use wlan_common::time::TimeUnit;
18use wlan_frame_writer::write_frame_to_vec;
19use {
20    fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211,
21    fidl_fuchsia_wlan_mlme as fidl_mlme, fidl_fuchsia_wlan_softmac as fidl_softmac,
22    fuchsia_trace as trace, wlan_trace as wtrace,
23};
24
25// TODO(https://fxbug.dev/42171393): Currently hardcoded until parameters supported.
26const MIN_HOME_TIME: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(0);
27const MIN_PROBES_PER_CHANNEL: u8 = 0;
28const MAX_PROBES_PER_CHANNEL: u8 = 0;
29
30#[derive(Error, Debug, PartialEq, Eq)]
31pub enum ScanError {
32    #[error("scanner is busy")]
33    Busy,
34    #[error("invalid arg: empty channel list")]
35    EmptyChannelList,
36    #[error("invalid arg: max_channel_time < min_channel_time")]
37    MaxChannelTimeLtMin,
38    #[error("fail starting device scan: {}", _0)]
39    StartOffloadScanFails(zx::Status),
40    #[error("invalid response")]
41    InvalidResponse,
42}
43
44impl From<ScanError> for zx::Status {
45    fn from(e: ScanError) -> Self {
46        match e {
47            ScanError::Busy => zx::Status::UNAVAILABLE,
48            ScanError::EmptyChannelList | ScanError::MaxChannelTimeLtMin => {
49                zx::Status::INVALID_ARGS
50            }
51            ScanError::StartOffloadScanFails(status) => status,
52            ScanError::InvalidResponse => zx::Status::INVALID_ARGS,
53        }
54    }
55}
56
57impl From<ScanError> for fidl_mlme::ScanResultCode {
58    fn from(e: ScanError) -> Self {
59        match e {
60            ScanError::Busy => fidl_mlme::ScanResultCode::NotSupported,
61            ScanError::EmptyChannelList | ScanError::MaxChannelTimeLtMin => {
62                fidl_mlme::ScanResultCode::InvalidArgs
63            }
64            ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED) => {
65                fidl_mlme::ScanResultCode::NotSupported
66            }
67            ScanError::StartOffloadScanFails(..) => fidl_mlme::ScanResultCode::InternalError,
68            ScanError::InvalidResponse => fidl_mlme::ScanResultCode::InternalError,
69        }
70    }
71}
72
73pub struct Scanner {
74    ongoing_scan: Option<OngoingScan>,
75    /// MAC address of current client interface
76    iface_mac: MacAddr,
77    scanning_enabled: bool,
78}
79
80impl Scanner {
81    pub fn new(iface_mac: MacAddr) -> Self {
82        Self { ongoing_scan: None, iface_mac, scanning_enabled: true }
83    }
84
85    pub fn bind<'a, D>(&'a mut self, ctx: &'a mut Context<D>) -> BoundScanner<'a, D> {
86        BoundScanner { scanner: self, ctx }
87    }
88
89    pub fn is_scanning(&self) -> bool {
90        self.ongoing_scan.is_some()
91    }
92}
93
94pub struct BoundScanner<'a, D> {
95    scanner: &'a mut Scanner,
96    ctx: &'a mut Context<D>,
97}
98
99enum OngoingScan {
100    PassiveOffloadScan {
101        /// Scan txn_id that's currently being serviced.
102        mlme_txn_id: u64,
103        /// Unique identifier returned from the device driver when the scan began.
104        in_progress_device_scan_id: u64,
105    },
106    ActiveOffloadScan {
107        /// Scan txn_id that's currently being serviced.
108        mlme_txn_id: u64,
109        /// Unique identifier returned from the device driver when the scan began.
110        in_progress_device_scan_id: u64,
111        /// Remaining arguments to be sent to future scan requests.
112        remaining_active_scan_requests: Vec<fidl_softmac::WlanSoftmacStartActiveScanRequest>,
113    },
114}
115
116impl OngoingScan {
117    fn scan_id(&self) -> u64 {
118        match self {
119            Self::PassiveOffloadScan { in_progress_device_scan_id, .. } => {
120                *in_progress_device_scan_id
121            }
122            Self::ActiveOffloadScan { in_progress_device_scan_id, .. } => {
123                *in_progress_device_scan_id
124            }
125        }
126    }
127}
128
129impl<'a, D: DeviceOps> BoundScanner<'a, D> {
130    /// Temporarily disable scanning. If scan cancellation is supported, any
131    /// ongoing scan will be cancelled when scanning is disabled. If a scan
132    /// is in progress but cannot be cancelled, this function returns
133    /// zx::Status::NOT_SUPPORTED and makes no changes to the system.
134    pub async fn disable_scanning(&mut self) -> Result<(), zx::Status> {
135        if self.scanner.scanning_enabled {
136            self.cancel_ongoing_scan().await?;
137            self.scanner.scanning_enabled = false;
138        }
139        Ok(())
140    }
141
142    pub fn enable_scanning(&mut self) {
143        self.scanner.scanning_enabled = true;
144    }
145
146    /// Canceling any software scan that's in progress
147    /// TODO(b/254290448): Remove 'pub' when all clients use enable/disable scanning.
148    pub async fn cancel_ongoing_scan(&mut self) -> Result<(), zx::Status> {
149        if let Some(scan) = &self.scanner.ongoing_scan {
150            let discovery_support = self.ctx.device.discovery_support().await?;
151            if discovery_support
152                .scan_offload
153                .and_then(|offload| offload.scan_cancel_supported)
154                .unwrap_or(false)
155            {
156                self.ctx
157                    .device
158                    .cancel_scan(&fidl_softmac::WlanSoftmacBaseCancelScanRequest {
159                        scan_id: Some(scan.scan_id()),
160                        ..Default::default()
161                    })
162                    .await
163            } else {
164                Err(zx::Status::NOT_SUPPORTED)
165            }
166        } else {
167            Ok(())
168        }
169    }
170
171    /// Handle scan request. Queue requested scan channels in channel scheduler.
172    ///
173    /// If a scan request is in progress, or the new request has invalid argument (empty channel
174    /// list or larger min channel time than max), then the request is rejected.
175    pub async fn on_sme_scan(&'a mut self, req: fidl_mlme::ScanRequest) -> Result<(), Error> {
176        if self.scanner.ongoing_scan.is_some() || !self.scanner.scanning_enabled {
177            return Err(Error::ScanError(ScanError::Busy));
178        }
179        if req.channel_list.is_empty() {
180            return Err(Error::ScanError(ScanError::EmptyChannelList));
181        }
182        if req.max_channel_time < req.min_channel_time {
183            return Err(Error::ScanError(ScanError::MaxChannelTimeLtMin));
184        }
185
186        let query_response = self
187            .ctx
188            .device
189            .wlan_softmac_query_response()
190            .await
191            .map_err(|status| Error::Status(String::from("Failed to query device."), status))?;
192        let discovery_support = device::try_query_discovery_support(&mut self.ctx.device).await?;
193
194        // TODO(https://fxbug.dev/321627682): MLME only supports offloaded scanning.
195        if discovery_support.scan_offload.and_then(|offload| offload.supported).unwrap_or(false) {
196            match req.scan_type {
197                fidl_mlme::ScanTypes::Passive => self.start_passive_scan(req).await,
198                fidl_mlme::ScanTypes::Active => self.start_active_scan(req, &query_response).await,
199            }
200            .map(|ongoing_scan| self.scanner.ongoing_scan = Some(ongoing_scan))
201            .map_err(|e| {
202                self.scanner.ongoing_scan.take();
203                e
204            })
205        } else {
206            Err(Error::ScanError(ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED)))
207        }
208    }
209
210    async fn start_passive_scan(
211        &mut self,
212        req: fidl_mlme::ScanRequest,
213    ) -> Result<OngoingScan, Error> {
214        Ok(OngoingScan::PassiveOffloadScan {
215            mlme_txn_id: req.txn_id,
216            in_progress_device_scan_id: self
217                .ctx
218                .device
219                .start_passive_scan(&fidl_softmac::WlanSoftmacBaseStartPassiveScanRequest {
220                    channels: Some(req.channel_list),
221                    // TODO(https://fxbug.dev/42171328): A TimeUnit is generally limited to 2 octets. Conversion here
222                    // is required since fuchsia.wlan.mlme/ScanRequest.min_channel_time has a width of
223                    // four octets.
224                    min_channel_time: Some(
225                        zx::MonotonicDuration::from(TimeUnit(req.min_channel_time as u16))
226                            .into_nanos(),
227                    ),
228                    max_channel_time: Some(
229                        zx::MonotonicDuration::from(TimeUnit(req.max_channel_time as u16))
230                            .into_nanos(),
231                    ),
232                    min_home_time: Some(MIN_HOME_TIME.into_nanos()),
233                    ..Default::default()
234                })
235                .await
236                .map_err(|status| Error::ScanError(ScanError::StartOffloadScanFails(status)))?
237                .scan_id
238                .ok_or(Error::ScanError(ScanError::InvalidResponse))?,
239        })
240    }
241
242    async fn start_active_scan(
243        &mut self,
244        req: fidl_mlme::ScanRequest,
245        query_response: &fidl_softmac::WlanSoftmacQueryResponse,
246    ) -> Result<OngoingScan, Error> {
247        let ssids_list = req.ssid_list.iter().map(cssid_from_ssid_unchecked).collect::<Vec<_>>();
248
249        let mac_header = write_frame_to_vec!({
250            headers: {
251                mac::MgmtHdr: &self.probe_request_mac_header(),
252            },
253        })?;
254
255        let mut remaining_active_scan_requests = active_scan_request_series(
256            // TODO(https://fxbug.dev/42171328): A TimeUnit is generally limited to 2 octets. Conversion here
257            // is required since fuchsia.wlan.mlme/ScanRequest.min_channel_time has a width of
258            // four octets.
259            query_response,
260            req.channel_list,
261            ssids_list,
262            mac_header,
263            zx::MonotonicDuration::from(TimeUnit(req.min_channel_time as u16)).into_nanos(),
264            zx::MonotonicDuration::from(TimeUnit(req.max_channel_time as u16)).into_nanos(),
265            MIN_HOME_TIME.into_nanos(),
266            MIN_PROBES_PER_CHANNEL,
267            MAX_PROBES_PER_CHANNEL,
268        )?;
269
270        match remaining_active_scan_requests.pop() {
271            None => {
272                error!("unexpected empty list of active scan args");
273                return Err(Error::ScanError(ScanError::StartOffloadScanFails(
274                    zx::Status::INVALID_ARGS,
275                )));
276            }
277            Some(active_scan_request) => Ok(OngoingScan::ActiveOffloadScan {
278                mlme_txn_id: req.txn_id,
279                in_progress_device_scan_id: self
280                    .start_next_active_scan(&active_scan_request)
281                    .await
282                    .map_err(|scan_error| Error::ScanError(scan_error))?,
283                remaining_active_scan_requests,
284            }),
285        }
286    }
287
288    async fn start_next_active_scan(
289        &mut self,
290        request: &fidl_softmac::WlanSoftmacStartActiveScanRequest,
291    ) -> Result<u64, ScanError> {
292        match self.ctx.device.start_active_scan(request).await {
293            Ok(response) => Ok(response.scan_id.ok_or_else(|| {
294                error!("Active scan response missing scan id!");
295                ScanError::InvalidResponse
296            })?),
297            Err(status) => Err(ScanError::StartOffloadScanFails(status)),
298        }
299    }
300
301    /// Called when MLME receives an advertisement from the AP, e.g. a Beacon or Probe
302    /// Response frame. If a scan is in progress, then the advertisement will be saved
303    /// in the BSS map.
304    pub fn handle_ap_advertisement(
305        &mut self,
306        bssid: Bssid,
307        beacon_interval: TimeUnit,
308        capability_info: CapabilityInfo,
309        ies: &[u8],
310        rx_info: fidl_softmac::WlanRxInfo,
311    ) {
312        wtrace::duration!("BoundScanner::handle_ap_advertisement");
313
314        let mlme_txn_id = match self.scanner.ongoing_scan {
315            Some(OngoingScan::PassiveOffloadScan { mlme_txn_id, .. }) => mlme_txn_id,
316            Some(OngoingScan::ActiveOffloadScan { mlme_txn_id, .. }) => mlme_txn_id,
317            None => return,
318        };
319        let bss_description =
320            construct_bss_description(bssid, beacon_interval, capability_info, ies, rx_info);
321        let bss_description = match bss_description {
322            Ok(bss) => bss,
323            Err(e) => {
324                warn!("Failed to process beacon or probe response: {}", e);
325                return;
326            }
327        };
328        send_scan_result(mlme_txn_id, bss_description, &mut self.ctx.device);
329    }
330
331    pub async fn handle_scan_complete(&mut self, status: zx::Status, scan_id: u64) {
332        macro_rules! send_on_scan_end {
333            ($mlme_txn_id: ident, $code:expr) => {
334                self.ctx
335                    .device
336                    .send_mlme_event(fidl_mlme::MlmeEvent::OnScanEnd {
337                        end: fidl_mlme::ScanEnd { txn_id: $mlme_txn_id, code: $code },
338                    })
339                    .unwrap_or_else(|e| error!("error sending MLME ScanEnd: {}", e));
340            };
341        }
342
343        match self.scanner.ongoing_scan.take() {
344            // TODO(https://fxbug.dev/42172565): A spurious ScanComplete should not silently cancel an
345            // MlmeScan by permanently taking the contents of ongoing_scan.
346            None => {
347                warn!("Unexpected ScanComplete when no scan in progress.");
348            }
349            Some(OngoingScan::PassiveOffloadScan { mlme_txn_id, in_progress_device_scan_id })
350                if in_progress_device_scan_id == scan_id =>
351            {
352                send_on_scan_end!(
353                    mlme_txn_id,
354                    if status == zx::Status::OK {
355                        fidl_mlme::ScanResultCode::Success
356                    } else {
357                        error!("passive offload scan failed: {}", status);
358                        fidl_mlme::ScanResultCode::InternalError
359                    }
360                );
361            }
362            Some(OngoingScan::ActiveOffloadScan {
363                mlme_txn_id,
364                in_progress_device_scan_id,
365                mut remaining_active_scan_requests,
366            }) if in_progress_device_scan_id == scan_id => {
367                if status != zx::Status::OK {
368                    error!("active offload scan failed: {}", status);
369                    send_on_scan_end!(mlme_txn_id, fidl_mlme::ScanResultCode::InternalError);
370                    return;
371                }
372
373                match remaining_active_scan_requests.pop() {
374                    None => {
375                        send_on_scan_end!(mlme_txn_id, fidl_mlme::ScanResultCode::Success);
376                    }
377                    Some(active_scan_request) => {
378                        match self.start_next_active_scan(&active_scan_request).await {
379                            Ok(in_progress_device_scan_id) => {
380                                self.scanner.ongoing_scan = Some(OngoingScan::ActiveOffloadScan {
381                                    mlme_txn_id,
382                                    in_progress_device_scan_id,
383                                    remaining_active_scan_requests,
384                                });
385                            }
386                            Err(scan_error) => {
387                                self.scanner.ongoing_scan.take();
388                                send_on_scan_end!(mlme_txn_id, scan_error.into());
389                            }
390                        }
391                    }
392                }
393            }
394            Some(other) => {
395                let in_progress_device_scan_id = match other {
396                    OngoingScan::ActiveOffloadScan { in_progress_device_scan_id, .. } => {
397                        in_progress_device_scan_id
398                    }
399                    OngoingScan::PassiveOffloadScan { in_progress_device_scan_id, .. } => {
400                        in_progress_device_scan_id
401                    }
402                };
403                warn!(
404                    "Unexpected scan ID upon scan completion. expected: {}, returned: {}",
405                    in_progress_device_scan_id, scan_id
406                );
407                self.scanner.ongoing_scan.replace(other);
408            }
409        }
410    }
411
412    fn probe_request_mac_header(&mut self) -> mac::MgmtHdr {
413        mgmt_writer::mgmt_hdr_to_ap(
414            mac::FrameControl(0)
415                .with_frame_type(mac::FrameType::MGMT)
416                .with_mgmt_subtype(mac::MgmtSubtype::PROBE_REQ),
417            ieee80211::BROADCAST_ADDR.into(),
418            self.scanner.iface_mac,
419            mac::SequenceControl(0)
420                .with_seq_num(self.ctx.seq_mgr.next_sns1(&ieee80211::BROADCAST_ADDR) as u16),
421        )
422    }
423}
424
425fn band_cap_for_band(
426    query_response: &fidl_softmac::WlanSoftmacQueryResponse,
427    band: fidl_ieee80211::WlanBand,
428) -> Option<&fidl_softmac::WlanSoftmacBandCapability> {
429    query_response
430        .band_caps
431        .as_ref()
432        .map(|band_caps| band_caps.iter())
433        .into_iter()
434        .flatten()
435        .filter(|band_cap| band_cap.band == Some(band))
436        .next()
437}
438
439// TODO(https://fxbug.dev/42172555): Zero should not mark a null rate.
440fn supported_rates_for_band(
441    query_response: &fidl_softmac::WlanSoftmacQueryResponse,
442    band: fidl_ieee80211::WlanBand,
443) -> Result<Vec<u8>, Error> {
444    let rates = band_cap_for_band(&query_response, band)
445        .ok_or_else(|| format_err!("no capabilities found for band {:?}", band))?
446        .basic_rates()
447        .map(From::from)
448        .ok_or_else(|| format_err!("no basic rates found for band capabilities"))?;
449    Ok(rates)
450}
451
452// TODO(https://fxbug.dev/42172557): This is not correct. Channel numbers do not imply band.
453fn band_from_channel_number(channel_number: u8) -> fidl_ieee80211::WlanBand {
454    if channel_number > 14 {
455        fidl_ieee80211::WlanBand::FiveGhz
456    } else {
457        fidl_ieee80211::WlanBand::TwoGhz
458    }
459}
460
461fn active_scan_request_series(
462    query_response: &fidl_softmac::WlanSoftmacQueryResponse,
463    channels: Vec<u8>,
464    ssids: Vec<fidl_ieee80211::CSsid>,
465    mac_header: Vec<u8>,
466    min_channel_time: zx::sys::zx_duration_t,
467    max_channel_time: zx::sys::zx_duration_t,
468    min_home_time: zx::sys::zx_duration_t,
469    min_probes_per_channel: u8,
470    max_probes_per_channel: u8,
471) -> Result<Vec<fidl_softmac::WlanSoftmacStartActiveScanRequest>, Error> {
472    // TODO(https://fxbug.dev/42172557): The fuchsia.wlan.mlme/MLME API assumes channels numbers imply bands
473    //                        and so partitioning channels must be done internally.
474    struct BandChannels {
475        band: fidl_ieee80211::WlanBand,
476        channels: Vec<u8>,
477    }
478    let band_channels_list: [BandChannels; 2] = channels.into_iter().fold(
479        [
480            BandChannels { band: fidl_ieee80211::WlanBand::FiveGhz, channels: vec![] },
481            BandChannels { band: fidl_ieee80211::WlanBand::TwoGhz, channels: vec![] },
482        ],
483        |mut band_channels_list, channel| {
484            for band_channels in &mut band_channels_list {
485                if band_from_channel_number(channel) == band_channels.band {
486                    band_channels.channels.push(channel);
487                }
488            }
489            band_channels_list
490        },
491    );
492
493    let mut active_scan_request_series = vec![];
494    for band_channels in band_channels_list {
495        let band = band_channels.band;
496        let channels = band_channels.channels;
497        if channels.is_empty() {
498            continue;
499        }
500        let supported_rates = supported_rates_for_band(query_response, band)?;
501        active_scan_request_series.push(fidl_softmac::WlanSoftmacStartActiveScanRequest {
502            channels: Some(channels),
503            ssids: Some(ssids.clone()),
504            mac_header: Some(mac_header.clone()),
505            // Exclude the SSID IE because the device driver will generate using ssids_list.
506            ies: Some(write_frame_to_vec!({
507                ies: {
508                    supported_rates: supported_rates,
509                    extended_supported_rates: {/* continue rates */},
510                }
511            })?),
512            min_channel_time: Some(min_channel_time),
513            max_channel_time: Some(max_channel_time),
514            min_home_time: Some(min_home_time),
515            min_probes_per_channel: Some(min_probes_per_channel),
516            max_probes_per_channel: Some(max_probes_per_channel),
517            ..Default::default()
518        });
519    }
520    Ok(active_scan_request_series)
521}
522
523fn send_scan_result<D: DeviceOps>(txn_id: u64, bss: fidl_common::BssDescription, device: &mut D) {
524    if trace::is_enabled() {
525        let trace_bss = wlan_common::bss::BssDescription::try_from(bss.clone())
526            .map(|bss| format!("{}", bss))
527            .unwrap_or_else(|e| format!("{}", e));
528        wtrace::duration!("send_scan_result", "bss" => &*trace_bss);
529    }
530    device
531        .send_mlme_event(fidl_mlme::MlmeEvent::OnScanResult {
532            result: fidl_mlme::ScanResult {
533                txn_id,
534                timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
535                bss,
536            },
537        })
538        .unwrap_or_else(|e| error!("error sending MLME ScanResult: {}", e));
539}
540
541#[cfg(test)]
542mod tests {
543    use super::*;
544    use crate::client::TimedEvent;
545    use crate::device::{FakeDevice, FakeDeviceState};
546    use crate::test_utils::{MockWlanRxInfo, fake_wlan_channel};
547    use assert_matches::assert_matches;
548    use fidl_fuchsia_wlan_common as fidl_common;
549    use fuchsia_sync::Mutex;
550    use ieee80211::{MacAddrBytes, Ssid};
551    use std::sync::{Arc, LazyLock};
552    use test_case::test_case;
553    use wlan_common::sequence::SequenceManager;
554    use wlan_common::timer::{self, Timer, create_timer};
555
556    static BSSID_FOO: LazyLock<Bssid> = LazyLock::new(|| [6u8; 6].into());
557    const CAPABILITY_INFO_FOO: CapabilityInfo = CapabilityInfo(1);
558    const BEACON_INTERVAL_FOO: u16 = 100;
559
560    #[rustfmt::skip]
561    static BEACON_IES_FOO: &'static [u8] = &[
562        // SSID: "ssid"
563        0x00, 0x03, b'f', b'o', b'o',
564        // Supported rates: 24(B), 36, 48, 54
565        0x01, 0x04, 0xb0, 0x48, 0x60, 0x6c,
566        // TIM - DTIM count: 0, DTIM period: 1, PVB: 2
567        0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
568    ];
569
570    static RX_INFO_FOO: LazyLock<fidl_softmac::WlanRxInfo> = LazyLock::new(|| {
571        MockWlanRxInfo { rssi_dbm: -30, ..MockWlanRxInfo::with_channel(fake_wlan_channel().into()) }
572            .into()
573    });
574    static BSS_DESCRIPTION_FOO: LazyLock<fidl_common::BssDescription> =
575        LazyLock::new(|| fidl_common::BssDescription {
576            bssid: BSSID_FOO.to_array(),
577            bss_type: fidl_common::BssType::Infrastructure,
578            beacon_period: BEACON_INTERVAL_FOO,
579            capability_info: CAPABILITY_INFO_FOO.0,
580            ies: BEACON_IES_FOO.to_vec(),
581            rssi_dbm: RX_INFO_FOO.rssi_dbm,
582            channel: fidl_ieee80211::WlanChannel {
583                primary: RX_INFO_FOO.channel.primary,
584                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
585                secondary80: 0,
586            },
587            snr_db: 0,
588        });
589    static BSSID_BAR: LazyLock<Bssid> = LazyLock::new(|| [1u8; 6].into());
590
591    const CAPABILITY_INFO_BAR: CapabilityInfo = CapabilityInfo(33);
592    const BEACON_INTERVAL_BAR: u16 = 150;
593    #[rustfmt::skip]
594    static BEACON_IES_BAR: &'static [u8] = &[
595        // SSID: "ss"
596        0x00, 0x03, b'b', b'a', b'r',
597        // Supported rates: 24(B), 36, 48, 54
598        0x01, 0x04, 0xb0, 0x48, 0x60, 0x6c,
599        // TIM - DTIM count: 0, DTIM period: 1, PVB: 2
600        0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
601    ];
602    static RX_INFO_BAR: LazyLock<fidl_softmac::WlanRxInfo> = LazyLock::new(|| {
603        MockWlanRxInfo { rssi_dbm: -60, ..MockWlanRxInfo::with_channel(fake_wlan_channel().into()) }
604            .into()
605    });
606    static BSS_DESCRIPTION_BAR: LazyLock<fidl_common::BssDescription> =
607        LazyLock::new(|| fidl_common::BssDescription {
608            bssid: BSSID_BAR.to_array(),
609            bss_type: fidl_common::BssType::Infrastructure,
610            beacon_period: BEACON_INTERVAL_BAR,
611            capability_info: CAPABILITY_INFO_BAR.0,
612            ies: BEACON_IES_BAR.to_vec(),
613            rssi_dbm: RX_INFO_BAR.rssi_dbm,
614            channel: fidl_ieee80211::WlanChannel {
615                primary: RX_INFO_BAR.channel.primary,
616                cbw: fidl_ieee80211::ChannelBandwidth::Cbw20,
617                secondary80: 0,
618            },
619            snr_db: 0,
620        });
621
622    static IFACE_MAC: LazyLock<MacAddr> = LazyLock::new(|| [7u8; 6].into());
623
624    fn passive_scan_req() -> fidl_mlme::ScanRequest {
625        fidl_mlme::ScanRequest {
626            txn_id: 1337,
627            scan_type: fidl_mlme::ScanTypes::Passive,
628            channel_list: vec![6],
629            ssid_list: vec![],
630            probe_delay: 0,
631            min_channel_time: 100,
632            max_channel_time: 300,
633        }
634    }
635
636    fn active_scan_req(channel_list: &[u8]) -> fidl_mlme::ScanRequest {
637        fidl_mlme::ScanRequest {
638            txn_id: 1337,
639            scan_type: fidl_mlme::ScanTypes::Active,
640            channel_list: Vec::from(channel_list),
641            ssid_list: vec![
642                Ssid::try_from("foo").unwrap().into(),
643                Ssid::try_from("bar").unwrap().into(),
644            ],
645            probe_delay: 3,
646            min_channel_time: 100,
647            max_channel_time: 300,
648        }
649    }
650
651    #[fuchsia::test(allow_stalls = false)]
652    async fn test_handle_scan_req_reject_if_busy() {
653        let mut m = MockObjects::new().await;
654        let mut ctx = m.make_ctx();
655        let mut scanner = Scanner::new(*IFACE_MAC);
656
657        scanner
658            .bind(&mut ctx)
659            .on_sme_scan(passive_scan_req())
660            .await
661            .expect("expect scan req accepted");
662        let scan_req = fidl_mlme::ScanRequest { txn_id: 1338, ..passive_scan_req() };
663        let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
664        assert_matches!(result, Err(Error::ScanError(ScanError::Busy)));
665        m.fake_device_state
666            .lock()
667            .next_mlme_msg::<fidl_mlme::ScanEnd>()
668            .expect_err("unexpected MLME ScanEnd from BoundScanner");
669    }
670
671    #[fuchsia::test(allow_stalls = false)]
672    async fn test_handle_scan_req_reject_if_disabled() {
673        let mut m = MockObjects::new().await;
674        let mut ctx = m.make_ctx();
675        let mut scanner = Scanner::new(*IFACE_MAC);
676
677        scanner.bind(&mut ctx).disable_scanning().await.expect("Failed to disable scanning");
678        let result = scanner.bind(&mut ctx).on_sme_scan(passive_scan_req()).await;
679        assert_matches!(result, Err(Error::ScanError(ScanError::Busy)));
680        m.fake_device_state
681            .lock()
682            .next_mlme_msg::<fidl_mlme::ScanEnd>()
683            .expect_err("unexpected MLME ScanEnd from BoundScanner");
684
685        // Accept after reenabled.
686        scanner.bind(&mut ctx).enable_scanning();
687        scanner
688            .bind(&mut ctx)
689            .on_sme_scan(passive_scan_req())
690            .await
691            .expect("expect scan req accepted");
692    }
693
694    #[fuchsia::test(allow_stalls = false)]
695    async fn test_handle_scan_req_empty_channel_list() {
696        let mut m = MockObjects::new().await;
697        let mut ctx = m.make_ctx();
698        let mut scanner = Scanner::new(*IFACE_MAC);
699
700        let scan_req = fidl_mlme::ScanRequest { channel_list: vec![], ..passive_scan_req() };
701        let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
702        assert_matches!(result, Err(Error::ScanError(ScanError::EmptyChannelList)));
703        m.fake_device_state
704            .lock()
705            .next_mlme_msg::<fidl_mlme::ScanEnd>()
706            .expect_err("unexpected MLME ScanEnd from BoundScanner");
707    }
708
709    #[fuchsia::test(allow_stalls = false)]
710    async fn test_handle_scan_req_invalid_channel_time() {
711        let mut m = MockObjects::new().await;
712        let mut ctx = m.make_ctx();
713        let mut scanner = Scanner::new(*IFACE_MAC);
714
715        let scan_req = fidl_mlme::ScanRequest {
716            min_channel_time: 101,
717            max_channel_time: 100,
718            ..passive_scan_req()
719        };
720        let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
721        assert_matches!(result, Err(Error::ScanError(ScanError::MaxChannelTimeLtMin)));
722        m.fake_device_state
723            .lock()
724            .next_mlme_msg::<fidl_mlme::ScanEnd>()
725            .expect_err("unexpected MLME ScanEnd from BoundScanner");
726    }
727
728    #[fuchsia::test(allow_stalls = false)]
729    async fn test_start_offload_passive_scan_success() {
730        let mut m = MockObjects::new().await;
731        let mut ctx = m.make_ctx();
732        let mut scanner = Scanner::new(*IFACE_MAC);
733        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
734
735        scanner
736            .bind(&mut ctx)
737            .on_sme_scan(passive_scan_req())
738            .await
739            .expect("expect scan req accepted");
740
741        // Verify that passive offload scan is requested
742        assert_eq!(
743            m.fake_device_state.lock().captured_passive_scan_request,
744            Some(fidl_softmac::WlanSoftmacBaseStartPassiveScanRequest {
745                channels: Some(vec![6]),
746                min_channel_time: Some(102_400_000),
747                max_channel_time: Some(307_200_000),
748                min_home_time: Some(0),
749                ..Default::default()
750            }),
751        );
752        let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
753
754        // Mock receiving a beacon
755        handle_beacon_foo(&mut scanner, &mut ctx);
756        let scan_result = m
757            .fake_device_state
758            .lock()
759            .next_mlme_msg::<fidl_mlme::ScanResult>()
760            .expect("error reading ScanResult");
761        assert_eq!(scan_result.txn_id, 1337);
762        assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
763        assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
764
765        // Verify ScanEnd sent after handle_scan_complete
766        scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, expected_scan_id).await;
767        let scan_end = m
768            .fake_device_state
769            .lock()
770            .next_mlme_msg::<fidl_mlme::ScanEnd>()
771            .expect("error reading MLME ScanEnd");
772        assert_eq!(
773            scan_end,
774            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
775        );
776    }
777
778    struct ExpectedDynamicActiveScanRequest {
779        channels: Vec<u8>,
780        ies: Vec<u8>,
781    }
782
783    #[test_case(&[6],
784                Some(ExpectedDynamicActiveScanRequest {
785                    channels: vec![6],
786                    ies: vec![ 0x01, // Element ID for Supported Rates
787                               0x08, // Length
788                               0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, // Supported Rates
789                               0x32, // Element ID for Extended Supported Rates
790                               0x04, // Length
791                               0x30, 0x48, 0x60, 0x6c // Extended Supported Rates
792                    ]}),
793                None; "single channel")]
794    #[test_case(&[1, 2, 3, 4, 5],
795                Some(ExpectedDynamicActiveScanRequest {
796                    channels: vec![1, 2, 3, 4, 5],
797                    ies: vec![ 0x01, // Element ID for Supported Rates
798                               0x08, // Length
799                               0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, // Supported Rates
800                               0x32, // Element ID for Extended Supported Rates
801                               0x04, // Length
802                               0x30, 0x48, 0x60, 0x6c // Extended Supported Rates
803                    ]}),
804                None; "multiple channels 2.4GHz band")]
805    #[test_case(&[36, 40, 100, 108],
806                None,
807                Some(ExpectedDynamicActiveScanRequest {
808                    channels: vec![36, 40, 100, 108],
809                    ies: vec![ 0x01, // Element ID for Supported Rates
810                               0x08, // Length
811                               0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f // Supported Rates
812                    ],
813                }); "multiple channels 5GHz band")]
814    #[test_case(&[1, 2, 3, 4, 5, 36, 40, 100, 108],
815                Some(ExpectedDynamicActiveScanRequest {
816                    channels: vec![1, 2, 3, 4, 5],
817                    ies: vec![ 0x01, // Element ID for Supported Rates
818                               0x08, // Length
819                               0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, // Supported Rates
820                               0x32, // Element ID for Extended Supported Rates
821                               0x04, // Length
822                               0x30, 0x48, 0x60, 0x6c // Extended Supported Rates
823                    ]}),
824                Some(ExpectedDynamicActiveScanRequest {
825                    channels: vec![36, 40, 100, 108],
826                    ies: vec![ 0x01, // Element ID for Supported Rates
827                               0x08, // Length
828                               0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f, // Supported Rates
829                    ],
830                }); "multiple bands")]
831    #[fuchsia::test(allow_stalls = false)]
832    async fn test_start_active_scan_success(
833        channel_list: &[u8],
834        expected_two_ghz_dynamic_args: Option<ExpectedDynamicActiveScanRequest>,
835        expected_five_ghz_dynamic_args: Option<ExpectedDynamicActiveScanRequest>,
836    ) {
837        let mut m = MockObjects::new().await;
838        let mut ctx = m.make_ctx();
839        let mut scanner = Scanner::new(*IFACE_MAC);
840        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
841
842        scanner
843            .bind(&mut ctx)
844            .on_sme_scan(active_scan_req(channel_list))
845            .await
846            .expect("expect scan req accepted");
847
848        for probe_request_ies in &[expected_two_ghz_dynamic_args, expected_five_ghz_dynamic_args] {
849            match probe_request_ies {
850                None => {}
851                Some(ExpectedDynamicActiveScanRequest { channels, ies, .. }) => {
852                    // Verify that active offload scan is requested
853                    assert_eq!(
854                        m.fake_device_state.lock().captured_active_scan_request,
855                        Some(fidl_softmac::WlanSoftmacStartActiveScanRequest {
856                            channels: Some(channels.clone()),
857                            ssids: Some(vec![
858                                cssid_from_ssid_unchecked(&Ssid::try_from("foo").unwrap().into()),
859                                cssid_from_ssid_unchecked(&Ssid::try_from("bar").unwrap().into()),
860                            ]),
861                            mac_header: Some(vec![
862                                0x40, 0x00, // Frame Control
863                                0x00, 0x00, // Duration
864                                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Address 1
865                                0x07, 0x07, 0x07, 0x07, 0x07, 0x07, // Address 2
866                                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Address 3
867                                0x10, 0x00, // Sequence Control
868                            ]),
869                            ies: Some(ies.clone()),
870                            min_channel_time: Some(102_400_000),
871                            max_channel_time: Some(307_200_000),
872                            min_home_time: Some(0),
873                            min_probes_per_channel: Some(0),
874                            max_probes_per_channel: Some(0),
875                            ..Default::default()
876                        }),
877                        "active offload scan not initiated"
878                    );
879                    let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
880
881                    // Mock receiving beacons
882                    handle_beacon_foo(&mut scanner, &mut ctx);
883                    let scan_result = m
884                        .fake_device_state
885                        .lock()
886                        .next_mlme_msg::<fidl_mlme::ScanResult>()
887                        .expect("error reading ScanResult");
888                    assert_eq!(scan_result.txn_id, 1337);
889                    assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
890                    assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
891
892                    handle_beacon_bar(&mut scanner, &mut ctx);
893                    let scan_result = m
894                        .fake_device_state
895                        .lock()
896                        .next_mlme_msg::<fidl_mlme::ScanResult>()
897                        .expect("error reading ScanResult");
898                    assert_eq!(scan_result.txn_id, 1337);
899                    assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
900                    assert_eq!(scan_result.bss, *BSS_DESCRIPTION_BAR);
901
902                    // Verify ScanEnd sent after handle_scan_complete
903                    scanner
904                        .bind(&mut ctx)
905                        .handle_scan_complete(zx::Status::OK, expected_scan_id)
906                        .await;
907                }
908            }
909        }
910        let scan_end = m
911            .fake_device_state
912            .lock()
913            .next_mlme_msg::<fidl_mlme::ScanEnd>()
914            .expect("error reading MLME ScanEnd");
915        assert_eq!(
916            scan_end,
917            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
918        );
919    }
920
921    #[fuchsia::test(allow_stalls = false)]
922    async fn test_start_passive_scan_fails() {
923        let mut m = MockObjects::new().await;
924        m.fake_device_state.lock().config.start_passive_scan_fails = true;
925        let mut ctx = m.make_ctx();
926        let mut scanner = Scanner::new(*IFACE_MAC);
927
928        let result = scanner.bind(&mut ctx).on_sme_scan(passive_scan_req()).await;
929        assert_matches!(
930            result,
931            Err(Error::ScanError(ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED)))
932        );
933        m.fake_device_state
934            .lock()
935            .next_mlme_msg::<fidl_mlme::ScanEnd>()
936            .expect_err("unexpected MLME ScanEnd from BoundScanner");
937    }
938
939    #[fuchsia::test(allow_stalls = false)]
940    async fn test_start_active_scan_fails() {
941        let mut m = MockObjects::new().await;
942        m.fake_device_state.lock().config.start_active_scan_fails = true;
943        let mut ctx = m.make_ctx();
944        let mut scanner = Scanner::new(*IFACE_MAC);
945
946        let result = scanner.bind(&mut ctx).on_sme_scan(active_scan_req(&[6])).await;
947        assert_matches!(
948            result,
949            Err(Error::ScanError(ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED)))
950        );
951        m.fake_device_state
952            .lock()
953            .next_mlme_msg::<fidl_mlme::ScanEnd>()
954            .expect_err("unexpected MLME ScanEnd from BoundScanner");
955    }
956
957    #[fuchsia::test(allow_stalls = false)]
958    async fn test_start_passive_scan_canceled() {
959        let mut m = MockObjects::new().await;
960        let mut ctx = m.make_ctx();
961        let mut scanner = Scanner::new(*IFACE_MAC);
962        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
963
964        scanner
965            .bind(&mut ctx)
966            .on_sme_scan(passive_scan_req())
967            .await
968            .expect("expect scan req accepted");
969
970        // Verify that passive offload scan is requested
971        assert_matches!(
972            m.fake_device_state.lock().captured_passive_scan_request,
973            Some(_),
974            "passive offload scan not initiated"
975        );
976        let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
977
978        // Mock receiving a beacon
979        handle_beacon_foo(&mut scanner, &mut ctx);
980        let scan_result = m
981            .fake_device_state
982            .lock()
983            .next_mlme_msg::<fidl_mlme::ScanResult>()
984            .expect("error reading ScanResult");
985        assert_eq!(scan_result.txn_id, 1337);
986        assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
987        assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
988
989        // Verify ScanEnd sent after handle_scan_complete
990        scanner.bind(&mut ctx).handle_scan_complete(zx::Status::CANCELED, expected_scan_id).await;
991        let scan_end = m
992            .fake_device_state
993            .lock()
994            .next_mlme_msg::<fidl_mlme::ScanEnd>()
995            .expect("error reading MLME ScanEnd");
996        assert_eq!(
997            scan_end,
998            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InternalError }
999        );
1000    }
1001
1002    #[fuchsia::test(allow_stalls = false)]
1003    async fn test_start_active_scan_canceled() {
1004        let mut m = MockObjects::new().await;
1005        let mut ctx = m.make_ctx();
1006        let mut scanner = Scanner::new(*IFACE_MAC);
1007        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1008
1009        scanner
1010            .bind(&mut ctx)
1011            .on_sme_scan(active_scan_req(&[6]))
1012            .await
1013            .expect("expect scan req accepted");
1014
1015        // Verify that active offload scan is requested
1016        assert!(
1017            m.fake_device_state.lock().captured_active_scan_request.is_some(),
1018            "active offload scan not initiated"
1019        );
1020        let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
1021
1022        // Mock receiving a beacon
1023        handle_beacon_foo(&mut scanner, &mut ctx);
1024        let scan_result = m
1025            .fake_device_state
1026            .lock()
1027            .next_mlme_msg::<fidl_mlme::ScanResult>()
1028            .expect("error reading ScanResult");
1029        assert_eq!(scan_result.txn_id, 1337);
1030        assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
1031        assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
1032
1033        // Verify ScanEnd sent after handle_scan_complete
1034        scanner.bind(&mut ctx).handle_scan_complete(zx::Status::CANCELED, expected_scan_id).await;
1035        let scan_end = m
1036            .fake_device_state
1037            .lock()
1038            .next_mlme_msg::<fidl_mlme::ScanEnd>()
1039            .expect("error reading MLME ScanEnd");
1040        assert_eq!(
1041            scan_end,
1042            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InternalError }
1043        );
1044    }
1045
1046    #[fuchsia::test(allow_stalls = false)]
1047    async fn test_handle_ap_advertisement() {
1048        let mut m = MockObjects::new().await;
1049        let mut ctx = m.make_ctx();
1050        let mut scanner = Scanner::new(*IFACE_MAC);
1051        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1052
1053        scanner
1054            .bind(&mut ctx)
1055            .on_sme_scan(passive_scan_req())
1056            .await
1057            .expect("expect scan req accepted");
1058        handle_beacon_foo(&mut scanner, &mut ctx);
1059        let ongoing_scan_id = scanner.ongoing_scan.as_ref().unwrap().scan_id();
1060        scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, ongoing_scan_id).await;
1061
1062        let scan_result = m
1063            .fake_device_state
1064            .lock()
1065            .next_mlme_msg::<fidl_mlme::ScanResult>()
1066            .expect("error reading MLME ScanResult");
1067        assert_eq!(scan_result.txn_id, 1337);
1068        assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
1069        assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
1070
1071        let scan_end = m
1072            .fake_device_state
1073            .lock()
1074            .next_mlme_msg::<fidl_mlme::ScanEnd>()
1075            .expect("error reading MLME ScanEnd");
1076        assert_eq!(
1077            scan_end,
1078            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
1079        );
1080    }
1081
1082    #[fuchsia::test(allow_stalls = false)]
1083    async fn test_handle_ap_advertisement_multiple() {
1084        let mut m = MockObjects::new().await;
1085        let mut ctx = m.make_ctx();
1086        let mut scanner = Scanner::new(*IFACE_MAC);
1087        let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1088
1089        scanner
1090            .bind(&mut ctx)
1091            .on_sme_scan(passive_scan_req())
1092            .await
1093            .expect("expect scan req accepted");
1094
1095        handle_beacon_foo(&mut scanner, &mut ctx);
1096        handle_beacon_bar(&mut scanner, &mut ctx);
1097        let ongoing_scan_id = scanner.ongoing_scan.as_ref().unwrap().scan_id();
1098        scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, ongoing_scan_id).await;
1099
1100        // Verify that one scan result is sent for each beacon
1101        let foo_scan_result = m
1102            .fake_device_state
1103            .lock()
1104            .next_mlme_msg::<fidl_mlme::ScanResult>()
1105            .expect("error reading MLME ScanResult");
1106        assert_eq!(foo_scan_result.txn_id, 1337);
1107        assert!(foo_scan_result.timestamp_nanos > test_start_timestamp_nanos);
1108        assert_eq!(foo_scan_result.bss, *BSS_DESCRIPTION_FOO);
1109
1110        let bar_scan_result = m
1111            .fake_device_state
1112            .lock()
1113            .next_mlme_msg::<fidl_mlme::ScanResult>()
1114            .expect("error reading MLME ScanResult");
1115        assert_eq!(bar_scan_result.txn_id, 1337);
1116        assert!(bar_scan_result.timestamp_nanos > foo_scan_result.timestamp_nanos);
1117        assert_eq!(bar_scan_result.bss, *BSS_DESCRIPTION_BAR);
1118
1119        let scan_end = m
1120            .fake_device_state
1121            .lock()
1122            .next_mlme_msg::<fidl_mlme::ScanEnd>()
1123            .expect("error reading MLME ScanEnd");
1124        assert_eq!(
1125            scan_end,
1126            fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
1127        );
1128    }
1129
1130    #[fuchsia::test(allow_stalls = false)]
1131    async fn not_scanning_vs_scanning() {
1132        let mut m = MockObjects::new().await;
1133        let mut ctx = m.make_ctx();
1134        let mut scanner = Scanner::new(*IFACE_MAC);
1135        assert_eq!(false, scanner.is_scanning());
1136
1137        scanner
1138            .bind(&mut ctx)
1139            .on_sme_scan(passive_scan_req())
1140            .await
1141            .expect("expect scan req accepted");
1142        assert_eq!(true, scanner.is_scanning());
1143    }
1144
1145    fn handle_beacon_foo(scanner: &mut Scanner, ctx: &mut Context<FakeDevice>) {
1146        scanner.bind(ctx).handle_ap_advertisement(
1147            *BSSID_FOO,
1148            TimeUnit(BEACON_INTERVAL_FOO),
1149            CAPABILITY_INFO_FOO,
1150            BEACON_IES_FOO,
1151            RX_INFO_FOO.clone(),
1152        );
1153    }
1154
1155    fn handle_beacon_bar(scanner: &mut Scanner, ctx: &mut Context<FakeDevice>) {
1156        scanner.bind(ctx).handle_ap_advertisement(
1157            *BSSID_BAR,
1158            TimeUnit(BEACON_INTERVAL_BAR),
1159            CAPABILITY_INFO_BAR,
1160            BEACON_IES_BAR,
1161            RX_INFO_BAR.clone(),
1162        );
1163    }
1164
1165    struct MockObjects {
1166        fake_device: FakeDevice,
1167        fake_device_state: Arc<Mutex<FakeDeviceState>>,
1168        _time_stream: timer::EventStream<TimedEvent>,
1169        timer: Option<Timer<TimedEvent>>,
1170    }
1171
1172    impl MockObjects {
1173        // TODO(https://fxbug.dev/327499461): This function is async to ensure MLME functions will
1174        // run in an async context and not call `wlan_common::timer::Timer::now` without an
1175        // executor.
1176        async fn new() -> Self {
1177            let (timer, _time_stream) = create_timer();
1178            let (fake_device, fake_device_state) = FakeDevice::new().await;
1179            Self { fake_device, fake_device_state, _time_stream, timer: Some(timer) }
1180        }
1181
1182        fn make_ctx(&mut self) -> Context<FakeDevice> {
1183            Context {
1184                _config: Default::default(),
1185                device: self.fake_device.clone(),
1186                timer: self.timer.take().unwrap(),
1187                seq_mgr: SequenceManager::new(),
1188            }
1189        }
1190    }
1191}