1use async_trait::async_trait;
6use fidl::Error as FidlError;
7use fidl_fuchsia_wlan_policy::{
8 Bss as WlanPolicyBss, ScanErrorCode, ScanResult, ScanResultIteratorProxyInterface,
9};
10use futures::future::BoxFuture;
11use futures::task::{Context, Poll};
12use futures::{Future, FutureExt, Stream, StreamExt};
13use rand::seq::IteratorRandom;
14use std::collections::HashMap;
15use std::pin::Pin;
16use thiserror::Error;
17
18#[async_trait(?Send)]
19pub trait BssCache {
20 async fn update<I: ScanResultIteratorProxyInterface>(
22 &mut self,
23 new_bsses: I,
24 ) -> Result<(), UpdateError>;
25
26 fn iter(&self) -> Box<dyn Iterator<Item = (&'_ BssId, &'_ Bss)> + '_>;
28}
29
30#[derive(Default)]
32pub struct RealBssCache {
33 bss_map: HashMap<BssId, Bss>,
34}
35
36pub type BssId = [u8; BSS_ADDR_LEN_BYTES];
37
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct Bss {
40 pub(crate) rssi: Option<i8>,
41 pub(crate) frequency: Option<u32>,
42}
43
44#[derive(Clone, Copy, Debug, Error, PartialEq)]
45pub enum UpdateError {
46 #[error("found BSSes, but no BSS IDs")]
47 NoBssIds,
48 #[error("found no BSSes")]
49 NoBsses,
50 #[error("connection to iterator failed")]
51 Ipc,
52 #[error("iterator reported error")]
53 Service,
54}
55
56const BSS_ADDR_LEN_BYTES: usize = 6;
58
59const MAX_BSSES: usize = 20;
66
67const MAX_IPCS: usize = MAX_BSSES;
72
73impl RealBssCache {
74 pub fn new() -> Self {
75 Default::default()
76 }
77}
78
79#[async_trait(?Send)]
80impl BssCache for RealBssCache {
81 async fn update<I: ScanResultIteratorProxyInterface>(
82 &mut self,
83 new_bsses: I,
84 ) -> Result<(), UpdateError> {
85 let mut iterator_service_result = ScanResultStream::new(new_bsses)
86 .take(MAX_IPCS)
87 .collect::<Vec<Result<Vec<ScanResult>, UpdateError>>>()
88 .await
89 .into_iter()
90 .peekable();
91 match iterator_service_result.peek() {
93 None => return Err(UpdateError::NoBsses), Some(Err(e)) => return Err(*e), Some(Ok(_)) => (),
96 };
97
98 let mut bss_list = iterator_service_result
99 .filter_map(|res| res.ok()) .flatten() .flat_map(|network| network.entries) .flatten() .peekable();
104 if bss_list.peek().is_none() {
105 return Err(UpdateError::NoBsses);
106 };
107
108 let mut valid_bss_list = bss_list
109 .filter_map(|bss: WlanPolicyBss| match bss.bssid {
110 Some(id) => Some((id, Bss { rssi: bss.rssi, frequency: bss.frequency })),
111 None => None,
112 })
113 .peekable();
114 if valid_bss_list.peek().is_none() {
115 return Err(UpdateError::NoBssIds);
116 }
117
118 let deduped_list: HashMap<BssId, Bss> = valid_bss_list.collect();
120
121 if deduped_list.len() > MAX_BSSES {
122 let mut rng = rand::rng();
123 self.bss_map =
124 deduped_list.into_iter().choose_multiple(&mut rng, MAX_BSSES).into_iter().collect();
125 } else {
126 self.bss_map = deduped_list;
127 }
128
129 Ok(())
130 }
131
132 fn iter(&self) -> Box<dyn Iterator<Item = (&'_ BssId, &'_ Bss)> + '_> {
133 Box::new(self.bss_map.iter())
134 }
135}
136
137type GetNextResponse = Result<Result<Vec<ScanResult>, ScanErrorCode>, FidlError>;
138
139struct ScanResultStream<'a, F, I>
143where
144 F: Future<Output = GetNextResponse> + Send,
145 I: ScanResultIteratorProxyInterface<GetNextResponseFut = F>,
146{
147 iterator_service: Option<I>,
148 pending_ipc: Option<BoxFuture<'a, F::Output>>,
149}
150
151impl<'a, F, I> ScanResultStream<'a, F, I>
152where
153 F: Future<Output = GetNextResponse> + Send,
154 I: ScanResultIteratorProxyInterface<GetNextResponseFut = F>,
155{
156 fn new(iterator_service: I) -> Self {
157 Self { iterator_service: Some(iterator_service), pending_ipc: None }
158 }
159}
160
161impl<'a, F, I> Unpin for ScanResultStream<'a, F, I>
162where
163 F: Future<Output = GetNextResponse> + Send,
164 I: ScanResultIteratorProxyInterface<GetNextResponseFut = F>,
165{
166}
167
168impl<'a, F, I> Stream for ScanResultStream<'a, F, I>
169where
170 F: Future<Output = GetNextResponse> + Send + 'a,
171 I: ScanResultIteratorProxyInterface<GetNextResponseFut = F>,
172{
173 type Item = Result<Vec<ScanResult>, UpdateError>;
174
175 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
181 let iterator_service = match self.iterator_service.take() {
191 Some(is) => is,
192 None => return Poll::Ready(None),
193 };
194 let mut fut = match self.pending_ipc.take() {
195 Some(ipc) => ipc,
196 None => iterator_service.get_next().boxed(),
197 };
198 match fut.poll_unpin(cx) {
199 Poll::Pending => {
200 self.pending_ipc = Some(fut);
201 self.iterator_service = Some(iterator_service);
202 Poll::Pending
203 }
204 Poll::Ready(fidl_result) => Poll::Ready(match flatten_get_next_error(fidl_result) {
205 Ok(res) => {
206 if res.is_empty() {
207 None
208 } else {
209 self.iterator_service = Some(iterator_service);
210 Some(Ok(res))
211 }
212 }
213 Err(e) => Some(Err(e)),
214 }),
215 }
216 }
217}
218
219fn flatten_get_next_error(fidl_result: GetNextResponse) -> Result<Vec<ScanResult>, UpdateError> {
220 match fidl_result {
221 Ok(service_result) => match service_result {
222 Ok(scan_results) => Ok(scan_results),
223 Err(_) => Err(UpdateError::Service),
224 },
225 Err(_) => Err(UpdateError::Ipc),
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 mod single_call_success {
232 use super::super::*;
233 use assert_matches::assert_matches;
234 use fidl_fuchsia_wlan_policy::Compatibility::Supported;
235 use fidl_fuchsia_wlan_policy::NetworkIdentifier;
236 use fidl_fuchsia_wlan_policy::SecurityType::Wpa2;
237 use test_doubles::FakeScanResultIterator;
238
239 #[fuchsia::test(allow_stalls = false)]
240 async fn caches_single_bss_with_just_bss_data() {
241 let mut cache = RealBssCache::new();
242 let result = cache
243 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
244 id: None,
245 entries: Some(vec![WlanPolicyBss {
246 bssid: Some([0, 1, 2, 3, 4, 5]),
247 rssi: None,
248 frequency: None,
249 timestamp_nanos: None,
250 ..Default::default()
251 }]),
252 compatibility: None,
253 ..Default::default()
254 }]))
255 .await;
256 assert_eq!(result, Ok(()));
257 assert_eq!(
258 cache.iter().next(),
259 Some((&[0, 1, 2, 3, 4, 5], &Bss { rssi: None, frequency: None }))
260 );
261 }
262
263 #[fuchsia::test(allow_stalls = false)]
264 async fn caches_single_bss_with_all_data() {
265 let mut cache = RealBssCache::new();
266 let result = cache
267 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
268 id: Some(NetworkIdentifier { ssid: vec![b'a'], type_: Wpa2 }),
269 entries: Some(vec![WlanPolicyBss {
270 bssid: Some([0, 1, 2, 3, 4, 5]),
271 rssi: Some(-1),
272 frequency: Some(2412),
273 timestamp_nanos: Some(1),
274 ..Default::default()
275 }]),
276 compatibility: Some(Supported),
277 ..Default::default()
278 }]))
279 .await;
280 assert_eq!(result, Ok(()));
281 assert_eq!(
282 cache.iter().next(),
283 Some((&[0, 1, 2, 3, 4, 5], &Bss { rssi: Some(-1), frequency: Some(2412) }))
284 );
285 }
286
287 #[fuchsia::test(allow_stalls = false)]
288 async fn caches_multiple_bsses_from_single_network() {
289 let mut cache = RealBssCache::new();
290 let result = cache
291 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
292 id: None,
293 entries: Some(vec![
294 WlanPolicyBss {
295 bssid: Some([0, 0, 0, 0, 0, 0]),
296 rssi: None,
297 frequency: None,
298 timestamp_nanos: None,
299 ..Default::default()
300 },
301 WlanPolicyBss {
302 bssid: Some([1, 1, 1, 1, 1, 1]),
303 rssi: None,
304 frequency: None,
305 timestamp_nanos: None,
306 ..Default::default()
307 },
308 ]),
309 compatibility: None,
310 ..Default::default()
311 }]))
312 .await;
313 assert_eq!(result, Ok(()));
314
315 assert_eq!(cache.iter().count(), 2);
316 assert!(cache.iter().any(|(&id, &bss)| id == [0, 0, 0, 0, 0, 0]
317 && bss == Bss { rssi: None, frequency: None }));
318 assert!(cache.iter().any(|(&id, &bss)| id == [1, 1, 1, 1, 1, 1]
319 && bss == Bss { rssi: None, frequency: None }));
320 }
321
322 #[fuchsia::test(allow_stalls = false)]
323 async fn deduplicates_bsses_from_single_network() {
324 let mut cache = RealBssCache::new();
325 let result = cache
326 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
327 id: None,
328 entries: Some(vec![
329 WlanPolicyBss {
330 bssid: Some([0, 1, 2, 3, 4, 5]),
331 rssi: Some(-1),
332 frequency: Some(2412),
333 timestamp_nanos: Some(1),
334 ..Default::default()
335 },
336 WlanPolicyBss {
337 bssid: Some([0, 1, 2, 3, 4, 5]),
338 rssi: Some(-2),
339 frequency: Some(2432),
340 timestamp_nanos: Some(2),
341 ..Default::default()
342 },
343 ]),
344 compatibility: None,
345 ..Default::default()
346 }]))
347 .await;
348 assert_eq!(result, Ok(()));
349
350 let mut bsses = cache.iter();
351 assert_matches!(bsses.next(), Some((&[0, 1, 2, 3, 4, 5], _)));
352 assert_eq!(bsses.next(), None);
353 }
354
355 #[fuchsia::test(allow_stalls = false)]
356 async fn caches_multiple_bsses_from_multiple_networks() {
357 let mut cache = RealBssCache::new();
358 let result = cache
359 .update(FakeScanResultIterator::new_single_step(vec![
360 ScanResult {
361 id: None,
362 entries: Some(vec![WlanPolicyBss {
363 bssid: Some([0, 0, 0, 0, 0, 0]),
364 rssi: None,
365 frequency: None,
366 timestamp_nanos: None,
367 ..Default::default()
368 }]),
369 compatibility: None,
370 ..Default::default()
371 },
372 ScanResult {
373 id: None,
374 entries: Some(vec![WlanPolicyBss {
375 bssid: Some([1, 1, 1, 1, 1, 1]),
376 rssi: None,
377 frequency: None,
378 timestamp_nanos: None,
379 ..Default::default()
380 }]),
381 compatibility: None,
382 ..Default::default()
383 },
384 ]))
385 .await;
386 assert_eq!(result, Ok(()));
387
388 assert_eq!(cache.iter().count(), 2);
389 assert!(cache.iter().any(|(&id, &bss)| id == [0, 0, 0, 0, 0, 0]
390 && bss == Bss { rssi: None, frequency: None }));
391 assert!(cache.iter().any(|(&id, &bss)| id == [1, 1, 1, 1, 1, 1]
392 && bss == Bss { rssi: None, frequency: None }));
393 }
394
395 #[fuchsia::test(allow_stalls = false)]
396 async fn deduplicates_bsses_from_multiple_networks() {
397 let mut cache = RealBssCache::new();
398 let result = cache
399 .update(FakeScanResultIterator::new_single_step(vec![
400 ScanResult {
401 id: None,
402 entries: Some(vec![WlanPolicyBss {
403 bssid: Some([0, 1, 2, 3, 4, 5]),
404 rssi: Some(-1),
405 frequency: Some(2412),
406 timestamp_nanos: Some(1),
407 ..Default::default()
408 }]),
409 compatibility: None,
410 ..Default::default()
411 },
412 ScanResult {
413 id: None,
414 entries: Some(vec![WlanPolicyBss {
415 bssid: Some([0, 1, 2, 3, 4, 5]),
416 rssi: Some(-2),
417 frequency: Some(2432),
418 timestamp_nanos: Some(2),
419 ..Default::default()
420 }]),
421 compatibility: None,
422 ..Default::default()
423 },
424 ]))
425 .await;
426 assert_eq!(result, Ok(()));
427
428 let mut bsses = cache.iter();
429 assert_matches!(bsses.next(), Some((&[0, 1, 2, 3, 4, 5], _)));
430 assert_eq!(bsses.next(), None);
431 }
432
433 #[fuchsia::test(allow_stalls = false)]
434 async fn honors_max_bss_limit() {
435 let mut cache = RealBssCache::new();
436 let bsses: Vec<_> = (0..MAX_BSSES + 1)
437 .map(|i| WlanPolicyBss {
438 bssid: Some(
439 BssId::try_from(&i.to_le_bytes()[0..BSS_ADDR_LEN_BYTES])
440 .expect("internal error"),
441 ),
442 rssi: None,
443 frequency: None,
444 timestamp_nanos: None,
445 ..Default::default()
446 })
447 .collect();
448 let scan_results = vec![ScanResult {
449 id: None,
450 entries: Some(bsses),
451 compatibility: None,
452 ..Default::default()
453 }];
454 let _ = cache.update(FakeScanResultIterator::new_single_step(scan_results)).await;
455 assert_eq!(cache.iter().count(), MAX_BSSES);
456 }
457
458 #[fuchsia::test(allow_stalls = false)]
459 async fn does_not_count_bad_bsses_toward_max_bss_limit() {
460 let mut cache = RealBssCache::new();
461 let bad_bss = std::iter::once(WlanPolicyBss {
462 bssid: None,
463 rssi: None,
464 frequency: None,
465 timestamp_nanos: None,
466 ..Default::default()
467 });
468 let good_bsses = (0..MAX_BSSES).map(|i| WlanPolicyBss {
469 bssid: Some(
470 BssId::try_from(&i.to_le_bytes()[0..BSS_ADDR_LEN_BYTES])
471 .expect("internal error"),
472 ),
473 rssi: None,
474 frequency: None,
475 timestamp_nanos: None,
476 ..Default::default()
477 });
478 let bsses: Vec<_> = bad_bss.chain(good_bsses).collect();
479 let scan_results = vec![ScanResult {
480 id: None,
481 entries: Some(bsses),
482 compatibility: None,
483 ..Default::default()
484 }];
485 let _ = cache.update(FakeScanResultIterator::new_single_step(scan_results)).await;
486 assert_eq!(cache.iter().count(), MAX_BSSES);
487 }
488
489 #[fuchsia::test(allow_stalls = false)]
490 async fn does_not_count_duplicate_bsses_toward_max_bss_limit() {
491 let mut cache = RealBssCache::new();
492 let duplicate_bsses = vec![
493 WlanPolicyBss {
494 bssid: Some([0, 0, 0, 0, 0, 0]),
495 rssi: None,
496 frequency: None,
497 timestamp_nanos: None,
498 ..Default::default()
499 },
500 WlanPolicyBss {
501 bssid: Some([0, 0, 0, 0, 0, 0]),
502 rssi: None,
503 frequency: None,
504 timestamp_nanos: None,
505 ..Default::default()
506 },
507 ];
508 let unique_bsses = (1..MAX_BSSES).map(|i| WlanPolicyBss {
509 bssid: Some(
510 BssId::try_from(&i.to_le_bytes()[0..BSS_ADDR_LEN_BYTES])
511 .expect("internal error"),
512 ),
513 rssi: None,
514 frequency: None,
515 timestamp_nanos: None,
516 ..Default::default()
517 });
518 let bsses: Vec<_> = duplicate_bsses.into_iter().chain(unique_bsses).collect();
519 let scan_results = vec![ScanResult {
520 id: None,
521 entries: Some(bsses),
522 compatibility: None,
523 ..Default::default()
524 }];
525 let _ = cache.update(FakeScanResultIterator::new_single_step(scan_results)).await;
526 assert_eq!(cache.iter().count(), MAX_BSSES);
527 }
528 }
529
530 mod single_call_failure {
531 use super::super::*;
532 use test_doubles::{FakeScanResultIterator, StubScanResultIterator};
533
534 #[fuchsia::test(allow_stalls = false)]
535 async fn returns_ipc_error_on_fidl_error() {
536 assert_eq!(
537 RealBssCache::new()
538 .update(StubScanResultIterator::new(|| Err(fidl::Error::InvalidHeader)))
539 .await,
540 Err(UpdateError::Ipc)
541 );
542 }
543
544 #[fuchsia::test(allow_stalls = false)]
545 async fn returns_service_error_on_general_scan_error() {
546 assert_eq!(
547 RealBssCache::new()
548 .update(StubScanResultIterator::new(|| Ok(Err(ScanErrorCode::GeneralError))))
549 .await,
550 Err(UpdateError::Service)
551 );
552 }
553
554 #[fuchsia::test(allow_stalls = false)]
555 async fn returns_no_bsses_error_on_empty_scan_results() {
556 assert_eq!(
557 RealBssCache::new().update(FakeScanResultIterator::new_single_step(vec![])).await,
558 Err(UpdateError::NoBsses)
559 );
560 }
561
562 #[fuchsia::test(allow_stalls = false)]
563 async fn returns_no_bsses_error_on_network_without_entries_vector() {
564 assert_eq!(
565 RealBssCache::new()
566 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
567 id: None,
568 entries: None,
569 compatibility: None,
570 ..Default::default()
571 }]))
572 .await,
573 Err(UpdateError::NoBsses)
574 );
575 }
576
577 #[fuchsia::test(allow_stalls = false)]
578 async fn returns_no_bsses_error_on_network_with_empty_entries_vector() {
579 assert_eq!(
580 RealBssCache::new()
581 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
582 id: None,
583 entries: Some(Vec::new()),
584 compatibility: None,
585 ..Default::default()
586 }]))
587 .await,
588 Err(UpdateError::NoBsses)
589 );
590 }
591
592 #[fuchsia::test(allow_stalls = false)]
593 async fn returns_no_bss_ids_error_on_bss_without_bssid() {
594 assert_eq!(
595 RealBssCache::new()
596 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
597 id: None,
598 entries: Some(vec![WlanPolicyBss {
599 bssid: None,
600 rssi: Some(-1),
601 frequency: Some(2414),
602 timestamp_nanos: Some(1),
603 ..Default::default()
604 }]),
605 compatibility: None,
606 ..Default::default()
607 }]))
608 .await,
609 Err(UpdateError::NoBssIds),
610 );
611 }
612 }
613
614 mod multiple_calls {
615 use super::super::*;
616 use fidl_fuchsia_wlan_policy::Compatibility::Supported;
617 use fidl_fuchsia_wlan_policy::NetworkIdentifier;
618 use fidl_fuchsia_wlan_policy::SecurityType::Wpa2;
619 use test_doubles::FakeScanResultIterator;
620
621 #[fuchsia::test(allow_stalls = false)]
622 async fn is_non_empty_after_new_non_empty_data() {
623 let mut cache = RealBssCache::new();
624 let _ = cache
625 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
626 id: None,
627 entries: Some(vec![WlanPolicyBss {
628 bssid: Some([0, 0, 0, 0, 0, 0]),
629 rssi: None,
630 frequency: None,
631 timestamp_nanos: None,
632 ..Default::default()
633 }]),
634 compatibility: None,
635 ..Default::default()
636 }]))
637 .await;
638 let _ = cache
639 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
640 id: None,
641 entries: Some(vec![WlanPolicyBss {
642 bssid: Some([1, 1, 1, 1, 1, 1]),
643 rssi: None,
644 frequency: None,
645 timestamp_nanos: None,
646 ..Default::default()
647 }]),
648 compatibility: None,
649 ..Default::default()
650 }]))
651 .await;
652
653 assert!(cache.iter().next().is_some());
664 }
665
666 #[fuchsia::test(allow_stalls = false)]
667 async fn is_non_empty_after_new_empty_data() {
668 let mut cache = RealBssCache::new();
669 let _ = cache
670 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
671 id: None,
672 entries: Some(vec![WlanPolicyBss {
673 bssid: Some([0, 0, 0, 0, 0, 0]),
674 rssi: None,
675 frequency: None,
676 timestamp_nanos: None,
677 ..Default::default()
678 }]),
679 compatibility: None,
680 ..Default::default()
681 }]))
682 .await;
683
684 let _ = cache
688 .update(FakeScanResultIterator::new_single_step(vec![ScanResult {
689 id: Some(NetworkIdentifier { ssid: vec![b'a'], type_: Wpa2 }),
690 entries: Some(vec![WlanPolicyBss {
691 bssid: None,
692 rssi: Some(-1),
693 frequency: Some(2412),
694 timestamp_nanos: Some(1),
695 ..Default::default()
696 }]),
697 compatibility: Some(Supported),
698 ..Default::default()
699 }]))
700 .await;
701
702 assert!(cache.iter().next().is_some());
707 }
708 }
709
710 mod multi_step_iteration {
711 use super::super::*;
712 use test_doubles::FakeScanResultIterator;
713
714 #[fuchsia::test(allow_stalls = false)]
715 async fn reads_all_scan_results() {
716 let mut cache = RealBssCache::new();
717 let result = cache
718 .update(FakeScanResultIterator::new_multi_step(vec![
719 vec![ScanResult {
720 id: None,
721 entries: Some(vec![WlanPolicyBss {
722 bssid: Some([0, 0, 0, 0, 0, 0]),
723 rssi: None,
724 frequency: None,
725 timestamp_nanos: None,
726 ..Default::default()
727 }]),
728 compatibility: None,
729 ..Default::default()
730 }],
731 vec![ScanResult {
732 id: None,
733 entries: Some(vec![WlanPolicyBss {
734 bssid: Some([1, 1, 1, 1, 1, 1]),
735 rssi: None,
736 frequency: None,
737 timestamp_nanos: None,
738 ..Default::default()
739 }]),
740 compatibility: None,
741 ..Default::default()
742 }],
743 ]))
744 .await;
745 assert_eq!(result, Ok(()));
746 assert_eq!(2, cache.iter().count());
747 }
748
749 #[fuchsia::test(allow_stalls = false)]
750 async fn finds_later_bsses_even_if_first_iteration_yields_no_bsses() {
751 let mut cache = RealBssCache::new();
752 let result = cache
753 .update(FakeScanResultIterator::new_multi_step(vec![
754 vec![ScanResult {
755 id: None,
756 entries: None,
757 compatibility: None,
758 ..Default::default()
759 }],
760 vec![ScanResult {
761 id: None,
762 entries: Some(vec![WlanPolicyBss {
763 bssid: Some([0, 0, 0, 0, 0, 0]),
764 rssi: None,
765 frequency: None,
766 timestamp_nanos: None,
767 ..Default::default()
768 }]),
769 compatibility: None,
770 ..Default::default()
771 }],
772 ]))
773 .await;
774 assert_eq!(result, Ok(()));
775 assert_eq!(1, cache.iter().count());
776 }
777
778 #[fuchsia::test(allow_stalls = false)]
779 async fn finds_later_bss_ids_even_if_first_iteration_yields_no_bss_ids() {
780 let mut cache = RealBssCache::new();
781 let result = cache
782 .update(FakeScanResultIterator::new_multi_step(vec![
783 vec![ScanResult {
784 id: None,
785 entries: Some(vec![WlanPolicyBss {
786 bssid: None,
787 rssi: None,
788 frequency: None,
789 timestamp_nanos: None,
790 ..Default::default()
791 }]),
792
793 compatibility: None,
794 ..Default::default()
795 }],
796 vec![ScanResult {
797 id: None,
798 entries: Some(vec![WlanPolicyBss {
799 bssid: Some([0, 0, 0, 0, 0, 0]),
800 rssi: None,
801 frequency: None,
802 timestamp_nanos: None,
803 ..Default::default()
804 }]),
805 compatibility: None,
806 ..Default::default()
807 }],
808 ]))
809 .await;
810 assert_eq!(result, Ok(()));
811 assert_eq!(1, cache.iter().count());
812 }
813 }
814
815 mod ipc_interactions {
816 use super::super::*;
817 use fidl_fuchsia_wlan_policy::NetworkIdentifier;
818 use fidl_fuchsia_wlan_policy::SecurityType::Wpa2;
819 use test_doubles::{RawStubScanResultIterator, StubScanResultIterator};
820
821 #[fuchsia::test(allow_stalls = false)]
822 async fn stops_sending_ipcs_when_get_next_yields_fidl_error() {
823 let mut cache = RealBssCache::new();
824 let mut scan_results = vec![Err(fidl::Error::InvalidHeader)].into_iter();
825 let _ = cache
826 .update(StubScanResultIterator::new(|| {
827 scan_results.next().expect("already consumed all `scan_results`")
828 }))
829 .await;
830 }
831
832 #[fuchsia::test(allow_stalls = false)]
833 async fn stops_sending_ipcs_when_get_next_yields_scan_error() {
834 let mut cache = RealBssCache::new();
835 let mut scan_results = vec![Ok(Err(ScanErrorCode::GeneralError))].into_iter();
836 let _ = cache
837 .update(StubScanResultIterator::new(|| {
838 scan_results.next().expect("already consumed all `scan_results`")
839 }))
840 .await;
841 }
842
843 #[fuchsia::test(allow_stalls = false)]
844 async fn stops_sending_ipcs_when_get_next_yields_empty_vec() {
845 let mut cache = RealBssCache::new();
846 let mut scan_results = vec![Ok(Ok(vec![]))].into_iter();
847 let _ = cache
848 .update(StubScanResultIterator::new(|| {
849 scan_results.next().expect("already consumed all `scan_results`")
850 }))
851 .await;
852 }
853
854 #[fuchsia::test(allow_stalls = false)]
855 async fn drives_pending_ipc_to_completion() {
856 let mut cache = RealBssCache::new();
857 let mut poll_results = vec![Poll::Pending, Poll::Ready(Ok(Ok(vec![])))].into_iter();
858 let mut futures = vec![futures::future::poll_fn(|cx| {
859 let res = poll_results.next().expect("already consumed all `poll_results`");
860 cx.waker().wake_by_ref();
861 res
862 })]
863 .into_iter();
864 let _ = cache
865 .update(RawStubScanResultIterator::new(|| {
866 futures.next().expect("already consumed all `futures`")
867 }))
868 .await;
869 }
870
871 #[fuchsia::test(allow_stalls = false)]
872 async fn honors_max_ipc_limit() {
873 let mut cache = RealBssCache::new();
874 let mut scan_results = (0..MAX_IPCS)
875 .map(|i| {
876 Ok(Ok(vec![ScanResult {
877 id: Some(NetworkIdentifier { ssid: i.to_le_bytes().to_vec(), type_: Wpa2 }),
878 entries: Some(vec![WlanPolicyBss {
879 bssid: Some(
880 BssId::try_from(&i.to_le_bytes()[0..BSS_ADDR_LEN_BYTES])
881 .expect("internal error"),
882 ),
883 rssi: None,
884 frequency: None,
885 timestamp_nanos: None,
886 ..Default::default()
887 }]),
888 compatibility: None,
889 ..Default::default()
890 }]))
891 })
892 .collect::<Vec<_>>()
893 .into_iter();
894 let _ = cache
895 .update(StubScanResultIterator::new(|| {
896 scan_results.next().expect("already consumed all `scan_results`")
897 }))
898 .await;
899 }
900 }
901}
902
903#[cfg(test)]
904mod test_doubles {
905 use super::*;
906 use fuchsia_sync::RwLock;
907 use futures::future::{Ready, ready};
908
909 pub(super) struct FakeScanResultIterator {
913 scan_results: RwLock<Vec<Vec<ScanResult>>>,
922 }
923
924 pub(super) struct StubScanResultIterator<F: FnMut() -> GetNextResponse>(RwLock<F>);
927
928 pub(super) struct RawStubScanResultIterator<F, R>(RwLock<F>)
931 where
932 F: FnMut() -> R,
933 R: Future<Output = GetNextResponse>;
934
935 impl FakeScanResultIterator {
936 pub(super) fn new_single_step(scan_results: Vec<ScanResult>) -> Self {
938 Self::new_multi_step(vec![scan_results])
939 }
940
941 pub(super) fn new_multi_step(scan_results: Vec<Vec<ScanResult>>) -> Self {
944 Self { scan_results: RwLock::new(scan_results) }
945 }
946 }
947
948 impl ScanResultIteratorProxyInterface for FakeScanResultIterator {
949 type GetNextResponseFut = Ready<GetNextResponse>;
950
951 fn get_next(&self) -> Self::GetNextResponseFut {
952 let mut scan_results = self.scan_results.write();
953 ready(Ok(Ok(if scan_results.is_empty() { Vec::new() } else { scan_results.remove(0) })))
954 }
955 }
956
957 impl<F: FnMut() -> GetNextResponse + Send + Sync> StubScanResultIterator<F> {
958 pub(super) fn new(get_next: F) -> Self {
959 Self(RwLock::new(get_next))
960 }
961 }
962
963 impl<F: FnMut() -> GetNextResponse + Send + Sync> ScanResultIteratorProxyInterface
964 for StubScanResultIterator<F>
965 {
966 type GetNextResponseFut = Ready<GetNextResponse>;
967
968 fn get_next(&self) -> Self::GetNextResponseFut {
969 let response_func = &mut *self.0.write();
971 ready(response_func())
972 }
973 }
974
975 impl<F, R> RawStubScanResultIterator<F, R>
976 where
977 F: FnMut() -> R + Send + Sync,
978 R: Future<Output = GetNextResponse> + Send,
979 {
980 pub(super) fn new(get_next: F) -> Self {
981 Self(RwLock::new(get_next))
982 }
983 }
984
985 impl<F, R> ScanResultIteratorProxyInterface for RawStubScanResultIterator<F, R>
986 where
987 F: FnMut() -> R + Send + Sync,
988 R: Future<Output = GetNextResponse> + Send,
989 {
990 type GetNextResponseFut = R;
991
992 fn get_next(&self) -> Self::GetNextResponseFut {
993 let response_func = &mut *self.0.write();
995 response_func()
996 }
997 }
998
999 mod tests {
1000 mod fake_scan_result_iterator {
1001 use super::super::*;
1002
1003 #[fuchsia::test(allow_stalls = false)]
1004 async fn single_step_yields_all_scan_results_at_once() {
1005 let iter = FakeScanResultIterator::new_single_step(vec![
1006 ScanResult {
1007 id: None,
1008 entries: None,
1009 compatibility: None,
1010 ..Default::default()
1011 },
1012 ScanResult {
1013 id: None,
1014 entries: None,
1015 compatibility: None,
1016 ..Default::default()
1017 },
1018 ]);
1019 assert_eq!(2, iter.get_next().await.unwrap().unwrap().len());
1020 }
1021
1022 #[fuchsia::test(allow_stalls = false)]
1023 async fn initially_empty_iterator_yields_empty_vec() {
1024 let iter = FakeScanResultIterator::new_single_step(Vec::new());
1025 assert_eq!(Vec::<ScanResult>::new(), iter.get_next().await.unwrap().unwrap());
1026 }
1027
1028 #[fuchsia::test(allow_stalls = false)]
1029 async fn emptied_iterator_yields_empty_vec() {
1030 let iter = FakeScanResultIterator::new_single_step(vec![ScanResult {
1031 id: None,
1032 entries: None,
1033 compatibility: None,
1034 ..Default::default()
1035 }]);
1036 let _ = iter.get_next().await.unwrap().unwrap();
1037 assert_eq!(Vec::<ScanResult>::new(), iter.get_next().await.unwrap().unwrap());
1038 }
1039
1040 #[fuchsia::test(allow_stalls = false)]
1041 async fn multi_step_yields_scan_results_iteratively() {
1042 let iter = FakeScanResultIterator::new_multi_step(vec![
1043 vec![ScanResult {
1044 id: None,
1045 entries: None,
1046 compatibility: None,
1047 ..Default::default()
1048 }],
1049 vec![ScanResult {
1050 id: None,
1051 entries: None,
1052 compatibility: None,
1053 ..Default::default()
1054 }],
1055 ]);
1056 assert_eq!(1, iter.get_next().await.unwrap().unwrap().len());
1057 assert_eq!(1, iter.get_next().await.unwrap().unwrap().len());
1058 assert_eq!(0, iter.get_next().await.unwrap().unwrap().len());
1059 }
1060 }
1061 }
1062}