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::{Bandwidth, 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.primary != existing_bss.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_primary_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_primary_channels_for_scan(
422 device_info: &fidl_mlme::DeviceInfo,
423 spectrum_management_support: fidl_common::SpectrumManagementSupport,
424 scan_request: &fidl_sme::ScanRequest,
425) -> Vec<fidl_ieee80211::ChannelNumber> {
426 let mut primary_channels: HashSet<u8> = HashSet::new();
427 for band in &device_info.bands {
428 primary_channels.extend(band.primary_channels.iter().map(|c| c.number));
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<fidl_ieee80211::ChannelNumber> = CANDIDATE_PRIMARY_CHANNELS
436 .iter()
437 .filter(|channel| primary_channels.contains(&channel.primary))
438 .filter(|channel| {
439 if let &fidl_sme::ScanRequest::Passive(_) = scan_request {
442 return true;
443 };
444 if channel.band == fidl_ieee80211::WlanBand::FiveGhz {
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 .copied()
461 .map(|channel| channel.into())
462 .collect();
463
464 if channels.is_empty() {
465 if !requested_channels.is_empty() {
466 warn!("All channels are filtered out. Requested channels: {:?}", requested_channels);
467 } else {
468 warn!("All channels are filtered out.");
469 };
470 }
471
472 channels
473}
474
475static CANDIDATE_PRIMARY_CHANNELS: LazyLock<&'static [Channel]> = LazyLock::new(|| {
479 let channels_two_ghz = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
480 let channels_five_ghz = vec![
481 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140, 144,
482 149, 153, 157, 161, 165,
483 ];
484
485 let mut channels_to_scan = Vec::new();
486 for channel in channels_two_ghz {
487 channels_to_scan.push(Channel::new(
488 channel,
489 Bandwidth::Cbw20,
490 fidl_ieee80211::WlanBand::TwoGhz,
491 ));
492 }
493 for channel in channels_five_ghz {
494 channels_to_scan.push(Channel::new(
495 channel,
496 Bandwidth::Cbw20,
497 fidl_ieee80211::WlanBand::FiveGhz,
498 ));
499 }
500
501 channels_to_scan.leak()
502});
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507 use crate::test_utils;
508 use assert_matches::assert_matches;
509 use fidl_ieee80211::WlanBand::{FiveGhz, TwoGhz};
510 use fuchsia_inspect::Inspector;
511
512 use ieee80211::MacAddr;
513 use regex::bytes::Regex;
514 use std::fmt::Write;
515 use std::sync::LazyLock;
516 use test_case::test_case;
517 use wlan_common::test_utils::fake_capabilities::fake_5ghz_band_capability;
518 use wlan_common::test_utils::fake_features::fake_spectrum_management_support_empty;
519 use wlan_common::{fake_bss_description, fake_fidl_bss_description};
520
521 static CLIENT_ADDR: LazyLock<MacAddr> =
522 LazyLock::new(|| [0x7A, 0xE7, 0x76, 0xD9, 0xF2, 0x67].into());
523
524 impl ScheduledScanReceiver {
525 pub(crate) fn try_next(&mut self) -> Result<Option<fidl::Vmo>, mpsc::TryRecvError> {
526 match self.scan_results_receiver.try_recv() {
527 Ok(vmo) => Ok(Some(vmo)),
528 Err(mpsc::TryRecvError::Closed) => {
529 self.stopped_by_firmware = true;
530 Ok(None)
531 }
532 Err(e @ mpsc::TryRecvError::Empty) => Err(e),
533 }
534 }
535 }
536
537 fn passive_discovery_scan(token: i32) -> DiscoveryScan<i32> {
538 DiscoveryScan::new(
539 token,
540 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] }),
541 )
542 }
543
544 #[test]
545 fn discovery_scan() {
546 let mut sched = create_sched();
547 let _next_txn_id = 0;
548 let (_inspector, sme_inspect) = sme_inspect();
549 let req = sched
550 .enqueue_scan_to_discover(passive_discovery_scan(10))
551 .expect("expected a ScanRequest");
552 let txn_id = req.txn_id;
553 sched
554 .on_mlme_scan_result(fidl_mlme::ScanResult {
555 txn_id,
556 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
557 bss: fidl_ieee80211::BssDescription {
558 bssid: [1; 6],
559 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
560 },
561 })
562 .expect("expect scan result received");
563 assert_matches!(
564 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
565 txn_id: txn_id + 100, timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
567 bss: fidl_ieee80211::BssDescription {
568 bssid: [2; 6],
569 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
570 },
571 },),
572 Err(Error::ScanResultWrongTxnId)
573 );
574 sched
575 .on_mlme_scan_result(fidl_mlme::ScanResult {
576 txn_id,
577 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
578 bss: fidl_ieee80211::BssDescription {
579 bssid: [3; 6],
580 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("qux").unwrap())
581 },
582 })
583 .expect("expect scan result received");
584 let (scan_end, mlme_req) = assert_matches!(
585 sched.on_mlme_scan_end(
586 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
587 &sme_inspect),
588 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
589 );
590 assert!(mlme_req.is_none());
591 let (tokens, bss_description_list) = assert_matches!(
592 scan_end,
593 ScanEnd {
594 tokens,
595 result_code: fidl_mlme::ScanResultCode::Success,
596 bss_description_list
597 } => (tokens, bss_description_list),
598 "expected discovery scan to be completed successfully"
599 );
600 assert_eq!(vec![10], tokens);
601 let mut ssid_list =
602 bss_description_list.into_iter().map(|bss| bss.ssid).collect::<Vec<_>>();
603 ssid_list.sort();
604 assert_eq!(vec![Ssid::try_from("foo").unwrap(), Ssid::try_from("qux").unwrap()], ssid_list);
605 }
606
607 #[test_case(vec![
608 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap()),
609 fake_fidl_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap()),
610 ], vec![fake_bss_description!(Open, ssid: Ssid::try_from("baz").unwrap())] ;
611 "when latest BSS Description is new")]
612 #[test_case(vec![
613 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Bandwidth::Cbw20, FiveGhz)),
614 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(165, Bandwidth::Cbw20, FiveGhz)),
615 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(149, Bandwidth::Cbw20, FiveGhz))] ;
616 "when strong signal is first")]
617 #[test_case(vec![
618 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Bandwidth::Cbw20, FiveGhz)),
619 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Bandwidth::Cbw20, FiveGhz)),
620 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Bandwidth::Cbw20, FiveGhz)),
621 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Bandwidth::Cbw20, FiveGhz))];
622 "when strong signal is middle")]
623 #[test_case(vec![
624 fake_fidl_bss_description!(Open, rssi_dbm: -84, channel: Channel::new(64, Bandwidth::Cbw20, FiveGhz)),
625 fake_fidl_bss_description!(Open, rssi_dbm: -80, channel: Channel::new(36, Bandwidth::Cbw20, FiveGhz)),
626 fake_fidl_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Bandwidth::Cbw20, FiveGhz)),
627 ], vec![fake_bss_description!(Open, rssi_dbm: -36, channel: Channel::new(50, Bandwidth::Cbw20, FiveGhz))];
628 "when strong signal is last")]
629 #[test_case(vec![
630 fake_fidl_bss_description!(Open, rssi_dbm: -84, ssid: Ssid::try_from("bar").unwrap(),
631 channel: Channel::new(149, Bandwidth::Cbw20, FiveGhz)),
632 fake_fidl_bss_description!(Open, rssi_dbm: -36, ssid: Ssid::try_from("bar").unwrap(),
633 channel: Channel::new(165, Bandwidth::Cbw20, FiveGhz)),
634 fake_fidl_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
635 channel: Channel::new(165, Bandwidth::Cbw20, FiveGhz)),
636 ], vec![fake_bss_description!(Open, rssi_dbm: -40, ssid: Ssid::try_from("baz").unwrap(),
637 channel: Channel::new(165, Bandwidth::Cbw20, FiveGhz))];
638 "overwrite latest chosen channel")]
639 fn deduplicate_by_bssid(
640 bss_description_list_from_mlme: Vec<fidl_ieee80211::BssDescription>,
641 returned_bss_description_list: Vec<BssDescription>,
642 ) {
643 let mut sched = create_sched();
644 let _next_txn_id = 0;
645 let (_inspector, sme_inspect) = sme_inspect();
646 let req = sched
647 .enqueue_scan_to_discover(passive_discovery_scan(10))
648 .expect("expected a ScanRequest");
649 let txn_id = req.txn_id;
650 for bss in bss_description_list_from_mlme {
651 sched
652 .on_mlme_scan_result(fidl_mlme::ScanResult {
653 txn_id,
654 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
655 bss,
656 })
657 .expect("expect scan result received");
658 }
659 let (scan_end, mlme_req) = assert_matches!(
660 sched.on_mlme_scan_end(
661 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
662 &sme_inspect),
663 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
664 );
665 assert!(mlme_req.is_none());
666 let (tokens, bss_description_list) = assert_matches!(
667 scan_end,
668 ScanEnd {
669 tokens,
670 result_code: fidl_mlme::ScanResultCode::Success,
671 bss_description_list
672 } => (tokens, bss_description_list),
673 "expected discovery scan to be completed successfully"
674 );
675 assert_eq!(vec![10], tokens);
676 assert_eq!(bss_description_list, returned_bss_description_list);
677 }
678
679 #[test]
680 fn discovery_scan_merge_ies() {
681 let mut sched = create_sched();
682 let _next_txn_id = 0;
683 let (_inspector, sme_inspect) = sme_inspect();
684 let req = sched
685 .enqueue_scan_to_discover(passive_discovery_scan(10))
686 .expect("expected a ScanRequest");
687 let txn_id = req.txn_id;
688
689 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
690 let ie_marker1 = &[0xdd, 0x07, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee];
692 bss.ies.extend_from_slice(ie_marker1);
693 sched
694 .on_mlme_scan_result(fidl_mlme::ScanResult {
695 txn_id,
696 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
697 bss,
698 })
699 .expect("expect scan result received");
700
701 let mut bss = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("ssid").unwrap());
702 let ie_marker2 = &[0xdd, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
704 bss.ies.extend_from_slice(ie_marker2);
705 sched
706 .on_mlme_scan_result(fidl_mlme::ScanResult {
707 txn_id,
708 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
709 bss,
710 })
711 .expect("expect scan result received");
712 let (scan_end, mlme_req) = assert_matches!(
713 sched.on_mlme_scan_end(
714 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
715 &sme_inspect),
716 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
717 );
718 assert!(mlme_req.is_none());
719 let (tokens, bss_description_list) = assert_matches!(
720 scan_end,
721 ScanEnd {
722 tokens,
723 result_code: fidl_mlme::ScanResultCode::Success,
724 bss_description_list
725 } => (tokens, bss_description_list),
726 "expected discovery scan to be completed successfully"
727 );
728 assert_eq!(vec![10], tokens);
729
730 assert_eq!(bss_description_list.len(), 1);
731 assert!(slice_contains(bss_description_list[0].ies(), ie_marker1));
733 assert!(slice_contains(bss_description_list[0].ies(), ie_marker2));
734 }
735
736 fn slice_contains(slice: &[u8], subslice: &[u8]) -> bool {
737 let re = {
739 let mut re_string = String::with_capacity(6 + subslice.len() * 4);
740 re_string += "(?-u:";
741 for b in subslice {
742 write!(re_string, "\\x{b:02X}").unwrap();
743 }
744 re_string += ")";
745 Regex::new(&re_string).unwrap()
746 };
747 re.is_match(slice)
748 }
749
750 #[test_case(&[1, 2, 3], &[] => true; "vacuous")]
751 #[test_case(&[1, 2, 3], &[1u8] => true; "one byte")]
752 #[test_case(&[1, 2, 3], &[2u8, 3] => true; "multiple bytes")]
753 #[test_case(&[1, 1, 1], &[1u8, 1] => true; "multiple matches")]
754 #[test_case(&[1, 2, 3], &[0u8] => false; "no match")]
755 #[test_case(&[1, 2, 3], &[1u8, 2, 3, 4] => false; "too large")]
756 #[test_case(&[0x87, 0x77, 0x78], &[0x77, 0x77] => false; "misaligned match")]
757 fn slice_contains_test(slice: &[u8], subslice: &[u8]) -> bool {
758 slice_contains(slice, subslice)
759 }
760
761 #[test]
762 fn test_passive_discovery_scan_args() {
763 let mut sched = create_sched();
764 let _next_txn_id = 0;
765 let req = sched
766 .enqueue_scan_to_discover(passive_discovery_scan(10))
767 .expect("expected a ScanRequest");
768 assert_eq!(req.txn_id, 1);
769 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
770 assert_eq!(
771 req.channel_list.into_iter().collect::<HashSet<_>>(),
772 CANDIDATE_PRIMARY_CHANNELS.iter().copied().map(|c| c.into()).collect::<HashSet<_>>()
773 );
774 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
775 assert_eq!(req.probe_delay, 0);
776 assert_eq!(req.min_channel_time, 200);
777 assert_eq!(req.max_channel_time, 200);
778 }
779
780 #[test_case(true, HashSet::from([
781 fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz },
782 fidl_ieee80211::ChannelNumber { number: 36, band: FiveGhz },
783 fidl_ieee80211::ChannelNumber { number: 165, band: FiveGhz },
784 ]); "dfs_enabled")]
785 #[test_case(false, HashSet::from([
786 fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz },
787 ]); "dfs_disabled")]
788 fn test_active_discovery_scan_args_empty(
789 dfs_supported: bool,
790 expected_channels: HashSet<fidl_ieee80211::ChannelNumber>,
791 ) {
792 let device_info = device_info_with_channel(vec![1, 36, 165]);
793 let mut spectrum_management = fake_spectrum_management_support_empty();
794 if dfs_supported {
795 spectrum_management.dfs.get_or_insert_with(Default::default).supported = Some(true);
796 }
797 let mut sched: ScanScheduler<i32> =
798 ScanScheduler::new(Arc::new(device_info), spectrum_management);
799 let _next_txn_id = 0;
800 let scan_cmd = DiscoveryScan::new(
801 10,
802 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
803 ssids: vec![],
804 channels: vec![],
805 }),
806 );
807 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
808
809 assert_eq!(req.txn_id, 1);
810 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
811 assert_eq!(req.channel_list.into_iter().collect::<HashSet<_>>(), expected_channels);
812 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
813 assert_eq!(req.probe_delay, 5);
814 assert_eq!(req.min_channel_time, 75);
815 assert_eq!(req.max_channel_time, 75);
816 }
817
818 #[test]
819 fn test_active_discovery_scan_args_filled() {
820 let device_info = device_info_with_channel(vec![1, 36, 165]);
821 let mut sched: ScanScheduler<i32> =
822 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
823 let _next_txn_id = 0;
824 let ssid1: Vec<u8> = Ssid::try_from("ssid1").unwrap().into();
825 let ssid2: Vec<u8> = Ssid::try_from("ssid2").unwrap().into();
826 let scan_cmd = DiscoveryScan::new(
827 10,
828 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
829 ssids: vec![ssid1.clone(), ssid2.clone()],
830 channels: vec![1, 20, 100],
832 }),
833 );
834 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
835
836 assert_eq!(req.txn_id, 1);
837 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Active);
838 assert_eq!(
839 req.channel_list,
840 vec![fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz }]
841 );
842 assert_eq!(req.ssid_list, vec![ssid1, ssid2]);
843 assert_eq!(req.probe_delay, 5);
844 assert_eq!(req.min_channel_time, 75);
845 assert_eq!(req.max_channel_time, 75);
846 }
847
848 #[test]
849 fn test_passive_discovery_scan_args_filled() {
850 let device_info = device_info_with_channel(vec![1, 36, 165]);
852 let mut sched: ScanScheduler<i32> =
853 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
854 let scan_cmd = DiscoveryScan::new(
856 10,
857 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![1, 36] }),
858 );
859 let _next_txn_id = 0;
860 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
861
862 assert_eq!(req.txn_id, 1);
863 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
864 assert_eq!(
866 req.channel_list.into_iter().collect::<HashSet<_>>(),
867 HashSet::from([
868 fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz },
869 fidl_ieee80211::ChannelNumber { number: 36, band: FiveGhz },
870 ])
871 );
872 assert_eq!(req.ssid_list, Vec::<Vec<u8>>::new());
873 assert_eq!(req.probe_delay, 0);
874 assert_eq!(req.min_channel_time, 200);
875 assert_eq!(req.max_channel_time, 200);
876 }
877
878 #[test]
879 fn test_passive_discovery_scan_args_unsupported_filtered() {
880 let device_info = device_info_with_channel(vec![1, 36]);
881 let mut sched: ScanScheduler<i32> =
882 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
883 let _next_txn_id = 0;
884 let scan_cmd = DiscoveryScan::new(
886 10,
887 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest {
888 channels: vec![1, 6, 36],
889 }),
890 );
891 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
892
893 assert_eq!(req.txn_id, 1);
894 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
895 assert_eq!(
897 req.channel_list.into_iter().collect::<HashSet<_>>(),
898 HashSet::from([
899 fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz },
900 fidl_ieee80211::ChannelNumber { number: 36, band: FiveGhz },
901 ])
902 );
903 }
904
905 #[test]
906 fn test_passive_discovery_scan_args_invalid_filtered() {
907 let device_info = device_info_with_channel(vec![1, 200]);
908 let mut sched: ScanScheduler<i32> =
909 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
910 let _next_txn_id = 0;
911 let scan_cmd = DiscoveryScan::new(
913 10,
914 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![1, 200] }),
915 );
916 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
917
918 assert_eq!(req.txn_id, 1);
919 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
920 assert_eq!(
922 req.channel_list,
923 vec![fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz }]
924 );
925 }
926
927 #[test]
928 fn test_passive_discovery_scan_args_empty_list() {
929 let device_info = device_info_with_channel(vec![1, 36, 165]);
930 let mut sched: ScanScheduler<i32> =
931 ScanScheduler::new(Arc::new(device_info), fake_spectrum_management_support_empty());
932 let _next_txn_id = 0;
933 let scan_cmd = DiscoveryScan::new(
934 10,
935 fidl_sme::ScanRequest::Passive(fidl_sme::PassiveScanRequest { channels: vec![] }),
936 );
937 let req = sched.enqueue_scan_to_discover(scan_cmd).expect("expected a ScanRequest");
938
939 assert_eq!(req.txn_id, 1);
940 assert_eq!(req.scan_type, fidl_mlme::ScanTypes::Passive);
941 assert_eq!(
942 req.channel_list.into_iter().collect::<HashSet<_>>(),
943 HashSet::from([
944 fidl_ieee80211::ChannelNumber { number: 1, band: TwoGhz },
945 fidl_ieee80211::ChannelNumber { number: 36, band: FiveGhz },
946 fidl_ieee80211::ChannelNumber { number: 165, band: FiveGhz },
947 ])
948 );
949 }
950
951 #[test]
952 fn test_discovery_scans_dedupe_single_group() {
953 let mut sched = create_sched();
954 let _next_txn_id = 0;
955 let (_inspector, sme_inspect) = sme_inspect();
956
957 let mlme_req = sched
959 .enqueue_scan_to_discover(passive_discovery_scan(10))
960 .expect("expected a ScanRequest");
961 let txn_id = mlme_req.txn_id;
962
963 sched
965 .on_mlme_scan_result(fidl_mlme::ScanResult {
966 txn_id,
967 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
968 bss: fidl_ieee80211::BssDescription {
969 bssid: [1; 6],
970 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
971 },
972 })
973 .expect("expect scan result received");
974
975 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(20)).is_none());
978
979 sched
981 .on_mlme_scan_result(fidl_mlme::ScanResult {
982 txn_id,
983 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
984 bss: fidl_ieee80211::BssDescription {
985 bssid: [2; 6],
986 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
987 },
988 })
989 .expect("expect scan result received");
990 let (scan_end, mlme_req) = assert_matches!(
991 sched.on_mlme_scan_end(
992 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
993 &sme_inspect),
994 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
995 );
996
997 assert!(mlme_req.is_none());
999
1000 assert_discovery_scan_result(
1002 scan_end,
1003 vec![10, 20],
1004 vec![Ssid::try_from("bar").unwrap(), Ssid::try_from("foo").unwrap()],
1005 );
1006 }
1007
1008 #[test]
1009 fn test_discovery_scans_dedupe_multiple_groups() {
1010 let mut sched = create_sched();
1011 let (_inspector, sme_inspect) = sme_inspect();
1012
1013 let mlme_req = sched
1015 .enqueue_scan_to_discover(passive_discovery_scan(10))
1016 .expect("expected a ScanRequest");
1017 let txn_id = mlme_req.txn_id;
1018
1019 let scan_cmd = DiscoveryScan::new(
1021 20,
1022 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
1023 ssids: vec![],
1024 channels: vec![],
1025 }),
1026 );
1027 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
1028
1029 assert!(sched.enqueue_scan_to_discover(passive_discovery_scan(30)).is_none());
1032
1033 let scan_cmd = DiscoveryScan::new(
1036 40,
1037 fidl_sme::ScanRequest::Active(fidl_sme::ActiveScanRequest {
1038 ssids: vec![],
1039 channels: vec![],
1040 }),
1041 );
1042 assert!(sched.enqueue_scan_to_discover(scan_cmd).is_none());
1043
1044 sched
1046 .on_mlme_scan_result(fidl_mlme::ScanResult {
1047 txn_id,
1048 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1049 bss: fidl_ieee80211::BssDescription {
1050 bssid: [1; 6],
1051 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1052 },
1053 })
1054 .expect("expect scan result received");
1055 let (scan_end, mlme_req) = assert_matches!(
1056 sched.on_mlme_scan_end(
1057 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
1058 &sme_inspect),
1059 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
1060 );
1061
1062 assert_discovery_scan_result(scan_end, vec![10, 30], vec![Ssid::try_from("foo").unwrap()]);
1064
1065 assert!(mlme_req.is_some());
1067 let mlme_req = mlme_req.unwrap();
1068 assert_eq!(mlme_req.scan_type, fidl_mlme::ScanTypes::Active);
1069 let txn_id = mlme_req.txn_id;
1070
1071 sched
1073 .on_mlme_scan_result(fidl_mlme::ScanResult {
1074 txn_id,
1075 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1076 bss: fidl_ieee80211::BssDescription {
1077 bssid: [2; 6],
1078 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("bar").unwrap())
1079 },
1080 })
1081 .expect("expect scan result received");
1082 let (scan_end, mlme_req) = assert_matches!(
1083 sched.on_mlme_scan_end(
1084 fidl_mlme::ScanEnd { txn_id, code: fidl_mlme::ScanResultCode::Success },
1085 &sme_inspect),
1086 Ok((scan_end, mlme_req)) => (scan_end, mlme_req)
1087 );
1088
1089 assert_discovery_scan_result(scan_end, vec![20, 40], vec![Ssid::try_from("bar").unwrap()]);
1091
1092 assert!(mlme_req.is_none());
1094 }
1095
1096 #[test]
1097 fn test_discovery_scan_result_wrong_txn_id() {
1098 let mut sched = create_sched();
1099 let _next_txn_id = 0;
1100
1101 let mlme_req = sched
1103 .enqueue_scan_to_discover(passive_discovery_scan(10))
1104 .expect("expected a ScanRequest");
1105 let txn_id = mlme_req.txn_id;
1106
1107 assert_matches!(
1109 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
1110 txn_id: txn_id + 1,
1111 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1112 bss: fidl_ieee80211::BssDescription {
1113 bssid: [1; 6],
1114 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1115 },
1116 },),
1117 Err(Error::ScanResultWrongTxnId)
1118 );
1119 }
1120
1121 #[test]
1122 fn test_discovery_scan_result_not_scanning() {
1123 let mut sched = create_sched();
1124 assert_matches!(
1125 sched.on_mlme_scan_result(fidl_mlme::ScanResult {
1126 txn_id: 0,
1127 timestamp_nanos: zx::MonotonicInstant::get().into_nanos(),
1128 bss: fidl_ieee80211::BssDescription {
1129 bssid: [1; 6],
1130 ..fake_fidl_bss_description!(Open, ssid: Ssid::try_from("foo").unwrap())
1131 },
1132 },),
1133 Err(Error::ScanResultNotScanning)
1134 );
1135 }
1136
1137 #[test]
1138 fn test_discovery_scan_end_wrong_txn_id() {
1139 let mut sched = create_sched();
1140 let _next_txn_id = 0;
1141 let (_inspector, sme_inspect) = sme_inspect();
1142
1143 let mlme_req = sched
1145 .enqueue_scan_to_discover(passive_discovery_scan(10))
1146 .expect("expected a ScanRequest");
1147 let txn_id = mlme_req.txn_id;
1148
1149 assert_matches!(
1150 sched.on_mlme_scan_end(
1151 fidl_mlme::ScanEnd { txn_id: txn_id + 1, code: fidl_mlme::ScanResultCode::Success },
1152 &sme_inspect
1153 ),
1154 Err(Error::ScanEndWrongTxnId)
1155 );
1156 }
1157
1158 #[test]
1159 fn test_discovery_scan_end_not_scanning() {
1160 let mut sched = create_sched();
1161 let _next_txn_id = 0;
1162 let (_inspector, sme_inspect) = sme_inspect();
1163 assert_matches!(
1164 sched.on_mlme_scan_end(
1165 fidl_mlme::ScanEnd { txn_id: 0, code: fidl_mlme::ScanResultCode::Success },
1166 &sme_inspect
1167 ),
1168 Err(Error::ScanEndNotScanning)
1169 );
1170 }
1171
1172 fn assert_discovery_scan_result(
1173 scan_end: ScanEnd<i32>,
1174 expected_tokens: Vec<i32>,
1175 expected_ssids: Vec<Ssid>,
1176 ) {
1177 let (tokens, bss_description_list) = assert_matches!(
1178 scan_end,
1179 ScanEnd {
1180 tokens,
1181 result_code: fidl_mlme::ScanResultCode::Success,
1182 bss_description_list
1183 } => (tokens, bss_description_list),
1184 "expected discovery scan to be completed successfully"
1185 );
1186 assert_eq!(tokens, expected_tokens);
1187 let mut ssid_list =
1188 bss_description_list.into_iter().map(|bss| bss.ssid.clone()).collect::<Vec<_>>();
1189 ssid_list.sort();
1190 assert_eq!(ssid_list, expected_ssids);
1191 }
1192
1193 fn create_sched() -> ScanScheduler<i32> {
1194 ScanScheduler::new(
1195 Arc::new(test_utils::fake_device_info(*CLIENT_ADDR)),
1196 fake_spectrum_management_support_empty(),
1197 )
1198 }
1199
1200 fn device_info_with_channel(operating_channels: Vec<u8>) -> fidl_mlme::DeviceInfo {
1201 fidl_mlme::DeviceInfo {
1202 bands: vec![fidl_mlme::BandCapability {
1203 primary_channels: operating_channels
1204 .into_iter()
1205 .map(|n| fidl_ieee80211::ChannelNumber {
1206 number: n,
1207 band: fidl_ieee80211::WlanBand::FiveGhz,
1208 })
1209 .collect(),
1210 ..fake_5ghz_band_capability()
1211 }],
1212 ..test_utils::fake_device_info(*CLIENT_ADDR)
1213 }
1214 }
1215
1216 fn sme_inspect() -> (Inspector, Arc<inspect::SmeTree>) {
1217 let inspector = Inspector::default();
1218 let sme_inspect = Arc::new(inspect::SmeTree::new(
1219 inspector.clone(),
1220 inspector.root().create_child("usme"),
1221 &test_utils::fake_device_info([1u8; 6].into()),
1222 &fake_spectrum_management_support_empty(),
1223 ));
1224 (inspector, sme_inspect)
1225 }
1226
1227 #[test]
1228 fn test_scan_scheduler_routing() {
1229 let mut sched = create_sched();
1230 let (mlme_sink, mut _mlme_stream) = mpsc::unbounded();
1231 let mlme_sink = MlmeSink::new(mlme_sink);
1232
1233 let (responder, _receiver) = Responder::new();
1234 let mut stream = sched.start_scheduled_scan(
1235 fidl_common::ScheduledScanRequest { ..Default::default() },
1236 mlme_sink,
1237 responder,
1238 );
1239 let sched_txn_id = stream.txn_id;
1240 assert_eq!(sched_txn_id, 1);
1241
1242 let req = sched.enqueue_scan_to_discover(passive_discovery_scan(10)).unwrap();
1244 let disc_txn_id = req.txn_id;
1245 assert_eq!(disc_txn_id, 2);
1246
1247 let bss1 = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("scheduled").unwrap());
1249 sched
1250 .on_mlme_scan_result(fidl_mlme::ScanResult {
1251 txn_id: sched_txn_id,
1252 timestamp_nanos: 1000,
1253 bss: bss1.clone(),
1254 })
1255 .unwrap();
1256
1257 let bss2 = fake_fidl_bss_description!(Open, ssid: Ssid::try_from("discovery").unwrap());
1259 sched
1260 .on_mlme_scan_result(fidl_mlme::ScanResult {
1261 txn_id: disc_txn_id,
1262 timestamp_nanos: 2000,
1263 bss: bss2.clone(),
1264 })
1265 .unwrap();
1266
1267 let (_inspector, sme_inspect) = sme_inspect();
1269 let cfg = crate::client::ClientConfig::default();
1270 let device_info = test_utils::fake_device_info(*CLIENT_ADDR);
1271 let security_support = wlan_common::test_utils::fake_features::fake_security_support();
1272 sched.on_scheduled_scan_matches_available(
1273 sched_txn_id,
1274 &sme_inspect,
1275 &cfg,
1276 &device_info,
1277 &security_support,
1278 );
1279
1280 assert_matches!(
1282 stream.try_next(),
1283 Ok(Some(scan_results)) => {
1284 let results = wlan_common::scan::read_vmo(scan_results).unwrap();
1285 assert_eq!(results.len(), 1);
1286 let parsed_bss = wlan_common::bss::BssDescription::try_from(results[0].bss_description.clone()).unwrap();
1287 assert_eq!(parsed_bss.ssid, Ssid::try_from("scheduled").unwrap());
1288 }
1289 );
1290
1291 if let ScanState::ScanningToDiscover { bss_map, .. } = &sched.current {
1293 assert!(bss_map.contains_key(&Bssid::from(bss2.bssid)));
1294 } else {
1295 panic!("Expected ScanState::ScanningToDiscover");
1296 }
1297 }
1298}