Skip to main content

emergency_lib/
bss_cache.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    /// Updates the cache with BSSes from `new_bsses`.
21    async fn update<I: ScanResultIteratorProxyInterface>(
22        &mut self,
23        new_bsses: I,
24    ) -> Result<(), UpdateError>;
25
26    /// Returns an iterator over the known BSSes.
27    fn iter(&self) -> Box<dyn Iterator<Item = (&'_ BssId, &'_ Bss)> + '_>;
28}
29
30/// A cache for WLAN Basic Service Sets, also known as WLAN base-stations.
31#[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
56// Length of a BSS ID. Governed by IEEE standards.
57const BSS_ADDR_LEN_BYTES: usize = 6;
58
59// Upper bound on the number of BSSes cached. Our goals in tuning this value are:
60// * retain enough BSSes to give a tight radius of confidence, and
61// * minimize the potential for exhausting memory
62//
63// This value was chosen somewhat arbitrarily, and may be tuned based on field
64// data about the radius of confidence we achieve.
65const MAX_BSSES: usize = 20;
66
67// Upper bound on the number of IPCs to `ScanResultIterator`. Every non-terminal
68// IPC should yield at least one `ScanResult`. And, in normal operation, we expect
69// that a `ScanResult` will have at least one BSS. Hence, we set this limit to
70// equal the limit on BSSes.
71const 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        // If we have no results, report the appropriate error.
92        match iterator_service_result.peek() {
93            None => return Err(UpdateError::NoBsses), // First IPC yielded empty set.
94            Some(Err(e)) => return Err(*e),           // First IPC yielded error.
95            Some(Ok(_)) => (),
96        };
97
98        let mut bss_list = iterator_service_result
99            .filter_map(|res| res.ok()) // Since we have at least one result, ignore errors.
100            .flatten() // Flatten per-IPC `Vec`s into single `Vec`.
101            .flat_map(|network| network.entries) // Project `Vec` of BSSes out of each network.
102            .flatten() // Flatten per-network `Vec`s of BSSes to single `Vec`.
103            .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        // Deduplicate BSSs and truncate the cache down to MAX_BSSES (20) BSSs.
119        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
139// ScanResultStream adapts the FIDL `ScanResultIterator` into a Rust
140// `Stream`.  For more details, see the documentation for
141// `ScanResultStream::poll_next()`, below.
142struct 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    // Calls through to `ScanResultIterator.GetNext()`.
176    // * If the call yields a `FidlError` or a `ScanErrorCode`, returns an `UpdateError`,
177    //   and ensures that subsequent calls yield None.
178    // * If the call yields an empty `Vec` yields None.
179    // * Otherwise, returns the `Vec` from the call to `GetNext()`.
180    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
181        // Note: we `take()` `iterator_service` here, and only replace
182        // `iterator_service` if
183        // a) the IPC yielded a `ScanResult`, or
184        // b) the IPC is still pending.
185        //
186        // By doing so, we ensure that, if the IPC yielded an empty
187        // result, or an error, then subsequent calls to `poll_next()`
188        // a) will _not_ issue an IPC, and
189        // b) will return None.
190        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            // Note: we refrain from making a stronger assertion
654            // (e.g. that only the new BSS is retained), to avoid
655            // needing to revise this test if we change the caching
656            // policy in the future.
657            //
658            // Said differently, we believe that
659            // a) the assertion below will hold true under any reasonable
660            //    caching policy, and
661            // b) there is no pressing need to validate the more specific
662            //    behavior of the current caching policy.
663            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            // Note: we populate everything except `entries.bssid`, to
685            // ensure that the implementation doesn't short-circuit
686            // due to any other data being missing.
687            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            // Note: for now, we make the assumption that having
703            // _some_ location information is better than having none,
704            // even if the data we have is old. If this changes, we
705            // should remove this test.
706            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    // Test double that returns scan results from initially provided data.
910    // After exhausting the initial data, perpetually returns an empty `Vec`.
911    // Useful for testing success cases.
912    pub(super) struct FakeScanResultIterator {
913        // Why do we need an RwLock here?
914        //
915        // 1) We need to return a `Vec<ScanResult>`
916        // 2) `ScanResult` is not `Copy` or `Clone`
917        // 3) Given 1 and 2, `get_next()` needs to move data
918        //
919        // Given just the constraints above, we might consider `Cell` or `RefCell`. However,
920        // `ScanResultIteratorProxyInterface` is `Sync`, while `Cell` and `RefCell` are not.
921        scan_results: RwLock<Vec<Vec<ScanResult>>>,
922    }
923
924    // Test double that invokes a function which yields a `GetNextResponse`.
925    // Useful for testing error handling.
926    pub(super) struct StubScanResultIterator<F: FnMut() -> GetNextResponse>(RwLock<F>);
927
928    // Test double that invokes a function with yields a `GetNextResponse` `Future`.
929    // Useful for testing interaction with asynchronous IPCs.
930    pub(super) struct RawStubScanResultIterator<F, R>(RwLock<F>)
931    where
932        F: FnMut() -> R,
933        R: Future<Output = GetNextResponse>;
934
935    impl FakeScanResultIterator {
936        // Returns an iterator which yields `scan_results` all at once.
937        pub(super) fn new_single_step(scan_results: Vec<ScanResult>) -> Self {
938            Self::new_multi_step(vec![scan_results])
939        }
940
941        // Returns an iterator which yields one element of `scan_results` at a time.
942        // Note, however, that each element is _itself_ a `Vec<ScanResult>`.
943        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            // Note: the `&mut *` here is due to https://github.com/rust-lang/rust/issues/65489
970            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            // Note: the `&mut *` here is due to https://github.com/rust-lang/rust/issues/65489
994            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}