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,
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.dfs.supported;
314 };
315 true
316 })
317 .filter(|channel| {
318 if !requested_channels.is_empty() {
321 return requested_channels.contains(&channel.primary);
322 }
323 true
324 })
325 .map(|channel| channel.primary)
326 .collect();
327
328 if channels.is_empty() {
329 if !requested_channels.is_empty() {
330 warn!("All channels are filtered out. Requested channels: {:?}", requested_channels);
331 } else {
332 warn!("All channels are filtered out.");
333 };
334 }
335
336 channels
337}
338
339static CANDIDATE_OPERATING_CHANNELS: LazyLock<&'static [Channel]> = LazyLock::new(|| {
343 #[rustfmt::skip]
344 let channels = vec![
345 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
347 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108,
349 112, 116, 120, 124, 128, 132, 136, 140, 144,
350 149, 153, 157, 161, 165,
351 ];
352
353 channels.iter().map(|primary| Channel::new(*primary, Cbw::Cbw20)).collect::<Vec<_>>().leak()
354});
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::test_utils;
360 use assert_matches::assert_matches;
361 use fuchsia_inspect::Inspector;
362
363 use ieee80211::MacAddr;
364 use regex::bytes::Regex;
365 use std::fmt::Write;
366 use std::sync::LazyLock;
367 use test_case::test_case;
368 use wlan_common::test_utils::fake_capabilities::fake_5ghz_band_capability;
369 use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
370 use wlan_common::{fake_bss_description, fake_fidl_bss_description};
371
372 static CLIENT_ADDR: LazyLock<MacAddr> =
373 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
374
375 fn passive_discovery_scan(token: i32) -> DiscoveryScan<i32> {
376 DiscoveryScan::new(token, fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {}))
377 }
378
379 #[test]
380 fn discovery_scan() {
381 let mut sched = create_sched();
382 let (_inspector, sme_inspect) = sme_inspect();
383 let req = sched
384 .enqueue_scan_to_discover(passive_discovery_scan(10))
385 .expect("expected a ScanRequest");
386 let txn_id = req.txn_id;
387 sched
388 .on_mlme_scan_result(fidl_mlme::ScanResult {
389 txn_id,
390 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
391 bss: fidl_common::BssDescription {
392 bssid: [1; 6],
393 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
394 },
395 })
396 .expect("expect scan result received");
397 assert_matches!(
398 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
399 txn_id: txn_id + 100, timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
401 bss: fidl_common::BssDescription {
402 bssid: [2; 6],
403 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
404 },
405 },),
406 Err(Error::ScanResultWrongTxnId)
407 );
408 sched
409 .on_mlme_scan_result(fidl_mlme::ScanResult {
410 txn_id,
411 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
412 bss: fidl_common::BssDescription {
413 bssid: [3; 6],
414 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("qux").unwrap())
415 },
416 })
417 .expect("expect scan result received");
418 let (scan_end, mlme_req) = assert_matches!(
419 sched.on_mlme_scan_end(
420 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
421 &sme_inspect,
422 ),
423 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
424 );
425 assert!(mlme_req.is_none());
426 let (tokens, bss_description_list) = assert_matches!(
427 scan_end,
428 ScanEnd {
429 tokens,
430 result_code: fidl_mlme::ScanResultCode::Success,
431 bss_description_list
432 } => (tokens, bss_description_list),
433 "expected discovery scan to be completed successfully"
434 );
435 assert_eq!(vec![10], tokens);
436 let mut ssid_list =
437 bss_description_list.into_iter().map(|bss| bss.ssid).collect::<Vec<_>>();
438 ssid_list.sort();
439 assert_eq!(vec![Ssid::try_from("foo").unwrap(), Ssid::try_from("qux").unwrap()], ssid_list);
440 }
441
442 #[test_case(vec![
443 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap()),
444 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap()),
445 ], vec![fake_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap())] ;
446 "when latest BSS Description is new")]
447 #[test_case(vec![
448 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20)),
449 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(165, Cbw::Cbw20)),
450 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20))] ;
451 "when strong signal is first")]
452 #[test_case(vec![
453 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
454 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
455 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
456 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
457 "when strong signal is middle")]
458 #[test_case(vec![
459 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
460 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
461 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
462 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
463 "when strong signal is last")]
464 #[test_case(vec![
465 fake_fidl_bss_description!(Open, rssi_dbm: -84, ssid: Ssid::try_from("bar").unwrap(),
466 channel: Channel::new(149, Cbw::Cbw20)),
467 fake_fidl_bss_description!(Open, rssi_dbm: -36, ssid: Ssid::try_from("bar").unwrap(),
468 channel: Channel::new(165, Cbw::Cbw20)),
469 fake_fidl_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
470 channel: Channel::new(165, Cbw::Cbw20)),
471 ], vec![fake_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
472 channel: Channel::new(165, Cbw::Cbw20))];
473 "overwrite latest chosen channel")]
474 fn deduplicate_by_bssid(
475 bss_description_list_from_mlme: Vec<fidl_common::BssDescription>,
476 returned_bss_description_list: Vec<BssDescription>,
477 ) {
478 let mut sched = create_sched();
479 let (_inspector, sme_inspect) = sme_inspect();
480 let req = sched
481 .enqueue_scan_to_discover(passive_discovery_scan(10))
482 .expect("expected a ScanRequest");
483 let txn_id = req.txn_id;
484 for bss in bss_description_list_from_mlme {
485 sched
486 .on_mlme_scan_result(fidl_mlme::ScanResult {
487 txn_id,
488 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
489 bss,
490 })
491 .expect("expect scan result received");
492 }
493 let (scan_end, mlme_req) = assert_matches!(
494 sched.on_mlme_scan_end(
495 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
496 &sme_inspect,
497 ),
498 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
499 );
500 assert!(mlme_req.is_none());
501 let (tokens, bss_description_list) = assert_matches!(
502 scan_end,
503 ScanEnd {
504 tokens,
505 result_code: fidl_mlme::ScanResultCode::Success,
506 bss_description_list
507 } => (tokens, bss_description_list),
508 "expected discovery scan to be completed successfully"
509 );
510 assert_eq!(vec![10], tokens);
511 assert_eq!(bss_description_list, returned_bss_description_list);
512 }
513
514 #[test]
515 fn discovery_scan_merge_ies() {
516 let mut sched = create_sched();
517 let (_inspector, sme_inspect) = sme_inspect();
518 let req = sched
519 .enqueue_scan_to_discover(passive_discovery_scan(10))
520 .expect("expected a ScanRequest");
521 let txn_id = req.txn_id;
522
523 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
524 let ie_marker1 = &[0xdd, 0x07, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee];
526 bss.ies.extend_from_slice(ie_marker1);
527 sched
528 .on_mlme_scan_result(fidl_mlme::ScanResult {
529 txn_id,
530 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
531 bss,
532 })
533 .expect("expect scan result received");
534
535 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
536 let ie_marker2 = &[0xdd, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
538 bss.ies.extend_from_slice(ie_marker2);
539 sched
540 .on_mlme_scan_result(fidl_mlme::ScanResult {
541 txn_id,
542 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
543 bss,
544 })
545 .expect("expect scan result received");
546 let (scan_end, mlme_req) = assert_matches!(
547 sched.on_mlme_scan_end(
548 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
549 &sme_inspect,
550 ),
551 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
552 );
553 assert!(mlme_req.is_none());
554 let (tokens, bss_description_list) = assert_matches!(
555 scan_end,
556 ScanEnd {
557 tokens,
558 result_code: fidl_mlme::ScanResultCode::Success,
559 bss_description_list
560 } => (tokens, bss_description_list),
561 "expected discovery scan to be completed successfully"
562 );
563 assert_eq!(vec![10], tokens);
564
565 assert_eq!(bss_description_list.len(), 1);
566 assert!(slice_contains(bss_description_list[0].ies(), ie_marker1));
568 assert!(slice_contains(bss_description_list[0].ies(), ie_marker2));
569 }
570
571 fn slice_contains(slice: &[u8], subslice: &[u8]) -> bool {
572 let re = {
574 let mut re_string = String::with_capacity(6 + subslice.len() * 4);
575 re_string += "(?-u:";
576 for b in subslice {
577 write!(re_string, "\\x{b:02X}").unwrap();
578 }
579 re_string += ")";
580 Regex::new(&re_string).unwrap()
581 };
582 re.is_match(slice)
583 }
584
585 #[test_case(&[1, 2, 3], &[] => true; "vacuous")]
586 #[test_case(&[1, 2, 3], &[1u8] => true; "one byte")]
587 #[test_case(&[1, 2, 3], &[2u8, 3] => true; "multiple bytes")]
588 #[test_case(&[1, 1, 1], &[1u8, 1] => true; "multiple matches")]
589 #[test_case(&[1, 2, 3], &[0u8] => false; "no match")]
590 #[test_case(&[1, 2, 3], &[1u8, 2, 3, 4] => false; "too large")]
591 #[test_case(&[0x87, 0x77, 0x78], &[0x77, 0x77] => false; "misaligned match")]
592 fn slice_contains_test(slice: &[u8], subslice: &[u8]) -> bool {
593 slice_contains(slice, subslice)
594 }
595
596 #[test]
597 fn test_passive_discovery_scan_args() {
598 let mut sched = create_sched();
599 let req = sched
600 .enqueue_scan_to_discover(passive_discovery_scan(10))
601 .expect("expected a ScanRequest");
602 assert_eq!(req.txn_id, 1);
603 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
604 assert_eq!(
605 req.channel_list.into_iter().collect::<HashSet<_>>(),
606 CANDIDATE_OPERATING_CHANNELS.iter().map(|c| c.primary).collect::<HashSet<_>>()
607 );
608 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
609 assert_eq!(req.probe_delay, 0);
610 assert_eq!(req.min_channel_time, 200);
611 assert_eq!(req.max_channel_time, 200);
612 }
613
614 #[test_case(true, HashSet::from([1, 36, 165]); "dfs_enabled")]
615 #[test_case(false, HashSet::from([1]); "dfs_disabled")]
616 fn test_active_discovery_scan_args_empty(dfs_supported: bool, expected_channels: HashSet<u8>) {
617 let device_info = device_info_with_channel(vec![1, 36, 165]);
618 let mut spectrum_management = fake_spectrum_management_support_empty();
619 if dfs_supported {
620 spectrum_management.dfs.supported = true;
621 }
622 let mut sched: ScanScheduler<i32> =
623 ScanScheduler::new(Arc::new(device_info), spectrum_management);
624 let scan_cmd = DiscoveryScan::new(
625 10,
626 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
627 ssids: vec![],
628 channels: vec![],
629 }),
630 );
631 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
632
633 assert_eq!(req.txn_id, 1);
634 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
635 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), expected_channels);
636 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
637 assert_eq!(req.probe_delay, 5);
638 assert_eq!(req.min_channel_time, 75);
639 assert_eq!(req.max_channel_time, 75);
640 }
641
642 #[test]
643 fn test_active_discovery_scan_args_filled() {
644 let device_info = device_info_with_channel(vec![1, 36, 165]);
645 let mut sched: ScanScheduler<i32> =
646 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
647 let ssid1: Vec<u8> = Ssid::try_from("ssid1").unwrap().into();
648 let ssid2: Vec<u8> = Ssid::try_from("ssid2").unwrap().into();
649 let scan_cmd = DiscoveryScan::new(
650 10,
651 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
652 ssids: vec![ssid1.clone(), ssid2.clone()],
653 channels: vec![1, 20, 100],
655 }),
656 );
657 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
658
659 assert_eq!(req.txn_id, 1);
660 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
661 assert_eq!(req.channel_list, vec![1]);
662 assert_eq!(req.ssid_list, vec![ssid1, ssid2]);
663 assert_eq!(req.probe_delay, 5);
664 assert_eq!(req.min_channel_time, 75);
665 assert_eq!(req.max_channel_time, 75);
666 }
667
668 #[test]
669 fn test_discovery_scans_dedupe_single_group() {
670 let mut sched = create_sched();
671 let (_inspector, sme_inspect) = sme_inspect();
672
673 let mlme_req = sched
675 .enqueue_scan_to_discover(passive_discovery_scan(10))
676 .expect("expected a ScanRequest");
677 let txn_id = mlme_req.txn_id;
678
679 sched
681 .on_mlme_scan_result(fidl_mlme::ScanResult {
682 txn_id,
683 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
684 bss: fidl_common::BssDescription {
685 bssid: [1; 6],
686 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
687 },
688 })
689 .expect("expect scan result received");
690
691 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(20)).is_none());
694
695 sched
697 .on_mlme_scan_result(fidl_mlme::ScanResult {
698 txn_id,
699 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
700 bss: fidl_common::BssDescription {
701 bssid: [2; 6],
702 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
703 },
704 })
705 .expect("expect scan result received");
706 let (scan_end, mlme_req) = assert_matches!(
707 sched.on_mlme_scan_end(
708 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
709 &sme_inspect,
710 ),
711 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
712 );
713
714 assert!(mlme_req.is_none());
716
717 assert_discovery_scan_result(
719 scan_end,
720 vec![10, 20],
721 vec![Ssid::try_from("bar").unwrap(), Ssid::try_from("foo").unwrap()],
722 );
723 }
724
725 #[test]
726 fn test_discovery_scans_dedupe_multiple_groups() {
727 let mut sched = create_sched();
728 let (_inspector, sme_inspect) = sme_inspect();
729
730 let mlme_req = sched
732 .enqueue_scan_to_discover(passive_discovery_scan(10))
733 .expect("expected a ScanRequest");
734 let txn_id = mlme_req.txn_id;
735
736 let scan_cmd = DiscoveryScan::new(
738 20,
739 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
740 ssids: vec![],
741 channels: vec![],
742 }),
743 );
744 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
745
746 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(30)).is_none());
749
750 let scan_cmd = DiscoveryScan::new(
753 40,
754 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
755 ssids: vec![],
756 channels: vec![],
757 }),
758 );
759 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
760
761 sched
763 .on_mlme_scan_result(fidl_mlme::ScanResult {
764 txn_id,
765 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
766 bss: fidl_common::BssDescription {
767 bssid: [1; 6],
768 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
769 },
770 })
771 .expect("expect scan result received");
772 let (scan_end, mlme_req) = assert_matches!(
773 sched.on_mlme_scan_end(
774 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
775 &sme_inspect,
776 ),
777 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
778 );
779
780 assert_discovery_scan_result(scan_end, vec![10, 30], vec![Ssid::try_from("foo").unwrap()]);
782
783 assert!(mlme_req.is_some());
785 let mlme_req = mlme_req.unwrap();
786 assert_eq!(mlme_req.scan_type, fidl_mlme::ScanTypes::Active);
787 let txn_id = mlme_req.txn_id;
788
789 sched
791 .on_mlme_scan_result(fidl_mlme::ScanResult {
792 txn_id,
793 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
794 bss: fidl_common::BssDescription {
795 bssid: [2; 6],
796 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
797 },
798 })
799 .expect("expect scan result received");
800 let (scan_end, mlme_req) = assert_matches!(
801 sched.on_mlme_scan_end(
802 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
803 &sme_inspect,
804 ),
805 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
806 );
807
808 assert_discovery_scan_result(scan_end, vec![20, 40], vec![Ssid::try_from("bar").unwrap()]);
810
811 assert!(mlme_req.is_none());
813 }
814
815 #[test]
816 fn test_discovery_scan_result_wrong_txn_id() {
817 let mut sched = create_sched();
818
819 let mlme_req = sched
821 .enqueue_scan_to_discover(passive_discovery_scan(10))
822 .expect("expected a ScanRequest");
823 let txn_id = mlme_req.txn_id;
824
825 assert_matches!(
827 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
828 txn_id: txn_id + 1,
829 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
830 bss: fidl_common::BssDescription {
831 bssid: [1; 6],
832 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
833 },
834 },),
835 Err(Error::ScanResultWrongTxnId)
836 );
837 }
838
839 #[test]
840 fn test_discovery_scan_result_not_scanning() {
841 let mut sched = create_sched();
842 assert_matches!(
843 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
844 txn_id: 0,
845 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
846 bss: fidl_common::BssDescription {
847 bssid: [1; 6],
848 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
849 },
850 },),
851 Err(Error::ScanResultNotScanning)
852 );
853 }
854
855 #[test]
856 fn test_discovery_scan_end_wrong_txn_id() {
857 let mut sched = create_sched();
858 let (_inspector, sme_inspect) = sme_inspect();
859
860 let mlme_req = sched
862 .enqueue_scan_to_discover(passive_discovery_scan(10))
863 .expect("expected a ScanRequest");
864 let txn_id = mlme_req.txn_id;
865
866 assert_matches!(
867 sched.on_mlme_scan_end(
868 fidl_mlme::ScanEnd { txn_id: txn_id + 1, code: fidl_mlme::ScanResultCode::Success },
869 &sme_inspect,
870 ),
871 Err(Error::ScanEndWrongTxnId)
872 );
873 }
874
875 #[test]
876 fn test_discovery_scan_end_not_scanning() {
877 let mut sched = create_sched();
878 let (_inspector, sme_inspect) = sme_inspect();
879 assert_matches!(
880 sched.on_mlme_scan_end(
881 fidl_mlme::ScanEnd { txn_id: 0, code: fidl_mlme::ScanResultCode::Success },
882 &sme_inspect,
883 ),
884 Err(Error::ScanEndNotScanning)
885 );
886 }
887
888 fn assert_discovery_scan_result(
889 scan_end: ScanEnd<i32>,
890 expected_tokens: Vec<i32>,
891 expected_ssids: Vec<Ssid>,
892 ) {
893 let (tokens, bss_description_list) = assert_matches!(
894 scan_end,
895 ScanEnd {
896 tokens,
897 result_code: fidl_mlme::ScanResultCode::Success,
898 bss_description_list
899 } => (tokens, bss_description_list),
900 "expected discovery scan to be completed successfully"
901 );
902 assert_eq!(tokens, expected_tokens);
903 let mut ssid_list =
904 bss_description_list.into_iter().map(|bss| bss.ssid.clone()).collect::<Vec<_>>();
905 ssid_list.sort();
906 assert_eq!(ssid_list, expected_ssids);
907 }
908
909 fn create_sched() -> ScanScheduler<i32> {
910 ScanScheduler::new(
911 Arc::new(test_utils::fake_device_info(*CLIENT_ADDR)),
912 fake_spectrum_management_support_empty(),
913 )
914 }
915
916 fn device_info_with_channel(operating_channels: Vec<u8>) -> fidl_mlme::DeviceInfo {
917 fidl_mlme::DeviceInfo {
918 bands: vec![fidl_mlme::BandCapability {
919 operating_channels,
920 ..fake_5ghz_band_capability()
921 }],
922 ..test_utils::fake_device_info(*CLIENT_ADDR)
923 }
924 }
925
926 fn sme_inspect() -> (Inspector, Arc<inspect::SmeTree>) {
927 let inspector = Inspector::default();
928 let sme_inspect = Arc::new(inspect::SmeTree::new(
929 inspector.clone(),
930 inspector.root().create_child("usme"),
931 &test_utils::fake_device_info([1u8; 6].into()),
932 &fake_spectrum_management_support_empty(),
933 ));
934 (inspector, sme_inspect)
935 }
936}