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