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