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 futures::lock::Mutex;
16use log::{error, info};
17use std::collections::hash_map::Entry;
18use std::collections::{HashMap, HashSet};
19use wlan_storage::policy::{POLICY_STORAGE_ID, PolicyStorage};
20use {
21 fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211, fidl_fuchsia_wlan_sme as fidl_sme,
22 fuchsia_async as fasync,
23};
24
25const MAX_CONFIGS_PER_SSID: usize = 1;
26
27pub struct SavedNetworksManager {
33 saved_networks: Mutex<NetworkConfigMap>,
34 store: Mutex<PolicyStorage>,
37 telemetry_sender: TelemetrySender,
38}
39
40type NetworkConfigMap = HashMap<NetworkIdentifier, Vec<NetworkConfig>>;
44
45#[async_trait(?Send)]
46pub trait SavedNetworksManagerApi {
47 async fn remove(
50 &self,
51 network_id: NetworkIdentifier,
52 credential: Credential,
53 ) -> Result<bool, NetworkConfigError>;
54
55 async fn known_network_count(&self) -> usize;
57
58 async fn lookup(&self, id: &NetworkIdentifier) -> Vec<NetworkConfig>;
60
61 async fn lookup_compatible(
65 &self,
66 ssid: &types::Ssid,
67 scan_security: types::SecurityTypeDetailed,
68 ) -> Vec<NetworkConfig>;
69
70 async fn store(
75 &self,
76 network_id: NetworkIdentifier,
77 credential: Credential,
78 ) -> Result<Option<NetworkConfig>, NetworkConfigError>;
79
80 async fn record_connect_result(
86 &self,
87 id: NetworkIdentifier,
88 credential: &Credential,
89 bssid: types::Bssid,
90 connect_result: fidl_sme::ConnectResult,
91 scan_type: types::ScanObservation,
92 );
93
94 async fn record_disconnect(
97 &self,
98 id: &NetworkIdentifier,
99 credential: &Credential,
100 data: PastConnectionData,
101 );
102
103 async fn record_periodic_metrics(&self);
104
105 async fn record_scan_result(
108 &self,
109 target_ssids: Vec<types::Ssid>,
110 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
111 );
112
113 async fn is_network_single_bss(
114 &self,
115 id: &NetworkIdentifier,
116 credential: &Credential,
117 ) -> Result<bool, anyhow::Error>;
118
119 async fn get_networks(&self) -> Vec<NetworkConfig>;
121
122 async fn get_past_connections(
124 &self,
125 id: &NetworkIdentifier,
126 credential: &Credential,
127 bssid: &types::Bssid,
128 ) -> PastConnectionList;
129}
130
131impl SavedNetworksManager {
132 pub async fn new(telemetry_sender: TelemetrySender) -> Self {
135 let storage = PolicyStorage::new_with_id(POLICY_STORAGE_ID).await;
136 Self::new_with_storage(storage, telemetry_sender).await
137 }
138
139 pub async fn new_with_storage(
141 mut store: PolicyStorage,
142 telemetry_sender: TelemetrySender,
143 ) -> Self {
144 let mut saved_networks: HashMap<NetworkIdentifier, Vec<NetworkConfig>> = HashMap::new();
145 let stored_networks = store.load().await.unwrap_or_else(|e| {
149 error!("No saved networks loaded; error loading saved networks from storage: {}", e);
151 Vec::new()
152 });
153 let mut errors_building_configs = HashSet::new();
154
155 for persisted_data in stored_networks.into_iter() {
157 let id = NetworkIdentifier::new(
158 types::Ssid::from_bytes_unchecked(persisted_data.ssid),
159 persisted_data.security_type.into(),
160 );
161 let config = NetworkConfig::new(
162 id.clone(),
163 persisted_data.credential.clone().into(),
164 persisted_data.has_ever_connected,
165 );
166 match config {
167 Ok(config) => saved_networks.entry(id).or_default().push(config),
168 Err(e) => {
169 _ = errors_building_configs.insert(e);
170 }
171 }
172 }
173
174 if !errors_building_configs.is_empty() {
176 error!(
177 "At least one error occurred building network config from persisted data: {:?}",
178 errors_building_configs
179 )
180 }
181
182 Self {
183 saved_networks: Mutex::new(saved_networks),
184 store: Mutex::new(store),
185 telemetry_sender,
186 }
187 }
188
189 #[cfg(test)]
192 pub async fn new_for_test() -> Self {
193 use crate::util::testing::generate_string;
194 use futures::channel::mpsc;
195
196 let store_id = generate_string();
197 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
198 let telemetry_sender = TelemetrySender::new(telemetry_sender);
199 let store = PolicyStorage::new_with_id(&store_id).await;
200 Self::new_with_storage(store, telemetry_sender).await
201 }
202
203 #[cfg(test)]
205 pub async fn clear(&self) -> Result<(), anyhow::Error> {
206 self.saved_networks.lock().await.clear();
207 self.store.lock().await.clear()
208 }
209}
210
211#[async_trait(?Send)]
212impl SavedNetworksManagerApi for SavedNetworksManager {
213 async fn remove(
214 &self,
215 network_id: NetworkIdentifier,
216 credential: Credential,
217 ) -> Result<bool, NetworkConfigError> {
218 let mut saved_networks = self.saved_networks.lock().await;
220 if let Some(network_configs) = saved_networks.get_mut(&network_id) {
221 let original_len = network_configs.len();
222 network_configs.retain(|cfg| cfg.credential != credential);
224 if original_len != network_configs.len() {
225 if network_configs.is_empty() {
227 _ = saved_networks.remove(&network_id);
228 }
229
230 self.store
232 .lock()
233 .await
234 .write(persistent_data_from_config_map(&saved_networks))
235 .map_err(|e| {
236 error!("error writing network to persistent storage: {}", e);
237 NetworkConfigError::FileWriteError
238 })?;
239
240 return Ok(true);
241 } else {
242 let credential_types = network_configs
245 .iter()
246 .map(|nc| nc.credential.type_str())
247 .collect::<HashSet<_>>();
248 if credential_types.contains(credential.type_str()) {
249 info!("No matching network with the provided credential was found to remove.");
250 } else {
251 info!(
252 "No credential matching type {:?} found to remove for this network identifier. Help: found credential type(s): {:?}",
253 credential.type_str(),
254 credential_types
255 );
256 }
257 }
258 } else {
259 let mut found_securities = SecurityType::list_variants();
262 found_securities.retain(|security| {
263 let id = NetworkIdentifier::new(network_id.ssid.clone(), *security);
264 saved_networks.contains_key(&id)
265 });
266 if found_securities.is_empty() {
267 info!("No network was found to remove with the provided SSID.");
268 } else {
269 info!(
270 "No config to remove with security type {:?}. Help: found different config(s) for this SSID with security {:?}",
271 network_id.security_type, found_securities
272 );
273 }
274 }
275 Ok(false)
276 }
277
278 async fn known_network_count(&self) -> usize {
280 self.saved_networks.lock().await.values().flatten().count()
281 }
282
283 async fn lookup(&self, id: &NetworkIdentifier) -> Vec<NetworkConfig> {
289 self.saved_networks.lock().await.get(id).cloned().unwrap_or_default()
290 }
291
292 async fn lookup_compatible(
293 &self,
294 ssid: &types::Ssid,
295 scan_security: types::SecurityTypeDetailed,
296 ) -> Vec<NetworkConfig> {
297 let saved_networks_guard = self.saved_networks.lock().await;
298 let mut matching_configs = Vec::new();
299 for security in compatible_policy_securities(&scan_security) {
300 let id = NetworkIdentifier::new(ssid.clone(), security);
301 let saved_configs = saved_networks_guard.get(&id);
302 if let Some(configs) = saved_configs {
303 matching_configs.extend(
304 configs
305 .iter()
306 .filter(|config| security_is_compatible(&scan_security, &config.credential))
308 .map(Clone::clone),
309 );
310 }
311 }
312 matching_configs
313 }
314
315 async fn store(
316 &self,
317 network_id: NetworkIdentifier,
318 credential: Credential,
319 ) -> Result<Option<NetworkConfig>, NetworkConfigError> {
320 let mut saved_networks = self.saved_networks.lock().await;
321 let network_entry = saved_networks.entry(network_id.clone());
322
323 if let Entry::Occupied(network_configs) = &network_entry
324 && network_configs.get().iter().any(|cfg| cfg.credential == credential)
325 {
326 info!("Saving a previously saved network with same password.");
327 return Ok(None);
328 }
329 let network_config = NetworkConfig::new(network_id.clone(), credential.clone(), false)?;
330 let network_configs = network_entry.or_default();
331 let evicted_config = evict_if_needed(network_configs);
332 network_configs.push(network_config);
333
334 self.store.lock().await.write(persistent_data_from_config_map(&saved_networks)).map_err(
335 |e| {
336 error!("error writing network to persistent storage: {}", e);
337 NetworkConfigError::FileWriteError
338 },
339 )?;
340
341 Ok(evicted_config)
342 }
343
344 async fn record_connect_result(
345 &self,
346 id: NetworkIdentifier,
347 credential: &Credential,
348 bssid: types::Bssid,
349 connect_result: fidl_sme::ConnectResult,
350 scan_type: types::ScanObservation,
351 ) {
352 let mut saved_networks = self.saved_networks.lock().await;
353 let networks = match saved_networks.get_mut(&id) {
354 Some(networks) => networks,
355 None => {
356 error!("Failed to find network to record result of connect attempt.");
357 return;
358 }
359 };
360 for network in networks.iter_mut() {
361 if &network.credential == credential {
362 match (connect_result.code, connect_result.is_credential_rejected) {
363 (fidl_ieee80211::StatusCode::Success, _) => {
364 let mut has_change = false;
365 if !network.has_ever_connected {
366 network.has_ever_connected = true;
367 has_change = true;
368 }
369 match scan_type {
371 types::ScanObservation::Passive => {
372 network.update_hidden_prob(HiddenProbEvent::ConnectPassive);
373 }
374 types::ScanObservation::Active => {
375 network.update_hidden_prob(HiddenProbEvent::ConnectActive);
376 }
377 types::ScanObservation::Unknown => {}
378 };
379
380 if has_change {
381 let data = persistent_data_from_config_map(&saved_networks);
383 if let Err(e) = self.store.lock().await.write(data) {
384 info!("Failed to record successful connect in store: {}", e);
385 }
386 }
387 }
388 (fidl_ieee80211::StatusCode::Canceled, _) => {}
389 (_, true) => {
390 network.perf_stats.connect_failures.add(
391 bssid,
392 ConnectFailure {
393 time: fasync::MonotonicInstant::now(),
394 reason: FailureReason::CredentialRejected,
395 bssid,
396 },
397 );
398 }
399 (_, _) => {
400 network.perf_stats.connect_failures.add(
401 bssid,
402 ConnectFailure {
403 time: fasync::MonotonicInstant::now(),
404 reason: FailureReason::GeneralFailure,
405 bssid,
406 },
407 );
408 }
409 }
410 return;
411 }
412 }
413 error!("Failed to find matching network to record result of connect attempt.");
415 }
416
417 async fn record_disconnect(
418 &self,
419 id: &NetworkIdentifier,
420 credential: &Credential,
421 data: PastConnectionData,
422 ) {
423 let bssid = data.bssid;
424 let mut saved_networks = self.saved_networks.lock().await;
425 let networks = match saved_networks.get_mut(id) {
426 Some(networks) => networks,
427 None => {
428 info!("Failed to find network to record disconnect stats");
429 return;
430 }
431 };
432 for network in networks.iter_mut() {
433 if &network.credential == credential {
434 network.perf_stats.past_connections.add(bssid, data);
435 return;
436 }
437 }
438 }
439
440 async fn record_periodic_metrics(&self) {
441 let saved_networks = self.saved_networks.lock().await;
442 let config_counts = saved_networks
444 .iter()
445 .map(|saved_network| {
446 let configs = saved_network.1;
447 configs.len()
448 })
449 .collect();
450 self.telemetry_sender.send(TelemetryEvent::SavedNetworkCount {
451 saved_network_count: saved_networks.len(),
452 config_count_per_saved_network: config_counts,
453 });
454 }
455
456 async fn record_scan_result(
457 &self,
458 target_ssids: Vec<types::Ssid>,
459 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
460 ) {
461 let mut saved_networks = self.saved_networks.lock().await;
462
463 for (network, bss_list) in results {
464 let has_multiple_bss = bss_list.len() > 1;
468 if bss_list.iter().any(|bss| bss.observation == ScanObservation::Passive) {
470 for security in compatible_policy_securities(&network.security_type) {
473 let configs = match saved_networks
474 .get_mut(&NetworkIdentifier::new(network.ssid.clone(), security))
475 {
476 Some(configs) => configs,
477 None => continue,
478 };
479 let compatible_configs = configs.iter_mut().filter(|config| {
482 security_is_compatible(&network.security_type, &config.credential)
483 });
484 for config in compatible_configs {
485 config.update_hidden_prob(HiddenProbEvent::SeenPassive);
486 config.update_seen_multiple_bss(has_multiple_bss)
487 }
488 }
489 }
490 }
491
492 for (id, configs) in saved_networks.iter_mut() {
494 if !target_ssids.contains(&id.ssid) {
495 continue;
496 }
497 let potential_scan_results =
500 results.iter().filter(|(scan_id, _)| scan_id.ssid == id.ssid).collect::<Vec<_>>();
501 for config in configs {
502 if !potential_scan_results.iter().any(|(scan_id, _)| {
503 compatible_policy_securities(&scan_id.security_type)
504 .contains(&config.security_type)
505 && security_is_compatible(&scan_id.security_type, &config.credential)
506 }) {
507 config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
508 }
509 }
510 }
511 }
513
514 async fn is_network_single_bss(
519 &self,
520 id: &NetworkIdentifier,
521 credential: &Credential,
522 ) -> Result<bool, anyhow::Error> {
523 let saved_networks_guard = self.saved_networks.lock().await;
524 let possible_configs = saved_networks_guard.get(id).ok_or_else(|| {
525 format_err!(
526 "error checking if network is single BSS; no config with matching identifier"
527 )
528 })?;
529 let config =
530 possible_configs.iter().find(|c| &c.credential == credential).ok_or_else(|| {
531 format_err!(
532 "error checking if network is single BSS; no config with matching credential"
533 )
534 })?;
535 return Ok(config.is_likely_single_bss());
536 }
537
538 async fn get_networks(&self) -> Vec<NetworkConfig> {
539 self.saved_networks.lock().await.values().flat_map(|cfgs| cfgs.clone()).collect()
540 }
541
542 async fn get_past_connections(
543 &self,
544 id: &NetworkIdentifier,
545 credential: &Credential,
546 bssid: &types::Bssid,
547 ) -> PastConnectionList {
548 self.saved_networks
549 .lock()
550 .await
551 .get(id)
552 .and_then(|configs| configs.iter().find(|config| &config.credential == credential))
553 .map(|config| config.perf_stats.past_connections.get_list_for_bss(bssid))
554 .unwrap_or_default()
555 }
556}
557
558pub fn select_subset_potentially_hidden_networks(
561 saved_networks: Vec<NetworkConfig>,
562) -> Vec<types::NetworkIdentifier> {
563 saved_networks
564 .into_iter()
565 .filter(|saved_network| {
566 saved_network.hidden_probability > rand::random_range(0.0..1.0)
572 })
573 .map(|network| types::NetworkIdentifier {
574 ssid: network.ssid,
575 security_type: network.security_type,
576 })
577 .collect()
578}
579
580pub fn select_high_probability_hidden_networks(
582 saved_networks: Vec<NetworkConfig>,
583) -> Vec<types::NetworkIdentifier> {
584 saved_networks
585 .into_iter()
586 .filter(|saved_network| saved_network.hidden_probability >= HIDDEN_PROBABILITY_HIGH)
587 .map(|network| types::NetworkIdentifier {
588 ssid: network.ssid,
589 security_type: network.security_type,
590 })
591 .collect()
592}
593
594pub fn compatible_policy_securities(
600 detailed_security: &types::SecurityTypeDetailed,
601) -> Vec<SecurityType> {
602 use fidl_sme::Protection::*;
603 match detailed_security {
604 Wpa3Enterprise | Wpa3Personal | Wpa2Wpa3Personal => {
605 vec![SecurityType::Wpa2, SecurityType::Wpa3]
606 }
607 Wpa2Enterprise
608 | Wpa2Personal
609 | Wpa1Wpa2Personal
610 | Wpa2PersonalTkipOnly
611 | Wpa1Wpa2PersonalTkipOnly => vec![SecurityType::Wpa, SecurityType::Wpa2],
612 Wpa1 => vec![SecurityType::Wpa],
613 Wep => vec![SecurityType::Wep],
614 Open => vec![SecurityType::None],
615 Unknown => vec![],
616 }
617}
618
619pub fn security_is_compatible(
620 scan_security: &types::SecurityTypeDetailed,
621 credential: &Credential,
622) -> bool {
623 if (scan_security == &types::SecurityTypeDetailed::Wpa3Personal
624 || scan_security == &types::SecurityTypeDetailed::Wpa3Enterprise)
625 && let Credential::Psk(_) = credential
626 {
627 return false;
628 }
629 true
630}
631
632fn evict_if_needed(configs: &mut Vec<NetworkConfig>) -> Option<NetworkConfig> {
641 if configs.len() < MAX_CONFIGS_PER_SSID {
642 return None;
643 }
644
645 for i in 0..configs.len() {
646 if let Some(config) = configs.get(i)
647 && !config.has_ever_connected
648 {
649 return Some(configs.remove(i));
650 }
651 }
652 Some(configs.remove(0))
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use crate::config_management::{
660 HistoricalListsByBssid, PROB_HIDDEN_DEFAULT, PROB_HIDDEN_IF_CONNECT_ACTIVE,
661 PROB_HIDDEN_IF_CONNECT_PASSIVE, PROB_HIDDEN_IF_SEEN_PASSIVE,
662 };
663 use crate::util::testing::{generate_random_bss, generate_string, random_connection_data};
664 use assert_matches::assert_matches;
665 use futures::channel::mpsc;
666 use futures::task::Poll;
667 use std::pin::pin;
668 use test_case::test_case;
669
670 #[fuchsia::test]
671 async fn store_and_lookup() {
672 let store_id = generate_string();
673 let saved_networks = create_saved_networks(&store_id).await;
674 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
675
676 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
677 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
678 assert_eq!(0, saved_networks.known_network_count().await);
679
680 assert!(
682 saved_networks
683 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
684 .await
685 .expect("storing 'foo' failed")
686 .is_none()
687 );
688 assert_eq!(
689 vec![network_config("foo", "qwertyuio")],
690 saved_networks.lookup(&network_id_foo).await
691 );
692 assert_eq!(1, saved_networks.known_network_count().await);
693
694 let popped_network = saved_networks
696 .store(network_id_foo.clone(), Credential::Password(b"12345678".to_vec()))
697 .await
698 .expect("storing 'foo' a second time failed");
699 assert_eq!(popped_network, Some(network_config("foo", "qwertyuio")));
700
701 assert_eq!(
704 vec![network_config("foo", "12345678")],
705 saved_networks.lookup(&network_id_foo).await
706 );
707 assert_eq!(1, saved_networks.known_network_count().await);
708
709 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
711 let psk = Credential::Psk(vec![1; 32]);
712 let config_baz = NetworkConfig::new(network_id_baz.clone(), psk.clone(), false)
713 .expect("failed to create network config");
714 assert!(
715 saved_networks
716 .store(network_id_baz.clone(), psk)
717 .await
718 .expect("storing 'baz' with PSK failed")
719 .is_none()
720 );
721 assert_eq!(vec![config_baz.clone()], saved_networks.lookup(&network_id_baz).await);
722 assert_eq!(2, saved_networks.known_network_count().await);
723
724 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
726 let store = PolicyStorage::new_with_id(&store_id).await;
727
728 let saved_networks =
729 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
730 .await;
731 assert_eq!(
732 vec![network_config("foo", "12345678")],
733 saved_networks.lookup(&network_id_foo).await
734 );
735 assert_eq!(vec![config_baz], saved_networks.lookup(&network_id_baz).await);
736 assert_eq!(2, saved_networks.known_network_count().await);
737 }
738
739 #[fuchsia::test]
740 async fn store_twice() {
741 let saved_networks = SavedNetworksManager::new_for_test().await;
742 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
743
744 assert!(
745 saved_networks
746 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
747 .await
748 .expect("storing 'foo' failed")
749 .is_none()
750 );
751 let popped_network = saved_networks
752 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
753 .await
754 .expect("storing 'foo' a second time failed");
755 assert_eq!(popped_network, None);
757 let expected_cfgs = vec![network_config("foo", "qwertyuio")];
758 assert_eq!(expected_cfgs, saved_networks.lookup(&network_id).await);
759 assert_eq!(1, saved_networks.known_network_count().await);
760 }
761
762 #[fuchsia::test]
763 async fn store_many_same_ssid() {
764 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
765 let saved_networks = SavedNetworksManager::new_for_test().await;
766
767 for i in 0..MAX_CONFIGS_PER_SSID + 1 {
769 let mut password = b"password".to_vec();
770 password.push(i as u8);
771 let popped_network = saved_networks
772 .store(network_id.clone(), Credential::Password(password))
773 .await
774 .expect("Failed to saved network");
775 if i >= MAX_CONFIGS_PER_SSID {
776 assert!(popped_network.is_some());
777 } else {
778 assert!(popped_network.is_none());
779 }
780 }
781
782 assert_eq!(MAX_CONFIGS_PER_SSID, saved_networks.lookup(&network_id).await.len());
784 }
785
786 #[fuchsia::test]
787 async fn store_and_remove() {
788 let store_id = generate_string();
789 let saved_networks = create_saved_networks(&store_id).await;
790
791 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
792 let credential = Credential::Password(b"qwertyuio".to_vec());
793 assert!(saved_networks.lookup(&network_id).await.is_empty());
794 assert_eq!(0, saved_networks.known_network_count().await);
795
796 assert!(
798 saved_networks
799 .store(network_id.clone(), credential.clone())
800 .await
801 .expect("storing 'foo' failed")
802 .is_none()
803 );
804 assert_eq!(
805 vec![network_config("foo", "qwertyuio")],
806 saved_networks.lookup(&network_id).await
807 );
808 assert_eq!(1, saved_networks.known_network_count().await);
809
810 assert!(
813 !saved_networks
814 .remove(network_id.clone(), Credential::Password(b"diff-password".to_vec()))
815 .await
816 .expect("removing 'foo' failed")
817 );
818 assert_eq!(1, saved_networks.known_network_count().await);
819
820 assert!(
822 saved_networks
823 .remove(network_id.clone(), credential.clone())
824 .await
825 .expect("removing 'foo' failed")
826 );
827 assert_eq!(0, saved_networks.known_network_count().await);
828 assert!(saved_networks.saved_networks.lock().await.get(&network_id).is_none());
830
831 assert!(
833 !saved_networks
834 .remove(network_id.clone(), credential)
835 .await
836 .expect("removing 'foo' failed")
837 );
838
839 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
841 let store = PolicyStorage::new_with_id(&store_id).await;
842 let saved_networks =
843 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
844 .await;
845 assert_eq!(0, saved_networks.known_network_count().await);
846 assert!(saved_networks.lookup(&network_id).await.is_empty());
847 }
848
849 #[fuchsia::test]
850 fn sme_protection_converts_to_lower_compatible() {
851 use fidl_sme::Protection::*;
852 let lower_compatible_pairs = vec![
853 (Wpa3Enterprise, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
854 (Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
855 (Wpa2Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
856 (Wpa2Enterprise, vec![SecurityType::Wpa, SecurityType::Wpa2]),
857 (Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
858 (Wpa1Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
859 (Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
860 (Wpa1Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
861 (Wpa1, vec![SecurityType::Wpa]),
862 (Wep, vec![SecurityType::Wep]),
863 (Open, vec![SecurityType::None]),
864 (Unknown, vec![]),
865 ];
866 for (detailed_security, security) in lower_compatible_pairs {
867 assert_eq!(compatible_policy_securities(&detailed_security), security);
868 }
869 }
870
871 #[fuchsia::test]
872 async fn lookup_compatible_returns_both_compatible_configs() {
873 let saved_networks = SavedNetworksManager::new_for_test().await;
874 let ssid = types::Ssid::try_from("foo").unwrap();
875 let network_id_wpa2 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
876 let network_id_wpa3 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
877 let credential_wpa2 = Credential::Password(b"password".to_vec());
878 let credential_wpa3 = Credential::Password(b"wpa3-password".to_vec());
879
880 let results = saved_networks
883 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
884 .await;
885 assert!(results.is_empty());
886 assert_eq!(saved_networks.known_network_count().await, 0);
887
888 assert!(
891 saved_networks
892 .store(network_id_wpa2.clone(), credential_wpa2.clone())
893 .await
894 .expect("Failed to store network")
895 .is_none()
896 );
897 assert!(
898 saved_networks
899 .store(network_id_wpa3.clone(), credential_wpa3.clone())
900 .await
901 .expect("Failed to store network")
902 .is_none()
903 );
904 let network_id_wep = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa);
906 assert!(
907 saved_networks
908 .store(network_id_wep.clone(), Credential::Password(b"abcdefgh".to_vec()))
909 .await
910 .expect("Failed to store network")
911 .is_none()
912 );
913
914 let results = saved_networks
915 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
916 .await;
917 let expected_config_wpa2 = NetworkConfig::new(network_id_wpa2, credential_wpa2, false)
918 .expect("Failed to create config");
919 let expected_config_wpa3 = NetworkConfig::new(network_id_wpa3, credential_wpa3, false)
920 .expect("Failed to create config");
921 assert_eq!(results.len(), 2);
922 assert!(results.contains(&expected_config_wpa2));
923 assert!(results.contains(&expected_config_wpa3));
924 }
925
926 #[test_case(types::SecurityTypeDetailed::Wpa3Personal)]
927 #[test_case(types::SecurityTypeDetailed::Wpa3Enterprise)]
928 #[fuchsia::test(add_test_attr = false)]
929 fn lookup_compatible_does_not_return_wpa3_psk(
930 wpa3_detailed_security: types::SecurityTypeDetailed,
931 ) {
932 let mut exec = fasync::TestExecutor::new();
933 let saved_networks = exec.run_singlethreaded(SavedNetworksManager::new_for_test());
934
935 let ssid = types::Ssid::try_from("foo").unwrap();
938 let network_id_psk = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
939 let network_id_password = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
940 let credential_psk = Credential::Psk(vec![5; 32]);
941 let credential_password = Credential::Password(b"mypassword".to_vec());
942 assert!(
943 exec.run_singlethreaded(
944 saved_networks.store(network_id_psk.clone(), credential_psk.clone()),
945 )
946 .expect("Failed to store network")
947 .is_none()
948 );
949 assert!(
950 exec.run_singlethreaded(
951 saved_networks.store(network_id_password.clone(), credential_password.clone()),
952 )
953 .expect("Failed to store network")
954 .is_none()
955 );
956
957 let expected_config_wpa3 =
959 NetworkConfig::new(network_id_password, credential_password, false)
960 .expect("Failed to create configc");
961 let results = exec
962 .run_singlethreaded(saved_networks.lookup_compatible(&ssid, wpa3_detailed_security));
963 assert_eq!(results, vec![expected_config_wpa3]);
964 }
965
966 #[fuchsia::test]
967 async fn connect_network() {
968 let store_id = generate_string();
969
970 let saved_networks = create_saved_networks(&store_id).await;
971
972 let network_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
973 let credential = Credential::Password(b"password".to_vec());
974 let bssid = types::Bssid::from([4; 6]);
975
976 saved_networks
978 .record_connect_result(
979 network_id.clone(),
980 &credential,
981 bssid,
982 fake_successful_connect_result(),
983 types::ScanObservation::Unknown,
984 )
985 .await;
986 assert!(saved_networks.lookup(&network_id).await.is_empty());
987 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
988 assert_eq!(0, saved_networks.known_network_count().await);
989
990 assert!(
992 saved_networks
993 .store(network_id.clone(), credential.clone())
994 .await
995 .expect("Failed save network")
996 .is_none()
997 );
998
999 let config = network_config("bar", "password");
1000 assert_eq!(vec![config], saved_networks.lookup(&network_id).await);
1001
1002 saved_networks
1003 .record_connect_result(
1004 network_id.clone(),
1005 &credential,
1006 bssid,
1007 fake_successful_connect_result(),
1008 types::ScanObservation::Unknown,
1009 )
1010 .await;
1011
1012 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1015 assert!(config.has_ever_connected);
1016 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1017 });
1018
1019 saved_networks
1020 .record_connect_result(
1021 network_id.clone(),
1022 &credential,
1023 bssid,
1024 fake_successful_connect_result(),
1025 types::ScanObservation::Active,
1026 )
1027 .await;
1028 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1030 assert!(config.has_ever_connected);
1031 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1032 });
1033
1034 saved_networks
1035 .record_connect_result(
1036 network_id.clone(),
1037 &credential,
1038 bssid,
1039 fake_successful_connect_result(),
1040 types::ScanObservation::Passive,
1041 )
1042 .await;
1043 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1045 assert!(config.has_ever_connected);
1046 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1047 });
1048
1049 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1051 let store = PolicyStorage::new_with_id(&store_id).await;
1052 let saved_networks =
1053 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1054 .await;
1055 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1056 assert!(config.has_ever_connected);
1057 });
1058 }
1059
1060 #[fuchsia::test]
1061 async fn test_record_connect_updates_one() {
1062 let saved_networks = SavedNetworksManager::new_for_test().await;
1063 let net_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1064 let net_id_also_valid = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1065 let credential = Credential::Password(b"some_password".to_vec());
1066 let bssid = types::Bssid::from([2; 6]);
1067
1068 assert!(
1070 saved_networks
1071 .store(net_id.clone(), credential.clone())
1072 .await
1073 .expect("Failed save network")
1074 .is_none()
1075 );
1076 assert!(
1077 saved_networks
1078 .store(net_id_also_valid.clone(), credential.clone())
1079 .await
1080 .expect("Failed save network")
1081 .is_none()
1082 );
1083 saved_networks
1084 .record_connect_result(
1085 net_id.clone(),
1086 &credential,
1087 bssid,
1088 fake_successful_connect_result(),
1089 types::ScanObservation::Unknown,
1090 )
1091 .await;
1092
1093 assert_matches!(saved_networks.lookup(&net_id).await.as_slice(), [config] => {
1094 assert!(config.has_ever_connected);
1095 });
1096 assert_matches!(saved_networks.lookup(&net_id_also_valid).await.as_slice(), [config] => {
1099 assert!(!config.has_ever_connected);
1100 });
1101 }
1102
1103 #[fuchsia::test]
1104 async fn test_record_connect_failure() {
1105 let saved_networks = SavedNetworksManager::new_for_test().await;
1106 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1107 let credential = Credential::None;
1108 let bssid = types::Bssid::from([1; 6]);
1109 let before_recording = fasync::MonotonicInstant::now();
1110
1111 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 ..fake_successful_connect_result()
1120 },
1121 types::ScanObservation::Unknown,
1122 )
1123 .await;
1124 assert!(saved_networks.lookup(&network_id).await.is_empty());
1125 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1126 assert_eq!(0, saved_networks.known_network_count().await);
1127
1128 assert!(
1130 saved_networks
1131 .store(network_id.clone(), credential.clone())
1132 .await
1133 .expect("Failed save network")
1134 .is_none()
1135 );
1136 saved_networks
1137 .record_connect_result(
1138 network_id.clone(),
1139 &credential,
1140 bssid,
1141 fidl_sme::ConnectResult {
1142 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1143 ..fake_successful_connect_result()
1144 },
1145 types::ScanObservation::Unknown,
1146 )
1147 .await;
1148 saved_networks
1149 .record_connect_result(
1150 network_id.clone(),
1151 &credential,
1152 bssid,
1153 fidl_sme::ConnectResult {
1154 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1155 is_credential_rejected: true,
1156 ..fake_successful_connect_result()
1157 },
1158 types::ScanObservation::Unknown,
1159 )
1160 .await;
1161
1162 assert_eq!(1, saved_networks.known_network_count().await);
1164 let saved_config = saved_networks
1165 .lookup(&network_id)
1166 .await
1167 .pop()
1168 .expect("Failed to get saved network config");
1169 let connect_failures =
1170 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1171 assert_matches!(connect_failures, failures => {
1172 assert_eq!(failures.len(), 2);
1174 assert!(failures.iter().any(|failure| failure.reason == FailureReason::GeneralFailure));
1175 assert!(failures.iter().any(|failure| failure.reason == FailureReason::CredentialRejected));
1176 for failure in failures.iter() {
1178 assert_eq!(failure.bssid, bssid);
1179 assert_eq!(failure.bssid, bssid);
1180 }
1181 });
1182 }
1183
1184 #[fuchsia::test]
1185 async fn test_record_connect_cancelled_ignored() {
1186 let saved_networks = SavedNetworksManager::new_for_test().await;
1187 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1188 let credential = Credential::None;
1189 let bssid = types::Bssid::from([0; 6]);
1190 let before_recording = fasync::MonotonicInstant::now();
1191
1192 saved_networks
1194 .record_connect_result(
1195 network_id.clone(),
1196 &credential,
1197 bssid,
1198 fidl_sme::ConnectResult {
1199 code: fidl_ieee80211::StatusCode::Canceled,
1200 ..fake_successful_connect_result()
1201 },
1202 types::ScanObservation::Unknown,
1203 )
1204 .await;
1205 assert!(saved_networks.lookup(&network_id).await.is_empty());
1206 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1207 assert_eq!(0, saved_networks.known_network_count().await);
1208
1209 assert!(
1211 saved_networks
1212 .store(network_id.clone(), credential.clone())
1213 .await
1214 .expect("Failed save network")
1215 .is_none()
1216 );
1217 saved_networks
1218 .record_connect_result(
1219 network_id.clone(),
1220 &credential,
1221 bssid,
1222 fidl_sme::ConnectResult {
1223 code: fidl_ieee80211::StatusCode::Canceled,
1224 ..fake_successful_connect_result()
1225 },
1226 types::ScanObservation::Unknown,
1227 )
1228 .await;
1229
1230 assert_eq!(1, saved_networks.known_network_count().await);
1232 let saved_config = saved_networks
1233 .lookup(&network_id)
1234 .await
1235 .pop()
1236 .expect("Failed to get saved network config");
1237 let connect_failures =
1238 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1239 assert_eq!(0, connect_failures.len());
1240 }
1241
1242 #[fuchsia::test]
1243 async fn test_record_disconnect() {
1244 let saved_networks = SavedNetworksManager::new_for_test().await;
1245 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1246 let credential = Credential::Psk(vec![1; 32]);
1247 let data = random_connection_data();
1248
1249 saved_networks.record_disconnect(&id, &credential, data).await;
1250 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1252 assert_eq!(saved_networks.known_network_count().await, 0);
1253
1254 assert!(
1256 saved_networks
1257 .store(id.clone(), credential.clone())
1258 .await
1259 .expect("Failed to save network")
1260 .is_none()
1261 );
1262 saved_networks.record_disconnect(&id, &credential, data).await;
1263
1264 let recent_connections = saved_networks
1266 .lookup(&id)
1267 .await
1268 .pop()
1269 .expect("Failed to get saved network")
1270 .perf_stats
1271 .past_connections
1272 .get_recent_for_network(fasync::MonotonicInstant::INFINITE_PAST);
1273 assert_matches!(recent_connections.as_slice(), [connection_data] => {
1274 assert_eq!(connection_data, &data);
1275 })
1276 }
1277
1278 #[fuchsia::test]
1279 async fn test_record_undirected_scan() {
1280 let saved_networks = SavedNetworksManager::new_for_test().await;
1281 let saved_seen_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1282 let saved_seen_network = types::NetworkIdentifierDetailed {
1283 ssid: saved_seen_id.ssid.clone(),
1284 security_type: types::SecurityTypeDetailed::Open,
1285 };
1286 let unsaved_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
1287 let unsaved_network = types::NetworkIdentifierDetailed {
1288 ssid: unsaved_id.ssid.clone(),
1289 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1290 };
1291 let saved_unseen_id = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1292 let seen_credential = Credential::None;
1293 let unseen_credential = Credential::Password(b"password".to_vec());
1294
1295 assert!(
1297 saved_networks
1298 .store(saved_seen_id.clone(), seen_credential.clone())
1299 .await
1300 .expect("Failed to save network")
1301 .is_none()
1302 );
1303 assert!(
1304 saved_networks
1305 .store(saved_unseen_id.clone(), unseen_credential.clone())
1306 .await
1307 .expect("Failed to save network")
1308 .is_none()
1309 );
1310
1311 let results: HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>> = HashMap::from([
1313 (
1314 saved_seen_network,
1315 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1316 ),
1317 (unsaved_network, vec![generate_random_bss()]),
1318 ]);
1319
1320 saved_networks
1321 .record_scan_result(vec!["some_other_ssid".try_into().unwrap()], &results)
1322 .await;
1323
1324 assert_matches!(saved_networks.lookup(&saved_seen_id).await.as_slice(), [config] => {
1325 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1326 });
1327 assert_matches!(saved_networks.lookup(&saved_unseen_id).await.as_slice(), [config] => {
1328 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1329 });
1330 }
1331
1332 #[fuchsia::test]
1333 async fn test_record_undirected_scan_with_upgraded_security() {
1334 let saved_networks = SavedNetworksManager::new_for_test().await;
1337 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1338 let credential = Credential::Password(b"credential".to_vec());
1339
1340 assert!(
1342 saved_networks
1343 .store(id.clone(), credential.clone())
1344 .await
1345 .expect("Failed to save network")
1346 .is_none()
1347 );
1348
1349 let results = HashMap::from([(
1351 types::NetworkIdentifierDetailed {
1352 ssid: id.ssid.clone(),
1353 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1354 },
1355 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1356 )]);
1357 saved_networks.record_scan_result(vec![], &results).await;
1358 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1360 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1361 });
1362 }
1363
1364 #[fuchsia::test]
1365 async fn test_record_undirected_scan_incompatible_credential() {
1366 let saved_networks = SavedNetworksManager::new_for_test().await;
1369 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1370 let credential = Credential::Psk(vec![8; 32]);
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
1381 let results = HashMap::from([(
1383 types::NetworkIdentifierDetailed {
1384 ssid: id.ssid.clone(),
1385 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1386 },
1387 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1388 )]);
1389 saved_networks.record_scan_result(vec![], &results).await;
1390 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1393 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1394 });
1395 }
1396
1397 #[fuchsia::test]
1398 async fn test_record_directed_scan_for_upgraded_security() {
1399 let saved_networks = SavedNetworksManager::new_for_test().await;
1402 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa).unwrap();
1403 let credential = Credential::Password(b"credential".to_vec());
1404
1405 assert!(
1407 saved_networks
1408 .store(id.clone(), credential.clone())
1409 .await
1410 .expect("Failed to save network")
1411 .is_none()
1412 );
1413 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1414 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1415
1416 let results = HashMap::from([(
1419 types::NetworkIdentifierDetailed {
1420 ssid: id.ssid.clone(),
1421 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1422 },
1423 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1424 )]);
1425 let target = vec![id.ssid.clone()];
1426 saved_networks.record_scan_result(target, &results).await;
1427
1428 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1429 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1430 }
1431
1432 #[fuchsia::test]
1433 async fn test_record_directed_scan_for_incompatible_credential() {
1434 let saved_networks = SavedNetworksManager::new_for_test().await;
1438 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1439 let credential = Credential::Psk(vec![11; 32]);
1440
1441 assert!(
1443 saved_networks
1444 .store(id.clone(), credential.clone())
1445 .await
1446 .expect("Failed to save network")
1447 .is_none()
1448 );
1449 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1450 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1451
1452 let target = vec![id.ssid.clone()];
1455 let results = HashMap::from([(
1456 types::NetworkIdentifierDetailed {
1457 ssid: id.ssid.clone(),
1458 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1459 },
1460 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1461 )]);
1462 saved_networks.record_scan_result(target, &results).await;
1463 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1466 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1467 }
1468
1469 #[fuchsia::test]
1470 async fn test_record_directed_scan_no_ssid_match() {
1471 let saved_networks = SavedNetworksManager::new_for_test().await;
1475 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1476 let credential = Credential::Psk(vec![11; 32]);
1477 let diff_ssid = types::Ssid::try_from("other-ssid").unwrap();
1478
1479 assert!(
1481 saved_networks
1482 .store(id.clone(), credential.clone())
1483 .await
1484 .expect("Failed to save network")
1485 .is_none()
1486 );
1487 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1488 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1489
1490 let target = vec![id.ssid.clone()];
1492 let results = HashMap::from([(
1493 types::NetworkIdentifierDetailed {
1494 ssid: diff_ssid,
1495 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1496 },
1497 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1498 )]);
1499 saved_networks.record_scan_result(target, &results).await;
1500
1501 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1502 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1503 }
1504
1505 #[fuchsia::test]
1506 async fn test_record_directed_one_not_compatible_one_compatible() {
1507 let saved_networks = SavedNetworksManager::new_for_test().await;
1511 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1512 let credential = Credential::Password(b"foo-pass".to_vec());
1513
1514 assert!(
1516 saved_networks
1517 .store(id.clone(), credential.clone())
1518 .await
1519 .expect("Failed to save network")
1520 .is_none()
1521 );
1522 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1523 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1524
1525 let target = vec![id.ssid.clone()];
1528 let results = HashMap::from([
1529 (
1530 types::NetworkIdentifierDetailed {
1531 ssid: id.ssid.clone(),
1532 security_type: types::SecurityTypeDetailed::Wpa1,
1533 },
1534 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1535 ),
1536 (
1537 types::NetworkIdentifierDetailed {
1538 ssid: id.ssid.clone(),
1539 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1540 },
1541 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1542 ),
1543 ]);
1544 saved_networks.record_scan_result(target, &results).await;
1545 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1548 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1549 }
1550
1551 #[fuchsia::test]
1552 async fn test_record_both_directed_and_undirected() {
1553 let saved_networks = SavedNetworksManager::new_for_test().await;
1554 let saved_undirected_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1555 let saved_undirected_network = types::NetworkIdentifierDetailed {
1556 ssid: saved_undirected_id.ssid.clone(),
1557 security_type: types::SecurityTypeDetailed::Open,
1558 };
1559 let saved_directed_id = NetworkIdentifier::try_from("bar", SecurityType::None).unwrap();
1560 let credential = Credential::None;
1561
1562 assert!(
1564 saved_networks
1565 .store(saved_undirected_id.clone(), credential.clone())
1566 .await
1567 .expect("Failed to save network")
1568 .is_none()
1569 );
1570 assert!(
1571 saved_networks
1572 .store(saved_directed_id.clone(), credential.clone())
1573 .await
1574 .expect("Failed to save network")
1575 .is_none()
1576 );
1577
1578 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1580 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1581 });
1582
1583 let results = HashMap::from([(
1585 saved_undirected_network,
1586 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1587 )]);
1588 saved_networks.record_scan_result(vec![saved_directed_id.ssid.clone()], &results).await;
1589
1590 assert_matches!(saved_networks.lookup(&saved_undirected_id).await.as_slice(), [config] => {
1592 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1593 });
1594 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1596 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1597 });
1598 }
1599
1600 #[fuchsia::test]
1601 fn evict_if_needed_removes_unconnected() {
1602 let unconnected_config = network_config("foo", "password");
1606 let mut connected_config = unconnected_config.clone();
1607 connected_config.has_ever_connected = false;
1608 let mut network_configs = vec![connected_config; MAX_CONFIGS_PER_SSID - 1];
1609 network_configs.insert(MAX_CONFIGS_PER_SSID / 2, unconnected_config.clone());
1610
1611 assert_eq!(evict_if_needed(&mut network_configs), Some(unconnected_config));
1612 assert_eq!(MAX_CONFIGS_PER_SSID - 1, network_configs.len());
1613 for config in network_configs.iter() {
1616 assert!(config.has_ever_connected);
1617 }
1618 }
1619
1620 #[fuchsia::test]
1621 fn evict_if_needed_already_has_space() {
1622 let mut configs = vec![];
1623 assert_eq!(evict_if_needed(&mut configs), None);
1624 let expected_cfgs: Vec<NetworkConfig> = vec![];
1625 assert_eq!(expected_cfgs, configs);
1626
1627 if MAX_CONFIGS_PER_SSID > 1 {
1628 let mut configs = vec![network_config("foo", "password")];
1629 assert_eq!(evict_if_needed(&mut configs), None);
1630 assert_eq!(vec![network_config("foo", "password")], configs);
1632 }
1633 }
1634
1635 #[fuchsia::test]
1636 async fn clear() {
1637 let store_id = "clear";
1638 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1639 let saved_networks = create_saved_networks(store_id).await;
1640
1641 assert!(
1642 saved_networks
1643 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
1644 .await
1645 .expect("storing 'foo' failed")
1646 .is_none()
1647 );
1648 assert_eq!(
1649 vec![network_config("foo", "qwertyuio")],
1650 saved_networks.lookup(&network_id).await
1651 );
1652 assert_eq!(1, saved_networks.known_network_count().await);
1653
1654 saved_networks.clear().await.expect("failed to clear saved networks");
1655 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1656 assert_eq!(0, saved_networks.known_network_count().await);
1657
1658 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1660 let store = PolicyStorage::new_with_id(store_id).await;
1661 let saved_networks =
1662 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1663 .await;
1664
1665 assert_eq!(0, saved_networks.known_network_count().await);
1666 }
1667
1668 impl std::fmt::Debug for SavedNetworksManager {
1669 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1670 f.debug_struct("SavedNetworksManager")
1671 .field("saved_networks", &self.saved_networks)
1672 .finish()
1673 }
1674 }
1675
1676 #[fuchsia::test]
1677 fn test_store_errors_cause_write_errors() {
1678 use fidl::endpoints::create_request_stream;
1679 use fidl_fuchsia_stash as fidl_stash;
1680 use futures::StreamExt;
1681 use std::sync::Arc;
1682 use std::sync::atomic::{AtomicBool, Ordering};
1683
1684 let store_path_str = "/////";
1686 let mut exec = fasync::TestExecutor::new();
1687
1688 let (stash_client, mut request_stream) =
1691 create_request_stream::<fidl_stash::SecureStoreMarker>();
1692
1693 let read_from_stash = Arc::new(AtomicBool::new(false));
1694
1695 let _task = {
1696 let read_from_stash = read_from_stash.clone();
1697 fasync::Task::local(async move {
1698 while let Some(request) = request_stream.next().await {
1699 match request.unwrap() {
1700 fidl_stash::SecureStoreRequest::Identify { .. } => {}
1701 fidl_stash::SecureStoreRequest::CreateAccessor {
1702 accessor_request, ..
1703 } => {
1704 let read_from_stash = read_from_stash.clone();
1705 fuchsia_async::EHandle::local().spawn_detached(async move {
1706 let mut request_stream = accessor_request.into_stream();
1707 while let Some(request) = request_stream.next().await {
1708 match request.unwrap() {
1709 fidl_stash::StoreAccessorRequest::ListPrefix { .. } => {
1710 read_from_stash.store(true, Ordering::Relaxed);
1711 }
1714 _ => unreachable!(),
1715 }
1716 }
1717 });
1718 }
1719 }
1720 }
1721 })
1722 };
1723
1724 let store =
1726 PolicyStorage::new_with_stash_proxy_and_id(stash_client.into_proxy(), store_path_str);
1727
1728 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1731 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1732 let init_fut = SavedNetworksManager::new_with_storage(store, telemetry_sender);
1733 let mut init_fut = pin!(init_fut);
1734 let saved_networks = assert_matches!(exec.run_until_stalled(&mut init_fut), Poll::Ready(snm) => {
1735 snm
1736 });
1737
1738 let ssid = "foo";
1740 let credential = Credential::None;
1741 let network_id = NetworkIdentifier::try_from(ssid, SecurityType::None).unwrap();
1742 let save_fut = saved_networks.store(network_id.clone(), credential);
1743 let mut save_fut = pin!(save_fut);
1744
1745 assert_matches!(
1746 exec.run_until_stalled(&mut save_fut),
1747 Poll::Ready(Err(NetworkConfigError::FileWriteError))
1748 );
1749
1750 assert_matches!(exec.run_until_stalled(&mut saved_networks.lookup(&network_id)), Poll::Ready(configs) => {
1752 assert_eq!(configs, vec![network_config(ssid, "")]);
1753 });
1754 assert_matches!(exec.run_until_stalled(&mut saved_networks.known_network_count()), Poll::Ready(count) => {
1755 assert_eq!(count, 1);
1756 });
1757 }
1758
1759 async fn create_saved_networks(store_id: &str) -> SavedNetworksManager {
1762 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1763 let store = PolicyStorage::new_with_id(store_id).await;
1764 let saved_networks =
1765 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1766 .await;
1767 saved_networks.clear().await.expect("failed to clear saved networks");
1768 saved_networks
1769 }
1770
1771 fn network_config(ssid: &str, password: impl Into<Vec<u8>>) -> NetworkConfig {
1775 let credential = Credential::from_bytes(password.into());
1776 let id = NetworkIdentifier::try_from(ssid, credential.derived_security_type()).unwrap();
1777 let has_ever_connected = false;
1778 NetworkConfig::new(id, credential, has_ever_connected).unwrap()
1779 }
1780
1781 #[fuchsia::test]
1782 async fn record_metrics_when_called_on_class() {
1783 let store_id = generate_string();
1784 let (telemetry_sender, mut telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1785 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1786 let store = PolicyStorage::new_with_id(&store_id).await;
1787
1788 let saved_networks = SavedNetworksManager::new_with_storage(store, telemetry_sender).await;
1789 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1790 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1791
1792 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
1793 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1794 assert_eq!(0, saved_networks.known_network_count().await);
1795
1796 assert!(
1798 saved_networks
1799 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
1800 .await
1801 .expect("storing 'foo' failed")
1802 .is_none()
1803 );
1804 assert_eq!(1, saved_networks.known_network_count().await);
1805
1806 assert!(
1808 saved_networks
1809 .store(network_id_baz.clone(), Credential::Psk(vec![1; 32]))
1810 .await
1811 .expect("storing 'baz' with PSK failed")
1812 .is_none()
1813 );
1814 assert_eq!(2, saved_networks.known_network_count().await);
1815
1816 saved_networks.record_periodic_metrics().await;
1818
1819 assert_matches!(telemetry_receiver.try_next(), Ok(Some(TelemetryEvent::SavedNetworkCount { saved_network_count, config_count_per_saved_network })) => {
1821 assert_eq!(saved_network_count, 2);
1822 assert_eq!(config_count_per_saved_network, [1, 1]);
1823 });
1824 }
1825
1826 #[fuchsia::test]
1827 async fn probabilistic_choosing_of_hidden_networks() {
1828 let id_hidden = types::NetworkIdentifier {
1830 ssid: types::Ssid::try_from("hidden").unwrap(),
1831 security_type: types::SecurityType::Wpa2,
1832 };
1833 let mut net_config_hidden = NetworkConfig::new(
1834 id_hidden.clone(),
1835 Credential::Password(b"password".to_vec()),
1836 false,
1837 )
1838 .expect("failed to create network config");
1839 net_config_hidden.hidden_probability = 1.0;
1840
1841 let id_not_hidden = types::NetworkIdentifier {
1842 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1843 security_type: types::SecurityType::Wpa2,
1844 };
1845 let mut net_config_not_hidden = NetworkConfig::new(
1846 id_not_hidden.clone(),
1847 Credential::Password(b"password".to_vec()),
1848 false,
1849 )
1850 .expect("failed to create network config");
1851 net_config_not_hidden.hidden_probability = 0.0;
1852
1853 let id_maybe_hidden = types::NetworkIdentifier {
1854 ssid: types::Ssid::try_from("maybe_hidden").unwrap(),
1855 security_type: types::SecurityType::Wpa2,
1856 };
1857 let mut net_config_maybe_hidden = NetworkConfig::new(
1858 id_maybe_hidden.clone(),
1859 Credential::Password(b"password".to_vec()),
1860 false,
1861 )
1862 .expect("failed to create network config");
1863 net_config_maybe_hidden.hidden_probability = 0.5;
1864
1865 let mut maybe_hidden_selection_count = 0;
1866 let mut hidden_selection_count = 0;
1867
1868 for _ in 1..100 {
1870 let selected_networks = select_subset_potentially_hidden_networks(vec![
1871 net_config_hidden.clone(),
1872 net_config_not_hidden.clone(),
1873 net_config_maybe_hidden.clone(),
1874 ]);
1875 assert!(selected_networks.contains(&id_hidden));
1877 assert!(!selected_networks.contains(&id_not_hidden));
1879
1880 if selected_networks.contains(&id_maybe_hidden) {
1882 maybe_hidden_selection_count += 1;
1883 }
1884 if selected_networks.contains(&id_hidden) {
1885 hidden_selection_count += 1;
1886 }
1887 }
1888
1889 assert!(maybe_hidden_selection_count > 0);
1894 assert!(maybe_hidden_selection_count < hidden_selection_count);
1895 }
1896
1897 #[fuchsia::test]
1898 async fn test_select_high_probability_hidden_networks() {
1899 let id_hidden = types::NetworkIdentifier {
1901 ssid: types::Ssid::try_from("hidden").unwrap(),
1902 security_type: types::SecurityType::Wpa2,
1903 };
1904 let mut net_config_hidden = NetworkConfig::new(
1905 id_hidden.clone(),
1906 Credential::Password(b"password".to_vec()),
1907 false,
1908 )
1909 .expect("failed to create network config");
1910 net_config_hidden.hidden_probability = 1.0;
1911
1912 let id_maybe_hidden_high = types::NetworkIdentifier {
1913 ssid: types::Ssid::try_from("maybe_hidden_high").unwrap(),
1914 security_type: types::SecurityType::Wpa2,
1915 };
1916 let mut net_config_maybe_hidden_high = NetworkConfig::new(
1917 id_maybe_hidden_high.clone(),
1918 Credential::Password(b"password".to_vec()),
1919 false,
1920 )
1921 .expect("failed to create network config");
1922 net_config_maybe_hidden_high.hidden_probability = 0.8;
1923
1924 let id_maybe_hidden_low = types::NetworkIdentifier {
1925 ssid: types::Ssid::try_from("maybe_hidden_low").unwrap(),
1926 security_type: types::SecurityType::Wpa2,
1927 };
1928 let mut net_config_maybe_hidden_low = NetworkConfig::new(
1929 id_maybe_hidden_low.clone(),
1930 Credential::Password(b"password".to_vec()),
1931 false,
1932 )
1933 .expect("failed to create network config");
1934 net_config_maybe_hidden_low.hidden_probability = 0.7;
1935
1936 let id_not_hidden = types::NetworkIdentifier {
1937 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1938 security_type: types::SecurityType::Wpa2,
1939 };
1940 let mut net_config_not_hidden = NetworkConfig::new(
1941 id_not_hidden.clone(),
1942 Credential::Password(b"password".to_vec()),
1943 false,
1944 )
1945 .expect("failed to create network config");
1946 net_config_not_hidden.hidden_probability = 0.0;
1947
1948 let selected_networks = select_high_probability_hidden_networks(vec![
1949 net_config_hidden.clone(),
1950 net_config_maybe_hidden_high.clone(),
1951 net_config_maybe_hidden_low.clone(),
1952 net_config_not_hidden.clone(),
1953 ]);
1954
1955 assert!(selected_networks.contains(&id_hidden));
1957 assert!(selected_networks.contains(&id_maybe_hidden_high));
1959 assert!(!selected_networks.contains(&id_maybe_hidden_low));
1961 assert!(!selected_networks.contains(&id_not_hidden));
1963 }
1964
1965 #[fuchsia::test]
1966 async fn test_record_not_seen_active_scan() {
1967 let saved_networks = SavedNetworksManager::new_for_test().await;
1970
1971 let id_1 = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1973 let credential_1 = Credential::Password(b"some_password".to_vec());
1974 let id_2 = NetworkIdentifier::try_from("bar", SecurityType::Wpa3).unwrap();
1975 let credential_2 = Credential::Password(b"another_password".to_vec());
1976 let id_3 = NetworkIdentifier::try_from("baz", SecurityType::None).unwrap();
1978 let id_4 = NetworkIdentifier::try_from("foobar", SecurityType::None).unwrap();
1980 let credential_4 = Credential::None;
1981
1982 assert!(
1984 saved_networks
1985 .store(id_1.clone(), credential_1)
1986 .await
1987 .expect("failed to store network")
1988 .is_none()
1989 );
1990 assert!(
1991 saved_networks
1992 .store(id_2.clone(), credential_2)
1993 .await
1994 .expect("failed to store network")
1995 .is_none()
1996 );
1997 assert!(
1998 saved_networks
1999 .store(id_4.clone(), credential_4)
2000 .await
2001 .expect("failed to store network")
2002 .is_none()
2003 );
2004 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2007 assert_eq!(config_1.hidden_probability, PROB_HIDDEN_DEFAULT);
2008 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2009 assert_eq!(config_2.hidden_probability, PROB_HIDDEN_DEFAULT);
2010 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2011 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2012
2013 let not_seen_ids = vec![id_1.ssid.clone(), id_2.ssid.clone(), id_3.ssid.clone()];
2014 saved_networks.record_scan_result(not_seen_ids, &HashMap::new()).await;
2015
2016 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2018 assert!(config_1.hidden_probability < PROB_HIDDEN_DEFAULT);
2019 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2020 assert!(config_2.hidden_probability < PROB_HIDDEN_DEFAULT);
2021
2022 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2025 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2026
2027 assert!(saved_networks.lookup(&id_3).await.is_empty());
2029 }
2030
2031 #[fuchsia::test]
2032 async fn test_update_scan_stats_for_single_bss() {
2033 let saved_networks = SavedNetworksManager::new_for_test().await;
2036
2037 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2038 let credential = Credential::Password(b"some_password".to_vec());
2039 assert!(
2040 saved_networks
2041 .store(id.clone(), credential.clone())
2042 .await
2043 .expect("failed to store network")
2044 .is_none()
2045 );
2046
2047 let id_detailed = types::NetworkIdentifierDetailed {
2048 ssid: id.ssid.clone(),
2049 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2050 };
2051 let scan_results = HashMap::from([(
2052 id_detailed.clone(),
2053 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2054 )]);
2055
2056 for _ in 0..5 {
2058 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2059 }
2060
2061 let is_single_bss = saved_networks
2062 .is_network_single_bss(&id, &credential)
2063 .await
2064 .expect("failed to lookup if network is single BSS");
2065 assert!(is_single_bss);
2066 }
2067
2068 #[fuchsia::test]
2069 async fn test_update_scan_stats_for_multiple_bss_at_least_once() {
2070 let saved_networks = SavedNetworksManager::new_for_test().await;
2073
2074 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2075 let credential = Credential::Password(b"some_password".to_vec());
2076 assert!(
2077 saved_networks
2078 .store(id.clone(), credential.clone())
2079 .await
2080 .expect("failed to store network")
2081 .is_none()
2082 );
2083
2084 let id_detailed = types::NetworkIdentifierDetailed {
2085 ssid: id.ssid.clone(),
2086 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2087 };
2088 let scan_results_single = HashMap::from([(
2089 id_detailed.clone(),
2090 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2091 )]);
2092
2093 let scan_results_multi = HashMap::from([(
2094 id_detailed.clone(),
2095 vec![
2096 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2097 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2098 ],
2099 )]);
2100
2101 for _ in 0..2 {
2103 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2104 }
2105
2106 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_multi).await;
2107 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2108
2109 let is_single_bss = saved_networks
2112 .is_network_single_bss(&id, &credential)
2113 .await
2114 .expect("failed to lookup if network is single BSS");
2115 assert!(!is_single_bss);
2116 }
2117
2118 #[fuchsia::test]
2119 async fn test_record_scan_more_than_once_to_decide_single_bss() {
2120 let saved_networks = SavedNetworksManager::new_for_test().await;
2122
2123 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2124 let credential = Credential::Password(b"some_password".to_vec());
2125 assert!(
2126 saved_networks
2127 .store(id.clone(), credential.clone())
2128 .await
2129 .expect("failed to store network")
2130 .is_none()
2131 );
2132
2133 let id_detailed = types::NetworkIdentifierDetailed {
2134 ssid: id.ssid.clone(),
2135 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2136 };
2137 let scan_results = HashMap::from([(
2138 id_detailed,
2139 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2140 )]);
2141
2142 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2145
2146 let is_single_bss = saved_networks
2147 .is_network_single_bss(&id, &credential)
2148 .await
2149 .expect("failed to lookup if network is single BSS");
2150 assert!(!is_single_bss);
2151 }
2152
2153 #[fuchsia::test]
2154 async fn test_get_past_connections() {
2155 let saved_networks_manager = SavedNetworksManager::new_for_test().await;
2156
2157 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2158 let credential = Credential::Password(b"some_password".to_vec());
2159 let mut config = NetworkConfig::new(id.clone(), credential.clone(), true)
2160 .expect("failed to create config");
2161 let mut past_connections = HistoricalListsByBssid::new();
2162
2163 let data_1 = random_connection_data();
2165 let bssid_1 = data_1.bssid;
2166 let mut data_2 = random_connection_data();
2167 data_2.bssid = bssid_1;
2168 past_connections.add(bssid_1, data_1);
2169 past_connections.add(bssid_1, data_2);
2170
2171 let data_3 = random_connection_data();
2173 let bssid_2 = data_3.bssid;
2174 past_connections.add(bssid_2, data_3);
2175 config.perf_stats.past_connections = past_connections;
2176
2177 assert!(
2179 saved_networks_manager
2180 .saved_networks
2181 .lock()
2182 .await
2183 .insert(id.clone(), vec![config])
2184 .is_none()
2185 );
2186
2187 let mut expected_past_connections = PastConnectionList::default();
2189 expected_past_connections.add(data_1);
2190 expected_past_connections.add(data_2);
2191 let actual_past_connections =
2192 saved_networks_manager.get_past_connections(&id, &credential, &bssid_1).await;
2193 assert_eq!(actual_past_connections, expected_past_connections);
2194
2195 let mut expected_past_connections = PastConnectionList::default();
2196 expected_past_connections.add(data_3);
2197 let actual_past_connections =
2198 saved_networks_manager.get_past_connections(&id, &credential, &bssid_2).await;
2199 assert_eq!(actual_past_connections, expected_past_connections);
2200
2201 let actual_past_connections = saved_networks_manager
2204 .get_past_connections(&id, &Credential::Password(b"other-password".to_vec()), &bssid_1)
2205 .await;
2206 assert_eq!(actual_past_connections, PastConnectionList::default());
2207 }
2208
2209 fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
2210 fidl_sme::ConnectResult {
2211 code: fidl_ieee80211::StatusCode::Success,
2212 is_credential_rejected: false,
2213 is_reconnect: false,
2214 }
2215 }
2216}