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