1use crate::client::inspect;
6use crate::responder::Responder;
7use crate::{Error, MlmeRequest, MlmeSink};
8use fidl_fuchsia_wlan_common as fidl_common;
9use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
10use fidl_fuchsia_wlan_mlme as fidl_mlme;
11use fidl_fuchsia_wlan_sme as fidl_sme;
12use fuchsia_inspect::NumericProperty;
13use futures::channel::mpsc;
14use ieee80211::{Bssid, Ssid};
15use log::warn;
16use std::collections::{HashMap, HashSet, hash_map};
17use std::mem;
18use std::sync::{Arc, LazyLock};
19use wlan_common::bss::BssDescription;
20use wlan_common::channel::{Cbw, Channel};
21use wlan_common::ie::IesMerger;
22
23type ScanTxnId = u64;
24
25const PASSIVE_SCAN_CHANNEL_MS: u32 = 200;
26const ACTIVE_SCAN_PROBE_DELAY_MS: u32 = 5;
27const ACTIVE_SCAN_CHANNEL_MS: u32 = 75;
28
29#[derive(Debug, PartialEq)]
31pub struct DiscoveryScan<T> {
32 tokens: Vec<T>,
33 scan_request: fidl_sme::ScanRequest,
34}
35
36impl<T> DiscoveryScan<T> {
37 pub fn new(token: T, scan_request: fidl_sme::ScanRequest) -> Self {
38 Self { tokens: vec![token], scan_request }
39 }
40
41 pub fn matches(&self, scan: &DiscoveryScan<T>) -> bool {
42 self.scan_request == scan.scan_request
43 }
44
45 pub fn merges(&mut self, mut scan: DiscoveryScan<T>) {
46 self.tokens.append(&mut scan.tokens)
47 }
48}
49pub struct ScheduledScanReceiver {
51 scan_results_receiver: mpsc::UnboundedReceiver<fidl::Vmo>,
52 pub(crate) txn_id: ScanTxnId,
53 mlme_sink: MlmeSink,
54 stopped_by_firmware: bool,
55}
56impl ScheduledScanReceiver {
57 fn new(
58 scan_results_receiver: mpsc::UnboundedReceiver<fidl::Vmo>,
59 txn_id: ScanTxnId,
60 mlme_sink: MlmeSink,
61 ) -> Self {
62 Self { scan_results_receiver, txn_id, mlme_sink, stopped_by_firmware: false }
63 }
64}
65impl futures::stream::Stream for ScheduledScanReceiver {
66 type Item = fidl::Vmo;
67
68 fn poll_next(
69 mut self: std::pin::Pin<&mut Self>,
70 cx: &mut std::task::Context<'_>,
71 ) -> std::task::Poll<Option<Self::Item>> {
72 let poll_result = std::pin::Pin::new(&mut self.scan_results_receiver).poll_next(cx);
73 if let std::task::Poll::Ready(None) = poll_result {
74 self.stopped_by_firmware = true;
75 }
76 poll_result
77 }
78}
79impl Drop for ScheduledScanReceiver {
80 fn drop(&mut self) {
81 if !self.stopped_by_firmware {
85 let mlme_req = fidl_mlme::MlmeStopScheduledScanRequest { txn_id: self.txn_id };
86 let (responder, _) = Responder::new();
87 self.mlme_sink.send(MlmeRequest::StopScheduledScan(mlme_req, responder));
88 }
89 }
90}
91
92pub(crate) struct ScheduledScanState {
95 scan_results_sender: mpsc::UnboundedSender<fidl::Vmo>,
96 bss_map: std::collections::HashMap<
97 Bssid,
98 (fidl_ieee80211::BssDescription, wlan_common::ie::IesMerger),
99 >,
100}
101impl ScheduledScanState {
102 fn new(scan_results_sender: mpsc::UnboundedSender<fidl::Vmo>) -> Self {
103 Self { scan_results_sender, bss_map: HashMap::new() }
104 }
105}
106pub struct ScanScheduler<T> {
107 current: ScanState<T>,
110 pending_discovery: Vec<DiscoveryScan<T>>,
112 device_info: Arc<fidl_mlme::DeviceInfo>,
113 spectrum_management_support: fidl_common::SpectrumManagementSupport,
114 pub(crate) scheduled_scan_receivers: HashMap<ScanTxnId, ScheduledScanState>,
116 last_mlme_txn_id: ScanTxnId,
117}
118
119#[derive(Debug)]
120enum ScanState<T> {
121 NotScanning,
122 ScanningToDiscover {
123 cmd: DiscoveryScan<T>,
124 mlme_txn_id: ScanTxnId,
125 bss_map: HashMap<Bssid, (fidl_ieee80211::BssDescription, IesMerger)>,
126 },
127}
128
129#[derive(Debug)]
130pub struct ScanEnd<T> {
131 pub tokens: Vec<T>,
132 pub result_code: fidl_mlme::ScanResultCode,
133 pub bss_description_list: Vec<BssDescription>,
134}
135
136impl<T> ScanScheduler<T> {
137 pub fn new(
138 device_info: Arc<fidl_mlme::DeviceInfo>,
139 spectrum_management_support: fidl_common::SpectrumManagementSupport,
140 ) -> Self {
141 ScanScheduler {
142 current: ScanState::NotScanning,
143 pending_discovery: Vec::new(),
144 device_info,
145 spectrum_management_support,
146 scheduled_scan_receivers: HashMap::new(),
147 last_mlme_txn_id: 0,
148 }
149 }
150
151 pub fn enqueue_scan_to_discover(
155 &mut self,
156 s: DiscoveryScan<T>,
157 ) -> Option<fidl_mlme::ScanRequest> {
158 if let ScanState::ScanningToDiscover { cmd, .. } = &mut self.current
159 && cmd.matches(&s)
160 {
161 cmd.merges(s);
162 return None;
163 }
164 if let Some(scan_cmd) = self.pending_discovery.iter_mut().find(|cmd| cmd.matches(&s)) {
165 scan_cmd.merges(s);
166 return None;
167 }
168 self.pending_discovery.push(s);
169 self.start_next_scan()
170 }
171
172 fn get_next_mlme_txn_id(&mut self) -> ScanTxnId {
174 self.last_mlme_txn_id += 1;
175 self.last_mlme_txn_id
176 }
177
178 pub(crate) fn start_scheduled_scan(
179 &mut self,
180 req: fidl_common::ScheduledScanRequest,
181 mlme_sink: MlmeSink,
182 responder: Responder<Result<(), i32>>,
183 ) -> ScheduledScanReceiver {
184 let txn_id = self.get_next_mlme_txn_id();
186 let mlme_req = fidl_mlme::MlmeStartScheduledScanRequest { txn_id, req };
187 mlme_sink.send(MlmeRequest::StartScheduledScan(mlme_req, responder));
188
189 let (sender, receiver) = mpsc::unbounded();
191 let _ = self.scheduled_scan_receivers.insert(txn_id, ScheduledScanState::new(sender));
192 ScheduledScanReceiver::new(receiver, txn_id, mlme_sink)
193 }
194
195 pub fn on_mlme_scan_result(&mut self, msg: fidl_mlme::ScanResult) -> Result<(), Error> {
197 if let Some(session) = self.scheduled_scan_receivers.get_mut(&msg.txn_id) {
199 maybe_insert_bss(&mut session.bss_map, msg.bss);
200 return Ok(());
201 }
202
203 match &mut self.current {
204 ScanState::NotScanning => Err(Error::ScanResultNotScanning),
205 ScanState::ScanningToDiscover { mlme_txn_id, .. } if *mlme_txn_id != msg.txn_id => {
206 Err(Error::ScanResultWrongTxnId)
207 }
208 ScanState::ScanningToDiscover { bss_map, .. } => {
209 maybe_insert_bss(bss_map, msg.bss);
210 Ok(())
211 }
212 }
213 }
214
215 pub(crate) fn on_scheduled_scan_matches_available(
216 &mut self,
217 txn_id: ScanTxnId,
218 sme_inspect: &Arc<inspect::SmeTree>,
219 cfg: &crate::client::ClientConfig,
220 device_info: &fidl_mlme::DeviceInfo,
221 security_support: &fidl_common::SecuritySupport,
222 ) {
223 if let Some(session) = self.scheduled_scan_receivers.get_mut(&txn_id) {
224 let bss_map = std::mem::take(&mut session.bss_map);
225 let bss_description_list = convert_bss_map(bss_map, None::<Ssid>, sme_inspect);
226 let results_fidl = bss_description_list
227 .into_iter()
228 .map(|bss_description| {
229 cfg.create_scan_result(
230 zx::MonotonicInstant::from_nanos(0),
232 bss_description,
233 device_info,
234 security_support,
235 )
236 })
237 .map(Into::into)
238 .collect::<Vec<_>>();
239
240 match wlan_common::scan::write_vmo(results_fidl) {
241 Ok(vmo) => {
242 let _ = session.scan_results_sender.unbounded_send(vmo);
243 }
244 Err(e) => {
245 log::error!("Failed to write VMO for sched scan results: {:?}", e);
246 }
247 }
248 }
249 }
250
251 pub(crate) fn on_scheduled_scan_stopped_by_firmware(&mut self, txn_id: ScanTxnId) {
252 let _ = self.scheduled_scan_receivers.remove(&txn_id);
253 }
254
255 pub fn on_mlme_scan_end(
258 &mut self,
259 msg: fidl_mlme::ScanEnd,
260 sme_inspect: &Arc<inspect::SmeTree>,
261 ) -> Result<(ScanEnd<T>, Option<fidl_mlme::ScanRequest>), Error> {
262 match mem::replace(&mut self.current, ScanState::NotScanning) {
263 ScanState::NotScanning => Err(Error::ScanEndNotScanning),
264 ScanState::ScanningToDiscover { mlme_txn_id, .. } if mlme_txn_id != msg.txn_id => {
265 Err(Error::ScanEndWrongTxnId)
266 }
267 ScanState::ScanningToDiscover { cmd, bss_map, .. } => {
268 let scan_end = ScanEnd {
269 tokens: cmd.tokens,
270 result_code: msg.code,
271 bss_description_list: convert_bss_map(bss_map, None::<Ssid>, sme_inspect),
272 };
273
274 let request = self.start_next_scan();
275 Ok((scan_end, request))
276 }
277 }
278 }
279
280 fn start_next_scan(&mut self) -> Option<fidl_mlme::ScanRequest> {
281 let has_pending = !self.pending_discovery.is_empty();
282 (matches!(self.current, ScanState::NotScanning) && has_pending).then(|| {
283 let txn_id = self.get_next_mlme_txn_id();
284 let scan_cmd = self.pending_discovery.remove(0);
285 let request = new_discovery_scan_request(
286 txn_id,
287 &scan_cmd,
288 &self.device_info,
289 self.spectrum_management_support.clone(),
290 );
291 self.current = ScanState::ScanningToDiscover {
292 cmd: scan_cmd,
293 mlme_txn_id: txn_id,
294 bss_map: HashMap::new(),
295 };
296 request
297 })
298 }
299}
300
301fn maybe_insert_bss(
302 bss_map: &mut HashMap<Bssid, (fidl_ieee80211::BssDescription, IesMerger)>,
303 mut fidl_bss: fidl_ieee80211::BssDescription,
304) {
305 let mut ies = vec![];
306 std::mem::swap(&mut ies, &mut fidl_bss.ies);
307
308 match bss_map.entry(Bssid::from(fidl_bss.bssid)) {
309 hash_map::Entry::Occupied(mut entry) => {
310 let (existing_bss, ies_merger) = entry.get_mut();
311
312 if (fidl_bss.channel.primary != existing_bss.channel.primary)
313 && (fidl_bss.rssi_dbm < existing_bss.rssi_dbm)
314 {
315 return;
317 }
318
319 ies_merger.merge(&ies[..]);
320 if ies_merger.buffer_overflow() {
321 warn!(
322 "Not merging some IEs due to running out of buffer. BSSID: {}",
323 Bssid::from(fidl_bss.bssid)
324 );
325 }
326 *existing_bss = fidl_bss;
327 }
328 hash_map::Entry::Vacant(entry) => {
329 let _ = entry.insert((fidl_bss, IesMerger::new(ies)));
330 }
331 }
332}
333
334fn convert_bss_map(
335 bss_map: HashMap<Bssid, (fidl_ieee80211::BssDescription, IesMerger)>,
336 ssid_selector: Option<Ssid>,
337 sme_inspect: &Arc<inspect::SmeTree>,
338) -> Vec<BssDescription> {
339 let bss_description_list =
340 bss_map.into_iter().filter_map(|(_bssid, (mut bss, mut ies_merger))| {
341 let _ = sme_inspect.scan_merge_ie_failures.add(ies_merger.merge_ie_failures() as u64);
342
343 let mut ies = ies_merger.finalize();
344 std::mem::swap(&mut ies, &mut bss.ies);
345 let bss: Option<BssDescription> = bss.try_into().ok();
346 if bss.is_none() {
347 let _ = sme_inspect.scan_discard_fidl_bss.add(1);
348 }
349 bss
350 });
351
352 match ssid_selector {
353 None => bss_description_list.collect(),
354 Some(ssid) => bss_description_list.filter(|v| v.ssid == ssid).collect(),
355 }
356}
357
358fn new_scan_request(
359 mlme_txn_id: ScanTxnId,
360 scan_request: fidl_sme::ScanRequest,
361 ssid_list: Vec<Ssid>,
362 device_info: &fidl_mlme::DeviceInfo,
363 spectrum_management_support: fidl_common::SpectrumManagementSupport,
364) -> fidl_mlme::ScanRequest {
365 let scan_req = fidl_mlme::ScanRequest {
366 txn_id: mlme_txn_id,
367 scan_type: fidl_mlme::ScanTypes::Passive,
368 probe_delay: 0,
369 channel_list: get_operating_channels_for_scan(
371 device_info,
372 spectrum_management_support,
373 &scan_request,
374 ),
375 ssid_list: ssid_list.into_iter().map(Ssid::into).collect(),
376 min_channel_time: PASSIVE_SCAN_CHANNEL_MS,
377 max_channel_time: PASSIVE_SCAN_CHANNEL_MS,
378 };
379 match scan_request {
380 fidl_sme::ScanRequest::Active(active_scan_params) => fidl_mlme::ScanRequest {
381 scan_type: fidl_mlme::ScanTypes::Active,
382 ssid_list: active_scan_params.ssids,
383 probe_delay: ACTIVE_SCAN_PROBE_DELAY_MS,
384 min_channel_time: ACTIVE_SCAN_CHANNEL_MS,
385 max_channel_time: ACTIVE_SCAN_CHANNEL_MS,
386 ..scan_req
387 },
388 fidl_sme::ScanRequest::Passive(_) => scan_req,
389 }
390}
391
392fn new_discovery_scan_request<T>(
393 mlme_txn_id: ScanTxnId,
394 discovery_scan: &DiscoveryScan<T>,
395 device_info: &fidl_mlme::DeviceInfo,
396 spectrum_management_support: fidl_common::SpectrumManagementSupport,
397) -> fidl_mlme::ScanRequest {
398 new_scan_request(
399 mlme_txn_id,
400 discovery_scan.scan_request.clone(),
401 vec![],
402 device_info,
403 spectrum_management_support,
404 )
405}
406
407fn get_operating_channels_for_scan(
422 device_info: &fidl_mlme::DeviceInfo,
423 spectrum_management_support: fidl_common::SpectrumManagementSupport,
424 scan_request: &fidl_sme::ScanRequest,
425) -> Vec<u8> {
426 let mut operating_channels: HashSet<u8> = HashSet::new();
427 for band in &device_info.bands {
428 operating_channels.extend(&band.operating_channels);
429 }
430
431 let requested_channels = match scan_request {
432 fidl_sme::ScanRequest::Active(options) => &options.channels[..],
433 fidl_sme::ScanRequest::Passive(options) => &options.channels[..],
434 };
435 let channels: Vec<u8> = CANDIDATE_OPERATING_CHANNELS
436 .iter()
437 .filter(|channel| operating_channels.contains(&channel.primary))
438 .filter(|channel| {
439 if let &fidl_sme::ScanRequest::Passive(_) = scan_request {
442 return true;
443 };
444 if channel.is_5ghz() {
445 return spectrum_management_support
446 .dfs
447 .as_ref()
448 .and_then(|dfs| dfs.supported)
449 .unwrap_or(false);
450 };
451 true
452 })
453 .filter(|channel| {
454 if !requested_channels.is_empty() {
456 return requested_channels.contains(&channel.primary);
457 }
458 true
459 })
460 .map(|channel| channel.primary)
461 .collect();
462
463 if channels.is_empty() {
464 if !requested_channels.is_empty() {
465 warn!("All channels are filtered out. Requested channels: {:?}", requested_channels);
466 } else {
467 warn!("All channels are filtered out.");
468 };
469 }
470
471 channels
472}
473
474static CANDIDATE_OPERATING_CHANNELS: LazyLock<&'static [Channel]> = LazyLock::new(|| {
478 #[rustfmt::skip]
479 let channels = vec![
480 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
482 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108,
484 112, 116, 120, 124, 128, 132, 136, 140, 144,
485 149, 153, 157, 161, 165,
486 ];
487
488 channels.iter().map(|primary| Channel::new(*primary, Cbw::Cbw20)).collect::<Vec<_>>().leak()
489});
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use crate::test_utils;
495 use assert_matches::assert_matches;
496 use fuchsia_inspect::Inspector;
497
498 use ieee80211::MacAddr;
499 use regex::bytes::Regex;
500 use std::fmt::Write;
501 use std::sync::LazyLock;
502 use test_case::test_case;
503 use wlan_common::test_utils::fake_capabilities::fake_5ghz_band_capability;
504 use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
505 use wlan_common::{fake_bss_description, fake_fidl_bss_description};
506
507 static CLIENT_ADDR: LazyLock<MacAddr> =
508 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
509
510 impl ScheduledScanReceiver {
511 pub(crate) fn try_next(&mut self) -> Result<Option<fidl::Vmo>, mpsc::TryRecvError> {
512 let res = self.scan_results_receiver.try_next();
513 if let Ok(None) = res {
514 self.stopped_by_firmware = true;
515 }
516 res
517 }
518 }
519
520 fn passive_discovery_scan(token: i32) -> DiscoveryScan<i32> {
521 DiscoveryScan::new(
522 token,
523 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] }),
524 )
525 }
526
527 #[test]
528 fn discovery_scan() {
529 let mut sched = create_sched();
530 let _next_txn_id = 0;
531 let (_inspector, sme_inspect) = sme_inspect();
532 let req = sched
533 .enqueue_scan_to_discover(passive_discovery_scan(10))
534 .expect("expected a ScanRequest");
535 let txn_id = req.txn_id;
536 sched
537 .on_mlme_scan_result(fidl_mlme::ScanResult {
538 txn_id,
539 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
540 bss: fidl_ieee80211::BssDescription {
541 bssid: [1; 6],
542 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
543 },
544 })
545 .expect("expect scan result received");
546 assert_matches!(
547 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
548 txn_id: txn_id + 100, timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
550 bss: fidl_ieee80211::BssDescription {
551 bssid: [2; 6],
552 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
553 },
554 },),
555 Err(Error::ScanResultWrongTxnId)
556 );
557 sched
558 .on_mlme_scan_result(fidl_mlme::ScanResult {
559 txn_id,
560 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
561 bss: fidl_ieee80211::BssDescription {
562 bssid: [3; 6],
563 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("qux").unwrap())
564 },
565 })
566 .expect("expect scan result received");
567 let (scan_end, mlme_req) = assert_matches!(
568 sched.on_mlme_scan_end(
569 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
570 &sme_inspect),
571 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
572 );
573 assert!(mlme_req.is_none());
574 let (tokens, bss_description_list) = assert_matches!(
575 scan_end,
576 ScanEnd {
577 tokens,
578 result_code: fidl_mlme::ScanResultCode::Success,
579 bss_description_list
580 } => (tokens, bss_description_list),
581 "expected discovery scan to be completed successfully"
582 );
583 assert_eq!(vec![10], tokens);
584 let mut ssid_list =
585 bss_description_list.into_iter().map(|bss| bss.ssid).collect::<Vec<_>>();
586 ssid_list.sort();
587 assert_eq!(vec![Ssid::try_from("foo").unwrap(), Ssid::try_from("qux").unwrap()], ssid_list);
588 }
589
590 #[test_case(vec![
591 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap()),
592 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap()),
593 ], vec![fake_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap())] ;
594 "when latest BSS Description is new")]
595 #[test_case(vec![
596 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20)),
597 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(165, Cbw::Cbw20)),
598 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Cbw::Cbw20))] ;
599 "when strong signal is first")]
600 #[test_case(vec![
601 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
602 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
603 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
604 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
605 "when strong signal is middle")]
606 #[test_case(vec![
607 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Cbw::Cbw20)),
608 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Cbw::Cbw20)),
609 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20)),
610 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Cbw::Cbw20))];
611 "when strong signal is last")]
612 #[test_case(vec![
613 fake_fidl_bss_description!(Open, rssi_dbm: -84, ssid: Ssid::try_from("bar").unwrap(),
614 channel: Channel::new(149, Cbw::Cbw20)),
615 fake_fidl_bss_description!(Open, rssi_dbm: -36, ssid: Ssid::try_from("bar").unwrap(),
616 channel: Channel::new(165, Cbw::Cbw20)),
617 fake_fidl_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
618 channel: Channel::new(165, Cbw::Cbw20)),
619 ], vec![fake_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
620 channel: Channel::new(165, Cbw::Cbw20))];
621 "overwrite latest chosen channel")]
622 fn deduplicate_by_bssid(
623 bss_description_list_from_mlme: Vec<fidl_ieee80211::BssDescription>,
624 returned_bss_description_list: Vec<BssDescription>,
625 ) {
626 let mut sched = create_sched();
627 let _next_txn_id = 0;
628 let (_inspector, sme_inspect) = sme_inspect();
629 let req = sched
630 .enqueue_scan_to_discover(passive_discovery_scan(10))
631 .expect("expected a ScanRequest");
632 let txn_id = req.txn_id;
633 for bss in bss_description_list_from_mlme {
634 sched
635 .on_mlme_scan_result(fidl_mlme::ScanResult {
636 txn_id,
637 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
638 bss,
639 })
640 .expect("expect scan result received");
641 }
642 let (scan_end, mlme_req) = assert_matches!(
643 sched.on_mlme_scan_end(
644 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
645 &sme_inspect),
646 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
647 );
648 assert!(mlme_req.is_none());
649 let (tokens, bss_description_list) = assert_matches!(
650 scan_end,
651 ScanEnd {
652 tokens,
653 result_code: fidl_mlme::ScanResultCode::Success,
654 bss_description_list
655 } => (tokens, bss_description_list),
656 "expected discovery scan to be completed successfully"
657 );
658 assert_eq!(vec![10], tokens);
659 assert_eq!(bss_description_list, returned_bss_description_list);
660 }
661
662 #[test]
663 fn discovery_scan_merge_ies() {
664 let mut sched = create_sched();
665 let _next_txn_id = 0;
666 let (_inspector, sme_inspect) = sme_inspect();
667 let req = sched
668 .enqueue_scan_to_discover(passive_discovery_scan(10))
669 .expect("expected a ScanRequest");
670 let txn_id = req.txn_id;
671
672 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
673 let ie_marker1 = &[0xdd, 0x07, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee];
675 bss.ies.extend_from_slice(ie_marker1);
676 sched
677 .on_mlme_scan_result(fidl_mlme::ScanResult {
678 txn_id,
679 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
680 bss,
681 })
682 .expect("expect scan result received");
683
684 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
685 let ie_marker2 = &[0xdd, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
687 bss.ies.extend_from_slice(ie_marker2);
688 sched
689 .on_mlme_scan_result(fidl_mlme::ScanResult {
690 txn_id,
691 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
692 bss,
693 })
694 .expect("expect scan result received");
695 let (scan_end, mlme_req) = assert_matches!(
696 sched.on_mlme_scan_end(
697 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
698 &sme_inspect),
699 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
700 );
701 assert!(mlme_req.is_none());
702 let (tokens, bss_description_list) = assert_matches!(
703 scan_end,
704 ScanEnd {
705 tokens,
706 result_code: fidl_mlme::ScanResultCode::Success,
707 bss_description_list
708 } => (tokens, bss_description_list),
709 "expected discovery scan to be completed successfully"
710 );
711 assert_eq!(vec![10], tokens);
712
713 assert_eq!(bss_description_list.len(), 1);
714 assert!(slice_contains(bss_description_list[0].ies(), ie_marker1));
716 assert!(slice_contains(bss_description_list[0].ies(), ie_marker2));
717 }
718
719 fn slice_contains(slice: &[u8], subslice: &[u8]) -> bool {
720 let re = {
722 let mut re_string = String::with_capacity(6 + subslice.len() * 4);
723 re_string += "(?-u:";
724 for b in subslice {
725 write!(re_string, "\\x{b:02X}").unwrap();
726 }
727 re_string += ")";
728 Regex::new(&re_string).unwrap()
729 };
730 re.is_match(slice)
731 }
732
733 #[test_case(&[1, 2, 3], &[] => true; "vacuous")]
734 #[test_case(&[1, 2, 3], &[1u8] => true; "one byte")]
735 #[test_case(&[1, 2, 3], &[2u8, 3] => true; "multiple bytes")]
736 #[test_case(&[1, 1, 1], &[1u8, 1] => true; "multiple matches")]
737 #[test_case(&[1, 2, 3], &[0u8] => false; "no match")]
738 #[test_case(&[1, 2, 3], &[1u8, 2, 3, 4] => false; "too large")]
739 #[test_case(&[0x87, 0x77, 0x78], &[0x77, 0x77] => false; "misaligned match")]
740 fn slice_contains_test(slice: &[u8], subslice: &[u8]) -> bool {
741 slice_contains(slice, subslice)
742 }
743
744 #[test]
745 fn test_passive_discovery_scan_args() {
746 let mut sched = create_sched();
747 let _next_txn_id = 0;
748 let req = sched
749 .enqueue_scan_to_discover(passive_discovery_scan(10))
750 .expect("expected a ScanRequest");
751 assert_eq!(req.txn_id, 1);
752 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
753 assert_eq!(
754 req.channel_list.into_iter().collect::<HashSet<_>>(),
755 CANDIDATE_OPERATING_CHANNELS.iter().map(|c| c.primary).collect::<HashSet<_>>()
756 );
757 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
758 assert_eq!(req.probe_delay, 0);
759 assert_eq!(req.min_channel_time, 200);
760 assert_eq!(req.max_channel_time, 200);
761 }
762
763 #[test_case(true, HashSet::from([1, 36, 165]); "dfs_enabled")]
764 #[test_case(false, HashSet::from([1]); "dfs_disabled")]
765 fn test_active_discovery_scan_args_empty(dfs_supported: bool, expected_channels: HashSet<u8>) {
766 let device_info = device_info_with_channel(vec![1, 36, 165]);
767 let mut spectrum_management = fake_spectrum_management_support_empty();
768 if dfs_supported {
769 spectrum_management.dfs.get_or_insert_with(Default::default).supported = Some(true);
770 }
771 let mut sched: ScanScheduler<i32> =
772 ScanScheduler::new(Arc::new(device_info), spectrum_management);
773 let _next_txn_id = 0;
774 let scan_cmd = DiscoveryScan::new(
775 10,
776 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
777 ssids: vec![],
778 channels: vec![],
779 }),
780 );
781 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
782
783 assert_eq!(req.txn_id, 1);
784 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
785 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), expected_channels);
786 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
787 assert_eq!(req.probe_delay, 5);
788 assert_eq!(req.min_channel_time, 75);
789 assert_eq!(req.max_channel_time, 75);
790 }
791
792 #[test]
793 fn test_active_discovery_scan_args_filled() {
794 let device_info = device_info_with_channel(vec![1, 36, 165]);
795 let mut sched: ScanScheduler<i32> =
796 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
797 let _next_txn_id = 0;
798 let ssid1: Vec<u8> = Ssid::try_from("ssid1").unwrap().into();
799 let ssid2: Vec<u8> = Ssid::try_from("ssid2").unwrap().into();
800 let scan_cmd = DiscoveryScan::new(
801 10,
802 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
803 ssids: vec![ssid1.clone(), ssid2.clone()],
804 channels: vec![1, 20, 100],
806 }),
807 );
808 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
809
810 assert_eq!(req.txn_id, 1);
811 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
812 assert_eq!(req.channel_list, vec![1]);
813 assert_eq!(req.ssid_list, vec![ssid1, ssid2]);
814 assert_eq!(req.probe_delay, 5);
815 assert_eq!(req.min_channel_time, 75);
816 assert_eq!(req.max_channel_time, 75);
817 }
818
819 #[test]
820 fn test_passive_discovery_scan_args_filled() {
821 let device_info = device_info_with_channel(vec![1, 36, 165]);
823 let mut sched: ScanScheduler<i32> =
824 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
825 let scan_cmd = DiscoveryScan::new(
827 10,
828 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![1, 36] }),
829 );
830 let _next_txn_id = 0;
831 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
832
833 assert_eq!(req.txn_id, 1);
834 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
835 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), HashSet::from([1, 36]));
837 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
838 assert_eq!(req.probe_delay, 0);
839 assert_eq!(req.min_channel_time, 200);
840 assert_eq!(req.max_channel_time, 200);
841 }
842
843 #[test]
844 fn test_passive_discovery_scan_args_unsupported_filtered() {
845 let device_info = device_info_with_channel(vec![1, 36]);
846 let mut sched: ScanScheduler<i32> =
847 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
848 let _next_txn_id = 0;
849 let scan_cmd = DiscoveryScan::new(
851 10,
852 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {
853 channels: vec![1, 6, 36],
854 }),
855 );
856 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
857
858 assert_eq!(req.txn_id, 1);
859 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
860 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), HashSet::from([1, 36]));
862 }
863
864 #[test]
865 fn test_passive_discovery_scan_args_invalid_filtered() {
866 let device_info = device_info_with_channel(vec![1, 200]);
867 let mut sched: ScanScheduler<i32> =
868 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
869 let _next_txn_id = 0;
870 let scan_cmd = DiscoveryScan::new(
872 10,
873 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![1, 200] }),
874 );
875 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
876
877 assert_eq!(req.txn_id, 1);
878 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
879 assert_eq!(req.channel_list, vec![1]);
881 }
882
883 #[test]
884 fn test_passive_discovery_scan_args_empty_list() {
885 let device_info = device_info_with_channel(vec![1, 36, 165]);
886 let mut sched: ScanScheduler<i32> =
887 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
888 let _next_txn_id = 0;
889 let scan_cmd = DiscoveryScan::new(
890 10,
891 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] }),
892 );
893 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
894
895 assert_eq!(req.txn_id, 1);
896 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
897 assert_eq!(
898 req.channel_list.into_iter().collect::<HashSet<_>>(),
899 HashSet::from([1, 36, 165])
900 );
901 }
902
903 #[test]
904 fn test_discovery_scans_dedupe_single_group() {
905 let mut sched = create_sched();
906 let _next_txn_id = 0;
907 let (_inspector, sme_inspect) = sme_inspect();
908
909 let mlme_req = sched
911 .enqueue_scan_to_discover(passive_discovery_scan(10))
912 .expect("expected a ScanRequest");
913 let txn_id = mlme_req.txn_id;
914
915 sched
917 .on_mlme_scan_result(fidl_mlme::ScanResult {
918 txn_id,
919 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
920 bss: fidl_ieee80211::BssDescription {
921 bssid: [1; 6],
922 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
923 },
924 })
925 .expect("expect scan result received");
926
927 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(20)).is_none());
930
931 sched
933 .on_mlme_scan_result(fidl_mlme::ScanResult {
934 txn_id,
935 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
936 bss: fidl_ieee80211::BssDescription {
937 bssid: [2; 6],
938 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
939 },
940 })
941 .expect("expect scan result received");
942 let (scan_end, mlme_req) = assert_matches!(
943 sched.on_mlme_scan_end(
944 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
945 &sme_inspect),
946 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
947 );
948
949 assert!(mlme_req.is_none());
951
952 assert_discovery_scan_result(
954 scan_end,
955 vec![10, 20],
956 vec![Ssid::try_from("bar").unwrap(), Ssid::try_from("foo").unwrap()],
957 );
958 }
959
960 #[test]
961 fn test_discovery_scans_dedupe_multiple_groups() {
962 let mut sched = create_sched();
963 let (_inspector, sme_inspect) = sme_inspect();
964
965 let mlme_req = sched
967 .enqueue_scan_to_discover(passive_discovery_scan(10))
968 .expect("expected a ScanRequest");
969 let txn_id = mlme_req.txn_id;
970
971 let scan_cmd = DiscoveryScan::new(
973 20,
974 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
975 ssids: vec![],
976 channels: vec![],
977 }),
978 );
979 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
980
981 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(30)).is_none());
984
985 let scan_cmd = DiscoveryScan::new(
988 40,
989 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
990 ssids: vec![],
991 channels: vec![],
992 }),
993 );
994 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
995
996 sched
998 .on_mlme_scan_result(fidl_mlme::ScanResult {
999 txn_id,
1000 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1001 bss: fidl_ieee80211::BssDescription {
1002 bssid: [1; 6],
1003 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1004 },
1005 })
1006 .expect("expect scan result received");
1007 let (scan_end, mlme_req) = assert_matches!(
1008 sched.on_mlme_scan_end(
1009 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
1010 &sme_inspect),
1011 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
1012 );
1013
1014 assert_discovery_scan_result(scan_end, vec![10, 30], vec![Ssid::try_from("foo").unwrap()]);
1016
1017 assert!(mlme_req.is_some());
1019 let mlme_req = mlme_req.unwrap();
1020 assert_eq!(mlme_req.scan_type, fidl_mlme::ScanTypes::Active);
1021 let txn_id = mlme_req.txn_id;
1022
1023 sched
1025 .on_mlme_scan_result(fidl_mlme::ScanResult {
1026 txn_id,
1027 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1028 bss: fidl_ieee80211::BssDescription {
1029 bssid: [2; 6],
1030 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
1031 },
1032 })
1033 .expect("expect scan result received");
1034 let (scan_end, mlme_req) = assert_matches!(
1035 sched.on_mlme_scan_end(
1036 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
1037 &sme_inspect),
1038 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
1039 );
1040
1041 assert_discovery_scan_result(scan_end, vec![20, 40], vec![Ssid::try_from("bar").unwrap()]);
1043
1044 assert!(mlme_req.is_none());
1046 }
1047
1048 #[test]
1049 fn test_discovery_scan_result_wrong_txn_id() {
1050 let mut sched = create_sched();
1051 let _next_txn_id = 0;
1052
1053 let mlme_req = sched
1055 .enqueue_scan_to_discover(passive_discovery_scan(10))
1056 .expect("expected a ScanRequest");
1057 let txn_id = mlme_req.txn_id;
1058
1059 assert_matches!(
1061 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
1062 txn_id: txn_id + 1,
1063 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1064 bss: fidl_ieee80211::BssDescription {
1065 bssid: [1; 6],
1066 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1067 },
1068 },),
1069 Err(Error::ScanResultWrongTxnId)
1070 );
1071 }
1072
1073 #[test]
1074 fn test_discovery_scan_result_not_scanning() {
1075 let mut sched = create_sched();
1076 assert_matches!(
1077 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
1078 txn_id: 0,
1079 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1080 bss: fidl_ieee80211::BssDescription {
1081 bssid: [1; 6],
1082 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1083 },
1084 },),
1085 Err(Error::ScanResultNotScanning)
1086 );
1087 }
1088
1089 #[test]
1090 fn test_discovery_scan_end_wrong_txn_id() {
1091 let mut sched = create_sched();
1092 let _next_txn_id = 0;
1093 let (_inspector, sme_inspect) = sme_inspect();
1094
1095 let mlme_req = sched
1097 .enqueue_scan_to_discover(passive_discovery_scan(10))
1098 .expect("expected a ScanRequest");
1099 let txn_id = mlme_req.txn_id;
1100
1101 assert_matches!(
1102 sched.on_mlme_scan_end(
1103 fidl_mlme::ScanEnd { txn_id: txn_id + 1, code: fidl_mlme::ScanResultCode::Success },
1104 &sme_inspect
1105 ),
1106 Err(Error::ScanEndWrongTxnId)
1107 );
1108 }
1109
1110 #[test]
1111 fn test_discovery_scan_end_not_scanning() {
1112 let mut sched = create_sched();
1113 let _next_txn_id = 0;
1114 let (_inspector, sme_inspect) = sme_inspect();
1115 assert_matches!(
1116 sched.on_mlme_scan_end(
1117 fidl_mlme::ScanEnd { txn_id: 0, code: fidl_mlme::ScanResultCode::Success },
1118 &sme_inspect
1119 ),
1120 Err(Error::ScanEndNotScanning)
1121 );
1122 }
1123
1124 fn assert_discovery_scan_result(
1125 scan_end: ScanEnd<i32>,
1126 expected_tokens: Vec<i32>,
1127 expected_ssids: Vec<Ssid>,
1128 ) {
1129 let (tokens, bss_description_list) = assert_matches!(
1130 scan_end,
1131 ScanEnd {
1132 tokens,
1133 result_code: fidl_mlme::ScanResultCode::Success,
1134 bss_description_list
1135 } => (tokens, bss_description_list),
1136 "expected discovery scan to be completed successfully"
1137 );
1138 assert_eq!(tokens, expected_tokens);
1139 let mut ssid_list =
1140 bss_description_list.into_iter().map(|bss| bss.ssid.clone()).collect::<Vec<_>>();
1141 ssid_list.sort();
1142 assert_eq!(ssid_list, expected_ssids);
1143 }
1144
1145 fn create_sched() -> ScanScheduler<i32> {
1146 ScanScheduler::new(
1147 Arc::new(test_utils::fake_device_info(*CLIENT_ADDR)),
1148 fake_spectrum_management_support_empty(),
1149 )
1150 }
1151
1152 fn device_info_with_channel(operating_channels: Vec<u8>) -> fidl_mlme::DeviceInfo {
1153 fidl_mlme::DeviceInfo {
1154 bands: vec![fidl_mlme::BandCapability {
1155 operating_channels,
1156 ..fake_5ghz_band_capability()
1157 }],
1158 ..test_utils::fake_device_info(*CLIENT_ADDR)
1159 }
1160 }
1161
1162 fn sme_inspect() -> (Inspector, Arc<inspect::SmeTree>) {
1163 let inspector = Inspector::default();
1164 let sme_inspect = Arc::new(inspect::SmeTree::new(
1165 inspector.clone(),
1166 inspector.root().create_child("usme"),
1167 &test_utils::fake_device_info([1u8; 6].into()),
1168 &fake_spectrum_management_support_empty(),
1169 ));
1170 (inspector, sme_inspect)
1171 }
1172
1173 #[test]
1174 fn test_scan_scheduler_routing() {
1175 let mut sched = create_sched();
1176 let (mlme_sink, mut _mlme_stream) = mpsc::unbounded();
1177 let mlme_sink = MlmeSink::new(mlme_sink);
1178
1179 let (responder, _receiver) = Responder::new();
1180 let mut stream = sched.start_scheduled_scan(
1181 fidl_common::ScheduledScanRequest { ..Default::default() },
1182 mlme_sink,
1183 responder,
1184 );
1185 let sched_txn_id = stream.txn_id;
1186 assert_eq!(sched_txn_id, 1);
1187
1188 let req = sched.enqueue_scan_to_discover(passive_discovery_scan(10)).unwrap();
1190 let disc_txn_id = req.txn_id;
1191 assert_eq!(disc_txn_id, 2);
1192
1193 let bss1 = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("scheduled").unwrap());
1195 sched
1196 .on_mlme_scan_result(fidl_mlme::ScanResult {
1197 txn_id: sched_txn_id,
1198 timestamp_nanos: 1000,
1199 bss: bss1.clone(),
1200 })
1201 .unwrap();
1202
1203 let bss2 = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("discovery").unwrap());
1205 sched
1206 .on_mlme_scan_result(fidl_mlme::ScanResult {
1207 txn_id: disc_txn_id,
1208 timestamp_nanos: 2000,
1209 bss: bss2.clone(),
1210 })
1211 .unwrap();
1212
1213 let (_inspector, sme_inspect) = sme_inspect();
1215 let cfg = crate::client::ClientConfig::default();
1216 let device_info = test_utils::fake_device_info(*CLIENT_ADDR);
1217 let security_support = wlan_common::test_utils::fake_features::fake_security_support();
1218 sched.on_scheduled_scan_matches_available(
1219 sched_txn_id,
1220 &sme_inspect,
1221 &cfg,
1222 &device_info,
1223 &security_support,
1224 );
1225
1226 assert_matches!(
1228 stream.try_next(),
1229 Ok(Some(scan_results)) => {
1230 let results = wlan_common::scan::read_vmo(scan_results).unwrap();
1231 assert_eq!(results.len(), 1);
1232 let parsed_bss = wlan_common::bss::BssDescription::try_from(results[0].bss_description.clone()).unwrap();
1233 assert_eq!(parsed_bss.ssid, Ssid::try_from("scheduled").unwrap());
1234 }
1235 );
1236
1237 if let ScanState::ScanningToDiscover { bss_map, .. } = &sched.current {
1239 assert!(bss_map.contains_key(&Bssid::from(bss2.bssid)));
1240 } else {
1241 panic!("Expected ScanState::ScanningToDiscover");
1242 }
1243 }
1244}