1use crate::Error;
6use crate::client::inspect;
7use fuchsia_inspect::NumericProperty;
8use ieee80211::{Bssid, Ssid};
9use log::warn;
10use std::collections::{HashMap, HashSet, hash_map};
11use std::mem;
12use std::sync::{Arc, LazyLock};
13use wlan_common::bss::BssDescription;
14use wlan_common::channel::{Cbw, Channel};
15use wlan_common::ie::IesMerger;
16use {
17 fidl_fuchsia_wlan_common as fidl_common, fidl_fuchsia_wlan_mlme as fidl_mlme,
18 fidl_fuchsia_wlan_sme as fidl_sme,
19};
20
21const PASSIVE_SCAN_CHANNEL_MS: u32 = 200;
22const ACTIVE_SCAN_PROBE_DELAY_MS: u32 = 5;
23const ACTIVE_SCAN_CHANNEL_MS: u32 = 75;
24
25#[derive(Debug, PartialEq)]
27pub struct DiscoveryScan<T> {
28 tokens: Vec<T>,
29 scan_request: fidl_sme::ScanRequest,
30}
31
32impl<T> DiscoveryScan<T> {
33 pub fn new(token: T, scan_request: fidl_sme::ScanRequest) -> Self {
34 Self { tokens: vec![token], scan_request }
35 }
36
37 pub fn matches(&self, scan: &DiscoveryScan<T>) -> bool {
38 self.scan_request == scan.scan_request
39 }
40
41 pub fn merges(&mut self, mut scan: DiscoveryScan<T>) {
42 self.tokens.append(&mut scan.tokens)
43 }
44}
45
46pub struct ScanScheduler<T> {
47 current: ScanState<T>,
50 pending_discovery: Vec<DiscoveryScan<T>>,
52 device_info: Arc<fidl_mlme::DeviceInfo>,
53 spectrum_management_support: fidl_common::SpectrumManagementSupport,
54 last_mlme_txn_id: u64,
55}
56
57#[derive(Debug)]
58enum ScanState<T> {
59 NotScanning,
60 ScanningToDiscover {
61 cmd: DiscoveryScan<T>,
62 mlme_txn_id: u64,
63 bss_map: HashMap<Bssid, (fidl_common::BssDescription, IesMerger)>,
64 },
65}
66
67#[derive(Debug)]
68pub struct ScanEnd<T> {
69 pub tokens: Vec<T>,
70 pub result_code: fidl_mlme::ScanResultCode,
71 pub bss_description_list: Vec<BssDescription>,
72}
73
74impl<T> ScanScheduler<T> {
75 pub fn new(
76 device_info: Arc<fidl_mlme::DeviceInfo>,
77 spectrum_management_support: fidl_common::SpectrumManagementSupport,
78 ) -> Self {
79 ScanScheduler {
80 current: ScanState::NotScanning,
81 pending_discovery: Vec::new(),
82 device_info,
83 spectrum_management_support,
84 last_mlme_txn_id: 0,
85 }
86 }
87
88 pub fn enqueue_scan_to_discover(
92 &mut self,
93 s: DiscoveryScan<T>,
94 ) -> Option<fidl_mlme::ScanRequest> {
95 if let ScanState::ScanningToDiscover { cmd, .. } = &mut self.current
96 && cmd.matches(&s)
97 {
98 cmd.merges(s);
99 return None;
100 }
101 if let Some(scan_cmd) = self.pending_discovery.iter_mut().find(|cmd| cmd.matches(&s)) {
102 scan_cmd.merges(s);
103 return None;
104 }
105 self.pending_discovery.push(s);
106 self.start_next_scan()
107 }
108
109 pub fn on_mlme_scan_result(&mut self, msg: fidl_mlme::ScanResult) -> Result<(), Error> {
111 match &mut self.current {
112 ScanState::NotScanning => Err(Error::ScanResultNotScanning),
113 ScanState::ScanningToDiscover { mlme_txn_id, .. } if *mlme_txn_id != msg.txn_id => {
114 Err(Error::ScanResultWrongTxnId)
115 }
116 ScanState::ScanningToDiscover { bss_map, .. } => {
117 maybe_insert_bss(bss_map, msg.bss);
118 Ok(())
119 }
120 }
121 }
122
123 pub fn on_mlme_scan_end(
126 &mut self,
127 msg: fidl_mlme::ScanEnd,
128 sme_inspect: &Arc<inspect::SmeTree>,
129 ) -> Result<(ScanEnd<T>, Option<fidl_mlme::ScanRequest>), Error> {
130 match mem::replace(&mut self.current, ScanState::NotScanning) {
131 ScanState::NotScanning => Err(Error::ScanEndNotScanning),
132 ScanState::ScanningToDiscover { mlme_txn_id, .. } if mlme_txn_id != msg.txn_id => {
133 Err(Error::ScanEndWrongTxnId)
134 }
135 ScanState::ScanningToDiscover { cmd, bss_map, .. } => {
136 let scan_end = ScanEnd {
137 tokens: cmd.tokens,
138 result_code: msg.code,
139 bss_description_list: convert_bss_map(bss_map, None::<Ssid>, sme_inspect),
140 };
141
142 let request = self.start_next_scan();
143 Ok((scan_end, request))
144 }
145 }
146 }
147
148 fn start_next_scan(&mut self) -> Option<fidl_mlme::ScanRequest> {
149 let has_pending = !self.pending_discovery.is_empty();
150 (matches!(self.current, ScanState::NotScanning) && has_pending).then(|| {
151 self.last_mlme_txn_id += 1;
152 let scan_cmd = self.pending_discovery.remove(0);
153 let request = new_discovery_scan_request(
154 self.last_mlme_txn_id,
155 &scan_cmd,
156 &self.device_info,
157 self.spectrum_management_support.clone(),
158 );
159 self.current = ScanState::ScanningToDiscover {
160 cmd: scan_cmd,
161 mlme_txn_id: self.last_mlme_txn_id,
162 bss_map: HashMap::new(),
163 };
164 request
165 })
166 }
167}
168
169fn maybe_insert_bss(
170 bss_map: &mut HashMap<Bssid, (fidl_common::BssDescription, IesMerger)>,
171 mut fidl_bss: fidl_common::BssDescription,
172) {
173 let mut ies = vec![];
174 std::mem::swap(&mut ies, &mut fidl_bss.ies);
175
176 match bss_map.entry(Bssid::from(fidl_bss.bssid)) {
177 hash_map::Entry::Occupied(mut entry) => {
178 let (existing_bss, ies_merger) = entry.get_mut();
179
180 if (fidl_bss.channel.primary != existing_bss.channel.primary)
181 && (fidl_bss.rssi_dbm < existing_bss.rssi_dbm)
182 {
183 return;
185 }
186
187 ies_merger.merge(&ies[..]);
188 if ies_merger.buffer_overflow() {
189 warn!(
190 "Not merging some IEs due to running out of buffer. BSSID: {}",
191 Bssid::from(fidl_bss.bssid)
192 );
193 }
194 *existing_bss = fidl_bss;
195 }
196 hash_map::Entry::Vacant(entry) => {
197 let _ = entry.insert((fidl_bss, IesMerger::new(ies)));
198 }
199 }
200}
201
202fn convert_bss_map(
203 bss_map: HashMap<Bssid, (fidl_common::BssDescription, IesMerger)>,
204 ssid_selector: Option<Ssid>,
205 sme_inspect: &Arc<inspect::SmeTree>,
206) -> Vec<BssDescription> {
207 let bss_description_list =
208 bss_map.into_iter().filter_map(|(_bssid, (mut bss, mut ies_merger))| {
209 let _ = sme_inspect.scan_merge_ie_failures.add(ies_merger.merge_ie_failures() as u64);
210
211 let mut ies = ies_merger.finalize();
212 std::mem::swap(&mut ies, &mut bss.ies);
213 let bss: Option<BssDescription> = bss.try_into().ok();
214 if bss.is_none() {
215 let _ = sme_inspect.scan_discard_fidl_bss.add(1);
216 }
217 bss
218 });
219
220 match ssid_selector {
221 None => bss_description_list.collect(),
222 Some(ssid) => bss_description_list.filter(|v| v.ssid == ssid).collect(),
223 }
224}
225
226fn new_scan_request(
227 mlme_txn_id: u64,
228 scan_request: fidl_sme::ScanRequest,
229 ssid_list: Vec<Ssid>,
230 device_info: &fidl_mlme::DeviceInfo,
231 spectrum_management_support: fidl_common::SpectrumManagementSupport,
232) -> fidl_mlme::ScanRequest {
233 let scan_req = fidl_mlme::ScanRequest {
234 txn_id: mlme_txn_id,
235 scan_type: fidl_mlme::ScanTypes::Passive,
236 probe_delay: 0,
237 channel_list: get_operating_channels_for_scan(
239 device_info,
240 spectrum_management_support,
241 &scan_request,
242 ),
243 ssid_list: ssid_list.into_iter().map(Ssid::into).collect(),
244 min_channel_time: PASSIVE_SCAN_CHANNEL_MS,
245 max_channel_time: PASSIVE_SCAN_CHANNEL_MS,
246 };
247 match scan_request {
248 fidl_sme::ScanRequest::Active(active_scan_params) => fidl_mlme::ScanRequest {
249 scan_type: fidl_mlme::ScanTypes::Active,
250 ssid_list: active_scan_params.ssids,
251 probe_delay: ACTIVE_SCAN_PROBE_DELAY_MS,
252 min_channel_time: ACTIVE_SCAN_CHANNEL_MS,
253 max_channel_time: ACTIVE_SCAN_CHANNEL_MS,
254 ..scan_req
255 },
256 fidl_sme::ScanRequest::Passive(_) => scan_req,
257 }
258}
259
260fn new_discovery_scan_request<T>(
261 mlme_txn_id: u64,
262 discovery_scan: &DiscoveryScan<T>,
263 device_info: &fidl_mlme::DeviceInfo,
264 spectrum_management_support: fidl_common::SpectrumManagementSupport,
265) -> fidl_mlme::ScanRequest {
266 new_scan_request(
267 mlme_txn_id,
268 discovery_scan.scan_request.clone(),
269 vec![],
270 device_info,
271 spectrum_management_support,
272 )
273}
274
275fn get_operating_channels_for_scan(
290 device_info: &fidl_mlme::DeviceInfo,
291 spectrum_management_support: fidl_common::SpectrumManagementSupport,
292 scan_request: &fidl_sme::ScanRequest,
293) -> Vec<u8> {
294 let mut operating_channels: HashSet<u8> = HashSet::new();
295 for band in &device_info.bands {
296 operating_channels.extend(&band.operating_channels);
297 }
298
299 let requested_channels = match scan_request {
300 fidl_sme::ScanRequest::Active(options) => &options.channels[..],
301 _ => &[],
302 };
303 let channels: Vec<u8> = CANDIDATE_OPERATING_CHANNELS
304 .iter()
305 .filter(|channel| operating_channels.contains(&channel.primary))
306 .filter(|channel| {
307 if let &fidl_sme::ScanRequest::Passive(_) = scan_request {
310 return true;
311 };
312 if channel.is_5ghz() {
313 return spectrum_management_support
314 .dfs
315 .as_ref()
316 .and_then(|dfs| dfs.supported)
317 .unwrap_or(false);
318 };
319 true
320 })
321 .filter(|channel| {
322 if !requested_channels.is_empty() {
325 return requested_channels.contains(&channel.primary);
326 }
327 true
328 })
329 .map(|channel| channel.primary)
330 .collect();
331
332 if channels.is_empty() {
333 if !requested_channels.is_empty() {
334 warn!("All channels are filtered out. Requested channels: {:?}", requested_channels);
335 } else {
336 warn!("All channels are filtered out.");
337 };
338 }
339
340 channels
341}
342
343static CANDIDATE_OPERATING_CHANNELS: LazyLock<&'static [Channel]> = LazyLock::new(|| {
347 #[rustfmt::skip]
348 let channels = vec![
349 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
351 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108,
353 112, 116, 120, 124, 128, 132, 136, 140, 144,
354 149, 153, 157, 161, 165,
355 ];
356
357 channels.iter().map(|primary| Channel::new(*primary, Cbw::Cbw20)).collect::<Vec<_>>().leak()
358});
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::test_utils;
364 use assert_matches::assert_matches;
365 use fuchsia_inspect::Inspector;
366
367 use ieee80211::MacAddr;
368 use regex::bytes::Regex;
369 use std::fmt::Write;
370 use std::sync::LazyLock;
371 use test_case::test_case;
372 use wlan_common::test_utils::fake_capabilities::fake_5ghz_band_capability;
373 use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
374 use wlan_common::{fake_bss_description, fake_fidl_bss_description};
375
376 static CLIENT_ADDR: LazyLock<MacAddr> =
377 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
378
379 fn passive_discovery_scan(token: i32) -> DiscoveryScan<i32> {
380 DiscoveryScan::new(token, fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}))
381 }
382
383 #[test]
384 fn discovery_scan() {
385 let mut sched = create_sched();
386 let (_inspector, sme_inspect) = sme_inspect();
387 let req = sched
388 .enqueue_scan_to_discover(passive_discovery_scan(10))
389 .expect("expected a ScanRequest");
390 let txn_id = req.txn_id;
391 sched
392 .on_mlme_scan_result(fidl_mlme::ScanResult {
393 txn_id,
394 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
395 bss: fidl_common::BssDescription {
396 bssid: [1; 6],
397 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
398 },
399 })
400 .expect("expect scan result received");
401 assert_matches!(
402 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
403 txn_id: txn_id + 100, timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
405 bss: fidl_common::BssDescription {
406 bssid: [2; 6],
407 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
408 },
409 },),
410 Err(Error::ScanResultWrongTxnId)
411 );
412 sched
413 .on_mlme_scan_result(fidl_mlme::ScanResult {
414 txn_id,
415 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
416 bss: fidl_common::BssDescription {
417 bssid: [3; 6],
418 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("qux").unwrap())
419 },
420 })
421 .expect("expect scan result received");
422 let (scan_end, mlme_req) = assert_matches!(
423 sched.on_mlme_scan_end(
424 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
425 &sme_inspect,
426 ),
427 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
428 );
429 assert!(mlme_req.is_none());
430 let (tokens, bss_description_list) = assert_matches!(
431 scan_end,
432 ScanEnd {
433 tokens,
434 result_code: fidl_mlme::ScanResultCode::Success,
435 bss_description_list
436 } => (tokens, bss_description_list),
437 "expected discovery scan to be completed successfully"
438 );
439 assert_eq!(vec![10], tokens);
440 let mut ssid_list =
441 bss_description_list.into_iter().map(|bss| bss.ssid).collect::<Vec<_>>();
442 ssid_list.sort();
443 assert_eq!(vec![Ssid::try_from("foo").unwrap(), Ssid::try_from("qux").unwrap()], ssid_list);
444 }
445
446 #[test_case(vec![
447 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap()),
448 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap()),
449 ], vec![fake_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap())] ;
450 "when latest BSS Description is new")]
451 #[test_case(vec![
452 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20)),
453 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(165, Cbw::Cbw20)),
454 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20))] ;
455 "when strong signal is first")]
456 #[test_case(vec![
457 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
458 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
459 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
460 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
461 "when strong signal is middle")]
462 #[test_case(vec![
463 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
464 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
465 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
466 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
467 "when strong signal is last")]
468 #[test_case(vec![
469 fake_fidl_bss_description!(Open, rssi_dbm: -84, ssid: Ssid::try_from("bar").unwrap(),
470 channel: Channel::new(149, Cbw::Cbw20)),
471 fake_fidl_bss_description!(Open, rssi_dbm: -36, ssid: Ssid::try_from("bar").unwrap(),
472 channel: Channel::new(165, Cbw::Cbw20)),
473 fake_fidl_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
474 channel: Channel::new(165, Cbw::Cbw20)),
475 ], vec![fake_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
476 channel: Channel::new(165, Cbw::Cbw20))];
477 "overwrite latest chosen channel")]
478 fn deduplicate_by_bssid(
479 bss_description_list_from_mlme: Vec<fidl_common::BssDescription>,
480 returned_bss_description_list: Vec<BssDescription>,
481 ) {
482 let mut sched = create_sched();
483 let (_inspector, sme_inspect) = sme_inspect();
484 let req = sched
485 .enqueue_scan_to_discover(passive_discovery_scan(10))
486 .expect("expected a ScanRequest");
487 let txn_id = req.txn_id;
488 for bss in bss_description_list_from_mlme {
489 sched
490 .on_mlme_scan_result(fidl_mlme::ScanResult {
491 txn_id,
492 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
493 bss,
494 })
495 .expect("expect scan result received");
496 }
497 let (scan_end, mlme_req) = assert_matches!(
498 sched.on_mlme_scan_end(
499 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
500 &sme_inspect,
501 ),
502 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
503 );
504 assert!(mlme_req.is_none());
505 let (tokens, bss_description_list) = assert_matches!(
506 scan_end,
507 ScanEnd {
508 tokens,
509 result_code: fidl_mlme::ScanResultCode::Success,
510 bss_description_list
511 } => (tokens, bss_description_list),
512 "expected discovery scan to be completed successfully"
513 );
514 assert_eq!(vec![10], tokens);
515 assert_eq!(bss_description_list, returned_bss_description_list);
516 }
517
518 #[test]
519 fn discovery_scan_merge_ies() {
520 let mut sched = create_sched();
521 let (_inspector, sme_inspect) = sme_inspect();
522 let req = sched
523 .enqueue_scan_to_discover(passive_discovery_scan(10))
524 .expect("expected a ScanRequest");
525 let txn_id = req.txn_id;
526
527 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
528 let ie_marker1 = &[0xdd, 0x07, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee];
530 bss.ies.extend_from_slice(ie_marker1);
531 sched
532 .on_mlme_scan_result(fidl_mlme::ScanResult {
533 txn_id,
534 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
535 bss,
536 })
537 .expect("expect scan result received");
538
539 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
540 let ie_marker2 = &[0xdd, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
542 bss.ies.extend_from_slice(ie_marker2);
543 sched
544 .on_mlme_scan_result(fidl_mlme::ScanResult {
545 txn_id,
546 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
547 bss,
548 })
549 .expect("expect scan result received");
550 let (scan_end, mlme_req) = assert_matches!(
551 sched.on_mlme_scan_end(
552 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
553 &sme_inspect,
554 ),
555 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
556 );
557 assert!(mlme_req.is_none());
558 let (tokens, bss_description_list) = assert_matches!(
559 scan_end,
560 ScanEnd {
561 tokens,
562 result_code: fidl_mlme::ScanResultCode::Success,
563 bss_description_list
564 } => (tokens, bss_description_list),
565 "expected discovery scan to be completed successfully"
566 );
567 assert_eq!(vec![10], tokens);
568
569 assert_eq!(bss_description_list.len(), 1);
570 assert!(slice_contains(bss_description_list[0].ies(), ie_marker1));
572 assert!(slice_contains(bss_description_list[0].ies(), ie_marker2));
573 }
574
575 fn slice_contains(slice: &[u8], subslice: &[u8]) -> bool {
576 let re = {
578 let mut re_string = String::with_capacity(6 + subslice.len() * 4);
579 re_string += "(?-u:";
580 for b in subslice {
581 write!(re_string, "\\x{b:02X}").unwrap();
582 }
583 re_string += ")";
584 Regex::new(&re_string).unwrap()
585 };
586 re.is_match(slice)
587 }
588
589 #[test_case(&[1, 2, 3], &[] => true; "vacuous")]
590 #[test_case(&[1, 2, 3], &[1u8] => true; "one byte")]
591 #[test_case(&[1, 2, 3], &[2u8, 3] => true; "multiple bytes")]
592 #[test_case(&[1, 1, 1], &[1u8, 1] => true; "multiple matches")]
593 #[test_case(&[1, 2, 3], &[0u8] => false; "no match")]
594 #[test_case(&[1, 2, 3], &[1u8, 2, 3, 4] => false; "too large")]
595 #[test_case(&[0x87, 0x77, 0x78], &[0x77, 0x77] => false; "misaligned match")]
596 fn slice_contains_test(slice: &[u8], subslice: &[u8]) -> bool {
597 slice_contains(slice, subslice)
598 }
599
600 #[test]
601 fn test_passive_discovery_scan_args() {
602 let mut sched = create_sched();
603 let req = sched
604 .enqueue_scan_to_discover(passive_discovery_scan(10))
605 .expect("expected a ScanRequest");
606 assert_eq!(req.txn_id, 1);
607 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
608 assert_eq!(
609 req.channel_list.into_iter().collect::<HashSet<_>>(),
610 CANDIDATE_OPERATING_CHANNELS.iter().map(|c| c.primary).collect::<HashSet<_>>()
611 );
612 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
613 assert_eq!(req.probe_delay, 0);
614 assert_eq!(req.min_channel_time, 200);
615 assert_eq!(req.max_channel_time, 200);
616 }
617
618 #[test_case(true, HashSet::from([1, 36, 165]); "dfs_enabled")]
619 #[test_case(false, HashSet::from([1]); "dfs_disabled")]
620 fn test_active_discovery_scan_args_empty(dfs_supported: bool, expected_channels: HashSet<u8>) {
621 let device_info = device_info_with_channel(vec![1, 36, 165]);
622 let mut spectrum_management = fake_spectrum_management_support_empty();
623 if dfs_supported {
624 spectrum_management.dfs.get_or_insert_with(Default::default).supported = Some(true);
625 }
626 let mut sched: ScanScheduler<i32> =
627 ScanScheduler::new(Arc::new(device_info), spectrum_management);
628 let scan_cmd = DiscoveryScan::new(
629 10,
630 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
631 ssids: vec![],
632 channels: vec![],
633 }),
634 );
635 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
636
637 assert_eq!(req.txn_id, 1);
638 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
639 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), expected_channels);
640 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
641 assert_eq!(req.probe_delay, 5);
642 assert_eq!(req.min_channel_time, 75);
643 assert_eq!(req.max_channel_time, 75);
644 }
645
646 #[test]
647 fn test_active_discovery_scan_args_filled() {
648 let device_info = device_info_with_channel(vec![1, 36, 165]);
649 let mut sched: ScanScheduler<i32> =
650 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
651 let ssid1: Vec<u8> = Ssid::try_from("ssid1").unwrap().into();
652 let ssid2: Vec<u8> = Ssid::try_from("ssid2").unwrap().into();
653 let scan_cmd = DiscoveryScan::new(
654 10,
655 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
656 ssids: vec![ssid1.clone(), ssid2.clone()],
657 channels: vec![1, 20, 100],
659 }),
660 );
661 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
662
663 assert_eq!(req.txn_id, 1);
664 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
665 assert_eq!(req.channel_list, vec![1]);
666 assert_eq!(req.ssid_list, vec![ssid1, ssid2]);
667 assert_eq!(req.probe_delay, 5);
668 assert_eq!(req.min_channel_time, 75);
669 assert_eq!(req.max_channel_time, 75);
670 }
671
672 #[test]
673 fn test_discovery_scans_dedupe_single_group() {
674 let mut sched = create_sched();
675 let (_inspector, sme_inspect) = sme_inspect();
676
677 let mlme_req = sched
679 .enqueue_scan_to_discover(passive_discovery_scan(10))
680 .expect("expected a ScanRequest");
681 let txn_id = mlme_req.txn_id;
682
683 sched
685 .on_mlme_scan_result(fidl_mlme::ScanResult {
686 txn_id,
687 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
688 bss: fidl_common::BssDescription {
689 bssid: [1; 6],
690 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
691 },
692 })
693 .expect("expect scan result received");
694
695 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(20)).is_none());
698
699 sched
701 .on_mlme_scan_result(fidl_mlme::ScanResult {
702 txn_id,
703 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
704 bss: fidl_common::BssDescription {
705 bssid: [2; 6],
706 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
707 },
708 })
709 .expect("expect scan result received");
710 let (scan_end, mlme_req) = assert_matches!(
711 sched.on_mlme_scan_end(
712 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
713 &sme_inspect,
714 ),
715 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
716 );
717
718 assert!(mlme_req.is_none());
720
721 assert_discovery_scan_result(
723 scan_end,
724 vec![10, 20],
725 vec![Ssid::try_from("bar").unwrap(), Ssid::try_from("foo").unwrap()],
726 );
727 }
728
729 #[test]
730 fn test_discovery_scans_dedupe_multiple_groups() {
731 let mut sched = create_sched();
732 let (_inspector, sme_inspect) = sme_inspect();
733
734 let mlme_req = sched
736 .enqueue_scan_to_discover(passive_discovery_scan(10))
737 .expect("expected a ScanRequest");
738 let txn_id = mlme_req.txn_id;
739
740 let scan_cmd = DiscoveryScan::new(
742 20,
743 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
744 ssids: vec![],
745 channels: vec![],
746 }),
747 );
748 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
749
750 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(30)).is_none());
753
754 let scan_cmd = DiscoveryScan::new(
757 40,
758 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
759 ssids: vec![],
760 channels: vec![],
761 }),
762 );
763 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
764
765 sched
767 .on_mlme_scan_result(fidl_mlme::ScanResult {
768 txn_id,
769 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
770 bss: fidl_common::BssDescription {
771 bssid: [1; 6],
772 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
773 },
774 })
775 .expect("expect scan result received");
776 let (scan_end, mlme_req) = assert_matches!(
777 sched.on_mlme_scan_end(
778 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
779 &sme_inspect,
780 ),
781 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
782 );
783
784 assert_discovery_scan_result(scan_end, vec![10, 30], vec![Ssid::try_from("foo").unwrap()]);
786
787 assert!(mlme_req.is_some());
789 let mlme_req = mlme_req.unwrap();
790 assert_eq!(mlme_req.scan_type, fidl_mlme::ScanTypes::Active);
791 let txn_id = mlme_req.txn_id;
792
793 sched
795 .on_mlme_scan_result(fidl_mlme::ScanResult {
796 txn_id,
797 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
798 bss: fidl_common::BssDescription {
799 bssid: [2; 6],
800 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
801 },
802 })
803 .expect("expect scan result received");
804 let (scan_end, mlme_req) = assert_matches!(
805 sched.on_mlme_scan_end(
806 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
807 &sme_inspect,
808 ),
809 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
810 );
811
812 assert_discovery_scan_result(scan_end, vec![20, 40], vec![Ssid::try_from("bar").unwrap()]);
814
815 assert!(mlme_req.is_none());
817 }
818
819 #[test]
820 fn test_discovery_scan_result_wrong_txn_id() {
821 let mut sched = create_sched();
822
823 let mlme_req = sched
825 .enqueue_scan_to_discover(passive_discovery_scan(10))
826 .expect("expected a ScanRequest");
827 let txn_id = mlme_req.txn_id;
828
829 assert_matches!(
831 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
832 txn_id: txn_id + 1,
833 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
834 bss: fidl_common::BssDescription {
835 bssid: [1; 6],
836 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
837 },
838 },),
839 Err(Error::ScanResultWrongTxnId)
840 );
841 }
842
843 #[test]
844 fn test_discovery_scan_result_not_scanning() {
845 let mut sched = create_sched();
846 assert_matches!(
847 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
848 txn_id: 0,
849 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
850 bss: fidl_common::BssDescription {
851 bssid: [1; 6],
852 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
853 },
854 },),
855 Err(Error::ScanResultNotScanning)
856 );
857 }
858
859 #[test]
860 fn test_discovery_scan_end_wrong_txn_id() {
861 let mut sched = create_sched();
862 let (_inspector, sme_inspect) = sme_inspect();
863
864 let mlme_req = sched
866 .enqueue_scan_to_discover(passive_discovery_scan(10))
867 .expect("expected a ScanRequest");
868 let txn_id = mlme_req.txn_id;
869
870 assert_matches!(
871 sched.on_mlme_scan_end(
872 fidl_mlme::ScanEnd { txn_id: txn_id + 1, code: fidl_mlme::ScanResultCode::Success },
873 &sme_inspect,
874 ),
875 Err(Error::ScanEndWrongTxnId)
876 );
877 }
878
879 #[test]
880 fn test_discovery_scan_end_not_scanning() {
881 let mut sched = create_sched();
882 let (_inspector, sme_inspect) = sme_inspect();
883 assert_matches!(
884 sched.on_mlme_scan_end(
885 fidl_mlme::ScanEnd { txn_id: 0, code: fidl_mlme::ScanResultCode::Success },
886 &sme_inspect,
887 ),
888 Err(Error::ScanEndNotScanning)
889 );
890 }
891
892 fn assert_discovery_scan_result(
893 scan_end: ScanEnd<i32>,
894 expected_tokens: Vec<i32>,
895 expected_ssids: Vec<Ssid>,
896 ) {
897 let (tokens, bss_description_list) = assert_matches!(
898 scan_end,
899 ScanEnd {
900 tokens,
901 result_code: fidl_mlme::ScanResultCode::Success,
902 bss_description_list
903 } => (tokens, bss_description_list),
904 "expected discovery scan to be completed successfully"
905 );
906 assert_eq!(tokens, expected_tokens);
907 let mut ssid_list =
908 bss_description_list.into_iter().map(|bss| bss.ssid.clone()).collect::<Vec<_>>();
909 ssid_list.sort();
910 assert_eq!(ssid_list, expected_ssids);
911 }
912
913 fn create_sched() -> ScanScheduler<i32> {
914 ScanScheduler::new(
915 Arc::new(test_utils::fake_device_info(*CLIENT_ADDR)),
916 fake_spectrum_management_support_empty(),
917 )
918 }
919
920 fn device_info_with_channel(operating_channels: Vec<u8>) -> fidl_mlme::DeviceInfo {
921 fidl_mlme::DeviceInfo {
922 bands: vec![fidl_mlme::BandCapability {
923 operating_channels,
924 ..fake_5ghz_band_capability()
925 }],
926 ..test_utils::fake_device_info(*CLIENT_ADDR)
927 }
928 }
929
930 fn sme_inspect() -> (Inspector, Arc<inspect::SmeTree>) {
931 let inspector = Inspector::default();
932 let sme_inspect = Arc::new(inspect::SmeTree::new(
933 inspector.clone(),
934 inspector.root().create_child("usme"),
935 &test_utils::fake_device_info([1u8; 6].into()),
936 &fake_spectrum_management_support_empty(),
937 ));
938 (inspector, sme_inspect)
939 }
940}