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::hash_map::Entry;
21use std::collections::{HashMap, HashSet};
22use wlan_storage::policy::{POLICY_STORAGE_ID, PolicyStorage};
23
24const MAX_CONFIGS_PER_SSID: usize = 1;
25
26pub struct SavedNetworksManager {
32 saved_networks: Mutex<NetworkConfigMap>,
33 store: Mutex<PolicyStorage>,
36 telemetry_sender: TelemetrySender,
37}
38
39type NetworkConfigMap = HashMap<NetworkIdentifier, Vec<NetworkConfig>>;
43
44#[async_trait(?Send)]
45pub trait SavedNetworksManagerApi {
46 async fn remove(
49 &self,
50 network_id: NetworkIdentifier,
51 credential: Credential,
52 ) -> Result<bool, NetworkConfigError>;
53
54 async fn known_network_count(&self) -> usize;
56
57 async fn lookup(&self, id: &NetworkIdentifier) -> Vec<NetworkConfig>;
59
60 async fn lookup_compatible(
64 &self,
65 ssid: &types::Ssid,
66 scan_security: types::SecurityTypeDetailed,
67 ) -> Vec<NetworkConfig>;
68
69 async fn store(
74 &self,
75 network_id: NetworkIdentifier,
76 credential: Credential,
77 ) -> Result<Option<NetworkConfig>, NetworkConfigError>;
78
79 async fn record_connect_result(
85 &self,
86 id: NetworkIdentifier,
87 credential: &Credential,
88 bssid: types::Bssid,
89 connect_result: fidl_sme::ConnectResult,
90 scan_type: types::ScanObservation,
91 );
92
93 async fn record_disconnect(
96 &self,
97 id: &NetworkIdentifier,
98 credential: &Credential,
99 data: PastConnectionData,
100 );
101
102 async fn record_periodic_metrics(&self);
103
104 async fn record_scan_result(
107 &self,
108 target_ssids: Vec<types::Ssid>,
109 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
110 );
111
112 async fn is_network_single_bss(
113 &self,
114 id: &NetworkIdentifier,
115 credential: &Credential,
116 ) -> Result<bool, anyhow::Error>;
117
118 async fn get_networks(&self) -> Vec<NetworkConfig>;
120
121 async fn get_past_connections(
123 &self,
124 id: &NetworkIdentifier,
125 credential: &Credential,
126 bssid: &types::Bssid,
127 ) -> PastConnectionList;
128}
129
130impl SavedNetworksManager {
131 pub async fn new(telemetry_sender: TelemetrySender) -> Self {
134 let storage = PolicyStorage::new_with_id(POLICY_STORAGE_ID).await;
135 Self::new_with_storage(storage, telemetry_sender).await
136 }
137
138 pub async fn new_with_storage(
140 mut store: PolicyStorage,
141 telemetry_sender: TelemetrySender,
142 ) -> Self {
143 let mut saved_networks: HashMap<NetworkIdentifier, Vec<NetworkConfig>> = HashMap::new();
144 let stored_networks = store.load().await.unwrap_or_else(|e| {
148 error!("No saved networks loaded; error loading saved networks from storage: {}", e);
150 Vec::new()
151 });
152 let mut errors_building_configs = HashSet::new();
153
154 for persisted_data in stored_networks.into_iter() {
156 let id = NetworkIdentifier::new(
157 types::Ssid::from_bytes_unchecked(persisted_data.ssid),
158 persisted_data.security_type.into(),
159 );
160 let config = NetworkConfig::new(
161 id.clone(),
162 persisted_data.credential.clone().into(),
163 persisted_data.has_ever_connected,
164 persisted_data.hidden_probability,
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 =
330 NetworkConfig::new(network_id.clone(), credential.clone(), false, None)?;
331 let network_configs = network_entry.or_default();
332 let evicted_config = evict_if_needed(network_configs);
333 network_configs.push(network_config);
334
335 self.store.lock().await.write(persistent_data_from_config_map(&saved_networks)).map_err(
336 |e| {
337 error!("error writing network to persistent storage: {}", e);
338 NetworkConfigError::FileWriteError
339 },
340 )?;
341
342 Ok(evicted_config)
343 }
344
345 async fn record_connect_result(
346 &self,
347 id: NetworkIdentifier,
348 credential: &Credential,
349 bssid: types::Bssid,
350 connect_result: fidl_sme::ConnectResult,
351 scan_type: types::ScanObservation,
352 ) {
353 let mut saved_networks = self.saved_networks.lock().await;
354 let networks = match saved_networks.get_mut(&id) {
355 Some(networks) => networks,
356 None => {
357 error!("Failed to find network to record result of connect attempt.");
358 return;
359 }
360 };
361 for network in networks.iter_mut() {
362 if &network.credential == credential {
363 match (connect_result.code, connect_result.is_credential_rejected) {
364 (fidl_ieee80211::StatusCode::Success, _) => {
365 let mut has_change = false;
366 let old_hidden_prob = network.hidden_probability;
367 if !network.has_ever_connected {
368 network.has_ever_connected = true;
369 has_change = true;
370 }
371 match scan_type {
373 types::ScanObservation::Passive => {
374 network.update_hidden_prob(HiddenProbEvent::ConnectPassive);
375 }
376 types::ScanObservation::Active => {
377 network.update_hidden_prob(HiddenProbEvent::ConnectActive);
378 }
379 types::ScanObservation::Unknown => {}
380 };
381
382 if network.hidden_probability != old_hidden_prob {
383 has_change = true;
384 }
385
386 if has_change {
387 let data = persistent_data_from_config_map(&saved_networks);
389 if let Err(e) = self.store.lock().await.write(data) {
390 info!("Failed to record successful connect in store: {}", e);
391 }
392 }
393 }
394 (fidl_ieee80211::StatusCode::Canceled, _) => {}
395 (_, true) => {
396 network.perf_stats.connect_failures.add(
397 bssid,
398 ConnectFailure {
399 time: fasync::MonotonicInstant::now(),
400 reason: FailureReason::CredentialRejected,
401 bssid,
402 },
403 );
404 }
405 (_, _) => {
406 network.perf_stats.connect_failures.add(
407 bssid,
408 ConnectFailure {
409 time: fasync::MonotonicInstant::now(),
410 reason: FailureReason::GeneralFailure,
411 bssid,
412 },
413 );
414 }
415 }
416 return;
417 }
418 }
419 error!("Failed to find matching network to record result of connect attempt.");
421 }
422
423 async fn record_disconnect(
424 &self,
425 id: &NetworkIdentifier,
426 credential: &Credential,
427 data: PastConnectionData,
428 ) {
429 let bssid = data.bssid;
430 let mut saved_networks = self.saved_networks.lock().await;
431 let networks = match saved_networks.get_mut(id) {
432 Some(networks) => networks,
433 None => {
434 info!("Failed to find network to record disconnect stats");
435 return;
436 }
437 };
438 for network in networks.iter_mut() {
439 if &network.credential == credential {
440 network.perf_stats.past_connections.add(bssid, data);
441 return;
442 }
443 }
444 }
445
446 async fn record_periodic_metrics(&self) {
447 let saved_networks = self.saved_networks.lock().await;
448 let config_counts = saved_networks
450 .iter()
451 .map(|saved_network| {
452 let configs = saved_network.1;
453 configs.len()
454 })
455 .collect();
456 self.telemetry_sender.send(TelemetryEvent::SavedNetworkCount {
457 saved_network_count: saved_networks.len(),
458 config_count_per_saved_network: config_counts,
459 });
460 }
461
462 async fn record_scan_result(
463 &self,
464 target_ssids: Vec<types::Ssid>,
465 results: &HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>>,
466 ) {
467 let mut saved_networks = self.saved_networks.lock().await;
468 let mut has_change = false;
469
470 for (network, bss_list) in results {
471 let has_multiple_bss = bss_list.len() > 1;
475 if bss_list.iter().any(|bss| bss.observation == ScanObservation::Passive) {
477 for security in compatible_policy_securities(&network.security_type) {
480 let configs = match saved_networks
481 .get_mut(&NetworkIdentifier::new(network.ssid.clone(), security))
482 {
483 Some(configs) => configs,
484 None => continue,
485 };
486 let compatible_configs = configs.iter_mut().filter(|config| {
489 security_is_compatible(&network.security_type, &config.credential)
490 });
491 for config in compatible_configs {
492 let old_hidden_prob = config.hidden_probability;
493 config.update_hidden_prob(HiddenProbEvent::SeenPassive);
494 config.update_seen_multiple_bss(has_multiple_bss);
495 if config.hidden_probability != old_hidden_prob {
496 has_change = true;
497 }
498 }
499 }
500 }
501 }
502
503 for (id, configs) in saved_networks.iter_mut() {
505 if !target_ssids.contains(&id.ssid) {
506 continue;
507 }
508 let potential_scan_results =
511 results.iter().filter(|(scan_id, _)| scan_id.ssid == id.ssid).collect::<Vec<_>>();
512 for config in configs {
513 if !potential_scan_results.iter().any(|(scan_id, _)| {
514 compatible_policy_securities(&scan_id.security_type)
515 .contains(&config.security_type)
516 && security_is_compatible(&scan_id.security_type, &config.credential)
517 }) {
518 let old_hidden_prob = config.hidden_probability;
519 config.update_hidden_prob(HiddenProbEvent::NotSeenActive);
520 if config.hidden_probability != old_hidden_prob {
521 has_change = true;
522 }
523 }
524 }
525 }
526 if has_change {
527 let data = persistent_data_from_config_map(&saved_networks);
528 if let Err(e) = self.store.lock().await.write(data) {
529 info!("Failed to record scan result updates in store: {}", e);
530 }
531 }
532 }
533
534 async fn is_network_single_bss(
539 &self,
540 id: &NetworkIdentifier,
541 credential: &Credential,
542 ) -> Result<bool, anyhow::Error> {
543 let saved_networks_guard = self.saved_networks.lock().await;
544 let possible_configs = saved_networks_guard.get(id).ok_or_else(|| {
545 format_err!(
546 "error checking if network is single BSS; no config with matching identifier"
547 )
548 })?;
549 let config =
550 possible_configs.iter().find(|c| &c.credential == credential).ok_or_else(|| {
551 format_err!(
552 "error checking if network is single BSS; no config with matching credential"
553 )
554 })?;
555 return Ok(config.is_likely_single_bss());
556 }
557
558 async fn get_networks(&self) -> Vec<NetworkConfig> {
559 self.saved_networks.lock().await.values().flat_map(|cfgs| cfgs.clone()).collect()
560 }
561
562 async fn get_past_connections(
563 &self,
564 id: &NetworkIdentifier,
565 credential: &Credential,
566 bssid: &types::Bssid,
567 ) -> PastConnectionList {
568 self.saved_networks
569 .lock()
570 .await
571 .get(id)
572 .and_then(|configs| configs.iter().find(|config| &config.credential == credential))
573 .map(|config| config.perf_stats.past_connections.get_list_for_bss(bssid))
574 .unwrap_or_default()
575 }
576}
577
578pub fn select_subset_potentially_hidden_networks(
581 saved_networks: Vec<NetworkConfig>,
582) -> Vec<types::NetworkIdentifier> {
583 saved_networks
584 .into_iter()
585 .filter(|saved_network| {
586 saved_network.hidden_probability > rand::random_range(0.0..1.0)
592 })
593 .map(|network| types::NetworkIdentifier {
594 ssid: network.ssid,
595 security_type: network.security_type,
596 })
597 .collect()
598}
599
600pub fn select_high_probability_hidden_networks(
602 saved_networks: Vec<NetworkConfig>,
603) -> Vec<types::NetworkIdentifier> {
604 saved_networks
605 .into_iter()
606 .filter(|saved_network| saved_network.hidden_probability >= HIDDEN_PROBABILITY_HIGH)
607 .map(|network| types::NetworkIdentifier {
608 ssid: network.ssid,
609 security_type: network.security_type,
610 })
611 .collect()
612}
613
614pub fn compatible_policy_securities(
620 detailed_security: &types::SecurityTypeDetailed,
621) -> Vec<SecurityType> {
622 use fidl_sme::Protection::*;
623 match detailed_security {
624 Wpa3Enterprise | Wpa3Personal | Wpa2Wpa3Personal => {
625 vec![SecurityType::Wpa2, SecurityType::Wpa3]
626 }
627 Wpa2Enterprise
628 | Wpa2Personal
629 | Wpa1Wpa2Personal
630 | Wpa2PersonalTkipOnly
631 | Wpa1Wpa2PersonalTkipOnly => vec![SecurityType::Wpa, SecurityType::Wpa2],
632 Wpa1 => vec![SecurityType::Wpa],
633 Wep => vec![SecurityType::Wep],
634 Owe => vec![SecurityType::None],
636 OpenOweTransition => vec![SecurityType::None],
637 Open => vec![SecurityType::None],
638 Unknown => vec![],
639 }
640}
641
642pub fn security_is_compatible(
643 scan_security: &types::SecurityTypeDetailed,
644 credential: &Credential,
645) -> bool {
646 if (scan_security == &types::SecurityTypeDetailed::Wpa3Personal
647 || scan_security == &types::SecurityTypeDetailed::Wpa3Enterprise)
648 && let Credential::Psk(_) = credential
649 {
650 return false;
651 }
652 true
653}
654
655fn evict_if_needed(configs: &mut Vec<NetworkConfig>) -> Option<NetworkConfig> {
664 if configs.len() < MAX_CONFIGS_PER_SSID {
665 return None;
666 }
667
668 for i in 0..configs.len() {
669 if let Some(config) = configs.get(i)
670 && !config.has_ever_connected
671 {
672 return Some(configs.remove(i));
673 }
674 }
675 Some(configs.remove(0))
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682 use crate::config_management::{
683 HistoricalListsByBssid, PROB_HIDDEN_DEFAULT, PROB_HIDDEN_IF_CONNECT_ACTIVE,
684 PROB_HIDDEN_IF_CONNECT_PASSIVE, PROB_HIDDEN_IF_SEEN_PASSIVE,
685 };
686 use crate::util::testing::{generate_random_bss, generate_string, random_connection_data};
687 use assert_matches::assert_matches;
688 use futures::channel::mpsc;
689 use futures::task::Poll;
690 use std::pin::pin;
691 use test_case::test_case;
692
693 #[fuchsia::test]
694 async fn store_and_lookup() {
695 let store_id = generate_string();
696 let saved_networks = create_saved_networks(&store_id).await;
697 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
698
699 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
700 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
701 assert_eq!(0, saved_networks.known_network_count().await);
702
703 assert!(
705 saved_networks
706 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
707 .await
708 .expect("storing 'foo' failed")
709 .is_none()
710 );
711 assert_eq!(
712 vec![network_config("foo", "qwertyuio")],
713 saved_networks.lookup(&network_id_foo).await
714 );
715 assert_eq!(1, saved_networks.known_network_count().await);
716
717 let popped_network = saved_networks
719 .store(network_id_foo.clone(), Credential::Password(b"12345678".to_vec()))
720 .await
721 .expect("storing 'foo' a second time failed");
722 assert_eq!(popped_network, Some(network_config("foo", "qwertyuio")));
723
724 assert_eq!(
727 vec![network_config("foo", "12345678")],
728 saved_networks.lookup(&network_id_foo).await
729 );
730 assert_eq!(1, saved_networks.known_network_count().await);
731
732 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
734 let psk = Credential::Psk(vec![1; 32]);
735 let config_baz = NetworkConfig::new(network_id_baz.clone(), psk.clone(), false, None)
736 .expect("failed to create network config");
737 assert!(
738 saved_networks
739 .store(network_id_baz.clone(), psk)
740 .await
741 .expect("storing 'baz' with PSK failed")
742 .is_none()
743 );
744 assert_eq!(vec![config_baz.clone()], saved_networks.lookup(&network_id_baz).await);
745 assert_eq!(2, saved_networks.known_network_count().await);
746
747 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
749 let store = PolicyStorage::new_with_id(&store_id).await;
750
751 let saved_networks =
752 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
753 .await;
754 assert_eq!(
755 vec![network_config("foo", "12345678")],
756 saved_networks.lookup(&network_id_foo).await
757 );
758 assert_eq!(vec![config_baz], saved_networks.lookup(&network_id_baz).await);
759 assert_eq!(2, saved_networks.known_network_count().await);
760 }
761
762 #[fuchsia::test]
763 async fn store_twice() {
764 let saved_networks = SavedNetworksManager::new_for_test().await;
765 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
766
767 assert!(
768 saved_networks
769 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
770 .await
771 .expect("storing 'foo' failed")
772 .is_none()
773 );
774 let popped_network = saved_networks
775 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
776 .await
777 .expect("storing 'foo' a second time failed");
778 assert_eq!(popped_network, None);
780 let expected_cfgs = vec![network_config("foo", "qwertyuio")];
781 assert_eq!(expected_cfgs, saved_networks.lookup(&network_id).await);
782 assert_eq!(1, saved_networks.known_network_count().await);
783 }
784
785 #[fuchsia::test]
786 async fn store_many_same_ssid() {
787 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
788 let saved_networks = SavedNetworksManager::new_for_test().await;
789
790 for i in 0..MAX_CONFIGS_PER_SSID + 1 {
792 let mut password = b"password".to_vec();
793 password.push(i as u8);
794 let popped_network = saved_networks
795 .store(network_id.clone(), Credential::Password(password))
796 .await
797 .expect("Failed to saved network");
798 if i >= MAX_CONFIGS_PER_SSID {
799 assert!(popped_network.is_some());
800 } else {
801 assert!(popped_network.is_none());
802 }
803 }
804
805 assert_eq!(MAX_CONFIGS_PER_SSID, saved_networks.lookup(&network_id).await.len());
807 }
808
809 #[fuchsia::test]
810 async fn store_and_remove() {
811 let store_id = generate_string();
812 let saved_networks = create_saved_networks(&store_id).await;
813
814 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
815 let credential = Credential::Password(b"qwertyuio".to_vec());
816 assert!(saved_networks.lookup(&network_id).await.is_empty());
817 assert_eq!(0, saved_networks.known_network_count().await);
818
819 assert!(
821 saved_networks
822 .store(network_id.clone(), credential.clone())
823 .await
824 .expect("storing 'foo' failed")
825 .is_none()
826 );
827 assert_eq!(
828 vec![network_config("foo", "qwertyuio")],
829 saved_networks.lookup(&network_id).await
830 );
831 assert_eq!(1, saved_networks.known_network_count().await);
832
833 assert!(
836 !saved_networks
837 .remove(network_id.clone(), Credential::Password(b"diff-password".to_vec()))
838 .await
839 .expect("removing 'foo' failed")
840 );
841 assert_eq!(1, saved_networks.known_network_count().await);
842
843 assert!(
845 saved_networks
846 .remove(network_id.clone(), credential.clone())
847 .await
848 .expect("removing 'foo' failed")
849 );
850 assert_eq!(0, saved_networks.known_network_count().await);
851 assert!(saved_networks.saved_networks.lock().await.get(&network_id).is_none());
853
854 assert!(
856 !saved_networks
857 .remove(network_id.clone(), credential)
858 .await
859 .expect("removing 'foo' failed")
860 );
861
862 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
864 let store = PolicyStorage::new_with_id(&store_id).await;
865 let saved_networks =
866 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
867 .await;
868 assert_eq!(0, saved_networks.known_network_count().await);
869 assert!(saved_networks.lookup(&network_id).await.is_empty());
870 }
871
872 #[fuchsia::test]
873 fn sme_protection_converts_to_lower_compatible() {
874 use fidl_sme::Protection::*;
875 let lower_compatible_pairs = vec![
876 (Wpa3Enterprise, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
877 (Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
878 (Wpa2Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
879 (Wpa2Enterprise, vec![SecurityType::Wpa, SecurityType::Wpa2]),
880 (Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
881 (Wpa1Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
882 (Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
883 (Wpa1Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
884 (Wpa1, vec![SecurityType::Wpa]),
885 (Wep, vec![SecurityType::Wep]),
886 (Open, vec![SecurityType::None]),
887 (Unknown, vec![]),
888 ];
889 for (detailed_security, security) in lower_compatible_pairs {
890 assert_eq!(compatible_policy_securities(&detailed_security), security);
891 }
892 }
893
894 #[fuchsia::test]
895 async fn lookup_compatible_returns_both_compatible_configs() {
896 let saved_networks = SavedNetworksManager::new_for_test().await;
897 let ssid = types::Ssid::try_from("foo").unwrap();
898 let network_id_wpa2 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
899 let network_id_wpa3 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
900 let credential_wpa2 = Credential::Password(b"password".to_vec());
901 let credential_wpa3 = Credential::Password(b"wpa3-password".to_vec());
902
903 let results = saved_networks
906 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
907 .await;
908 assert!(results.is_empty());
909 assert_eq!(saved_networks.known_network_count().await, 0);
910
911 assert!(
914 saved_networks
915 .store(network_id_wpa2.clone(), credential_wpa2.clone())
916 .await
917 .expect("Failed to store network")
918 .is_none()
919 );
920 assert!(
921 saved_networks
922 .store(network_id_wpa3.clone(), credential_wpa3.clone())
923 .await
924 .expect("Failed to store network")
925 .is_none()
926 );
927 let network_id_wep = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa);
929 assert!(
930 saved_networks
931 .store(network_id_wep.clone(), Credential::Password(b"abcdefgh".to_vec()))
932 .await
933 .expect("Failed to store network")
934 .is_none()
935 );
936
937 let results = saved_networks
938 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
939 .await;
940 let expected_config_wpa2 =
941 NetworkConfig::new(network_id_wpa2, credential_wpa2, false, None)
942 .expect("Failed to create config");
943 let expected_config_wpa3 =
944 NetworkConfig::new(network_id_wpa3, credential_wpa3, false, None)
945 .expect("Failed to create config");
946 assert_eq!(results.len(), 2);
947 assert!(results.contains(&expected_config_wpa2));
948 assert!(results.contains(&expected_config_wpa3));
949 }
950
951 #[test_case(types::SecurityTypeDetailed::Wpa3Personal)]
952 #[test_case(types::SecurityTypeDetailed::Wpa3Enterprise)]
953 #[fuchsia::test(add_test_attr = false)]
954 fn lookup_compatible_does_not_return_wpa3_psk(
955 wpa3_detailed_security: types::SecurityTypeDetailed,
956 ) {
957 let mut exec = fasync::TestExecutor::new();
958 let saved_networks = exec.run_singlethreaded(SavedNetworksManager::new_for_test());
959
960 let ssid = types::Ssid::try_from("foo").unwrap();
963 let network_id_psk = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
964 let network_id_password = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
965 let credential_psk = Credential::Psk(vec![5; 32]);
966 let credential_password = Credential::Password(b"mypassword".to_vec());
967 assert!(
968 exec.run_singlethreaded(
969 saved_networks.store(network_id_psk.clone(), credential_psk.clone()),
970 )
971 .expect("Failed to store network")
972 .is_none()
973 );
974 assert!(
975 exec.run_singlethreaded(
976 saved_networks.store(network_id_password.clone(), credential_password.clone()),
977 )
978 .expect("Failed to store network")
979 .is_none()
980 );
981
982 let expected_config_wpa3 =
984 NetworkConfig::new(network_id_password, credential_password, false, None)
985 .expect("Failed to create configc");
986 let results = exec
987 .run_singlethreaded(saved_networks.lookup_compatible(&ssid, wpa3_detailed_security));
988 assert_eq!(results, vec![expected_config_wpa3]);
989 }
990
991 #[fuchsia::test]
992 async fn connect_network() {
993 let store_id = generate_string();
994
995 let saved_networks = create_saved_networks(&store_id).await;
996
997 let network_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
998 let credential = Credential::Password(b"password".to_vec());
999 let bssid = types::Bssid::from([4; 6]);
1000
1001 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 assert!(saved_networks.lookup(&network_id).await.is_empty());
1012 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1013 assert_eq!(0, saved_networks.known_network_count().await);
1014
1015 assert!(
1017 saved_networks
1018 .store(network_id.clone(), credential.clone())
1019 .await
1020 .expect("Failed save network")
1021 .is_none()
1022 );
1023
1024 let config = network_config("bar", "password");
1025 assert_eq!(vec![config], saved_networks.lookup(&network_id).await);
1026
1027 saved_networks
1028 .record_connect_result(
1029 network_id.clone(),
1030 &credential,
1031 bssid,
1032 fake_successful_connect_result(),
1033 types::ScanObservation::Unknown,
1034 )
1035 .await;
1036
1037 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1040 assert!(config.has_ever_connected);
1041 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1042 });
1043
1044 saved_networks
1045 .record_connect_result(
1046 network_id.clone(),
1047 &credential,
1048 bssid,
1049 fake_successful_connect_result(),
1050 types::ScanObservation::Active,
1051 )
1052 .await;
1053 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1055 assert!(config.has_ever_connected);
1056 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1057 });
1058
1059 saved_networks
1060 .record_connect_result(
1061 network_id.clone(),
1062 &credential,
1063 bssid,
1064 fake_successful_connect_result(),
1065 types::ScanObservation::Passive,
1066 )
1067 .await;
1068 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1070 assert!(config.has_ever_connected);
1071 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1072 });
1073
1074 let store_reloaded = PolicyStorage::new_with_id(&store_id).await;
1076 let (telemetry_sender_reloaded, _) = mpsc::channel::<TelemetryEvent>(100);
1077 let saved_networks_reloaded = SavedNetworksManager::new_with_storage(
1078 store_reloaded,
1079 TelemetrySender::new(telemetry_sender_reloaded),
1080 )
1081 .await;
1082 assert_matches!(saved_networks_reloaded.lookup(&network_id).await.as_slice(), [config] => {
1083 assert!(config.has_ever_connected);
1084 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1085 });
1086 }
1087
1088 #[fuchsia::test]
1089 async fn test_record_connect_updates_one() {
1090 let saved_networks = SavedNetworksManager::new_for_test().await;
1091 let net_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1092 let net_id_also_valid = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1093 let credential = Credential::Password(b"some_password".to_vec());
1094 let bssid = types::Bssid::from([2; 6]);
1095
1096 assert!(
1098 saved_networks
1099 .store(net_id.clone(), credential.clone())
1100 .await
1101 .expect("Failed save network")
1102 .is_none()
1103 );
1104 assert!(
1105 saved_networks
1106 .store(net_id_also_valid.clone(), credential.clone())
1107 .await
1108 .expect("Failed save network")
1109 .is_none()
1110 );
1111 saved_networks
1112 .record_connect_result(
1113 net_id.clone(),
1114 &credential,
1115 bssid,
1116 fake_successful_connect_result(),
1117 types::ScanObservation::Unknown,
1118 )
1119 .await;
1120
1121 assert_matches!(saved_networks.lookup(&net_id).await.as_slice(), [config] => {
1122 assert!(config.has_ever_connected);
1123 });
1124 assert_matches!(saved_networks.lookup(&net_id_also_valid).await.as_slice(), [config] => {
1127 assert!(!config.has_ever_connected);
1128 });
1129 }
1130
1131 #[fuchsia::test]
1132 async fn test_record_connect_failure() {
1133 let saved_networks = SavedNetworksManager::new_for_test().await;
1134 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1135 let credential = Credential::None;
1136 let bssid = types::Bssid::from([1; 6]);
1137 let before_recording = fasync::MonotonicInstant::now();
1138
1139 saved_networks
1141 .record_connect_result(
1142 network_id.clone(),
1143 &credential,
1144 bssid,
1145 fidl_sme::ConnectResult {
1146 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1147 ..fake_successful_connect_result()
1148 },
1149 types::ScanObservation::Unknown,
1150 )
1151 .await;
1152 assert!(saved_networks.lookup(&network_id).await.is_empty());
1153 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1154 assert_eq!(0, saved_networks.known_network_count().await);
1155
1156 assert!(
1158 saved_networks
1159 .store(network_id.clone(), credential.clone())
1160 .await
1161 .expect("Failed save network")
1162 .is_none()
1163 );
1164 saved_networks
1165 .record_connect_result(
1166 network_id.clone(),
1167 &credential,
1168 bssid,
1169 fidl_sme::ConnectResult {
1170 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1171 ..fake_successful_connect_result()
1172 },
1173 types::ScanObservation::Unknown,
1174 )
1175 .await;
1176 saved_networks
1177 .record_connect_result(
1178 network_id.clone(),
1179 &credential,
1180 bssid,
1181 fidl_sme::ConnectResult {
1182 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1183 is_credential_rejected: true,
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 = saved_networks
1193 .lookup(&network_id)
1194 .await
1195 .pop()
1196 .expect("Failed to get saved network config");
1197 let connect_failures =
1198 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1199 assert_matches!(connect_failures, failures => {
1200 assert_eq!(failures.len(), 2);
1202 assert!(failures.iter().any(|failure| failure.reason == FailureReason::GeneralFailure));
1203 assert!(failures.iter().any(|failure| failure.reason == FailureReason::CredentialRejected));
1204 for failure in failures.iter() {
1206 assert_eq!(failure.bssid, bssid);
1207 assert_eq!(failure.bssid, bssid);
1208 }
1209 });
1210 }
1211
1212 #[fuchsia::test]
1213 async fn test_record_connect_cancelled_ignored() {
1214 let saved_networks = SavedNetworksManager::new_for_test().await;
1215 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1216 let credential = Credential::None;
1217 let bssid = types::Bssid::from([0; 6]);
1218 let before_recording = fasync::MonotonicInstant::now();
1219
1220 saved_networks
1222 .record_connect_result(
1223 network_id.clone(),
1224 &credential,
1225 bssid,
1226 fidl_sme::ConnectResult {
1227 code: fidl_ieee80211::StatusCode::Canceled,
1228 ..fake_successful_connect_result()
1229 },
1230 types::ScanObservation::Unknown,
1231 )
1232 .await;
1233 assert!(saved_networks.lookup(&network_id).await.is_empty());
1234 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1235 assert_eq!(0, saved_networks.known_network_count().await);
1236
1237 assert!(
1239 saved_networks
1240 .store(network_id.clone(), credential.clone())
1241 .await
1242 .expect("Failed save network")
1243 .is_none()
1244 );
1245 saved_networks
1246 .record_connect_result(
1247 network_id.clone(),
1248 &credential,
1249 bssid,
1250 fidl_sme::ConnectResult {
1251 code: fidl_ieee80211::StatusCode::Canceled,
1252 ..fake_successful_connect_result()
1253 },
1254 types::ScanObservation::Unknown,
1255 )
1256 .await;
1257
1258 assert_eq!(1, saved_networks.known_network_count().await);
1260 let saved_config = saved_networks
1261 .lookup(&network_id)
1262 .await
1263 .pop()
1264 .expect("Failed to get saved network config");
1265 let connect_failures =
1266 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1267 assert_eq!(0, connect_failures.len());
1268 }
1269
1270 #[fuchsia::test]
1271 async fn test_record_disconnect() {
1272 let saved_networks = SavedNetworksManager::new_for_test().await;
1273 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1274 let credential = Credential::Psk(vec![1; 32]);
1275 let data = random_connection_data();
1276
1277 saved_networks.record_disconnect(&id, &credential, data).await;
1278 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1280 assert_eq!(saved_networks.known_network_count().await, 0);
1281
1282 assert!(
1284 saved_networks
1285 .store(id.clone(), credential.clone())
1286 .await
1287 .expect("Failed to save network")
1288 .is_none()
1289 );
1290 saved_networks.record_disconnect(&id, &credential, data).await;
1291
1292 let recent_connections = saved_networks
1294 .lookup(&id)
1295 .await
1296 .pop()
1297 .expect("Failed to get saved network")
1298 .perf_stats
1299 .past_connections
1300 .get_recent_for_network(fasync::MonotonicInstant::INFINITE_PAST);
1301 assert_matches!(recent_connections.as_slice(), [connection_data] => {
1302 assert_eq!(connection_data, &data);
1303 })
1304 }
1305
1306 #[fuchsia::test]
1307 async fn test_record_undirected_scan() {
1308 let store_id = generate_string();
1309 let saved_networks = create_saved_networks(&store_id).await;
1310 let saved_seen_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1311 let saved_seen_network = types::NetworkIdentifierDetailed {
1312 ssid: saved_seen_id.ssid.clone(),
1313 security_type: types::SecurityTypeDetailed::Open,
1314 };
1315 let unsaved_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
1316 let unsaved_network = types::NetworkIdentifierDetailed {
1317 ssid: unsaved_id.ssid.clone(),
1318 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1319 };
1320 let saved_unseen_id = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1321 let seen_credential = Credential::None;
1322 let unseen_credential = Credential::Password(b"password".to_vec());
1323
1324 assert!(
1326 saved_networks
1327 .store(saved_seen_id.clone(), seen_credential.clone())
1328 .await
1329 .expect("Failed to save network")
1330 .is_none()
1331 );
1332 assert!(
1333 saved_networks
1334 .store(saved_unseen_id.clone(), unseen_credential.clone())
1335 .await
1336 .expect("Failed to save network")
1337 .is_none()
1338 );
1339
1340 let results: HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>> = HashMap::from([
1342 (
1343 saved_seen_network,
1344 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1345 ),
1346 (unsaved_network, vec![generate_random_bss()]),
1347 ]);
1348
1349 saved_networks
1350 .record_scan_result(vec!["some_other_ssid".try_into().unwrap()], &results)
1351 .await;
1352
1353 assert_matches!(saved_networks.lookup(&saved_seen_id).await.as_slice(), [config] => {
1354 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1355 });
1356 assert_matches!(saved_networks.lookup(&saved_unseen_id).await.as_slice(), [config] => {
1357 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1358 });
1359
1360 let store_reloaded = PolicyStorage::new_with_id(&store_id).await;
1362 let (telemetry_sender_reloaded, _) = mpsc::channel::<TelemetryEvent>(100);
1363 let saved_networks_reloaded = SavedNetworksManager::new_with_storage(
1364 store_reloaded,
1365 TelemetrySender::new(telemetry_sender_reloaded),
1366 )
1367 .await;
1368 assert_matches!(saved_networks_reloaded.lookup(&saved_seen_id).await.as_slice(), [config] => {
1369 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1370 });
1371 assert_matches!(saved_networks_reloaded.lookup(&saved_unseen_id).await.as_slice(), [config] => {
1372 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1373 });
1374 }
1375
1376 #[fuchsia::test]
1377 async fn test_record_undirected_scan_with_upgraded_security() {
1378 let saved_networks = SavedNetworksManager::new_for_test().await;
1381 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1382 let credential = Credential::Password(b"credential".to_vec());
1383
1384 assert!(
1386 saved_networks
1387 .store(id.clone(), credential.clone())
1388 .await
1389 .expect("Failed to save network")
1390 .is_none()
1391 );
1392
1393 let results = HashMap::from([(
1395 types::NetworkIdentifierDetailed {
1396 ssid: id.ssid.clone(),
1397 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1398 },
1399 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1400 )]);
1401 saved_networks.record_scan_result(vec![], &results).await;
1402 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1404 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1405 });
1406 }
1407
1408 #[fuchsia::test]
1409 async fn test_record_undirected_scan_incompatible_credential() {
1410 let saved_networks = SavedNetworksManager::new_for_test().await;
1413 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1414 let credential = Credential::Psk(vec![8; 32]);
1415
1416 assert!(
1418 saved_networks
1419 .store(id.clone(), credential.clone())
1420 .await
1421 .expect("Failed to save network")
1422 .is_none()
1423 );
1424
1425 let results = HashMap::from([(
1427 types::NetworkIdentifierDetailed {
1428 ssid: id.ssid.clone(),
1429 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1430 },
1431 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1432 )]);
1433 saved_networks.record_scan_result(vec![], &results).await;
1434 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1437 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1438 });
1439 }
1440
1441 #[fuchsia::test]
1442 async fn test_record_directed_scan_for_upgraded_security() {
1443 let saved_networks = SavedNetworksManager::new_for_test().await;
1446 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa).unwrap();
1447 let credential = Credential::Password(b"credential".to_vec());
1448
1449 assert!(
1451 saved_networks
1452 .store(id.clone(), credential.clone())
1453 .await
1454 .expect("Failed to save network")
1455 .is_none()
1456 );
1457 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1458 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1459
1460 let results = HashMap::from([(
1463 types::NetworkIdentifierDetailed {
1464 ssid: id.ssid.clone(),
1465 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1466 },
1467 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1468 )]);
1469 let target = vec![id.ssid.clone()];
1470 saved_networks.record_scan_result(target, &results).await;
1471
1472 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1473 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1474 }
1475
1476 #[fuchsia::test]
1477 async fn test_record_directed_scan_for_incompatible_credential() {
1478 let saved_networks = SavedNetworksManager::new_for_test().await;
1482 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1483 let credential = Credential::Psk(vec![11; 32]);
1484
1485 assert!(
1487 saved_networks
1488 .store(id.clone(), credential.clone())
1489 .await
1490 .expect("Failed to save network")
1491 .is_none()
1492 );
1493 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1494 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1495
1496 let target = vec![id.ssid.clone()];
1499 let results = HashMap::from([(
1500 types::NetworkIdentifierDetailed {
1501 ssid: id.ssid.clone(),
1502 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1503 },
1504 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1505 )]);
1506 saved_networks.record_scan_result(target, &results).await;
1507 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1510 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1511 }
1512
1513 #[fuchsia::test]
1514 async fn test_record_directed_scan_no_ssid_match() {
1515 let saved_networks = SavedNetworksManager::new_for_test().await;
1519 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1520 let credential = Credential::Psk(vec![11; 32]);
1521 let diff_ssid = types::Ssid::try_from("other-ssid").unwrap();
1522
1523 assert!(
1525 saved_networks
1526 .store(id.clone(), credential.clone())
1527 .await
1528 .expect("Failed to save network")
1529 .is_none()
1530 );
1531 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1532 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1533
1534 let target = vec![id.ssid.clone()];
1536 let results = HashMap::from([(
1537 types::NetworkIdentifierDetailed {
1538 ssid: diff_ssid,
1539 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1540 },
1541 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1542 )]);
1543 saved_networks.record_scan_result(target, &results).await;
1544
1545 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1546 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1547 }
1548
1549 #[fuchsia::test]
1550 async fn test_record_directed_one_not_compatible_one_compatible() {
1551 let saved_networks = SavedNetworksManager::new_for_test().await;
1555 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1556 let credential = Credential::Password(b"foo-pass".to_vec());
1557
1558 assert!(
1560 saved_networks
1561 .store(id.clone(), credential.clone())
1562 .await
1563 .expect("Failed to save network")
1564 .is_none()
1565 );
1566 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1567 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1568
1569 let target = vec![id.ssid.clone()];
1572 let results = HashMap::from([
1573 (
1574 types::NetworkIdentifierDetailed {
1575 ssid: id.ssid.clone(),
1576 security_type: types::SecurityTypeDetailed::Wpa1,
1577 },
1578 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1579 ),
1580 (
1581 types::NetworkIdentifierDetailed {
1582 ssid: id.ssid.clone(),
1583 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1584 },
1585 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1586 ),
1587 ]);
1588 saved_networks.record_scan_result(target, &results).await;
1589 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1592 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1593 }
1594
1595 #[fuchsia::test]
1596 async fn test_record_both_directed_and_undirected() {
1597 let saved_networks = SavedNetworksManager::new_for_test().await;
1598 let saved_undirected_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1599 let saved_undirected_network = types::NetworkIdentifierDetailed {
1600 ssid: saved_undirected_id.ssid.clone(),
1601 security_type: types::SecurityTypeDetailed::Open,
1602 };
1603 let saved_directed_id = NetworkIdentifier::try_from("bar", SecurityType::None).unwrap();
1604 let credential = Credential::None;
1605
1606 assert!(
1608 saved_networks
1609 .store(saved_undirected_id.clone(), credential.clone())
1610 .await
1611 .expect("Failed to save network")
1612 .is_none()
1613 );
1614 assert!(
1615 saved_networks
1616 .store(saved_directed_id.clone(), credential.clone())
1617 .await
1618 .expect("Failed to save network")
1619 .is_none()
1620 );
1621
1622 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1624 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1625 });
1626
1627 let results = HashMap::from([(
1629 saved_undirected_network,
1630 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1631 )]);
1632 saved_networks.record_scan_result(vec![saved_directed_id.ssid.clone()], &results).await;
1633
1634 assert_matches!(saved_networks.lookup(&saved_undirected_id).await.as_slice(), [config] => {
1636 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1637 });
1638 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1640 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1641 });
1642 }
1643
1644 #[fuchsia::test]
1645 fn evict_if_needed_removes_unconnected() {
1646 let unconnected_config = network_config("foo", "password");
1650 let mut connected_config = unconnected_config.clone();
1651 connected_config.has_ever_connected = false;
1652 let mut network_configs = vec![connected_config; MAX_CONFIGS_PER_SSID - 1];
1653 network_configs.insert(MAX_CONFIGS_PER_SSID / 2, unconnected_config.clone());
1654
1655 assert_eq!(evict_if_needed(&mut network_configs), Some(unconnected_config));
1656 assert_eq!(MAX_CONFIGS_PER_SSID - 1, network_configs.len());
1657 for config in network_configs.iter() {
1660 assert!(config.has_ever_connected);
1661 }
1662 }
1663
1664 #[fuchsia::test]
1665 fn evict_if_needed_already_has_space() {
1666 let mut configs = vec![];
1667 assert_eq!(evict_if_needed(&mut configs), None);
1668 let expected_cfgs: Vec<NetworkConfig> = vec![];
1669 assert_eq!(expected_cfgs, configs);
1670
1671 if MAX_CONFIGS_PER_SSID > 1 {
1672 let mut configs = vec![network_config("foo", "password")];
1673 assert_eq!(evict_if_needed(&mut configs), None);
1674 assert_eq!(vec![network_config("foo", "password")], configs);
1676 }
1677 }
1678
1679 #[fuchsia::test]
1680 async fn clear() {
1681 let store_id = "clear";
1682 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1683 let saved_networks = create_saved_networks(store_id).await;
1684
1685 assert!(
1686 saved_networks
1687 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
1688 .await
1689 .expect("storing 'foo' failed")
1690 .is_none()
1691 );
1692 assert_eq!(
1693 vec![network_config("foo", "qwertyuio")],
1694 saved_networks.lookup(&network_id).await
1695 );
1696 assert_eq!(1, saved_networks.known_network_count().await);
1697
1698 saved_networks.clear().await.expect("failed to clear saved networks");
1699 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1700 assert_eq!(0, saved_networks.known_network_count().await);
1701
1702 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1704 let store = PolicyStorage::new_with_id(store_id).await;
1705 let saved_networks =
1706 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1707 .await;
1708
1709 assert_eq!(0, saved_networks.known_network_count().await);
1710 }
1711
1712 impl std::fmt::Debug for SavedNetworksManager {
1713 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1714 f.debug_struct("SavedNetworksManager")
1715 .field("saved_networks", &self.saved_networks)
1716 .finish()
1717 }
1718 }
1719
1720 #[fuchsia::test]
1721 fn test_store_errors_cause_write_errors() {
1722 use fidl::endpoints::create_request_stream;
1723 use fidl_fuchsia_stash as fidl_stash;
1724 use futures::StreamExt;
1725 use std::sync::Arc;
1726 use std::sync::atomic::{AtomicBool, Ordering};
1727
1728 let store_path_str = "/////";
1730 let mut exec = fasync::TestExecutor::new();
1731
1732 let (stash_client, mut request_stream) =
1735 create_request_stream::<fidl_stash::SecureStoreMarker>();
1736
1737 let read_from_stash = Arc::new(AtomicBool::new(false));
1738
1739 let _task = {
1740 let read_from_stash = read_from_stash.clone();
1741 fasync::Task::local(async move {
1742 while let Some(request) = request_stream.next().await {
1743 match request.unwrap() {
1744 fidl_stash::SecureStoreRequest::Identify { .. } => {}
1745 fidl_stash::SecureStoreRequest::CreateAccessor {
1746 accessor_request, ..
1747 } => {
1748 let read_from_stash = read_from_stash.clone();
1749 fuchsia_async::EHandle::local().spawn_detached(async move {
1750 let mut request_stream = accessor_request.into_stream();
1751 while let Some(request) = request_stream.next().await {
1752 match request.unwrap() {
1753 fidl_stash::StoreAccessorRequest::ListPrefix { .. } => {
1754 read_from_stash.store(true, Ordering::Relaxed);
1755 }
1758 _ => unreachable!(),
1759 }
1760 }
1761 });
1762 }
1763 }
1764 }
1765 })
1766 };
1767
1768 let store =
1770 PolicyStorage::new_with_stash_proxy_and_id(stash_client.into_proxy(), store_path_str);
1771
1772 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1775 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1776 let init_fut = SavedNetworksManager::new_with_storage(store, telemetry_sender);
1777 let mut init_fut = pin!(init_fut);
1778 let saved_networks = assert_matches!(exec.run_until_stalled(&mut init_fut), Poll::Ready(snm) => {
1779 snm
1780 });
1781
1782 let ssid = "foo";
1784 let credential = Credential::None;
1785 let network_id = NetworkIdentifier::try_from(ssid, SecurityType::None).unwrap();
1786 let save_fut = saved_networks.store(network_id.clone(), credential);
1787 let mut save_fut = pin!(save_fut);
1788
1789 assert_matches!(
1790 exec.run_until_stalled(&mut save_fut),
1791 Poll::Ready(Err(NetworkConfigError::FileWriteError))
1792 );
1793
1794 assert_matches!(exec.run_until_stalled(&mut saved_networks.lookup(&network_id)), Poll::Ready(configs) => {
1796 assert_eq!(configs, vec![network_config(ssid, "")]);
1797 });
1798 assert_matches!(exec.run_until_stalled(&mut saved_networks.known_network_count()), Poll::Ready(count) => {
1799 assert_eq!(count, 1);
1800 });
1801 }
1802
1803 async fn create_saved_networks(store_id: &str) -> SavedNetworksManager {
1806 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1807 let store = PolicyStorage::new_with_id(store_id).await;
1808 let saved_networks =
1809 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1810 .await;
1811 saved_networks.clear().await.expect("failed to clear saved networks");
1812 saved_networks
1813 }
1814
1815 fn network_config(ssid: &str, password: impl Into<Vec<u8>>) -> NetworkConfig {
1819 let credential = Credential::from_bytes(password.into());
1820 let id = NetworkIdentifier::try_from(ssid, credential.derived_security_type()).unwrap();
1821 let has_ever_connected = false;
1822 NetworkConfig::new(id, credential, has_ever_connected, None).unwrap()
1823 }
1824
1825 #[fuchsia::test]
1826 async fn record_metrics_when_called_on_class() {
1827 let store_id = generate_string();
1828 let (telemetry_sender, mut telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1829 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1830 let store = PolicyStorage::new_with_id(&store_id).await;
1831
1832 let saved_networks = SavedNetworksManager::new_with_storage(store, telemetry_sender).await;
1833 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1834 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1835
1836 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
1837 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1838 assert_eq!(0, saved_networks.known_network_count().await);
1839
1840 assert!(
1842 saved_networks
1843 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
1844 .await
1845 .expect("storing 'foo' failed")
1846 .is_none()
1847 );
1848 assert_eq!(1, saved_networks.known_network_count().await);
1849
1850 assert!(
1852 saved_networks
1853 .store(network_id_baz.clone(), Credential::Psk(vec![1; 32]))
1854 .await
1855 .expect("storing 'baz' with PSK failed")
1856 .is_none()
1857 );
1858 assert_eq!(2, saved_networks.known_network_count().await);
1859
1860 saved_networks.record_periodic_metrics().await;
1862
1863 assert_matches!(telemetry_receiver.try_next(), Ok(Some(TelemetryEvent::SavedNetworkCount { saved_network_count, config_count_per_saved_network })) => {
1865 assert_eq!(saved_network_count, 2);
1866 assert_eq!(config_count_per_saved_network, [1, 1]);
1867 });
1868 }
1869
1870 #[fuchsia::test]
1871 async fn probabilistic_choosing_of_hidden_networks() {
1872 let id_hidden = types::NetworkIdentifier {
1874 ssid: types::Ssid::try_from("hidden").unwrap(),
1875 security_type: types::SecurityType::Wpa2,
1876 };
1877 let net_config_hidden = NetworkConfig::new(
1878 id_hidden.clone(),
1879 Credential::Password(b"password".to_vec()),
1880 false,
1881 Some(1.0),
1882 )
1883 .expect("failed to create network config");
1884
1885 let id_not_hidden = types::NetworkIdentifier {
1886 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1887 security_type: types::SecurityType::Wpa2,
1888 };
1889 let net_config_not_hidden = NetworkConfig::new(
1890 id_not_hidden.clone(),
1891 Credential::Password(b"password".to_vec()),
1892 false,
1893 Some(0.0),
1894 )
1895 .expect("failed to create network config");
1896
1897 let id_maybe_hidden = types::NetworkIdentifier {
1898 ssid: types::Ssid::try_from("maybe_hidden").unwrap(),
1899 security_type: types::SecurityType::Wpa2,
1900 };
1901 let net_config_maybe_hidden = NetworkConfig::new(
1902 id_maybe_hidden.clone(),
1903 Credential::Password(b"password".to_vec()),
1904 false,
1905 Some(0.5),
1906 )
1907 .expect("failed to create network config");
1908
1909 let mut maybe_hidden_selection_count = 0;
1910 let mut hidden_selection_count = 0;
1911
1912 for _ in 1..100 {
1914 let selected_networks = select_subset_potentially_hidden_networks(vec![
1915 net_config_hidden.clone(),
1916 net_config_not_hidden.clone(),
1917 net_config_maybe_hidden.clone(),
1918 ]);
1919 assert!(selected_networks.contains(&id_hidden));
1921 assert!(!selected_networks.contains(&id_not_hidden));
1923
1924 if selected_networks.contains(&id_maybe_hidden) {
1926 maybe_hidden_selection_count += 1;
1927 }
1928 if selected_networks.contains(&id_hidden) {
1929 hidden_selection_count += 1;
1930 }
1931 }
1932
1933 assert!(maybe_hidden_selection_count > 0);
1938 assert!(maybe_hidden_selection_count < hidden_selection_count);
1939 }
1940
1941 #[fuchsia::test]
1942 async fn test_select_high_probability_hidden_networks() {
1943 let id_hidden = types::NetworkIdentifier {
1945 ssid: types::Ssid::try_from("hidden").unwrap(),
1946 security_type: types::SecurityType::Wpa2,
1947 };
1948 let net_config_hidden = NetworkConfig::new(
1949 id_hidden.clone(),
1950 Credential::Password(b"password".to_vec()),
1951 false,
1952 Some(1.0),
1953 )
1954 .expect("failed to create network config");
1955
1956 let id_maybe_hidden_high = types::NetworkIdentifier {
1957 ssid: types::Ssid::try_from("maybe_hidden_high").unwrap(),
1958 security_type: types::SecurityType::Wpa2,
1959 };
1960 let net_config_maybe_hidden_high = NetworkConfig::new(
1961 id_maybe_hidden_high.clone(),
1962 Credential::Password(b"password".to_vec()),
1963 false,
1964 Some(0.8),
1965 )
1966 .expect("failed to create network config");
1967
1968 let id_maybe_hidden_low = types::NetworkIdentifier {
1969 ssid: types::Ssid::try_from("maybe_hidden_low").unwrap(),
1970 security_type: types::SecurityType::Wpa2,
1971 };
1972 let net_config_maybe_hidden_low = NetworkConfig::new(
1973 id_maybe_hidden_low.clone(),
1974 Credential::Password(b"password".to_vec()),
1975 false,
1976 Some(0.7),
1977 )
1978 .expect("failed to create network config");
1979
1980 let id_not_hidden = types::NetworkIdentifier {
1981 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1982 security_type: types::SecurityType::Wpa2,
1983 };
1984 let net_config_not_hidden = NetworkConfig::new(
1985 id_not_hidden.clone(),
1986 Credential::Password(b"password".to_vec()),
1987 false,
1988 Some(0.0),
1989 )
1990 .expect("failed to create network config");
1991
1992 let selected_networks = select_high_probability_hidden_networks(vec![
1993 net_config_hidden.clone(),
1994 net_config_maybe_hidden_high.clone(),
1995 net_config_maybe_hidden_low.clone(),
1996 net_config_not_hidden.clone(),
1997 ]);
1998
1999 assert!(selected_networks.contains(&id_hidden));
2001 assert!(selected_networks.contains(&id_maybe_hidden_high));
2003 assert!(!selected_networks.contains(&id_maybe_hidden_low));
2005 assert!(!selected_networks.contains(&id_not_hidden));
2007 }
2008
2009 #[fuchsia::test]
2010 async fn test_record_not_seen_active_scan() {
2011 let saved_networks = SavedNetworksManager::new_for_test().await;
2014
2015 let id_1 = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2017 let credential_1 = Credential::Password(b"some_password".to_vec());
2018 let id_2 = NetworkIdentifier::try_from("bar", SecurityType::Wpa3).unwrap();
2019 let credential_2 = Credential::Password(b"another_password".to_vec());
2020 let id_3 = NetworkIdentifier::try_from("baz", SecurityType::None).unwrap();
2022 let id_4 = NetworkIdentifier::try_from("foobar", SecurityType::None).unwrap();
2024 let credential_4 = Credential::None;
2025
2026 assert!(
2028 saved_networks
2029 .store(id_1.clone(), credential_1)
2030 .await
2031 .expect("failed to store network")
2032 .is_none()
2033 );
2034 assert!(
2035 saved_networks
2036 .store(id_2.clone(), credential_2)
2037 .await
2038 .expect("failed to store network")
2039 .is_none()
2040 );
2041 assert!(
2042 saved_networks
2043 .store(id_4.clone(), credential_4)
2044 .await
2045 .expect("failed to store network")
2046 .is_none()
2047 );
2048 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2051 assert_eq!(config_1.hidden_probability, PROB_HIDDEN_DEFAULT);
2052 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2053 assert_eq!(config_2.hidden_probability, PROB_HIDDEN_DEFAULT);
2054 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2055 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2056
2057 let not_seen_ids = vec![id_1.ssid.clone(), id_2.ssid.clone(), id_3.ssid.clone()];
2058 saved_networks.record_scan_result(not_seen_ids, &HashMap::new()).await;
2059
2060 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2062 assert!(config_1.hidden_probability < PROB_HIDDEN_DEFAULT);
2063 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2064 assert!(config_2.hidden_probability < PROB_HIDDEN_DEFAULT);
2065
2066 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2069 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2070
2071 assert!(saved_networks.lookup(&id_3).await.is_empty());
2073 }
2074
2075 #[fuchsia::test]
2076 async fn test_update_scan_stats_for_single_bss() {
2077 let saved_networks = SavedNetworksManager::new_for_test().await;
2080
2081 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2082 let credential = Credential::Password(b"some_password".to_vec());
2083 assert!(
2084 saved_networks
2085 .store(id.clone(), credential.clone())
2086 .await
2087 .expect("failed to store network")
2088 .is_none()
2089 );
2090
2091 let id_detailed = types::NetworkIdentifierDetailed {
2092 ssid: id.ssid.clone(),
2093 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2094 };
2095 let scan_results = HashMap::from([(
2096 id_detailed.clone(),
2097 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2098 )]);
2099
2100 for _ in 0..5 {
2102 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2103 }
2104
2105 let is_single_bss = saved_networks
2106 .is_network_single_bss(&id, &credential)
2107 .await
2108 .expect("failed to lookup if network is single BSS");
2109 assert!(is_single_bss);
2110 }
2111
2112 #[fuchsia::test]
2113 async fn test_update_scan_stats_for_multiple_bss_at_least_once() {
2114 let saved_networks = SavedNetworksManager::new_for_test().await;
2117
2118 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2119 let credential = Credential::Password(b"some_password".to_vec());
2120 assert!(
2121 saved_networks
2122 .store(id.clone(), credential.clone())
2123 .await
2124 .expect("failed to store network")
2125 .is_none()
2126 );
2127
2128 let id_detailed = types::NetworkIdentifierDetailed {
2129 ssid: id.ssid.clone(),
2130 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2131 };
2132 let scan_results_single = HashMap::from([(
2133 id_detailed.clone(),
2134 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2135 )]);
2136
2137 let scan_results_multi = HashMap::from([(
2138 id_detailed.clone(),
2139 vec![
2140 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2141 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2142 ],
2143 )]);
2144
2145 for _ in 0..2 {
2147 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2148 }
2149
2150 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_multi).await;
2151 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2152
2153 let is_single_bss = saved_networks
2156 .is_network_single_bss(&id, &credential)
2157 .await
2158 .expect("failed to lookup if network is single BSS");
2159 assert!(!is_single_bss);
2160 }
2161
2162 #[fuchsia::test]
2163 async fn test_record_scan_more_than_once_to_decide_single_bss() {
2164 let saved_networks = SavedNetworksManager::new_for_test().await;
2166
2167 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2168 let credential = Credential::Password(b"some_password".to_vec());
2169 assert!(
2170 saved_networks
2171 .store(id.clone(), credential.clone())
2172 .await
2173 .expect("failed to store network")
2174 .is_none()
2175 );
2176
2177 let id_detailed = types::NetworkIdentifierDetailed {
2178 ssid: id.ssid.clone(),
2179 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2180 };
2181 let scan_results = HashMap::from([(
2182 id_detailed,
2183 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2184 )]);
2185
2186 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2189
2190 let is_single_bss = saved_networks
2191 .is_network_single_bss(&id, &credential)
2192 .await
2193 .expect("failed to lookup if network is single BSS");
2194 assert!(!is_single_bss);
2195 }
2196
2197 #[fuchsia::test]
2198 async fn test_get_past_connections() {
2199 let saved_networks_manager = SavedNetworksManager::new_for_test().await;
2200
2201 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2202 let credential = Credential::Password(b"some_password".to_vec());
2203 let mut config = NetworkConfig::new(id.clone(), credential.clone(), true, None)
2204 .expect("failed to create config");
2205 let mut past_connections = HistoricalListsByBssid::new();
2206
2207 let data_1 = random_connection_data();
2209 let bssid_1 = data_1.bssid;
2210 let mut data_2 = random_connection_data();
2211 data_2.bssid = bssid_1;
2212 past_connections.add(bssid_1, data_1);
2213 past_connections.add(bssid_1, data_2);
2214
2215 let data_3 = random_connection_data();
2217 let bssid_2 = data_3.bssid;
2218 past_connections.add(bssid_2, data_3);
2219 config.perf_stats.past_connections = past_connections;
2220
2221 assert!(
2223 saved_networks_manager
2224 .saved_networks
2225 .lock()
2226 .await
2227 .insert(id.clone(), vec![config])
2228 .is_none()
2229 );
2230
2231 let mut expected_past_connections = PastConnectionList::default();
2233 expected_past_connections.add(data_1);
2234 expected_past_connections.add(data_2);
2235 let actual_past_connections =
2236 saved_networks_manager.get_past_connections(&id, &credential, &bssid_1).await;
2237 assert_eq!(actual_past_connections, expected_past_connections);
2238
2239 let mut expected_past_connections = PastConnectionList::default();
2240 expected_past_connections.add(data_3);
2241 let actual_past_connections =
2242 saved_networks_manager.get_past_connections(&id, &credential, &bssid_2).await;
2243 assert_eq!(actual_past_connections, expected_past_connections);
2244
2245 let actual_past_connections = saved_networks_manager
2248 .get_past_connections(&id, &Credential::Password(b"other-password".to_vec()), &bssid_1)
2249 .await;
2250 assert_eq!(actual_past_connections, PastConnectionList::default());
2251 }
2252
2253 fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
2254 fidl_sme::ConnectResult {
2255 code: fidl_ieee80211::StatusCode::Success,
2256 is_credential_rejected: false,
2257 is_reconnect: false,
2258 }
2259 }
2260}