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