1use crate::client::convert_beacon::construct_bss_description;
6use crate::client::Context;
7use crate::ddk_converter::cssid_from_ssid_unchecked;
8use crate::device::{self, DeviceOps};
9use crate::error::Error;
10use crate::WlanSoftmacBandCapabilityExt as _;
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.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 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 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 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 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 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 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
435fn 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
448fn 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 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 ies: Some(write_frame_to_vec!({
503 ies: {
504 supported_rates: supported_rates,
505 extended_supported_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::{fake_wlan_channel, MockWlanRxInfo};
543 use fidl_fuchsia_wlan_common as fidl_common;
544 use fuchsia_sync::Mutex;
545 use ieee80211::{MacAddrBytes, Ssid};
546 use lazy_static::lazy_static;
547 use std::sync::Arc;
548 use test_case::test_case;
549 use wlan_common::assert_variant;
550 use wlan_common::sequence::SequenceManager;
551 use wlan_common::timer::{self, create_timer, Timer};
552
553 lazy_static! {
554 static ref BSSID_FOO: Bssid = [6u8; 6].into();
555 }
556 const CAPABILITY_INFO_FOO: CapabilityInfo = CapabilityInfo(1);
557 const BEACON_INTERVAL_FOO: u16 = 100;
558
559 #[rustfmt::skip]
560 static BEACON_IES_FOO: &'static [u8] = &[
561 0x00, 0x03, b'f', b'o', b'o',
563 0x01, 0x04, 0xb0, 0x48, 0x60, 0x6c,
565 0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
567 ];
568
569 lazy_static! {
570 static ref RX_INFO_FOO: fidl_softmac::WlanRxInfo = MockWlanRxInfo {
571 rssi_dbm: -30,
572 ..MockWlanRxInfo::with_channel(fake_wlan_channel().into())
573 }
574 .into();
575 static ref BSS_DESCRIPTION_FOO: fidl_common::BssDescription = 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_common::WlanChannel {
583 primary: RX_INFO_FOO.channel.primary,
584 cbw: fidl_common::ChannelBandwidth::Cbw20,
585 secondary80: 0,
586 },
587 snr_db: 0,
588 };
589 static ref BSSID_BAR: Bssid = [1u8; 6].into();
590 }
591
592 const CAPABILITY_INFO_BAR: CapabilityInfo = CapabilityInfo(33);
593 const BEACON_INTERVAL_BAR: u16 = 150;
594 #[rustfmt::skip]
595 static BEACON_IES_BAR: &'static [u8] = &[
596 0x00, 0x03, b'b', b'a', b'r',
598 0x01, 0x04, 0xb0, 0x48, 0x60, 0x6c,
600 0x05, 0x04, 0x00, 0x01, 0x00, 0x02,
602 ];
603 lazy_static! {
604 static ref RX_INFO_BAR: fidl_softmac::WlanRxInfo = MockWlanRxInfo {
605 rssi_dbm: -60,
606 ..MockWlanRxInfo::with_channel(fake_wlan_channel().into())
607 }
608 .into();
609 static ref BSS_DESCRIPTION_BAR: fidl_common::BssDescription = fidl_common::BssDescription {
610 bssid: BSSID_BAR.to_array(),
611 bss_type: fidl_common::BssType::Infrastructure,
612 beacon_period: BEACON_INTERVAL_BAR,
613 capability_info: CAPABILITY_INFO_BAR.0,
614 ies: BEACON_IES_BAR.to_vec(),
615 rssi_dbm: RX_INFO_BAR.rssi_dbm,
616 channel: fidl_common::WlanChannel {
617 primary: RX_INFO_BAR.channel.primary,
618 cbw: fidl_common::ChannelBandwidth::Cbw20,
619 secondary80: 0,
620 },
621 snr_db: 0,
622 };
623 }
624
625 lazy_static! {
626 static ref IFACE_MAC: MacAddr = [7u8; 6].into();
627 }
628
629 fn passive_scan_req() -> fidl_mlme::ScanRequest {
630 fidl_mlme::ScanRequest {
631 txn_id: 1337,
632 scan_type: fidl_mlme::ScanTypes::Passive,
633 channel_list: vec![6],
634 ssid_list: vec![],
635 probe_delay: 0,
636 min_channel_time: 100,
637 max_channel_time: 300,
638 }
639 }
640
641 fn active_scan_req(channel_list: &[u8]) -> fidl_mlme::ScanRequest {
642 fidl_mlme::ScanRequest {
643 txn_id: 1337,
644 scan_type: fidl_mlme::ScanTypes::Active,
645 channel_list: Vec::from(channel_list),
646 ssid_list: vec![
647 Ssid::try_from("foo").unwrap().into(),
648 Ssid::try_from("bar").unwrap().into(),
649 ],
650 probe_delay: 3,
651 min_channel_time: 100,
652 max_channel_time: 300,
653 }
654 }
655
656 #[fuchsia::test(allow_stalls = false)]
657 async fn test_handle_scan_req_reject_if_busy() {
658 let mut m = MockObjects::new().await;
659 let mut ctx = m.make_ctx();
660 let mut scanner = Scanner::new(*IFACE_MAC);
661
662 scanner
663 .bind(&mut ctx)
664 .on_sme_scan(passive_scan_req())
665 .await
666 .expect("expect scan req accepted");
667 let scan_req = fidl_mlme::ScanRequest { txn_id: 1338, ..passive_scan_req() };
668 let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
669 assert_variant!(result, Err(Error::ScanError(ScanError::Busy)));
670 m.fake_device_state
671 .lock()
672 .next_mlme_msg::<fidl_mlme::ScanEnd>()
673 .expect_err("unexpected MLME ScanEnd from BoundScanner");
674 }
675
676 #[fuchsia::test(allow_stalls = false)]
677 async fn test_handle_scan_req_reject_if_disabled() {
678 let mut m = MockObjects::new().await;
679 let mut ctx = m.make_ctx();
680 let mut scanner = Scanner::new(*IFACE_MAC);
681
682 scanner.bind(&mut ctx).disable_scanning().await.expect("Failed to disable scanning");
683 let result = scanner.bind(&mut ctx).on_sme_scan(passive_scan_req()).await;
684 assert_variant!(result, Err(Error::ScanError(ScanError::Busy)));
685 m.fake_device_state
686 .lock()
687 .next_mlme_msg::<fidl_mlme::ScanEnd>()
688 .expect_err("unexpected MLME ScanEnd from BoundScanner");
689
690 scanner.bind(&mut ctx).enable_scanning();
692 scanner
693 .bind(&mut ctx)
694 .on_sme_scan(passive_scan_req())
695 .await
696 .expect("expect scan req accepted");
697 }
698
699 #[fuchsia::test(allow_stalls = false)]
700 async fn test_handle_scan_req_empty_channel_list() {
701 let mut m = MockObjects::new().await;
702 let mut ctx = m.make_ctx();
703 let mut scanner = Scanner::new(*IFACE_MAC);
704
705 let scan_req = fidl_mlme::ScanRequest { channel_list: vec![], ..passive_scan_req() };
706 let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
707 assert_variant!(result, Err(Error::ScanError(ScanError::EmptyChannelList)));
708 m.fake_device_state
709 .lock()
710 .next_mlme_msg::<fidl_mlme::ScanEnd>()
711 .expect_err("unexpected MLME ScanEnd from BoundScanner");
712 }
713
714 #[fuchsia::test(allow_stalls = false)]
715 async fn test_handle_scan_req_invalid_channel_time() {
716 let mut m = MockObjects::new().await;
717 let mut ctx = m.make_ctx();
718 let mut scanner = Scanner::new(*IFACE_MAC);
719
720 let scan_req = fidl_mlme::ScanRequest {
721 min_channel_time: 101,
722 max_channel_time: 100,
723 ..passive_scan_req()
724 };
725 let result = scanner.bind(&mut ctx).on_sme_scan(scan_req).await;
726 assert_variant!(result, Err(Error::ScanError(ScanError::MaxChannelTimeLtMin)));
727 m.fake_device_state
728 .lock()
729 .next_mlme_msg::<fidl_mlme::ScanEnd>()
730 .expect_err("unexpected MLME ScanEnd from BoundScanner");
731 }
732
733 #[fuchsia::test(allow_stalls = false)]
734 async fn test_start_offload_passive_scan_success() {
735 let mut m = MockObjects::new().await;
736 let mut ctx = m.make_ctx();
737 let mut scanner = Scanner::new(*IFACE_MAC);
738 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
739
740 scanner
741 .bind(&mut ctx)
742 .on_sme_scan(passive_scan_req())
743 .await
744 .expect("expect scan req accepted");
745
746 assert_eq!(
748 m.fake_device_state.lock().captured_passive_scan_request,
749 Some(fidl_softmac::WlanSoftmacBaseStartPassiveScanRequest {
750 channels: Some(vec![6]),
751 min_channel_time: Some(102_400_000),
752 max_channel_time: Some(307_200_000),
753 min_home_time: Some(0),
754 ..Default::default()
755 }),
756 );
757 let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
758
759 handle_beacon_foo(&mut scanner, &mut ctx);
761 let scan_result = m
762 .fake_device_state
763 .lock()
764 .next_mlme_msg::<fidl_mlme::ScanResult>()
765 .expect("error reading ScanResult");
766 assert_eq!(scan_result.txn_id, 1337);
767 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
768 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
769
770 scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, expected_scan_id).await;
772 let scan_end = m
773 .fake_device_state
774 .lock()
775 .next_mlme_msg::<fidl_mlme::ScanEnd>()
776 .expect("error reading MLME ScanEnd");
777 assert_eq!(
778 scan_end,
779 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
780 );
781 }
782
783 struct ExpectedDynamicActiveScanRequest {
784 channels: Vec<u8>,
785 ies: Vec<u8>,
786 }
787
788 #[test_case(&[6],
789 Some(ExpectedDynamicActiveScanRequest {
790 channels: vec![6],
791 ies: vec![ 0x01, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
798 None; "single channel")]
799 #[test_case(&[1, 2, 3, 4, 5],
800 Some(ExpectedDynamicActiveScanRequest {
801 channels: vec![1, 2, 3, 4, 5],
802 ies: vec![ 0x01, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
809 None; "multiple channels 2.4GHz band")]
810 #[test_case(&[36, 40, 100, 108],
811 None,
812 Some(ExpectedDynamicActiveScanRequest {
813 channels: vec![36, 40, 100, 108],
814 ies: vec![ 0x01, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f ],
818 }); "multiple channels 5GHz band")]
819 #[test_case(&[1, 2, 3, 4, 5, 36, 40, 100, 108],
820 Some(ExpectedDynamicActiveScanRequest {
821 channels: vec![1, 2, 3, 4, 5],
822 ies: vec![ 0x01, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
829 Some(ExpectedDynamicActiveScanRequest {
830 channels: vec![36, 40, 100, 108],
831 ies: vec![ 0x01, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f, ],
835 }); "multiple bands")]
836 #[fuchsia::test(allow_stalls = false)]
837 async fn test_start_active_scan_success(
838 channel_list: &[u8],
839 expected_two_ghz_dynamic_args: Option<ExpectedDynamicActiveScanRequest>,
840 expected_five_ghz_dynamic_args: Option<ExpectedDynamicActiveScanRequest>,
841 ) {
842 let mut m = MockObjects::new().await;
843 let mut ctx = m.make_ctx();
844 let mut scanner = Scanner::new(*IFACE_MAC);
845 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
846
847 scanner
848 .bind(&mut ctx)
849 .on_sme_scan(active_scan_req(channel_list))
850 .await
851 .expect("expect scan req accepted");
852
853 for probe_request_ies in &[expected_two_ghz_dynamic_args, expected_five_ghz_dynamic_args] {
854 match probe_request_ies {
855 None => {}
856 Some(ExpectedDynamicActiveScanRequest { channels, ies, .. }) => {
857 assert_eq!(
859 m.fake_device_state.lock().captured_active_scan_request,
860 Some(fidl_softmac::WlanSoftmacStartActiveScanRequest {
861 channels: Some(channels.clone()),
862 ssids: Some(vec![
863 cssid_from_ssid_unchecked(&Ssid::try_from("foo").unwrap().into()),
864 cssid_from_ssid_unchecked(&Ssid::try_from("bar").unwrap().into()),
865 ]),
866 mac_header: Some(vec![
867 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, ]),
874 ies: Some(ies.clone()),
875 min_channel_time: Some(102_400_000),
876 max_channel_time: Some(307_200_000),
877 min_home_time: Some(0),
878 min_probes_per_channel: Some(0),
879 max_probes_per_channel: Some(0),
880 ..Default::default()
881 }),
882 "active offload scan not initiated"
883 );
884 let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
885
886 handle_beacon_foo(&mut scanner, &mut ctx);
888 let scan_result = m
889 .fake_device_state
890 .lock()
891 .next_mlme_msg::<fidl_mlme::ScanResult>()
892 .expect("error reading ScanResult");
893 assert_eq!(scan_result.txn_id, 1337);
894 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
895 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
896
897 handle_beacon_bar(&mut scanner, &mut ctx);
898 let scan_result = m
899 .fake_device_state
900 .lock()
901 .next_mlme_msg::<fidl_mlme::ScanResult>()
902 .expect("error reading ScanResult");
903 assert_eq!(scan_result.txn_id, 1337);
904 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
905 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_BAR);
906
907 scanner
909 .bind(&mut ctx)
910 .handle_scan_complete(zx::Status::OK, expected_scan_id)
911 .await;
912 }
913 }
914 }
915 let scan_end = m
916 .fake_device_state
917 .lock()
918 .next_mlme_msg::<fidl_mlme::ScanEnd>()
919 .expect("error reading MLME ScanEnd");
920 assert_eq!(
921 scan_end,
922 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
923 );
924 }
925
926 #[fuchsia::test(allow_stalls = false)]
927 async fn test_start_passive_scan_fails() {
928 let mut m = MockObjects::new().await;
929 m.fake_device_state.lock().config.start_passive_scan_fails = true;
930 let mut ctx = m.make_ctx();
931 let mut scanner = Scanner::new(*IFACE_MAC);
932
933 let result = scanner.bind(&mut ctx).on_sme_scan(passive_scan_req()).await;
934 assert_variant!(
935 result,
936 Err(Error::ScanError(ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED)))
937 );
938 m.fake_device_state
939 .lock()
940 .next_mlme_msg::<fidl_mlme::ScanEnd>()
941 .expect_err("unexpected MLME ScanEnd from BoundScanner");
942 }
943
944 #[fuchsia::test(allow_stalls = false)]
945 async fn test_start_active_scan_fails() {
946 let mut m = MockObjects::new().await;
947 m.fake_device_state.lock().config.start_active_scan_fails = true;
948 let mut ctx = m.make_ctx();
949 let mut scanner = Scanner::new(*IFACE_MAC);
950
951 let result = scanner.bind(&mut ctx).on_sme_scan(active_scan_req(&[6])).await;
952 assert_variant!(
953 result,
954 Err(Error::ScanError(ScanError::StartOffloadScanFails(zx::Status::NOT_SUPPORTED)))
955 );
956 m.fake_device_state
957 .lock()
958 .next_mlme_msg::<fidl_mlme::ScanEnd>()
959 .expect_err("unexpected MLME ScanEnd from BoundScanner");
960 }
961
962 #[fuchsia::test(allow_stalls = false)]
963 async fn test_start_passive_scan_canceled() {
964 let mut m = MockObjects::new().await;
965 let mut ctx = m.make_ctx();
966 let mut scanner = Scanner::new(*IFACE_MAC);
967 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
968
969 scanner
970 .bind(&mut ctx)
971 .on_sme_scan(passive_scan_req())
972 .await
973 .expect("expect scan req accepted");
974
975 assert_variant!(
977 m.fake_device_state.lock().captured_passive_scan_request,
978 Some(_),
979 "passive offload scan not initiated"
980 );
981 let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
982
983 handle_beacon_foo(&mut scanner, &mut ctx);
985 let scan_result = m
986 .fake_device_state
987 .lock()
988 .next_mlme_msg::<fidl_mlme::ScanResult>()
989 .expect("error reading ScanResult");
990 assert_eq!(scan_result.txn_id, 1337);
991 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
992 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
993
994 scanner.bind(&mut ctx).handle_scan_complete(zx::Status::CANCELED, expected_scan_id).await;
996 let scan_end = m
997 .fake_device_state
998 .lock()
999 .next_mlme_msg::<fidl_mlme::ScanEnd>()
1000 .expect("error reading MLME ScanEnd");
1001 assert_eq!(
1002 scan_end,
1003 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InternalError }
1004 );
1005 }
1006
1007 #[fuchsia::test(allow_stalls = false)]
1008 async fn test_start_active_scan_canceled() {
1009 let mut m = MockObjects::new().await;
1010 let mut ctx = m.make_ctx();
1011 let mut scanner = Scanner::new(*IFACE_MAC);
1012 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1013
1014 scanner
1015 .bind(&mut ctx)
1016 .on_sme_scan(active_scan_req(&[6]))
1017 .await
1018 .expect("expect scan req accepted");
1019
1020 assert!(
1022 m.fake_device_state.lock().captured_active_scan_request.is_some(),
1023 "active offload scan not initiated"
1024 );
1025 let expected_scan_id = m.fake_device_state.lock().next_scan_id - 1;
1026
1027 handle_beacon_foo(&mut scanner, &mut ctx);
1029 let scan_result = m
1030 .fake_device_state
1031 .lock()
1032 .next_mlme_msg::<fidl_mlme::ScanResult>()
1033 .expect("error reading ScanResult");
1034 assert_eq!(scan_result.txn_id, 1337);
1035 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
1036 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
1037
1038 scanner.bind(&mut ctx).handle_scan_complete(zx::Status::CANCELED, expected_scan_id).await;
1040 let scan_end = m
1041 .fake_device_state
1042 .lock()
1043 .next_mlme_msg::<fidl_mlme::ScanEnd>()
1044 .expect("error reading MLME ScanEnd");
1045 assert_eq!(
1046 scan_end,
1047 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::InternalError }
1048 );
1049 }
1050
1051 #[fuchsia::test(allow_stalls = false)]
1052 async fn test_handle_ap_advertisement() {
1053 let mut m = MockObjects::new().await;
1054 let mut ctx = m.make_ctx();
1055 let mut scanner = Scanner::new(*IFACE_MAC);
1056 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1057
1058 scanner
1059 .bind(&mut ctx)
1060 .on_sme_scan(passive_scan_req())
1061 .await
1062 .expect("expect scan req accepted");
1063 handle_beacon_foo(&mut scanner, &mut ctx);
1064 let ongoing_scan_id = scanner.ongoing_scan.as_ref().unwrap().scan_id();
1065 scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, ongoing_scan_id).await;
1066
1067 let scan_result = m
1068 .fake_device_state
1069 .lock()
1070 .next_mlme_msg::<fidl_mlme::ScanResult>()
1071 .expect("error reading MLME ScanResult");
1072 assert_eq!(scan_result.txn_id, 1337);
1073 assert!(scan_result.timestamp_nanos > test_start_timestamp_nanos);
1074 assert_eq!(scan_result.bss, *BSS_DESCRIPTION_FOO);
1075
1076 let scan_end = m
1077 .fake_device_state
1078 .lock()
1079 .next_mlme_msg::<fidl_mlme::ScanEnd>()
1080 .expect("error reading MLME ScanEnd");
1081 assert_eq!(
1082 scan_end,
1083 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
1084 );
1085 }
1086
1087 #[fuchsia::test(allow_stalls = false)]
1088 async fn test_handle_ap_advertisement_multiple() {
1089 let mut m = MockObjects::new().await;
1090 let mut ctx = m.make_ctx();
1091 let mut scanner = Scanner::new(*IFACE_MAC);
1092 let test_start_timestamp_nanos = zx::MonotonicInstant::get().into_nanos();
1093
1094 scanner
1095 .bind(&mut ctx)
1096 .on_sme_scan(passive_scan_req())
1097 .await
1098 .expect("expect scan req accepted");
1099
1100 handle_beacon_foo(&mut scanner, &mut ctx);
1101 handle_beacon_bar(&mut scanner, &mut ctx);
1102 let ongoing_scan_id = scanner.ongoing_scan.as_ref().unwrap().scan_id();
1103 scanner.bind(&mut ctx).handle_scan_complete(zx::Status::OK, ongoing_scan_id).await;
1104
1105 let foo_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!(foo_scan_result.txn_id, 1337);
1112 assert!(foo_scan_result.timestamp_nanos > test_start_timestamp_nanos);
1113 assert_eq!(foo_scan_result.bss, *BSS_DESCRIPTION_FOO);
1114
1115 let bar_scan_result = m
1116 .fake_device_state
1117 .lock()
1118 .next_mlme_msg::<fidl_mlme::ScanResult>()
1119 .expect("error reading MLME ScanResult");
1120 assert_eq!(bar_scan_result.txn_id, 1337);
1121 assert!(bar_scan_result.timestamp_nanos > foo_scan_result.timestamp_nanos);
1122 assert_eq!(bar_scan_result.bss, *BSS_DESCRIPTION_BAR);
1123
1124 let scan_end = m
1125 .fake_device_state
1126 .lock()
1127 .next_mlme_msg::<fidl_mlme::ScanEnd>()
1128 .expect("error reading MLME ScanEnd");
1129 assert_eq!(
1130 scan_end,
1131 fidl_mlme::ScanEnd { txn_id: 1337, code: fidl_mlme::ScanResultCode::Success }
1132 );
1133 }
1134
1135 #[fuchsia::test(allow_stalls = false)]
1136 async fn not_scanning_vs_scanning() {
1137 let mut m = MockObjects::new().await;
1138 let mut ctx = m.make_ctx();
1139 let mut scanner = Scanner::new(*IFACE_MAC);
1140 assert_eq!(false, scanner.is_scanning());
1141
1142 scanner
1143 .bind(&mut ctx)
1144 .on_sme_scan(passive_scan_req())
1145 .await
1146 .expect("expect scan req accepted");
1147 assert_eq!(true, scanner.is_scanning());
1148 }
1149
1150 fn handle_beacon_foo(scanner: &mut Scanner, ctx: &mut Context<FakeDevice>) {
1151 scanner.bind(ctx).handle_ap_advertisement(
1152 *BSSID_FOO,
1153 TimeUnit(BEACON_INTERVAL_FOO),
1154 CAPABILITY_INFO_FOO,
1155 BEACON_IES_FOO,
1156 RX_INFO_FOO.clone(),
1157 );
1158 }
1159
1160 fn handle_beacon_bar(scanner: &mut Scanner, ctx: &mut Context<FakeDevice>) {
1161 scanner.bind(ctx).handle_ap_advertisement(
1162 *BSSID_BAR,
1163 TimeUnit(BEACON_INTERVAL_BAR),
1164 CAPABILITY_INFO_BAR,
1165 BEACON_IES_BAR,
1166 RX_INFO_BAR.clone(),
1167 );
1168 }
1169
1170 struct MockObjects {
1171 fake_device: FakeDevice,
1172 fake_device_state: Arc<Mutex<FakeDeviceState>>,
1173 _time_stream: timer::EventStream<TimedEvent>,
1174 timer: Option<Timer<TimedEvent>>,
1175 }
1176
1177 impl MockObjects {
1178 async fn new() -> Self {
1182 let (timer, _time_stream) = create_timer();
1183 let (fake_device, fake_device_state) = FakeDevice::new().await;
1184 Self { fake_device, fake_device_state, _time_stream, timer: Some(timer) }
1185 }
1186
1187 fn make_ctx(&mut self) -> Context<FakeDevice> {
1188 Context {
1189 _config: Default::default(),
1190 device: self.fake_device.clone(),
1191 timer: self.timer.take().unwrap(),
1192 seq_mgr: SequenceManager::new(),
1193 }
1194 }
1195 }
1196}