Skip to main content

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