1use 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
24const 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 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 mlme_txn_id: u64,
102 in_progress_device_scan_id: u64,
104 },
105 ActiveOffloadScan {
106 mlme_txn_id: u64,
108 in_progress_device_scan_id: u64,
110 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 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 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 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 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 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 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 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 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
438fn 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 ies: Some(write_frame_to_vec!({
497 ies: {
498 supported_rates: supported_rates,
499 extended_supported_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 0x00, 0x03, b'f', b'o', b'o',
560 0x01, 0x04, 0xb0, 0x48, 0x60, 0x6c,
562 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 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_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 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 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 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 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, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
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, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
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, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f ],
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, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x0c, 0x12, 0x18, 0x24, 0x32, 0x04, 0x30, 0x48, 0x60, 0x6c ]}),
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, 0x08, 0x02, 0x04, 0x0b, 0x16, 0x30, 0x60, 0x7e, 0x7f, ],
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 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, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, ]),
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 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 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 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 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 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 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 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 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 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 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}