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