1use super::network_config::{
6 ConnectFailure, Credential, FailureReason, HIDDEN_PROBABILITY_HIGH, HiddenProbEvent,
7 NetworkConfig, NetworkConfigError, NetworkIdentifier, PastConnectionData, PastConnectionList,
8 SecurityType,
9};
10use super::stash_conversion::*;
11use crate::client::types::{self, ScanObservation};
12use crate::config_management::new_past_connection_list;
13use crate::telemetry::{TelemetryEvent, TelemetrySender};
14use anyhow::format_err;
15use async_trait::async_trait;
16use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
17use fidl_fuchsia_wlan_sme as fidl_sme;
18use fuchsia_async as fasync;
19use futures::lock::Mutex;
20use log::{error, info};
21use std::collections::{HashMap, HashSet};
22use wlan_storage::policy::{POLICY_STORAGE_ID, PolicyStorage};
23
24const MAX_SAVED_NETWORKS: usize = 1000;
25
26pub struct SavedNetworksManager {
32 saved_networks: Mutex<NetworkConfigMap>,
33 store: Mutex<PolicyStorage>,
36 telemetry_sender: TelemetrySender,
37}
38
39type NetworkConfigMap = HashMap<NetworkIdentifier, NetworkConfig>;
41
42#[async_trait(?Send)]
43pub trait SavedNetworksManagerApi {
44 async fn remove(&self, network_id: NetworkIdentifier) -> Result<bool, NetworkConfigError>;
47
48 async fn known_network_count(&self) -> usize;
50
51 async fn lookup(&self, id: &NetworkIdentifier) -> Option<NetworkConfig>;
53
54 async fn lookup_compatible(
58 &self,
59 ssid: &types::Ssid,
60 scan_security: types::SecurityTypeDetailed,
61 ) -> Vec<NetworkConfig>;
62
63 async fn store(
66 &self,
67 network_id: NetworkIdentifier,
68 credential: Credential,
69 ) -> Result<Option<NetworkConfig>, NetworkConfigError>;
70
71 async fn record_connect_result(
77 &self,
78 id: NetworkIdentifier,
79 credential: &Credential,
80 bssid: types::Bssid,
81 connect_result: fidl_sme::ConnectResult,
82 scan_type: types::ScanObservation,
83 );
84
85 async fn record_disconnect(
88 &self,
89 id: &NetworkIdentifier,
90 credential: &Credential,
91 data: PastConnectionData,
92 );
93
94 async fn record_periodic_metrics(&self);
95
96 async fn record_scan_result(
99 &self,
100 target_ssids: Vec<types::Ssid>,
101 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
102 );
103
104 async fn is_network_single_bss(
105 &self,
106 id: &NetworkIdentifier,
107 credential: &Credential,
108 ) -> Result<bool, anyhow::Error>;
109
110 async fn get_networks(&self) -> Vec<NetworkConfig>;
112
113 async fn get_past_connections(
115 &self,
116 id: &NetworkIdentifier,
117 credential: &Credential,
118 bssid: &types::Bssid,
119 ) -> PastConnectionList;
120}
121
122impl SavedNetworksManager {
123 pub async fn new(telemetry_sender: TelemetrySender) -> Self {
126 let storage = PolicyStorage::new_with_id(POLICY_STORAGE_ID).await;
127 Self::new_with_storage(storage, telemetry_sender).await
128 }
129
130 pub async fn new_with_storage(
132 mut store: PolicyStorage,
133 telemetry_sender: TelemetrySender,
134 ) -> Self {
135 let mut saved_networks: HashMap<NetworkIdentifier, NetworkConfig> = HashMap::new();
136 let stored_networks = store.load().await.unwrap_or_else(|e| {
140 error!("No saved networks loaded; error loading saved networks from storage: {}", e);
142 Vec::new()
143 });
144 let mut errors_building_configs = HashSet::new();
145
146 for persisted_data in stored_networks.into_iter() {
148 let id = NetworkIdentifier::new(
149 types::Ssid::from_bytes_unchecked(persisted_data.ssid),
150 persisted_data.security_type.into(),
151 );
152 let config = NetworkConfig::new(
153 id.clone(),
154 persisted_data.credential.clone().into(),
155 persisted_data.has_ever_connected,
156 persisted_data.hidden_probability,
157 );
158 match config {
159 Ok(config) => {
160 _ = saved_networks.insert(id, config);
161 }
162 Err(e) => {
163 _ = errors_building_configs.insert(e);
164 }
165 }
166 }
167
168 if !errors_building_configs.is_empty() {
170 error!(
171 "At least one error occurred building network config from persisted data: {:?}",
172 errors_building_configs
173 )
174 }
175
176 Self {
177 saved_networks: Mutex::new(saved_networks),
178 store: Mutex::new(store),
179 telemetry_sender,
180 }
181 }
182
183 #[cfg(test)]
186 pub async fn new_for_test() -> Self {
187 use crate::util::testing::generate_string;
188 use futures::channel::mpsc;
189
190 let store_id = generate_string();
191 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
192 let telemetry_sender = TelemetrySender::new(telemetry_sender);
193 let store = PolicyStorage::new_with_id(&store_id).await;
194 Self::new_with_storage(store, telemetry_sender).await
195 }
196
197 #[cfg(test)]
199 pub async fn clear(&self) -> Result<(), anyhow::Error> {
200 self.saved_networks.lock().await.clear();
201 self.store.lock().await.clear()
202 }
203}
204
205#[async_trait(?Send)]
206impl SavedNetworksManagerApi for SavedNetworksManager {
207 async fn remove(&self, network_id: NetworkIdentifier) -> Result<bool, NetworkConfigError> {
208 let mut saved_networks = self.saved_networks.lock().await;
209 if saved_networks.remove(&network_id).is_some() {
210 self.store
212 .lock()
213 .await
214 .write(persistent_data_from_config_map(&saved_networks))
215 .map_err(|e| {
216 error!("error writing network to persistent storage: {}", e);
217 NetworkConfigError::FileWriteError
218 })?;
219 return Ok(true);
220 } else {
221 let mut found_securities = SecurityType::list_variants();
224 found_securities.retain(|security| {
225 let id = NetworkIdentifier::new(network_id.ssid.clone(), *security);
226 saved_networks.contains_key(&id)
227 });
228 if found_securities.is_empty() {
229 info!("No network was found to remove with the provided SSID.");
230 } else {
231 info!(
232 "No config to remove with security type {:?}. Help: found different config(s) for this SSID with security {:?}",
233 network_id.security_type, found_securities
234 );
235 }
236 }
237 Ok(false)
238 }
239
240 async fn known_network_count(&self) -> usize {
242 self.saved_networks.lock().await.values().count()
243 }
244
245 async fn lookup(&self, id: &NetworkIdentifier) -> Option<NetworkConfig> {
250 self.saved_networks.lock().await.get(id).cloned()
251 }
252
253 async fn lookup_compatible(
254 &self,
255 ssid: &types::Ssid,
256 scan_security: types::SecurityTypeDetailed,
257 ) -> Vec<NetworkConfig> {
258 let saved_networks_guard = self.saved_networks.lock().await;
259 let mut matching_configs = Vec::new();
260 for security in compatible_policy_securities(&scan_security) {
261 let id = NetworkIdentifier::new(ssid.clone(), security);
262 if let Some(config) = saved_networks_guard.get(&id)
263 && security_is_compatible(&scan_security, &config.credential)
264 {
265 matching_configs.push(config.clone());
266 }
267 }
268 matching_configs
269 }
270
271 async fn store(
272 &self,
273 network_id: NetworkIdentifier,
274 credential: Credential,
275 ) -> Result<Option<NetworkConfig>, NetworkConfigError> {
276 let mut saved_networks = self.saved_networks.lock().await;
277 let num_saved_networks = saved_networks.len();
278
279 if let Some(config) = saved_networks.get(&network_id)
280 && config.credential == credential
281 {
282 info!("Saving a previously saved network with same password.");
283 return Ok(None);
284 }
285
286 if num_saved_networks >= MAX_SAVED_NETWORKS {
288 return Err(NetworkConfigError::MaxSavedNetworksReached);
289 }
290
291 let network_config =
292 NetworkConfig::new(network_id.clone(), credential.clone(), false, None)?;
293 let evicted_config = saved_networks.insert(network_id, network_config);
294
295 self.store.lock().await.write(persistent_data_from_config_map(&saved_networks)).map_err(
296 |e| {
297 error!("error writing network to persistent storage: {}", e);
298 NetworkConfigError::FileWriteError
299 },
300 )?;
301
302 Ok(evicted_config)
303 }
304
305 async fn record_connect_result(
306 &self,
307 id: NetworkIdentifier,
308 credential: &Credential,
309 bssid: types::Bssid,
310 connect_result: fidl_sme::ConnectResult,
311 scan_type: types::ScanObservation,
312 ) {
313 let mut saved_networks = self.saved_networks.lock().await;
314 let network = match saved_networks.get_mut(&id) {
315 Some(n) => n,
316 None => {
317 error!("Failed to find network to record result of connect attempt.");
318 return;
319 }
320 };
321 if &network.credential == credential {
322 match (connect_result.code, connect_result.is_credential_rejected) {
323 (fidl_ieee80211::StatusCode::Success, _) => {
324 let mut has_change = false;
325 let old_hidden_prob = network.hidden_probability;
326 if !network.has_ever_connected {
327 network.has_ever_connected = true;
328 has_change = true;
329 }
330 match scan_type {
332 types::ScanObservation::Passive => {
333 network.update_hidden_prob(HiddenProbEvent::ConnectPassive);
334 }
335 types::ScanObservation::Active => {
336 network.update_hidden_prob(HiddenProbEvent::ConnectActive);
337 }
338 types::ScanObservation::Unknown => {}
339 };
340
341 if network.hidden_probability != old_hidden_prob {
342 has_change = true;
343 }
344
345 if has_change {
346 let data = persistent_data_from_config_map(&saved_networks);
348 if let Err(e) = self.store.lock().await.write(data) {
349 info!("Failed to record successful connect in store: {}", e);
350 }
351 }
352 }
353 (fidl_ieee80211::StatusCode::Canceled, _) => {}
354 (_, true) => {
355 network.perf_stats.connect_failures.entry(bssid).or_default().add(
356 ConnectFailure {
357 time: fasync::MonotonicInstant::now(),
358 reason: FailureReason::CredentialRejected,
359 bssid,
360 },
361 );
362 }
363 (_, _) => {
364 network.perf_stats.connect_failures.entry(bssid).or_default().add(
365 ConnectFailure {
366 time: fasync::MonotonicInstant::now(),
367 reason: FailureReason::GeneralFailure,
368 bssid,
369 },
370 );
371 }
372 }
373 return;
374 }
375 error!("Failed to find matching network to record result of connect attempt.");
377 }
378
379 async fn record_disconnect(
380 &self,
381 id: &NetworkIdentifier,
382 credential: &Credential,
383 data: PastConnectionData,
384 ) {
385 let bssid = data.bssid;
386 let mut saved_networks = self.saved_networks.lock().await;
387 let network = match saved_networks.get_mut(id) {
388 Some(n) => n,
389 None => {
390 info!("Failed to find network to record disconnect stats");
391 return;
392 }
393 };
394 if &network.credential == credential {
395 network.perf_stats.past_connections.entry(bssid).or_default().add(data);
396 }
397 }
398
399 async fn record_periodic_metrics(&self) {
400 let saved_networks = self.saved_networks.lock().await;
401 let config_counts = saved_networks.iter().map(|_| 1).collect();
403 self.telemetry_sender.send(TelemetryEvent::SavedNetworkCount {
404 saved_network_count: saved_networks.len(),
405 config_count_per_saved_network: config_counts,
406 });
407 }
408
409 async fn record_scan_result(
410 &self,
411 target_ssids: Vec<types::Ssid>,
412 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
413 ) {
414 let mut saved_networks = self.saved_networks.lock().await;
415 let mut has_change = false;
416
417 for (network, bss_list) in results {
418 let has_multiple_bss = bss_list.len() > 1;
422 if bss_list.iter().any(|bss| bss.observation == ScanObservation::Passive) {
424 for security in compatible_policy_securities(&network.security_type) {
427 let config = match saved_networks
428 .get_mut(&NetworkIdentifier::new(network.ssid.clone(), security))
429 {
430 Some(config) => config,
431 None => continue,
432 };
433 if security_is_compatible(&network.security_type, &config.credential) {
436 let old_hidden_prob = config.hidden_probability;
437 config.update_hidden_prob(HiddenProbEvent::SeenPassive);
438 config.update_seen_multiple_bss(has_multiple_bss);
439 if config.hidden_probability != old_hidden_prob {
440 has_change = true;
441 }
442 }
443 }
444 }
445 }
446
447 for (id, config) in saved_networks.iter_mut() {
449 if !target_ssids.contains(&id.ssid) {
450 continue;
451 }
452 let potential_scan_results =
455 results.iter().filter(|(scan_id, _)| scan_id.ssid == id.ssid).collect::<Vec<_>>();
456 if !potential_scan_results.iter().any(|(scan_id, _)| {
457 compatible_policy_securities(&scan_id.security_type).contains(&config.security_type)
458 && security_is_compatible(&scan_id.security_type, &config.credential)
459 }) {
460 let old_hidden_prob = config.hidden_probability;
461 config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
462 if config.hidden_probability != old_hidden_prob {
463 has_change = true;
464 }
465 }
466 }
467 if has_change {
468 let data = persistent_data_from_config_map(&saved_networks);
469 if let Err(e) = self.store.lock().await.write(data) {
470 info!("Failed to record scan result updates in store: {}", e);
471 }
472 }
473 }
474
475 async fn is_network_single_bss(
480 &self,
481 id: &NetworkIdentifier,
482 credential: &Credential,
483 ) -> Result<bool, anyhow::Error> {
484 let saved_networks_guard = self.saved_networks.lock().await;
485 let config = saved_networks_guard.get(id).ok_or_else(|| {
486 format_err!(
487 "error checking if network is single BSS; no config with matching identifier"
488 )
489 })?;
490 if &config.credential != credential {
491 return Err(format_err!(
492 "error checking if network is single BSS; saved credential does not match"
493 ));
494 }
495 return Ok(config.is_likely_single_bss());
496 }
497
498 async fn get_networks(&self) -> Vec<NetworkConfig> {
499 self.saved_networks.lock().await.values().cloned().collect()
500 }
501
502 async fn get_past_connections(
503 &self,
504 id: &NetworkIdentifier,
505 credential: &Credential,
506 bssid: &types::Bssid,
507 ) -> PastConnectionList {
508 self.saved_networks
509 .lock()
510 .await
511 .get(id)
512 .filter(|config| &config.credential == credential)
513 .map(|config| {
514 config.perf_stats.past_connections.get(bssid).cloned().unwrap_or_default()
515 })
516 .unwrap_or_else(|| new_past_connection_list())
517 }
518}
519
520pub fn select_subset_potentially_hidden_networks(
523 saved_networks: Vec<NetworkConfig>,
524) -> Vec<types::NetworkIdentifier> {
525 saved_networks
526 .into_iter()
527 .filter(|saved_network| {
528 saved_network.hidden_probability > rand::random_range(0.0..1.0)
534 })
535 .map(|network| types::NetworkIdentifier {
536 ssid: network.ssid,
537 security_type: network.security_type,
538 })
539 .collect()
540}
541
542pub fn select_high_probability_hidden_networks(
544 saved_networks: Vec<NetworkConfig>,
545) -> Vec<types::NetworkIdentifier> {
546 saved_networks
547 .into_iter()
548 .filter(|saved_network| saved_network.hidden_probability >= HIDDEN_PROBABILITY_HIGH)
549 .map(|network| types::NetworkIdentifier {
550 ssid: network.ssid,
551 security_type: network.security_type,
552 })
553 .collect()
554}
555
556pub fn compatible_policy_securities(
562 detailed_security: &types::SecurityTypeDetailed,
563) -> Vec<SecurityType> {
564 use fidl_sme::Protection::*;
565 match detailed_security {
566 Wpa3Enterprise | Wpa3Personal | Wpa2Wpa3Personal => {
567 vec![SecurityType::Wpa2, SecurityType::Wpa3]
568 }
569 Wpa2Enterprise
570 | Wpa2Personal
571 | Wpa1Wpa2Personal
572 | Wpa2PersonalTkipOnly
573 | Wpa1Wpa2PersonalTkipOnly => vec![SecurityType::Wpa, SecurityType::Wpa2],
574 Wpa1 => vec![SecurityType::Wpa],
575 Wep => vec![SecurityType::Wep],
576 Owe => vec![SecurityType::None],
578 OpenOweTransition => vec![SecurityType::None],
579 Open => vec![SecurityType::None],
580 Unknown => vec![],
581 }
582}
583
584pub fn security_is_compatible(
585 scan_security: &types::SecurityTypeDetailed,
586 credential: &Credential,
587) -> bool {
588 if (scan_security == &types::SecurityTypeDetailed::Wpa3Personal
589 || scan_security == &types::SecurityTypeDetailed::Wpa3Enterprise)
590 && let Credential::Psk(_) = credential
591 {
592 return false;
593 }
594 true
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600 use crate::config_management::{
601 PROB_HIDDEN_DEFAULT, PROB_HIDDEN_IF_CONNECT_ACTIVE, PROB_HIDDEN_IF_CONNECT_PASSIVE,
602 PROB_HIDDEN_IF_SEEN_PASSIVE,
603 };
604 use crate::util::testing::{generate_random_bss, generate_string, random_connection_data};
605 use assert_matches::assert_matches;
606 use futures::channel::mpsc;
607 use futures::task::Poll;
608 use std::pin::pin;
609 use test_case::test_case;
610
611 #[fuchsia::test]
612 async fn store_and_lookup() {
613 let store_id = generate_string();
614 let saved_networks = create_saved_networks(&store_id).await;
615 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
616
617 assert!(saved_networks.lookup(&network_id_foo).await.is_none());
618 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
619 assert_eq!(0, saved_networks.known_network_count().await);
620
621 assert!(
623 saved_networks
624 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
625 .await
626 .expect("storing 'foo' failed")
627 .is_none()
628 );
629 assert_eq!(
630 Some(network_config("foo", "qwertyuio")),
631 saved_networks.lookup(&network_id_foo).await
632 );
633 assert_eq!(1, saved_networks.known_network_count().await);
634
635 let popped_network = saved_networks
637 .store(network_id_foo.clone(), Credential::Password(b"12345678".to_vec()))
638 .await
639 .expect("storing 'foo' a second time failed");
640 assert_eq!(popped_network, Some(network_config("foo", "qwertyuio")));
641
642 assert_eq!(
645 Some(network_config("foo", "12345678")),
646 saved_networks.lookup(&network_id_foo).await
647 );
648 assert_eq!(1, saved_networks.known_network_count().await);
649
650 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
652 let psk = Credential::Psk(vec![1; 32]);
653 let config_baz = NetworkConfig::new(network_id_baz.clone(), psk.clone(), false, None)
654 .expect("failed to create network config");
655 assert!(
656 saved_networks
657 .store(network_id_baz.clone(), psk)
658 .await
659 .expect("storing 'baz' with PSK failed")
660 .is_none()
661 );
662 assert_eq!(Some(config_baz.clone()), saved_networks.lookup(&network_id_baz).await);
663 assert_eq!(2, saved_networks.known_network_count().await);
664
665 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
667 let store = PolicyStorage::new_with_id(&store_id).await;
668
669 let saved_networks =
670 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
671 .await;
672 assert_eq!(
673 Some(network_config("foo", "12345678")),
674 saved_networks.lookup(&network_id_foo).await
675 );
676 assert_eq!(Some(config_baz), saved_networks.lookup(&network_id_baz).await);
677 assert_eq!(2, saved_networks.known_network_count().await);
678 }
679
680 #[fuchsia::test]
681 async fn store_twice() {
682 let saved_networks = SavedNetworksManager::new_for_test().await;
683 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
684
685 assert!(
686 saved_networks
687 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
688 .await
689 .expect("storing 'foo' failed")
690 .is_none()
691 );
692 let popped_network = saved_networks
693 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
694 .await
695 .expect("storing 'foo' a second time failed");
696 assert_eq!(popped_network, None);
698 let expected_cfg = Some(network_config("foo", "qwertyuio"));
699 assert_eq!(expected_cfg, saved_networks.lookup(&network_id).await);
700 assert_eq!(1, saved_networks.known_network_count().await);
701 }
702
703 #[fuchsia::test]
704 async fn store_many_same_ssid() {
705 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
706 let saved_networks = SavedNetworksManager::new_for_test().await;
707
708 for i in 0..3 {
710 let mut password = b"password".to_vec();
711 password.push(i as u8);
712 let popped_network = saved_networks
713 .store(network_id.clone(), Credential::Password(password))
714 .await
715 .expect("Failed to saved network");
716 if i >= 1 {
717 assert!(popped_network.is_some());
718 } else {
719 assert!(popped_network.is_none());
720 }
721 }
722
723 assert!(saved_networks.lookup(&network_id).await.is_some());
725 }
726
727 #[fuchsia::test]
728 async fn store_fails_when_max_saved_networks_reached() {
729 let saved_networks = SavedNetworksManager::new_for_test().await;
730
731 let mut map = HashMap::with_capacity(MAX_SAVED_NETWORKS);
734 for i in 0..MAX_SAVED_NETWORKS {
735 let ssid = format!("ssid_{i}");
736 let network_id =
737 NetworkIdentifier::try_from(ssid.as_str(), SecurityType::Wpa2).unwrap();
738 let config = NetworkConfig::new(
739 network_id.clone(),
740 Credential::Password(b"password123".to_vec()),
741 false,
742 None,
743 )
744 .unwrap();
745 assert!(map.insert(network_id, config).is_none());
746 }
747
748 {
749 let mut saved_map = saved_networks.saved_networks.lock().await;
750 *saved_map = map;
751 }
752
753 assert_eq!(saved_networks.known_network_count().await, MAX_SAVED_NETWORKS);
754
755 let new_network_id =
757 NetworkIdentifier::try_from("overflow_ssid", SecurityType::Wpa2).unwrap();
758 let result = saved_networks
759 .store(new_network_id, Credential::Password(b"password123".to_vec()))
760 .await;
761
762 assert_eq!(result, Err(NetworkConfigError::MaxSavedNetworksReached));
763 }
764
765 #[fuchsia::test]
766 async fn store_and_remove() {
767 let store_id = generate_string();
768 let saved_networks = create_saved_networks(&store_id).await;
769
770 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
771 let credential = Credential::Password(b"qwertyuio".to_vec());
772 assert!(saved_networks.lookup(&network_id).await.is_none());
773 assert_eq!(0, saved_networks.known_network_count().await);
774
775 assert!(
777 saved_networks
778 .store(network_id.clone(), credential.clone())
779 .await
780 .expect("storing 'foo' failed")
781 .is_none()
782 );
783 assert_eq!(
784 Some(network_config("foo", "qwertyuio")),
785 saved_networks.lookup(&network_id).await
786 );
787 assert_eq!(1, saved_networks.known_network_count().await);
788
789 assert!(saved_networks.remove(network_id.clone()).await.expect("removing 'foo' failed"));
791 assert_eq!(0, saved_networks.known_network_count().await);
792 assert!(saved_networks.saved_networks.lock().await.get(&network_id).is_none());
794
795 assert!(!saved_networks.remove(network_id.clone()).await.expect("removing 'foo' failed"));
797
798 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
800 let store = PolicyStorage::new_with_id(&store_id).await;
801 let saved_networks =
802 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
803 .await;
804 assert_eq!(0, saved_networks.known_network_count().await);
805 assert!(saved_networks.lookup(&network_id).await.is_none());
806 }
807
808 #[fuchsia::test]
809 fn sme_protection_converts_to_lower_compatible() {
810 use fidl_sme::Protection::*;
811 let lower_compatible_pairs = vec![
812 (Wpa3Enterprise, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
813 (Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
814 (Wpa2Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
815 (Wpa2Enterprise, vec![SecurityType::Wpa, SecurityType::Wpa2]),
816 (Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
817 (Wpa1Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
818 (Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
819 (Wpa1Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
820 (Wpa1, vec![SecurityType::Wpa]),
821 (Wep, vec![SecurityType::Wep]),
822 (Open, vec![SecurityType::None]),
823 (Unknown, vec![]),
824 ];
825 for (detailed_security, security) in lower_compatible_pairs {
826 assert_eq!(compatible_policy_securities(&detailed_security), security);
827 }
828 }
829
830 #[fuchsia::test]
831 async fn lookup_compatible_returns_both_compatible_configs() {
832 let saved_networks = SavedNetworksManager::new_for_test().await;
833 let ssid = types::Ssid::try_from("foo").unwrap();
834 let network_id_wpa2 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
835 let network_id_wpa3 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
836 let credential_wpa2 = Credential::Password(b"password".to_vec());
837 let credential_wpa3 = Credential::Password(b"wpa3-password".to_vec());
838
839 let results = saved_networks
842 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
843 .await;
844 assert!(results.is_empty());
845 assert_eq!(saved_networks.known_network_count().await, 0);
846
847 assert!(
850 saved_networks
851 .store(network_id_wpa2.clone(), credential_wpa2.clone())
852 .await
853 .expect("Failed to store network")
854 .is_none()
855 );
856 assert!(
857 saved_networks
858 .store(network_id_wpa3.clone(), credential_wpa3.clone())
859 .await
860 .expect("Failed to store network")
861 .is_none()
862 );
863 let network_id_wep = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa);
865 assert!(
866 saved_networks
867 .store(network_id_wep.clone(), Credential::Password(b"abcdefgh".to_vec()))
868 .await
869 .expect("Failed to store network")
870 .is_none()
871 );
872
873 let results = saved_networks
874 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
875 .await;
876 let expected_config_wpa2 =
877 NetworkConfig::new(network_id_wpa2, credential_wpa2, false, None)
878 .expect("Failed to create config");
879 let expected_config_wpa3 =
880 NetworkConfig::new(network_id_wpa3, credential_wpa3, false, None)
881 .expect("Failed to create config");
882 assert_eq!(results.len(), 2);
883 assert!(results.contains(&expected_config_wpa2));
884 assert!(results.contains(&expected_config_wpa3));
885 }
886
887 #[test_case(types::SecurityTypeDetailed::Wpa3Personal)]
888 #[test_case(types::SecurityTypeDetailed::Wpa3Enterprise)]
889 #[fuchsia::test(add_test_attr = false)]
890 fn lookup_compatible_does_not_return_wpa3_psk(
891 wpa3_detailed_security: types::SecurityTypeDetailed,
892 ) {
893 let mut exec = fasync::TestExecutor::new();
894 let saved_networks = exec.run_singlethreaded(SavedNetworksManager::new_for_test());
895
896 let ssid = types::Ssid::try_from("foo").unwrap();
899 let network_id_psk = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
900 let network_id_password = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
901 let credential_psk = Credential::Psk(vec![5; 32]);
902 let credential_password = Credential::Password(b"mypassword".to_vec());
903 assert!(
904 exec.run_singlethreaded(
905 saved_networks.store(network_id_psk.clone(), credential_psk.clone()),
906 )
907 .expect("Failed to store network")
908 .is_none()
909 );
910 assert!(
911 exec.run_singlethreaded(
912 saved_networks.store(network_id_password.clone(), credential_password.clone()),
913 )
914 .expect("Failed to store network")
915 .is_none()
916 );
917
918 let expected_config_wpa3 =
920 NetworkConfig::new(network_id_password, credential_password, false, None)
921 .expect("Failed to create configc");
922 let results = exec
923 .run_singlethreaded(saved_networks.lookup_compatible(&ssid, wpa3_detailed_security));
924 assert_eq!(results, vec![expected_config_wpa3]);
925 }
926
927 #[fuchsia::test]
928 async fn connect_network() {
929 let store_id = generate_string();
930
931 let saved_networks = create_saved_networks(&store_id).await;
932
933 let network_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
934 let credential = Credential::Password(b"password".to_vec());
935 let bssid = types::Bssid::from([4; 6]);
936
937 saved_networks
939 .record_connect_result(
940 network_id.clone(),
941 &credential,
942 bssid,
943 fake_successful_connect_result(),
944 types::ScanObservation::Unknown,
945 )
946 .await;
947 assert!(saved_networks.lookup(&network_id).await.is_none());
948 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
949 assert_eq!(0, saved_networks.known_network_count().await);
950
951 assert!(
953 saved_networks
954 .store(network_id.clone(), credential.clone())
955 .await
956 .expect("Failed save network")
957 .is_none()
958 );
959
960 let config = network_config("bar", "password");
961 assert_eq!(Some(config), saved_networks.lookup(&network_id).await);
962
963 saved_networks
964 .record_connect_result(
965 network_id.clone(),
966 &credential,
967 bssid,
968 fake_successful_connect_result(),
969 types::ScanObservation::Unknown,
970 )
971 .await;
972
973 assert_matches!(saved_networks.lookup(&network_id).await, Some(config) => {
976 assert!(config.has_ever_connected);
977 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
978 });
979
980 saved_networks
981 .record_connect_result(
982 network_id.clone(),
983 &credential,
984 bssid,
985 fake_successful_connect_result(),
986 types::ScanObservation::Active,
987 )
988 .await;
989 assert_matches!(saved_networks.lookup(&network_id).await, Some(config) => {
991 assert!(config.has_ever_connected);
992 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
993 });
994
995 saved_networks
996 .record_connect_result(
997 network_id.clone(),
998 &credential,
999 bssid,
1000 fake_successful_connect_result(),
1001 types::ScanObservation::Passive,
1002 )
1003 .await;
1004 assert_matches!(saved_networks.lookup(&network_id).await, Some(config) => {
1006 assert!(config.has_ever_connected);
1007 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1008 });
1009
1010 let store_reloaded = PolicyStorage::new_with_id(&store_id).await;
1012 let (telemetry_sender_reloaded, _) = mpsc::channel::<TelemetryEvent>(100);
1013 let saved_networks_reloaded = SavedNetworksManager::new_with_storage(
1014 store_reloaded,
1015 TelemetrySender::new(telemetry_sender_reloaded),
1016 )
1017 .await;
1018 assert_matches!(saved_networks_reloaded.lookup(&network_id).await, Some(config) => {
1019 assert!(config.has_ever_connected);
1020 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1021 });
1022 }
1023
1024 #[fuchsia::test]
1025 async fn test_record_connect_updates_one() {
1026 let saved_networks = SavedNetworksManager::new_for_test().await;
1027 let net_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1028 let net_id_also_valid = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1029 let credential = Credential::Password(b"some_password".to_vec());
1030 let bssid = types::Bssid::from([2; 6]);
1031
1032 assert!(
1034 saved_networks
1035 .store(net_id.clone(), credential.clone())
1036 .await
1037 .expect("Failed save network")
1038 .is_none()
1039 );
1040 assert!(
1041 saved_networks
1042 .store(net_id_also_valid.clone(), credential.clone())
1043 .await
1044 .expect("Failed save network")
1045 .is_none()
1046 );
1047 saved_networks
1048 .record_connect_result(
1049 net_id.clone(),
1050 &credential,
1051 bssid,
1052 fake_successful_connect_result(),
1053 types::ScanObservation::Unknown,
1054 )
1055 .await;
1056
1057 assert_matches!(saved_networks.lookup(&net_id).await, Some(config) => {
1058 assert!(config.has_ever_connected);
1059 });
1060 assert_matches!(saved_networks.lookup(&net_id_also_valid).await, Some(config) => {
1063 assert!(!config.has_ever_connected);
1064 });
1065 }
1066
1067 #[fuchsia::test]
1068 async fn test_record_connect_failure() {
1069 let saved_networks = SavedNetworksManager::new_for_test().await;
1070 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1071 let credential = Credential::None;
1072 let bssid = types::Bssid::from([1; 6]);
1073 let before_recording = fasync::MonotonicInstant::now();
1074
1075 saved_networks
1077 .record_connect_result(
1078 network_id.clone(),
1079 &credential,
1080 bssid,
1081 fidl_sme::ConnectResult {
1082 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1083 ..fake_successful_connect_result()
1084 },
1085 types::ScanObservation::Unknown,
1086 )
1087 .await;
1088 assert!(saved_networks.lookup(&network_id).await.is_none());
1089 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1090 assert_eq!(0, saved_networks.known_network_count().await);
1091
1092 assert!(
1094 saved_networks
1095 .store(network_id.clone(), credential.clone())
1096 .await
1097 .expect("Failed save network")
1098 .is_none()
1099 );
1100 saved_networks
1101 .record_connect_result(
1102 network_id.clone(),
1103 &credential,
1104 bssid,
1105 fidl_sme::ConnectResult {
1106 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1107 ..fake_successful_connect_result()
1108 },
1109 types::ScanObservation::Unknown,
1110 )
1111 .await;
1112 saved_networks
1113 .record_connect_result(
1114 network_id.clone(),
1115 &credential,
1116 bssid,
1117 fidl_sme::ConnectResult {
1118 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1119 is_credential_rejected: true,
1120 ..fake_successful_connect_result()
1121 },
1122 types::ScanObservation::Unknown,
1123 )
1124 .await;
1125
1126 assert_eq!(1, saved_networks.known_network_count().await);
1128 let saved_config =
1129 saved_networks.lookup(&network_id).await.expect("Failed to get saved network config");
1130 let connect_failures = saved_config.get_recent_connection_failures(before_recording);
1131 assert_matches!(connect_failures, failures => {
1132 assert_eq!(failures.len(), 2);
1134 assert!(failures.iter().any(|failure| failure.reason == FailureReason::GeneralFailure));
1135 assert!(failures.iter().any(|failure| failure.reason == FailureReason::CredentialRejected));
1136 for failure in failures.iter() {
1138 assert_eq!(failure.bssid, bssid);
1139 assert_eq!(failure.bssid, bssid);
1140 }
1141 });
1142 }
1143
1144 #[fuchsia::test]
1145 async fn test_record_connect_cancelled_ignored() {
1146 let saved_networks = SavedNetworksManager::new_for_test().await;
1147 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1148 let credential = Credential::None;
1149 let bssid = types::Bssid::from([0; 6]);
1150 let before_recording = fasync::MonotonicInstant::now();
1151
1152 saved_networks
1154 .record_connect_result(
1155 network_id.clone(),
1156 &credential,
1157 bssid,
1158 fidl_sme::ConnectResult {
1159 code: fidl_ieee80211::StatusCode::Canceled,
1160 ..fake_successful_connect_result()
1161 },
1162 types::ScanObservation::Unknown,
1163 )
1164 .await;
1165 assert!(saved_networks.lookup(&network_id).await.is_none());
1166 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1167 assert_eq!(0, saved_networks.known_network_count().await);
1168
1169 assert!(
1171 saved_networks
1172 .store(network_id.clone(), credential.clone())
1173 .await
1174 .expect("Failed save network")
1175 .is_none()
1176 );
1177 saved_networks
1178 .record_connect_result(
1179 network_id.clone(),
1180 &credential,
1181 bssid,
1182 fidl_sme::ConnectResult {
1183 code: fidl_ieee80211::StatusCode::Canceled,
1184 ..fake_successful_connect_result()
1185 },
1186 types::ScanObservation::Unknown,
1187 )
1188 .await;
1189
1190 assert_eq!(1, saved_networks.known_network_count().await);
1192 let saved_config =
1193 saved_networks.lookup(&network_id).await.expect("Failed to get saved network config");
1194 let connect_failures = saved_config.get_recent_connection_failures(before_recording);
1195 assert_eq!(0, connect_failures.len());
1196 }
1197
1198 #[fuchsia::test]
1199 async fn test_record_disconnect() {
1200 let saved_networks = SavedNetworksManager::new_for_test().await;
1201 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1202 let credential = Credential::Psk(vec![1; 32]);
1203 let data = random_connection_data();
1204
1205 saved_networks.record_disconnect(&id, &credential, data).await;
1206 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1208 assert_eq!(saved_networks.known_network_count().await, 0);
1209
1210 assert!(
1212 saved_networks
1213 .store(id.clone(), credential.clone())
1214 .await
1215 .expect("Failed to save network")
1216 .is_none()
1217 );
1218 saved_networks.record_disconnect(&id, &credential, data).await;
1219
1220 let saved_config = saved_networks.lookup(&id).await.expect("Failed to get saved network");
1222 let recent_connections =
1223 saved_config.get_recent_connections(fasync::MonotonicInstant::INFINITE_PAST);
1224 assert_matches!(recent_connections.as_slice(), [connection_data] => {
1225 assert_eq!(connection_data, &data);
1226 })
1227 }
1228
1229 #[fuchsia::test]
1230 async fn test_record_undirected_scan() {
1231 let store_id = generate_string();
1232 let saved_networks = create_saved_networks(&store_id).await;
1233 let saved_seen_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1234 let saved_seen_network = types::NetworkIdentifierDetailed {
1235 ssid: saved_seen_id.ssid.clone(),
1236 security_type: types::SecurityTypeDetailed::Open,
1237 };
1238 let unsaved_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
1239 let unsaved_network = types::NetworkIdentifierDetailed {
1240 ssid: unsaved_id.ssid.clone(),
1241 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1242 };
1243 let saved_unseen_id = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1244 let seen_credential = Credential::None;
1245 let unseen_credential = Credential::Password(b"password".to_vec());
1246
1247 assert!(
1249 saved_networks
1250 .store(saved_seen_id.clone(), seen_credential.clone())
1251 .await
1252 .expect("Failed to save network")
1253 .is_none()
1254 );
1255 assert!(
1256 saved_networks
1257 .store(saved_unseen_id.clone(), unseen_credential.clone())
1258 .await
1259 .expect("Failed to save network")
1260 .is_none()
1261 );
1262
1263 let results: HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>> = HashMap::from([
1265 (
1266 saved_seen_network,
1267 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1268 ),
1269 (unsaved_network, vec![generate_random_bss()]),
1270 ]);
1271
1272 saved_networks
1273 .record_scan_result(vec!["some_other_ssid".try_into().unwrap()], &results)
1274 .await;
1275
1276 assert_matches!(saved_networks.lookup(&saved_seen_id).await, Some(config) => {
1277 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1278 });
1279 assert_matches!(saved_networks.lookup(&saved_unseen_id).await, Some(config) => {
1280 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1281 });
1282
1283 let store_reloaded = PolicyStorage::new_with_id(&store_id).await;
1285 let (telemetry_sender_reloaded, _) = mpsc::channel::<TelemetryEvent>(100);
1286 let saved_networks_reloaded = SavedNetworksManager::new_with_storage(
1287 store_reloaded,
1288 TelemetrySender::new(telemetry_sender_reloaded),
1289 )
1290 .await;
1291 assert_matches!(saved_networks_reloaded.lookup(&saved_seen_id).await, Some(config) => {
1292 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1293 });
1294 assert_matches!(saved_networks_reloaded.lookup(&saved_unseen_id).await, Some(config) => {
1295 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1296 });
1297 }
1298
1299 #[fuchsia::test]
1300 async fn test_record_undirected_scan_with_upgraded_security() {
1301 let saved_networks = SavedNetworksManager::new_for_test().await;
1304 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1305 let credential = Credential::Password(b"credential".to_vec());
1306
1307 assert!(
1309 saved_networks
1310 .store(id.clone(), credential.clone())
1311 .await
1312 .expect("Failed to save network")
1313 .is_none()
1314 );
1315
1316 let results = HashMap::from([(
1318 types::NetworkIdentifierDetailed {
1319 ssid: id.ssid.clone(),
1320 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1321 },
1322 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1323 )]);
1324 saved_networks.record_scan_result(vec![], &results).await;
1325 assert_matches!(saved_networks.lookup(&id).await, Some(config) => {
1327 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1328 });
1329 }
1330
1331 #[fuchsia::test]
1332 async fn test_record_undirected_scan_incompatible_credential() {
1333 let saved_networks = SavedNetworksManager::new_for_test().await;
1336 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1337 let credential = Credential::Psk(vec![8; 32]);
1338
1339 assert!(
1341 saved_networks
1342 .store(id.clone(), credential.clone())
1343 .await
1344 .expect("Failed to save network")
1345 .is_none()
1346 );
1347
1348 let results = HashMap::from([(
1350 types::NetworkIdentifierDetailed {
1351 ssid: id.ssid.clone(),
1352 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1353 },
1354 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1355 )]);
1356 saved_networks.record_scan_result(vec![], &results).await;
1357 assert_matches!(saved_networks.lookup(&id).await, Some(config) => {
1360 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1361 });
1362 }
1363
1364 #[fuchsia::test]
1365 async fn test_record_directed_scan_for_upgraded_security() {
1366 let saved_networks = SavedNetworksManager::new_for_test().await;
1369 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa).unwrap();
1370 let credential = Credential::Password(b"credential".to_vec());
1371
1372 assert!(
1374 saved_networks
1375 .store(id.clone(), credential.clone())
1376 .await
1377 .expect("Failed to save network")
1378 .is_none()
1379 );
1380 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1381 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1382
1383 let results = HashMap::from([(
1386 types::NetworkIdentifierDetailed {
1387 ssid: id.ssid.clone(),
1388 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1389 },
1390 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1391 )]);
1392 let target = vec![id.ssid.clone()];
1393 saved_networks.record_scan_result(target, &results).await;
1394
1395 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1396 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1397 }
1398
1399 #[fuchsia::test]
1400 async fn test_record_directed_scan_for_incompatible_credential() {
1401 let saved_networks = SavedNetworksManager::new_for_test().await;
1405 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1406 let credential = Credential::Psk(vec![11; 32]);
1407
1408 assert!(
1410 saved_networks
1411 .store(id.clone(), credential.clone())
1412 .await
1413 .expect("Failed to save network")
1414 .is_none()
1415 );
1416 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1417 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1418
1419 let target = vec![id.ssid.clone()];
1422 let results = HashMap::from([(
1423 types::NetworkIdentifierDetailed {
1424 ssid: id.ssid.clone(),
1425 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1426 },
1427 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1428 )]);
1429 saved_networks.record_scan_result(target, &results).await;
1430 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1433 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1434 }
1435
1436 #[fuchsia::test]
1437 async fn test_record_directed_scan_no_ssid_match() {
1438 let saved_networks = SavedNetworksManager::new_for_test().await;
1442 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1443 let credential = Credential::Psk(vec![11; 32]);
1444 let diff_ssid = types::Ssid::try_from("other-ssid").unwrap();
1445
1446 assert!(
1448 saved_networks
1449 .store(id.clone(), credential.clone())
1450 .await
1451 .expect("Failed to save network")
1452 .is_none()
1453 );
1454 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1455 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1456
1457 let target = vec![id.ssid.clone()];
1459 let results = HashMap::from([(
1460 types::NetworkIdentifierDetailed {
1461 ssid: diff_ssid,
1462 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1463 },
1464 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1465 )]);
1466 saved_networks.record_scan_result(target, &results).await;
1467
1468 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1469 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1470 }
1471
1472 #[fuchsia::test]
1473 async fn test_record_directed_one_not_compatible_one_compatible() {
1474 let saved_networks = SavedNetworksManager::new_for_test().await;
1478 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1479 let credential = Credential::Password(b"foo-pass".to_vec());
1480
1481 assert!(
1483 saved_networks
1484 .store(id.clone(), credential.clone())
1485 .await
1486 .expect("Failed to save network")
1487 .is_none()
1488 );
1489 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1490 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1491
1492 let target = vec![id.ssid.clone()];
1495 let results = HashMap::from([
1496 (
1497 types::NetworkIdentifierDetailed {
1498 ssid: id.ssid.clone(),
1499 security_type: types::SecurityTypeDetailed::Wpa1,
1500 },
1501 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1502 ),
1503 (
1504 types::NetworkIdentifierDetailed {
1505 ssid: id.ssid.clone(),
1506 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1507 },
1508 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1509 ),
1510 ]);
1511 saved_networks.record_scan_result(target, &results).await;
1512 let config = saved_networks.lookup(&id).await.expect("failed to lookup config");
1515 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1516 }
1517
1518 #[fuchsia::test]
1519 async fn test_record_both_directed_and_undirected() {
1520 let saved_networks = SavedNetworksManager::new_for_test().await;
1521 let saved_undirected_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1522 let saved_undirected_network = types::NetworkIdentifierDetailed {
1523 ssid: saved_undirected_id.ssid.clone(),
1524 security_type: types::SecurityTypeDetailed::Open,
1525 };
1526 let saved_directed_id = NetworkIdentifier::try_from("bar", SecurityType::None).unwrap();
1527 let credential = Credential::None;
1528
1529 assert!(
1531 saved_networks
1532 .store(saved_undirected_id.clone(), credential.clone())
1533 .await
1534 .expect("Failed to save network")
1535 .is_none()
1536 );
1537 assert!(
1538 saved_networks
1539 .store(saved_directed_id.clone(), credential.clone())
1540 .await
1541 .expect("Failed to save network")
1542 .is_none()
1543 );
1544
1545 assert_matches!(saved_networks.lookup(&saved_directed_id).await, Some(config) => {
1547 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1548 });
1549
1550 let results = HashMap::from([(
1552 saved_undirected_network,
1553 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1554 )]);
1555 saved_networks.record_scan_result(vec![saved_directed_id.ssid.clone()], &results).await;
1556
1557 assert_matches!(saved_networks.lookup(&saved_undirected_id).await, Some(config) => {
1559 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1560 });
1561 assert_matches!(saved_networks.lookup(&saved_directed_id).await, Some(config) => {
1563 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1564 });
1565 }
1566
1567 #[fuchsia::test]
1568 async fn clear() {
1569 let store_id = "clear";
1570 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1571 let saved_networks = create_saved_networks(store_id).await;
1572
1573 assert!(
1574 saved_networks
1575 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
1576 .await
1577 .expect("storing 'foo' failed")
1578 .is_none()
1579 );
1580 assert_eq!(
1581 Some(network_config("foo", "qwertyuio")),
1582 saved_networks.lookup(&network_id).await
1583 );
1584 assert_eq!(1, saved_networks.known_network_count().await);
1585
1586 saved_networks.clear().await.expect("failed to clear saved networks");
1587 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1588 assert_eq!(0, saved_networks.known_network_count().await);
1589
1590 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1592 let store = PolicyStorage::new_with_id(store_id).await;
1593 let saved_networks =
1594 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1595 .await;
1596
1597 assert_eq!(0, saved_networks.known_network_count().await);
1598 }
1599
1600 impl std::fmt::Debug for SavedNetworksManager {
1601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1602 f.debug_struct("SavedNetworksManager")
1603 .field("saved_networks", &self.saved_networks)
1604 .finish()
1605 }
1606 }
1607
1608 #[fuchsia::test]
1609 fn test_store_errors_cause_write_errors() {
1610 use fidl::endpoints::create_request_stream;
1611 use fidl_fuchsia_stash as fidl_stash;
1612 use futures::StreamExt;
1613 use std::sync::Arc;
1614 use std::sync::atomic::{AtomicBool, Ordering};
1615
1616 let store_path_str = "/////";
1618 let mut exec = fasync::TestExecutor::new();
1619
1620 let (stash_client, mut request_stream) =
1623 create_request_stream::<fidl_stash::SecureStoreMarker>();
1624
1625 let read_from_stash = Arc::new(AtomicBool::new(false));
1626
1627 let _task = {
1628 let read_from_stash = read_from_stash.clone();
1629 fasync::Task::local(async move {
1630 while let Some(request) = request_stream.next().await {
1631 match request.unwrap() {
1632 fidl_stash::SecureStoreRequest::Identify { .. } => {}
1633 fidl_stash::SecureStoreRequest::CreateAccessor {
1634 accessor_request, ..
1635 } => {
1636 let read_from_stash = read_from_stash.clone();
1637 fuchsia_async::EHandle::local().spawn_detached(async move {
1638 let mut request_stream = accessor_request.into_stream();
1639 while let Some(request) = request_stream.next().await {
1640 match request.unwrap() {
1641 fidl_stash::StoreAccessorRequest::ListPrefix { .. } => {
1642 read_from_stash.store(true, Ordering::Relaxed);
1643 }
1646 _ => unreachable!(),
1647 }
1648 }
1649 });
1650 }
1651 }
1652 }
1653 })
1654 };
1655
1656 let store =
1658 PolicyStorage::new_with_stash_proxy_and_id(stash_client.into_proxy(), store_path_str);
1659
1660 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1663 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1664 let init_fut = SavedNetworksManager::new_with_storage(store, telemetry_sender);
1665 let mut init_fut = pin!(init_fut);
1666 let saved_networks = assert_matches!(exec.run_until_stalled(&mut init_fut), Poll::Ready(snm) => {
1667 snm
1668 });
1669
1670 let ssid = "foo";
1672 let credential = Credential::None;
1673 let network_id = NetworkIdentifier::try_from(ssid, SecurityType::None).unwrap();
1674 let save_fut = saved_networks.store(network_id.clone(), credential);
1675 let mut save_fut = pin!(save_fut);
1676
1677 assert_matches!(
1678 exec.run_until_stalled(&mut save_fut),
1679 Poll::Ready(Err(NetworkConfigError::FileWriteError))
1680 );
1681
1682 assert_matches!(exec.run_until_stalled(&mut saved_networks.lookup(&network_id)), Poll::Ready(config) => {
1684 assert_eq!(config, Some(network_config(ssid, "")));
1685 });
1686 assert_matches!(exec.run_until_stalled(&mut saved_networks.known_network_count()), Poll::Ready(count) => {
1687 assert_eq!(count, 1);
1688 });
1689 }
1690
1691 async fn create_saved_networks(store_id: &str) -> SavedNetworksManager {
1694 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1695 let store = PolicyStorage::new_with_id(store_id).await;
1696 let saved_networks =
1697 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1698 .await;
1699 saved_networks.clear().await.expect("failed to clear saved networks");
1700 saved_networks
1701 }
1702
1703 fn network_config(ssid: &str, password: impl Into<Vec<u8>>) -> NetworkConfig {
1707 let credential = Credential::from_bytes(password.into());
1708 let id = NetworkIdentifier::try_from(ssid, credential.derived_security_type()).unwrap();
1709 let has_ever_connected = false;
1710 NetworkConfig::new(id, credential, has_ever_connected, None).unwrap()
1711 }
1712
1713 #[fuchsia::test]
1714 async fn record_metrics_when_called_on_class() {
1715 let store_id = generate_string();
1716 let (telemetry_sender, mut telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1717 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1718 let store = PolicyStorage::new_with_id(&store_id).await;
1719
1720 let saved_networks = SavedNetworksManager::new_with_storage(store, telemetry_sender).await;
1721 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1722 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1723
1724 assert!(saved_networks.lookup(&network_id_foo).await.is_none());
1725 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1726 assert_eq!(0, saved_networks.known_network_count().await);
1727
1728 assert!(
1730 saved_networks
1731 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
1732 .await
1733 .expect("storing 'foo' failed")
1734 .is_none()
1735 );
1736 assert_eq!(1, saved_networks.known_network_count().await);
1737
1738 assert!(
1740 saved_networks
1741 .store(network_id_baz.clone(), Credential::Psk(vec![1; 32]))
1742 .await
1743 .expect("storing 'baz' with PSK failed")
1744 .is_none()
1745 );
1746 assert_eq!(2, saved_networks.known_network_count().await);
1747
1748 saved_networks.record_periodic_metrics().await;
1750
1751 assert_matches!(telemetry_receiver.try_recv(), Ok(TelemetryEvent::SavedNetworkCount { saved_network_count, config_count_per_saved_network }) => {
1753 assert_eq!(saved_network_count, 2);
1754 assert_eq!(config_count_per_saved_network, [1, 1]);
1755 });
1756 }
1757
1758 #[fuchsia::test]
1759 async fn probabilistic_choosing_of_hidden_networks() {
1760 let id_hidden = types::NetworkIdentifier {
1762 ssid: types::Ssid::try_from("hidden").unwrap(),
1763 security_type: types::SecurityType::Wpa2,
1764 };
1765 let net_config_hidden = NetworkConfig::new(
1766 id_hidden.clone(),
1767 Credential::Password(b"password".to_vec()),
1768 false,
1769 Some(1.0),
1770 )
1771 .expect("failed to create network config");
1772
1773 let id_not_hidden = types::NetworkIdentifier {
1774 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1775 security_type: types::SecurityType::Wpa2,
1776 };
1777 let net_config_not_hidden = NetworkConfig::new(
1778 id_not_hidden.clone(),
1779 Credential::Password(b"password".to_vec()),
1780 false,
1781 Some(0.0),
1782 )
1783 .expect("failed to create network config");
1784
1785 let id_maybe_hidden = types::NetworkIdentifier {
1786 ssid: types::Ssid::try_from("maybe_hidden").unwrap(),
1787 security_type: types::SecurityType::Wpa2,
1788 };
1789 let net_config_maybe_hidden = NetworkConfig::new(
1790 id_maybe_hidden.clone(),
1791 Credential::Password(b"password".to_vec()),
1792 false,
1793 Some(0.5),
1794 )
1795 .expect("failed to create network config");
1796
1797 let mut maybe_hidden_selection_count = 0;
1798 let mut hidden_selection_count = 0;
1799
1800 for _ in 1..100 {
1802 let selected_networks = select_subset_potentially_hidden_networks(vec![
1803 net_config_hidden.clone(),
1804 net_config_not_hidden.clone(),
1805 net_config_maybe_hidden.clone(),
1806 ]);
1807 assert!(selected_networks.contains(&id_hidden));
1809 assert!(!selected_networks.contains(&id_not_hidden));
1811
1812 if selected_networks.contains(&id_maybe_hidden) {
1814 maybe_hidden_selection_count += 1;
1815 }
1816 if selected_networks.contains(&id_hidden) {
1817 hidden_selection_count += 1;
1818 }
1819 }
1820
1821 assert!(maybe_hidden_selection_count > 0);
1826 assert!(maybe_hidden_selection_count < hidden_selection_count);
1827 }
1828
1829 #[fuchsia::test]
1830 async fn test_select_high_probability_hidden_networks() {
1831 let id_hidden = types::NetworkIdentifier {
1833 ssid: types::Ssid::try_from("hidden").unwrap(),
1834 security_type: types::SecurityType::Wpa2,
1835 };
1836 let net_config_hidden = NetworkConfig::new(
1837 id_hidden.clone(),
1838 Credential::Password(b"password".to_vec()),
1839 false,
1840 Some(1.0),
1841 )
1842 .expect("failed to create network config");
1843
1844 let id_maybe_hidden_high = types::NetworkIdentifier {
1845 ssid: types::Ssid::try_from("maybe_hidden_high").unwrap(),
1846 security_type: types::SecurityType::Wpa2,
1847 };
1848 let net_config_maybe_hidden_high = NetworkConfig::new(
1849 id_maybe_hidden_high.clone(),
1850 Credential::Password(b"password".to_vec()),
1851 false,
1852 Some(0.8),
1853 )
1854 .expect("failed to create network config");
1855
1856 let id_maybe_hidden_low = types::NetworkIdentifier {
1857 ssid: types::Ssid::try_from("maybe_hidden_low").unwrap(),
1858 security_type: types::SecurityType::Wpa2,
1859 };
1860 let net_config_maybe_hidden_low = NetworkConfig::new(
1861 id_maybe_hidden_low.clone(),
1862 Credential::Password(b"password".to_vec()),
1863 false,
1864 Some(0.7),
1865 )
1866 .expect("failed to create network config");
1867
1868 let id_not_hidden = types::NetworkIdentifier {
1869 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1870 security_type: types::SecurityType::Wpa2,
1871 };
1872 let net_config_not_hidden = NetworkConfig::new(
1873 id_not_hidden.clone(),
1874 Credential::Password(b"password".to_vec()),
1875 false,
1876 Some(0.0),
1877 )
1878 .expect("failed to create network config");
1879
1880 let selected_networks = select_high_probability_hidden_networks(vec![
1881 net_config_hidden.clone(),
1882 net_config_maybe_hidden_high.clone(),
1883 net_config_maybe_hidden_low.clone(),
1884 net_config_not_hidden.clone(),
1885 ]);
1886
1887 assert!(selected_networks.contains(&id_hidden));
1889 assert!(selected_networks.contains(&id_maybe_hidden_high));
1891 assert!(!selected_networks.contains(&id_maybe_hidden_low));
1893 assert!(!selected_networks.contains(&id_not_hidden));
1895 }
1896
1897 #[fuchsia::test]
1898 async fn test_record_not_seen_active_scan() {
1899 let saved_networks = SavedNetworksManager::new_for_test().await;
1902
1903 let id_1 = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1905 let credential_1 = Credential::Password(b"some_password".to_vec());
1906 let id_2 = NetworkIdentifier::try_from("bar", SecurityType::Wpa3).unwrap();
1907 let credential_2 = Credential::Password(b"another_password".to_vec());
1908 let id_3 = NetworkIdentifier::try_from("baz", SecurityType::None).unwrap();
1910 let id_4 = NetworkIdentifier::try_from("foobar", SecurityType::None).unwrap();
1912 let credential_4 = Credential::None;
1913
1914 assert!(
1916 saved_networks
1917 .store(id_1.clone(), credential_1)
1918 .await
1919 .expect("failed to store network")
1920 .is_none()
1921 );
1922 assert!(
1923 saved_networks
1924 .store(id_2.clone(), credential_2)
1925 .await
1926 .expect("failed to store network")
1927 .is_none()
1928 );
1929 assert!(
1930 saved_networks
1931 .store(id_4.clone(), credential_4)
1932 .await
1933 .expect("failed to store network")
1934 .is_none()
1935 );
1936 let config_1 = saved_networks.lookup(&id_1).await.expect("failed to lookup");
1939 assert_eq!(config_1.hidden_probability, PROB_HIDDEN_DEFAULT);
1940 let config_2 = saved_networks.lookup(&id_2).await.expect("failed to lookup");
1941 assert_eq!(config_2.hidden_probability, PROB_HIDDEN_DEFAULT);
1942 let config_4 = saved_networks.lookup(&id_4).await.expect("failed to lookup");
1943 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
1944
1945 let not_seen_ids = vec![id_1.ssid.clone(), id_2.ssid.clone(), id_3.ssid.clone()];
1946 saved_networks.record_scan_result(not_seen_ids, &HashMap::new()).await;
1947
1948 let config_1 = saved_networks.lookup(&id_1).await.expect("failed to lookup");
1950 assert!(config_1.hidden_probability < PROB_HIDDEN_DEFAULT);
1951 let config_2 = saved_networks.lookup(&id_2).await.expect("failed to lookup");
1952 assert!(config_2.hidden_probability < PROB_HIDDEN_DEFAULT);
1953
1954 let config_4 = saved_networks.lookup(&id_4).await.expect("failed to lookup");
1957 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
1958
1959 assert!(saved_networks.lookup(&id_3).await.is_none());
1961 }
1962
1963 #[fuchsia::test]
1964 async fn test_update_scan_stats_for_single_bss() {
1965 let saved_networks = SavedNetworksManager::new_for_test().await;
1968
1969 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1970 let credential = Credential::Password(b"some_password".to_vec());
1971 assert!(
1972 saved_networks
1973 .store(id.clone(), credential.clone())
1974 .await
1975 .expect("failed to store network")
1976 .is_none()
1977 );
1978
1979 let id_detailed = types::NetworkIdentifierDetailed {
1980 ssid: id.ssid.clone(),
1981 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1982 };
1983 let scan_results = HashMap::from([(
1984 id_detailed.clone(),
1985 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1986 )]);
1987
1988 for _ in 0..5 {
1990 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
1991 }
1992
1993 let is_single_bss = saved_networks
1994 .is_network_single_bss(&id, &credential)
1995 .await
1996 .expect("failed to lookup if network is single BSS");
1997 assert!(is_single_bss);
1998 }
1999
2000 #[fuchsia::test]
2001 async fn test_update_scan_stats_for_multiple_bss_at_least_once() {
2002 let saved_networks = SavedNetworksManager::new_for_test().await;
2005
2006 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2007 let credential = Credential::Password(b"some_password".to_vec());
2008 assert!(
2009 saved_networks
2010 .store(id.clone(), credential.clone())
2011 .await
2012 .expect("failed to store network")
2013 .is_none()
2014 );
2015
2016 let id_detailed = types::NetworkIdentifierDetailed {
2017 ssid: id.ssid.clone(),
2018 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2019 };
2020 let scan_results_single = HashMap::from([(
2021 id_detailed.clone(),
2022 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2023 )]);
2024
2025 let scan_results_multi = HashMap::from([(
2026 id_detailed.clone(),
2027 vec![
2028 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2029 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2030 ],
2031 )]);
2032
2033 for _ in 0..2 {
2035 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2036 }
2037
2038 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_multi).await;
2039 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2040
2041 let is_single_bss = saved_networks
2044 .is_network_single_bss(&id, &credential)
2045 .await
2046 .expect("failed to lookup if network is single BSS");
2047 assert!(!is_single_bss);
2048 }
2049
2050 #[fuchsia::test]
2051 async fn test_record_scan_more_than_once_to_decide_single_bss() {
2052 let saved_networks = SavedNetworksManager::new_for_test().await;
2054
2055 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2056 let credential = Credential::Password(b"some_password".to_vec());
2057 assert!(
2058 saved_networks
2059 .store(id.clone(), credential.clone())
2060 .await
2061 .expect("failed to store network")
2062 .is_none()
2063 );
2064
2065 let id_detailed = types::NetworkIdentifierDetailed {
2066 ssid: id.ssid.clone(),
2067 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2068 };
2069 let scan_results = HashMap::from([(
2070 id_detailed,
2071 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2072 )]);
2073
2074 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2077
2078 let is_single_bss = saved_networks
2079 .is_network_single_bss(&id, &credential)
2080 .await
2081 .expect("failed to lookup if network is single BSS");
2082 assert!(!is_single_bss);
2083 }
2084
2085 #[fuchsia::test]
2086 async fn test_get_past_connections() {
2087 let saved_networks_manager = SavedNetworksManager::new_for_test().await;
2088
2089 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2090 let credential = Credential::Password(b"some_password".to_vec());
2091 let mut config = NetworkConfig::new(id.clone(), credential.clone(), true, None)
2092 .expect("failed to create config");
2093 let mut past_connections = HashMap::<_, PastConnectionList>::new();
2094
2095 let data_1 = random_connection_data();
2097 let bssid_1 = data_1.bssid;
2098 let mut data_2 = random_connection_data();
2099 data_2.bssid = bssid_1;
2100 past_connections.entry(bssid_1).or_default().add(data_1);
2101 past_connections.entry(bssid_1).or_default().add(data_2);
2102
2103 let data_3 = random_connection_data();
2105 let bssid_2 = data_3.bssid;
2106 past_connections.entry(bssid_2).or_default().add(data_3);
2107 config.perf_stats.past_connections = past_connections;
2108
2109 assert!(
2111 saved_networks_manager.saved_networks.lock().await.insert(id.clone(), config).is_none()
2112 );
2113
2114 let mut expected_past_connections = new_past_connection_list();
2116 expected_past_connections.add(data_1);
2117 expected_past_connections.add(data_2);
2118 let actual_past_connections =
2119 saved_networks_manager.get_past_connections(&id, &credential, &bssid_1).await;
2120 assert_eq!(actual_past_connections, expected_past_connections);
2121
2122 let mut expected_past_connections = new_past_connection_list();
2123 expected_past_connections.add(data_3);
2124 let actual_past_connections =
2125 saved_networks_manager.get_past_connections(&id, &credential, &bssid_2).await;
2126 assert_eq!(actual_past_connections, expected_past_connections);
2127
2128 let actual_past_connections = saved_networks_manager
2131 .get_past_connections(&id, &Credential::Password(b"other-password".to_vec()), &bssid_1)
2132 .await;
2133 assert_eq!(actual_past_connections, new_past_connection_list());
2134 }
2135
2136 fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
2137 fidl_sme::ConnectResult {
2138 code: fidl_ieee80211::StatusCode::Success,
2139 is_credential_rejected: false,
2140 is_reconnect: false,
2141 }
2142 }
2143}