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 Owe => vec![SecurityType::None],
616 OpenOweTransition => vec![SecurityType::None],
617 Open => vec![SecurityType::None],
618 Unknown => vec![],
619 }
620}
621
622pub fn security_is_compatible(
623 scan_security: &types::SecurityTypeDetailed,
624 credential: &Credential,
625) -> bool {
626 if (scan_security == &types::SecurityTypeDetailed::Wpa3Personal
627 || scan_security == &types::SecurityTypeDetailed::Wpa3Enterprise)
628 && let Credential::Psk(_) = credential
629 {
630 return false;
631 }
632 true
633}
634
635fn evict_if_needed(configs: &mut Vec<NetworkConfig>) -> Option<NetworkConfig> {
644 if configs.len() < MAX_CONFIGS_PER_SSID {
645 return None;
646 }
647
648 for i in 0..configs.len() {
649 if let Some(config) = configs.get(i)
650 && !config.has_ever_connected
651 {
652 return Some(configs.remove(i));
653 }
654 }
655 Some(configs.remove(0))
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use crate::config_management::{
663 HistoricalListsByBssid, PROB_HIDDEN_DEFAULT, PROB_HIDDEN_IF_CONNECT_ACTIVE,
664 PROB_HIDDEN_IF_CONNECT_PASSIVE, PROB_HIDDEN_IF_SEEN_PASSIVE,
665 };
666 use crate::util::testing::{generate_random_bss, generate_string, random_connection_data};
667 use assert_matches::assert_matches;
668 use futures::channel::mpsc;
669 use futures::task::Poll;
670 use std::pin::pin;
671 use test_case::test_case;
672
673 #[fuchsia::test]
674 async fn store_and_lookup() {
675 let store_id = generate_string();
676 let saved_networks = create_saved_networks(&store_id).await;
677 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
678
679 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
680 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
681 assert_eq!(0, saved_networks.known_network_count().await);
682
683 assert!(
685 saved_networks
686 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
687 .await
688 .expect("storing 'foo' failed")
689 .is_none()
690 );
691 assert_eq!(
692 vec![network_config("foo", "qwertyuio")],
693 saved_networks.lookup(&network_id_foo).await
694 );
695 assert_eq!(1, saved_networks.known_network_count().await);
696
697 let popped_network = saved_networks
699 .store(network_id_foo.clone(), Credential::Password(b"12345678".to_vec()))
700 .await
701 .expect("storing 'foo' a second time failed");
702 assert_eq!(popped_network, Some(network_config("foo", "qwertyuio")));
703
704 assert_eq!(
707 vec![network_config("foo", "12345678")],
708 saved_networks.lookup(&network_id_foo).await
709 );
710 assert_eq!(1, saved_networks.known_network_count().await);
711
712 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
714 let psk = Credential::Psk(vec![1; 32]);
715 let config_baz = NetworkConfig::new(network_id_baz.clone(), psk.clone(), false)
716 .expect("failed to create network config");
717 assert!(
718 saved_networks
719 .store(network_id_baz.clone(), psk)
720 .await
721 .expect("storing 'baz' with PSK failed")
722 .is_none()
723 );
724 assert_eq!(vec![config_baz.clone()], saved_networks.lookup(&network_id_baz).await);
725 assert_eq!(2, saved_networks.known_network_count().await);
726
727 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
729 let store = PolicyStorage::new_with_id(&store_id).await;
730
731 let saved_networks =
732 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
733 .await;
734 assert_eq!(
735 vec![network_config("foo", "12345678")],
736 saved_networks.lookup(&network_id_foo).await
737 );
738 assert_eq!(vec![config_baz], saved_networks.lookup(&network_id_baz).await);
739 assert_eq!(2, saved_networks.known_network_count().await);
740 }
741
742 #[fuchsia::test]
743 async fn store_twice() {
744 let saved_networks = SavedNetworksManager::new_for_test().await;
745 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
746
747 assert!(
748 saved_networks
749 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
750 .await
751 .expect("storing 'foo' failed")
752 .is_none()
753 );
754 let popped_network = saved_networks
755 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
756 .await
757 .expect("storing 'foo' a second time failed");
758 assert_eq!(popped_network, None);
760 let expected_cfgs = vec![network_config("foo", "qwertyuio")];
761 assert_eq!(expected_cfgs, saved_networks.lookup(&network_id).await);
762 assert_eq!(1, saved_networks.known_network_count().await);
763 }
764
765 #[fuchsia::test]
766 async fn store_many_same_ssid() {
767 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
768 let saved_networks = SavedNetworksManager::new_for_test().await;
769
770 for i in 0..MAX_CONFIGS_PER_SSID + 1 {
772 let mut password = b"password".to_vec();
773 password.push(i as u8);
774 let popped_network = saved_networks
775 .store(network_id.clone(), Credential::Password(password))
776 .await
777 .expect("Failed to saved network");
778 if i >= MAX_CONFIGS_PER_SSID {
779 assert!(popped_network.is_some());
780 } else {
781 assert!(popped_network.is_none());
782 }
783 }
784
785 assert_eq!(MAX_CONFIGS_PER_SSID, saved_networks.lookup(&network_id).await.len());
787 }
788
789 #[fuchsia::test]
790 async fn store_and_remove() {
791 let store_id = generate_string();
792 let saved_networks = create_saved_networks(&store_id).await;
793
794 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
795 let credential = Credential::Password(b"qwertyuio".to_vec());
796 assert!(saved_networks.lookup(&network_id).await.is_empty());
797 assert_eq!(0, saved_networks.known_network_count().await);
798
799 assert!(
801 saved_networks
802 .store(network_id.clone(), credential.clone())
803 .await
804 .expect("storing 'foo' failed")
805 .is_none()
806 );
807 assert_eq!(
808 vec![network_config("foo", "qwertyuio")],
809 saved_networks.lookup(&network_id).await
810 );
811 assert_eq!(1, saved_networks.known_network_count().await);
812
813 assert!(
816 !saved_networks
817 .remove(network_id.clone(), Credential::Password(b"diff-password".to_vec()))
818 .await
819 .expect("removing 'foo' failed")
820 );
821 assert_eq!(1, saved_networks.known_network_count().await);
822
823 assert!(
825 saved_networks
826 .remove(network_id.clone(), credential.clone())
827 .await
828 .expect("removing 'foo' failed")
829 );
830 assert_eq!(0, saved_networks.known_network_count().await);
831 assert!(saved_networks.saved_networks.lock().await.get(&network_id).is_none());
833
834 assert!(
836 !saved_networks
837 .remove(network_id.clone(), credential)
838 .await
839 .expect("removing 'foo' failed")
840 );
841
842 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
844 let store = PolicyStorage::new_with_id(&store_id).await;
845 let saved_networks =
846 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
847 .await;
848 assert_eq!(0, saved_networks.known_network_count().await);
849 assert!(saved_networks.lookup(&network_id).await.is_empty());
850 }
851
852 #[fuchsia::test]
853 fn sme_protection_converts_to_lower_compatible() {
854 use fidl_sme::Protection::*;
855 let lower_compatible_pairs = vec![
856 (Wpa3Enterprise, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
857 (Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
858 (Wpa2Wpa3Personal, vec![SecurityType::Wpa2, SecurityType::Wpa3]),
859 (Wpa2Enterprise, vec![SecurityType::Wpa, SecurityType::Wpa2]),
860 (Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
861 (Wpa1Wpa2Personal, vec![SecurityType::Wpa, SecurityType::Wpa2]),
862 (Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
863 (Wpa1Wpa2PersonalTkipOnly, vec![SecurityType::Wpa, SecurityType::Wpa2]),
864 (Wpa1, vec![SecurityType::Wpa]),
865 (Wep, vec![SecurityType::Wep]),
866 (Open, vec![SecurityType::None]),
867 (Unknown, vec![]),
868 ];
869 for (detailed_security, security) in lower_compatible_pairs {
870 assert_eq!(compatible_policy_securities(&detailed_security), security);
871 }
872 }
873
874 #[fuchsia::test]
875 async fn lookup_compatible_returns_both_compatible_configs() {
876 let saved_networks = SavedNetworksManager::new_for_test().await;
877 let ssid = types::Ssid::try_from("foo").unwrap();
878 let network_id_wpa2 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
879 let network_id_wpa3 = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
880 let credential_wpa2 = Credential::Password(b"password".to_vec());
881 let credential_wpa3 = Credential::Password(b"wpa3-password".to_vec());
882
883 let results = saved_networks
886 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
887 .await;
888 assert!(results.is_empty());
889 assert_eq!(saved_networks.known_network_count().await, 0);
890
891 assert!(
894 saved_networks
895 .store(network_id_wpa2.clone(), credential_wpa2.clone())
896 .await
897 .expect("Failed to store network")
898 .is_none()
899 );
900 assert!(
901 saved_networks
902 .store(network_id_wpa3.clone(), credential_wpa3.clone())
903 .await
904 .expect("Failed to store network")
905 .is_none()
906 );
907 let network_id_wep = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa);
909 assert!(
910 saved_networks
911 .store(network_id_wep.clone(), Credential::Password(b"abcdefgh".to_vec()))
912 .await
913 .expect("Failed to store network")
914 .is_none()
915 );
916
917 let results = saved_networks
918 .lookup_compatible(&ssid, types::SecurityTypeDetailed::Wpa2Wpa3Personal)
919 .await;
920 let expected_config_wpa2 = NetworkConfig::new(network_id_wpa2, credential_wpa2, false)
921 .expect("Failed to create config");
922 let expected_config_wpa3 = NetworkConfig::new(network_id_wpa3, credential_wpa3, false)
923 .expect("Failed to create config");
924 assert_eq!(results.len(), 2);
925 assert!(results.contains(&expected_config_wpa2));
926 assert!(results.contains(&expected_config_wpa3));
927 }
928
929 #[test_case(types::SecurityTypeDetailed::Wpa3Personal)]
930 #[test_case(types::SecurityTypeDetailed::Wpa3Enterprise)]
931 #[fuchsia::test(add_test_attr = false)]
932 fn lookup_compatible_does_not_return_wpa3_psk(
933 wpa3_detailed_security: types::SecurityTypeDetailed,
934 ) {
935 let mut exec = fasync::TestExecutor::new();
936 let saved_networks = exec.run_singlethreaded(SavedNetworksManager::new_for_test());
937
938 let ssid = types::Ssid::try_from("foo").unwrap();
941 let network_id_psk = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa2);
942 let network_id_password = NetworkIdentifier::new(ssid.clone(), SecurityType::Wpa3);
943 let credential_psk = Credential::Psk(vec![5; 32]);
944 let credential_password = Credential::Password(b"mypassword".to_vec());
945 assert!(
946 exec.run_singlethreaded(
947 saved_networks.store(network_id_psk.clone(), credential_psk.clone()),
948 )
949 .expect("Failed to store network")
950 .is_none()
951 );
952 assert!(
953 exec.run_singlethreaded(
954 saved_networks.store(network_id_password.clone(), credential_password.clone()),
955 )
956 .expect("Failed to store network")
957 .is_none()
958 );
959
960 let expected_config_wpa3 =
962 NetworkConfig::new(network_id_password, credential_password, false)
963 .expect("Failed to create configc");
964 let results = exec
965 .run_singlethreaded(saved_networks.lookup_compatible(&ssid, wpa3_detailed_security));
966 assert_eq!(results, vec![expected_config_wpa3]);
967 }
968
969 #[fuchsia::test]
970 async fn connect_network() {
971 let store_id = generate_string();
972
973 let saved_networks = create_saved_networks(&store_id).await;
974
975 let network_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
976 let credential = Credential::Password(b"password".to_vec());
977 let bssid = types::Bssid::from([4; 6]);
978
979 saved_networks
981 .record_connect_result(
982 network_id.clone(),
983 &credential,
984 bssid,
985 fake_successful_connect_result(),
986 types::ScanObservation::Unknown,
987 )
988 .await;
989 assert!(saved_networks.lookup(&network_id).await.is_empty());
990 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
991 assert_eq!(0, saved_networks.known_network_count().await);
992
993 assert!(
995 saved_networks
996 .store(network_id.clone(), credential.clone())
997 .await
998 .expect("Failed save network")
999 .is_none()
1000 );
1001
1002 let config = network_config("bar", "password");
1003 assert_eq!(vec![config], saved_networks.lookup(&network_id).await);
1004
1005 saved_networks
1006 .record_connect_result(
1007 network_id.clone(),
1008 &credential,
1009 bssid,
1010 fake_successful_connect_result(),
1011 types::ScanObservation::Unknown,
1012 )
1013 .await;
1014
1015 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1018 assert!(config.has_ever_connected);
1019 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1020 });
1021
1022 saved_networks
1023 .record_connect_result(
1024 network_id.clone(),
1025 &credential,
1026 bssid,
1027 fake_successful_connect_result(),
1028 types::ScanObservation::Active,
1029 )
1030 .await;
1031 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1033 assert!(config.has_ever_connected);
1034 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_ACTIVE);
1035 });
1036
1037 saved_networks
1038 .record_connect_result(
1039 network_id.clone(),
1040 &credential,
1041 bssid,
1042 fake_successful_connect_result(),
1043 types::ScanObservation::Passive,
1044 )
1045 .await;
1046 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1048 assert!(config.has_ever_connected);
1049 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_CONNECT_PASSIVE);
1050 });
1051
1052 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1054 let store = PolicyStorage::new_with_id(&store_id).await;
1055 let saved_networks =
1056 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1057 .await;
1058 assert_matches!(saved_networks.lookup(&network_id).await.as_slice(), [config] => {
1059 assert!(config.has_ever_connected);
1060 });
1061 }
1062
1063 #[fuchsia::test]
1064 async fn test_record_connect_updates_one() {
1065 let saved_networks = SavedNetworksManager::new_for_test().await;
1066 let net_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1067 let net_id_also_valid = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1068 let credential = Credential::Password(b"some_password".to_vec());
1069 let bssid = types::Bssid::from([2; 6]);
1070
1071 assert!(
1073 saved_networks
1074 .store(net_id.clone(), credential.clone())
1075 .await
1076 .expect("Failed save network")
1077 .is_none()
1078 );
1079 assert!(
1080 saved_networks
1081 .store(net_id_also_valid.clone(), credential.clone())
1082 .await
1083 .expect("Failed save network")
1084 .is_none()
1085 );
1086 saved_networks
1087 .record_connect_result(
1088 net_id.clone(),
1089 &credential,
1090 bssid,
1091 fake_successful_connect_result(),
1092 types::ScanObservation::Unknown,
1093 )
1094 .await;
1095
1096 assert_matches!(saved_networks.lookup(&net_id).await.as_slice(), [config] => {
1097 assert!(config.has_ever_connected);
1098 });
1099 assert_matches!(saved_networks.lookup(&net_id_also_valid).await.as_slice(), [config] => {
1102 assert!(!config.has_ever_connected);
1103 });
1104 }
1105
1106 #[fuchsia::test]
1107 async fn test_record_connect_failure() {
1108 let saved_networks = SavedNetworksManager::new_for_test().await;
1109 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1110 let credential = Credential::None;
1111 let bssid = types::Bssid::from([1; 6]);
1112 let before_recording = fasync::MonotonicInstant::now();
1113
1114 saved_networks
1116 .record_connect_result(
1117 network_id.clone(),
1118 &credential,
1119 bssid,
1120 fidl_sme::ConnectResult {
1121 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1122 ..fake_successful_connect_result()
1123 },
1124 types::ScanObservation::Unknown,
1125 )
1126 .await;
1127 assert!(saved_networks.lookup(&network_id).await.is_empty());
1128 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1129 assert_eq!(0, saved_networks.known_network_count().await);
1130
1131 assert!(
1133 saved_networks
1134 .store(network_id.clone(), credential.clone())
1135 .await
1136 .expect("Failed save network")
1137 .is_none()
1138 );
1139 saved_networks
1140 .record_connect_result(
1141 network_id.clone(),
1142 &credential,
1143 bssid,
1144 fidl_sme::ConnectResult {
1145 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1146 ..fake_successful_connect_result()
1147 },
1148 types::ScanObservation::Unknown,
1149 )
1150 .await;
1151 saved_networks
1152 .record_connect_result(
1153 network_id.clone(),
1154 &credential,
1155 bssid,
1156 fidl_sme::ConnectResult {
1157 code: fidl_ieee80211::StatusCode::RefusedReasonUnspecified,
1158 is_credential_rejected: true,
1159 ..fake_successful_connect_result()
1160 },
1161 types::ScanObservation::Unknown,
1162 )
1163 .await;
1164
1165 assert_eq!(1, saved_networks.known_network_count().await);
1167 let saved_config = saved_networks
1168 .lookup(&network_id)
1169 .await
1170 .pop()
1171 .expect("Failed to get saved network config");
1172 let connect_failures =
1173 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1174 assert_matches!(connect_failures, failures => {
1175 assert_eq!(failures.len(), 2);
1177 assert!(failures.iter().any(|failure| failure.reason == FailureReason::GeneralFailure));
1178 assert!(failures.iter().any(|failure| failure.reason == FailureReason::CredentialRejected));
1179 for failure in failures.iter() {
1181 assert_eq!(failure.bssid, bssid);
1182 assert_eq!(failure.bssid, bssid);
1183 }
1184 });
1185 }
1186
1187 #[fuchsia::test]
1188 async fn test_record_connect_cancelled_ignored() {
1189 let saved_networks = SavedNetworksManager::new_for_test().await;
1190 let network_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1191 let credential = Credential::None;
1192 let bssid = types::Bssid::from([0; 6]);
1193 let before_recording = fasync::MonotonicInstant::now();
1194
1195 saved_networks
1197 .record_connect_result(
1198 network_id.clone(),
1199 &credential,
1200 bssid,
1201 fidl_sme::ConnectResult {
1202 code: fidl_ieee80211::StatusCode::Canceled,
1203 ..fake_successful_connect_result()
1204 },
1205 types::ScanObservation::Unknown,
1206 )
1207 .await;
1208 assert!(saved_networks.lookup(&network_id).await.is_empty());
1209 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1210 assert_eq!(0, saved_networks.known_network_count().await);
1211
1212 assert!(
1214 saved_networks
1215 .store(network_id.clone(), credential.clone())
1216 .await
1217 .expect("Failed save network")
1218 .is_none()
1219 );
1220 saved_networks
1221 .record_connect_result(
1222 network_id.clone(),
1223 &credential,
1224 bssid,
1225 fidl_sme::ConnectResult {
1226 code: fidl_ieee80211::StatusCode::Canceled,
1227 ..fake_successful_connect_result()
1228 },
1229 types::ScanObservation::Unknown,
1230 )
1231 .await;
1232
1233 assert_eq!(1, saved_networks.known_network_count().await);
1235 let saved_config = saved_networks
1236 .lookup(&network_id)
1237 .await
1238 .pop()
1239 .expect("Failed to get saved network config");
1240 let connect_failures =
1241 saved_config.perf_stats.connect_failures.get_recent_for_network(before_recording);
1242 assert_eq!(0, connect_failures.len());
1243 }
1244
1245 #[fuchsia::test]
1246 async fn test_record_disconnect() {
1247 let saved_networks = SavedNetworksManager::new_for_test().await;
1248 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1249 let credential = Credential::Psk(vec![1; 32]);
1250 let data = random_connection_data();
1251
1252 saved_networks.record_disconnect(&id, &credential, data).await;
1253 assert_eq!(saved_networks.saved_networks.lock().await.len(), 0);
1255 assert_eq!(saved_networks.known_network_count().await, 0);
1256
1257 assert!(
1259 saved_networks
1260 .store(id.clone(), credential.clone())
1261 .await
1262 .expect("Failed to save network")
1263 .is_none()
1264 );
1265 saved_networks.record_disconnect(&id, &credential, data).await;
1266
1267 let recent_connections = saved_networks
1269 .lookup(&id)
1270 .await
1271 .pop()
1272 .expect("Failed to get saved network")
1273 .perf_stats
1274 .past_connections
1275 .get_recent_for_network(fasync::MonotonicInstant::INFINITE_PAST);
1276 assert_matches!(recent_connections.as_slice(), [connection_data] => {
1277 assert_eq!(connection_data, &data);
1278 })
1279 }
1280
1281 #[fuchsia::test]
1282 async fn test_record_undirected_scan() {
1283 let saved_networks = SavedNetworksManager::new_for_test().await;
1284 let saved_seen_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1285 let saved_seen_network = types::NetworkIdentifierDetailed {
1286 ssid: saved_seen_id.ssid.clone(),
1287 security_type: types::SecurityTypeDetailed::Open,
1288 };
1289 let unsaved_id = NetworkIdentifier::try_from("bar", SecurityType::Wpa2).unwrap();
1290 let unsaved_network = types::NetworkIdentifierDetailed {
1291 ssid: unsaved_id.ssid.clone(),
1292 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1293 };
1294 let saved_unseen_id = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1295 let seen_credential = Credential::None;
1296 let unseen_credential = Credential::Password(b"password".to_vec());
1297
1298 assert!(
1300 saved_networks
1301 .store(saved_seen_id.clone(), seen_credential.clone())
1302 .await
1303 .expect("Failed to save network")
1304 .is_none()
1305 );
1306 assert!(
1307 saved_networks
1308 .store(saved_unseen_id.clone(), unseen_credential.clone())
1309 .await
1310 .expect("Failed to save network")
1311 .is_none()
1312 );
1313
1314 let results: HashMap<types::NetworkIdentifierDetailed, Vec<types::Bss>> = HashMap::from([
1316 (
1317 saved_seen_network,
1318 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1319 ),
1320 (unsaved_network, vec![generate_random_bss()]),
1321 ]);
1322
1323 saved_networks
1324 .record_scan_result(vec!["some_other_ssid".try_into().unwrap()], &results)
1325 .await;
1326
1327 assert_matches!(saved_networks.lookup(&saved_seen_id).await.as_slice(), [config] => {
1328 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1329 });
1330 assert_matches!(saved_networks.lookup(&saved_unseen_id).await.as_slice(), [config] => {
1331 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1332 });
1333 }
1334
1335 #[fuchsia::test]
1336 async fn test_record_undirected_scan_with_upgraded_security() {
1337 let saved_networks = SavedNetworksManager::new_for_test().await;
1340 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1341 let credential = Credential::Password(b"credential".to_vec());
1342
1343 assert!(
1345 saved_networks
1346 .store(id.clone(), credential.clone())
1347 .await
1348 .expect("Failed to save network")
1349 .is_none()
1350 );
1351
1352 let results = HashMap::from([(
1354 types::NetworkIdentifierDetailed {
1355 ssid: id.ssid.clone(),
1356 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1357 },
1358 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1359 )]);
1360 saved_networks.record_scan_result(vec![], &results).await;
1361 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1363 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1364 });
1365 }
1366
1367 #[fuchsia::test]
1368 async fn test_record_undirected_scan_incompatible_credential() {
1369 let saved_networks = SavedNetworksManager::new_for_test().await;
1372 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa2).unwrap();
1373 let credential = Credential::Psk(vec![8; 32]);
1374
1375 assert!(
1377 saved_networks
1378 .store(id.clone(), credential.clone())
1379 .await
1380 .expect("Failed to save network")
1381 .is_none()
1382 );
1383
1384 let results = HashMap::from([(
1386 types::NetworkIdentifierDetailed {
1387 ssid: id.ssid.clone(),
1388 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1389 },
1390 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1391 )]);
1392 saved_networks.record_scan_result(vec![], &results).await;
1393 assert_matches!(saved_networks.lookup(&id).await.as_slice(), [config] => {
1396 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1397 });
1398 }
1399
1400 #[fuchsia::test]
1401 async fn test_record_directed_scan_for_upgraded_security() {
1402 let saved_networks = SavedNetworksManager::new_for_test().await;
1405 let id = NetworkIdentifier::try_from("foobar", SecurityType::Wpa).unwrap();
1406 let credential = Credential::Password(b"credential".to_vec());
1407
1408 assert!(
1410 saved_networks
1411 .store(id.clone(), credential.clone())
1412 .await
1413 .expect("Failed to save network")
1414 .is_none()
1415 );
1416 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1417 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1418
1419 let results = HashMap::from([(
1422 types::NetworkIdentifierDetailed {
1423 ssid: id.ssid.clone(),
1424 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1425 },
1426 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1427 )]);
1428 let target = vec![id.ssid.clone()];
1429 saved_networks.record_scan_result(target, &results).await;
1430
1431 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1432 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1433 }
1434
1435 #[fuchsia::test]
1436 async fn test_record_directed_scan_for_incompatible_credential() {
1437 let saved_networks = SavedNetworksManager::new_for_test().await;
1441 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1442 let credential = Credential::Psk(vec![11; 32]);
1443
1444 assert!(
1446 saved_networks
1447 .store(id.clone(), credential.clone())
1448 .await
1449 .expect("Failed to save network")
1450 .is_none()
1451 );
1452 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1453 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1454
1455 let target = vec![id.ssid.clone()];
1458 let results = HashMap::from([(
1459 types::NetworkIdentifierDetailed {
1460 ssid: id.ssid.clone(),
1461 security_type: types::SecurityTypeDetailed::Wpa3Personal,
1462 },
1463 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1464 )]);
1465 saved_networks.record_scan_result(target, &results).await;
1466 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1469 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1470 }
1471
1472 #[fuchsia::test]
1473 async fn test_record_directed_scan_no_ssid_match() {
1474 let saved_networks = SavedNetworksManager::new_for_test().await;
1478 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1479 let credential = Credential::Psk(vec![11; 32]);
1480 let diff_ssid = types::Ssid::try_from("other-ssid").unwrap();
1481
1482 assert!(
1484 saved_networks
1485 .store(id.clone(), credential.clone())
1486 .await
1487 .expect("Failed to save network")
1488 .is_none()
1489 );
1490 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1491 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1492
1493 let target = vec![id.ssid.clone()];
1495 let results = HashMap::from([(
1496 types::NetworkIdentifierDetailed {
1497 ssid: diff_ssid,
1498 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1499 },
1500 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1501 )]);
1502 saved_networks.record_scan_result(target, &results).await;
1503
1504 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1505 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1506 }
1507
1508 #[fuchsia::test]
1509 async fn test_record_directed_one_not_compatible_one_compatible() {
1510 let saved_networks = SavedNetworksManager::new_for_test().await;
1514 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1515 let credential = Credential::Password(b"foo-pass".to_vec());
1516
1517 assert!(
1519 saved_networks
1520 .store(id.clone(), credential.clone())
1521 .await
1522 .expect("Failed to save network")
1523 .is_none()
1524 );
1525 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1526 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1527
1528 let target = vec![id.ssid.clone()];
1531 let results = HashMap::from([
1532 (
1533 types::NetworkIdentifierDetailed {
1534 ssid: id.ssid.clone(),
1535 security_type: types::SecurityTypeDetailed::Wpa1,
1536 },
1537 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1538 ),
1539 (
1540 types::NetworkIdentifierDetailed {
1541 ssid: id.ssid.clone(),
1542 security_type: types::SecurityTypeDetailed::Wpa2Personal,
1543 },
1544 vec![types::Bss { observation: ScanObservation::Active, ..generate_random_bss() }],
1545 ),
1546 ]);
1547 saved_networks.record_scan_result(target, &results).await;
1548 let config = saved_networks.lookup(&id).await.pop().expect("failed to lookup config");
1551 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1552 }
1553
1554 #[fuchsia::test]
1555 async fn test_record_both_directed_and_undirected() {
1556 let saved_networks = SavedNetworksManager::new_for_test().await;
1557 let saved_undirected_id = NetworkIdentifier::try_from("foo", SecurityType::None).unwrap();
1558 let saved_undirected_network = types::NetworkIdentifierDetailed {
1559 ssid: saved_undirected_id.ssid.clone(),
1560 security_type: types::SecurityTypeDetailed::Open,
1561 };
1562 let saved_directed_id = NetworkIdentifier::try_from("bar", SecurityType::None).unwrap();
1563 let credential = Credential::None;
1564
1565 assert!(
1567 saved_networks
1568 .store(saved_undirected_id.clone(), credential.clone())
1569 .await
1570 .expect("Failed to save network")
1571 .is_none()
1572 );
1573 assert!(
1574 saved_networks
1575 .store(saved_directed_id.clone(), credential.clone())
1576 .await
1577 .expect("Failed to save network")
1578 .is_none()
1579 );
1580
1581 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1583 assert_eq!(config.hidden_probability, PROB_HIDDEN_DEFAULT);
1584 });
1585
1586 let results = HashMap::from([(
1588 saved_undirected_network,
1589 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
1590 )]);
1591 saved_networks.record_scan_result(vec![saved_directed_id.ssid.clone()], &results).await;
1592
1593 assert_matches!(saved_networks.lookup(&saved_undirected_id).await.as_slice(), [config] => {
1595 assert_eq!(config.hidden_probability, PROB_HIDDEN_IF_SEEN_PASSIVE);
1596 });
1597 assert_matches!(saved_networks.lookup(&saved_directed_id).await.as_slice(), [config] => {
1599 assert!(config.hidden_probability < PROB_HIDDEN_DEFAULT);
1600 });
1601 }
1602
1603 #[fuchsia::test]
1604 fn evict_if_needed_removes_unconnected() {
1605 let unconnected_config = network_config("foo", "password");
1609 let mut connected_config = unconnected_config.clone();
1610 connected_config.has_ever_connected = false;
1611 let mut network_configs = vec![connected_config; MAX_CONFIGS_PER_SSID - 1];
1612 network_configs.insert(MAX_CONFIGS_PER_SSID / 2, unconnected_config.clone());
1613
1614 assert_eq!(evict_if_needed(&mut network_configs), Some(unconnected_config));
1615 assert_eq!(MAX_CONFIGS_PER_SSID - 1, network_configs.len());
1616 for config in network_configs.iter() {
1619 assert!(config.has_ever_connected);
1620 }
1621 }
1622
1623 #[fuchsia::test]
1624 fn evict_if_needed_already_has_space() {
1625 let mut configs = vec![];
1626 assert_eq!(evict_if_needed(&mut configs), None);
1627 let expected_cfgs: Vec<NetworkConfig> = vec![];
1628 assert_eq!(expected_cfgs, configs);
1629
1630 if MAX_CONFIGS_PER_SSID > 1 {
1631 let mut configs = vec![network_config("foo", "password")];
1632 assert_eq!(evict_if_needed(&mut configs), None);
1633 assert_eq!(vec![network_config("foo", "password")], configs);
1635 }
1636 }
1637
1638 #[fuchsia::test]
1639 async fn clear() {
1640 let store_id = "clear";
1641 let network_id = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1642 let saved_networks = create_saved_networks(store_id).await;
1643
1644 assert!(
1645 saved_networks
1646 .store(network_id.clone(), Credential::Password(b"qwertyuio".to_vec()))
1647 .await
1648 .expect("storing 'foo' failed")
1649 .is_none()
1650 );
1651 assert_eq!(
1652 vec![network_config("foo", "qwertyuio")],
1653 saved_networks.lookup(&network_id).await
1654 );
1655 assert_eq!(1, saved_networks.known_network_count().await);
1656
1657 saved_networks.clear().await.expect("failed to clear saved networks");
1658 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1659 assert_eq!(0, saved_networks.known_network_count().await);
1660
1661 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1663 let store = PolicyStorage::new_with_id(store_id).await;
1664 let saved_networks =
1665 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1666 .await;
1667
1668 assert_eq!(0, saved_networks.known_network_count().await);
1669 }
1670
1671 impl std::fmt::Debug for SavedNetworksManager {
1672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1673 f.debug_struct("SavedNetworksManager")
1674 .field("saved_networks", &self.saved_networks)
1675 .finish()
1676 }
1677 }
1678
1679 #[fuchsia::test]
1680 fn test_store_errors_cause_write_errors() {
1681 use fidl::endpoints::create_request_stream;
1682 use fidl_fuchsia_stash as fidl_stash;
1683 use futures::StreamExt;
1684 use std::sync::Arc;
1685 use std::sync::atomic::{AtomicBool, Ordering};
1686
1687 let store_path_str = "/////";
1689 let mut exec = fasync::TestExecutor::new();
1690
1691 let (stash_client, mut request_stream) =
1694 create_request_stream::<fidl_stash::SecureStoreMarker>();
1695
1696 let read_from_stash = Arc::new(AtomicBool::new(false));
1697
1698 let _task = {
1699 let read_from_stash = read_from_stash.clone();
1700 fasync::Task::local(async move {
1701 while let Some(request) = request_stream.next().await {
1702 match request.unwrap() {
1703 fidl_stash::SecureStoreRequest::Identify { .. } => {}
1704 fidl_stash::SecureStoreRequest::CreateAccessor {
1705 accessor_request, ..
1706 } => {
1707 let read_from_stash = read_from_stash.clone();
1708 fuchsia_async::EHandle::local().spawn_detached(async move {
1709 let mut request_stream = accessor_request.into_stream();
1710 while let Some(request) = request_stream.next().await {
1711 match request.unwrap() {
1712 fidl_stash::StoreAccessorRequest::ListPrefix { .. } => {
1713 read_from_stash.store(true, Ordering::Relaxed);
1714 }
1717 _ => unreachable!(),
1718 }
1719 }
1720 });
1721 }
1722 }
1723 }
1724 })
1725 };
1726
1727 let store =
1729 PolicyStorage::new_with_stash_proxy_and_id(stash_client.into_proxy(), store_path_str);
1730
1731 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1734 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1735 let init_fut = SavedNetworksManager::new_with_storage(store, telemetry_sender);
1736 let mut init_fut = pin!(init_fut);
1737 let saved_networks = assert_matches!(exec.run_until_stalled(&mut init_fut), Poll::Ready(snm) => {
1738 snm
1739 });
1740
1741 let ssid = "foo";
1743 let credential = Credential::None;
1744 let network_id = NetworkIdentifier::try_from(ssid, SecurityType::None).unwrap();
1745 let save_fut = saved_networks.store(network_id.clone(), credential);
1746 let mut save_fut = pin!(save_fut);
1747
1748 assert_matches!(
1749 exec.run_until_stalled(&mut save_fut),
1750 Poll::Ready(Err(NetworkConfigError::FileWriteError))
1751 );
1752
1753 assert_matches!(exec.run_until_stalled(&mut saved_networks.lookup(&network_id)), Poll::Ready(configs) => {
1755 assert_eq!(configs, vec![network_config(ssid, "")]);
1756 });
1757 assert_matches!(exec.run_until_stalled(&mut saved_networks.known_network_count()), Poll::Ready(count) => {
1758 assert_eq!(count, 1);
1759 });
1760 }
1761
1762 async fn create_saved_networks(store_id: &str) -> SavedNetworksManager {
1765 let (telemetry_sender, _telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1766 let store = PolicyStorage::new_with_id(store_id).await;
1767 let saved_networks =
1768 SavedNetworksManager::new_with_storage(store, TelemetrySender::new(telemetry_sender))
1769 .await;
1770 saved_networks.clear().await.expect("failed to clear saved networks");
1771 saved_networks
1772 }
1773
1774 fn network_config(ssid: &str, password: impl Into<Vec<u8>>) -> NetworkConfig {
1778 let credential = Credential::from_bytes(password.into());
1779 let id = NetworkIdentifier::try_from(ssid, credential.derived_security_type()).unwrap();
1780 let has_ever_connected = false;
1781 NetworkConfig::new(id, credential, has_ever_connected).unwrap()
1782 }
1783
1784 #[fuchsia::test]
1785 async fn record_metrics_when_called_on_class() {
1786 let store_id = generate_string();
1787 let (telemetry_sender, mut telemetry_receiver) = mpsc::channel::<TelemetryEvent>(100);
1788 let telemetry_sender = TelemetrySender::new(telemetry_sender);
1789 let store = PolicyStorage::new_with_id(&store_id).await;
1790
1791 let saved_networks = SavedNetworksManager::new_with_storage(store, telemetry_sender).await;
1792 let network_id_foo = NetworkIdentifier::try_from("foo", SecurityType::Wpa2).unwrap();
1793 let network_id_baz = NetworkIdentifier::try_from("baz", SecurityType::Wpa2).unwrap();
1794
1795 assert!(saved_networks.lookup(&network_id_foo).await.is_empty());
1796 assert_eq!(0, saved_networks.saved_networks.lock().await.len());
1797 assert_eq!(0, saved_networks.known_network_count().await);
1798
1799 assert!(
1801 saved_networks
1802 .store(network_id_foo.clone(), Credential::Password(b"qwertyuio".to_vec()))
1803 .await
1804 .expect("storing 'foo' failed")
1805 .is_none()
1806 );
1807 assert_eq!(1, saved_networks.known_network_count().await);
1808
1809 assert!(
1811 saved_networks
1812 .store(network_id_baz.clone(), Credential::Psk(vec![1; 32]))
1813 .await
1814 .expect("storing 'baz' with PSK failed")
1815 .is_none()
1816 );
1817 assert_eq!(2, saved_networks.known_network_count().await);
1818
1819 saved_networks.record_periodic_metrics().await;
1821
1822 assert_matches!(telemetry_receiver.try_next(), Ok(Some(TelemetryEvent::SavedNetworkCount { saved_network_count, config_count_per_saved_network })) => {
1824 assert_eq!(saved_network_count, 2);
1825 assert_eq!(config_count_per_saved_network, [1, 1]);
1826 });
1827 }
1828
1829 #[fuchsia::test]
1830 async fn probabilistic_choosing_of_hidden_networks() {
1831 let id_hidden = types::NetworkIdentifier {
1833 ssid: types::Ssid::try_from("hidden").unwrap(),
1834 security_type: types::SecurityType::Wpa2,
1835 };
1836 let mut net_config_hidden = NetworkConfig::new(
1837 id_hidden.clone(),
1838 Credential::Password(b"password".to_vec()),
1839 false,
1840 )
1841 .expect("failed to create network config");
1842 net_config_hidden.hidden_probability = 1.0;
1843
1844 let id_not_hidden = types::NetworkIdentifier {
1845 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1846 security_type: types::SecurityType::Wpa2,
1847 };
1848 let mut net_config_not_hidden = NetworkConfig::new(
1849 id_not_hidden.clone(),
1850 Credential::Password(b"password".to_vec()),
1851 false,
1852 )
1853 .expect("failed to create network config");
1854 net_config_not_hidden.hidden_probability = 0.0;
1855
1856 let id_maybe_hidden = types::NetworkIdentifier {
1857 ssid: types::Ssid::try_from("maybe_hidden").unwrap(),
1858 security_type: types::SecurityType::Wpa2,
1859 };
1860 let mut net_config_maybe_hidden = NetworkConfig::new(
1861 id_maybe_hidden.clone(),
1862 Credential::Password(b"password".to_vec()),
1863 false,
1864 )
1865 .expect("failed to create network config");
1866 net_config_maybe_hidden.hidden_probability = 0.5;
1867
1868 let mut maybe_hidden_selection_count = 0;
1869 let mut hidden_selection_count = 0;
1870
1871 for _ in 1..100 {
1873 let selected_networks = select_subset_potentially_hidden_networks(vec![
1874 net_config_hidden.clone(),
1875 net_config_not_hidden.clone(),
1876 net_config_maybe_hidden.clone(),
1877 ]);
1878 assert!(selected_networks.contains(&id_hidden));
1880 assert!(!selected_networks.contains(&id_not_hidden));
1882
1883 if selected_networks.contains(&id_maybe_hidden) {
1885 maybe_hidden_selection_count += 1;
1886 }
1887 if selected_networks.contains(&id_hidden) {
1888 hidden_selection_count += 1;
1889 }
1890 }
1891
1892 assert!(maybe_hidden_selection_count > 0);
1897 assert!(maybe_hidden_selection_count < hidden_selection_count);
1898 }
1899
1900 #[fuchsia::test]
1901 async fn test_select_high_probability_hidden_networks() {
1902 let id_hidden = types::NetworkIdentifier {
1904 ssid: types::Ssid::try_from("hidden").unwrap(),
1905 security_type: types::SecurityType::Wpa2,
1906 };
1907 let mut net_config_hidden = NetworkConfig::new(
1908 id_hidden.clone(),
1909 Credential::Password(b"password".to_vec()),
1910 false,
1911 )
1912 .expect("failed to create network config");
1913 net_config_hidden.hidden_probability = 1.0;
1914
1915 let id_maybe_hidden_high = types::NetworkIdentifier {
1916 ssid: types::Ssid::try_from("maybe_hidden_high").unwrap(),
1917 security_type: types::SecurityType::Wpa2,
1918 };
1919 let mut net_config_maybe_hidden_high = NetworkConfig::new(
1920 id_maybe_hidden_high.clone(),
1921 Credential::Password(b"password".to_vec()),
1922 false,
1923 )
1924 .expect("failed to create network config");
1925 net_config_maybe_hidden_high.hidden_probability = 0.8;
1926
1927 let id_maybe_hidden_low = types::NetworkIdentifier {
1928 ssid: types::Ssid::try_from("maybe_hidden_low").unwrap(),
1929 security_type: types::SecurityType::Wpa2,
1930 };
1931 let mut net_config_maybe_hidden_low = NetworkConfig::new(
1932 id_maybe_hidden_low.clone(),
1933 Credential::Password(b"password".to_vec()),
1934 false,
1935 )
1936 .expect("failed to create network config");
1937 net_config_maybe_hidden_low.hidden_probability = 0.7;
1938
1939 let id_not_hidden = types::NetworkIdentifier {
1940 ssid: types::Ssid::try_from("not_hidden").unwrap(),
1941 security_type: types::SecurityType::Wpa2,
1942 };
1943 let mut net_config_not_hidden = NetworkConfig::new(
1944 id_not_hidden.clone(),
1945 Credential::Password(b"password".to_vec()),
1946 false,
1947 )
1948 .expect("failed to create network config");
1949 net_config_not_hidden.hidden_probability = 0.0;
1950
1951 let selected_networks = select_high_probability_hidden_networks(vec![
1952 net_config_hidden.clone(),
1953 net_config_maybe_hidden_high.clone(),
1954 net_config_maybe_hidden_low.clone(),
1955 net_config_not_hidden.clone(),
1956 ]);
1957
1958 assert!(selected_networks.contains(&id_hidden));
1960 assert!(selected_networks.contains(&id_maybe_hidden_high));
1962 assert!(!selected_networks.contains(&id_maybe_hidden_low));
1964 assert!(!selected_networks.contains(&id_not_hidden));
1966 }
1967
1968 #[fuchsia::test]
1969 async fn test_record_not_seen_active_scan() {
1970 let saved_networks = SavedNetworksManager::new_for_test().await;
1973
1974 let id_1 = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
1976 let credential_1 = Credential::Password(b"some_password".to_vec());
1977 let id_2 = NetworkIdentifier::try_from("bar", SecurityType::Wpa3).unwrap();
1978 let credential_2 = Credential::Password(b"another_password".to_vec());
1979 let id_3 = NetworkIdentifier::try_from("baz", SecurityType::None).unwrap();
1981 let id_4 = NetworkIdentifier::try_from("foobar", SecurityType::None).unwrap();
1983 let credential_4 = Credential::None;
1984
1985 assert!(
1987 saved_networks
1988 .store(id_1.clone(), credential_1)
1989 .await
1990 .expect("failed to store network")
1991 .is_none()
1992 );
1993 assert!(
1994 saved_networks
1995 .store(id_2.clone(), credential_2)
1996 .await
1997 .expect("failed to store network")
1998 .is_none()
1999 );
2000 assert!(
2001 saved_networks
2002 .store(id_4.clone(), credential_4)
2003 .await
2004 .expect("failed to store network")
2005 .is_none()
2006 );
2007 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2010 assert_eq!(config_1.hidden_probability, PROB_HIDDEN_DEFAULT);
2011 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2012 assert_eq!(config_2.hidden_probability, PROB_HIDDEN_DEFAULT);
2013 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2014 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2015
2016 let not_seen_ids = vec![id_1.ssid.clone(), id_2.ssid.clone(), id_3.ssid.clone()];
2017 saved_networks.record_scan_result(not_seen_ids, &HashMap::new()).await;
2018
2019 let config_1 = saved_networks.lookup(&id_1).await.pop().expect("failed to lookup");
2021 assert!(config_1.hidden_probability < PROB_HIDDEN_DEFAULT);
2022 let config_2 = saved_networks.lookup(&id_2).await.pop().expect("failed to lookup");
2023 assert!(config_2.hidden_probability < PROB_HIDDEN_DEFAULT);
2024
2025 let config_4 = saved_networks.lookup(&id_4).await.pop().expect("failed to lookup");
2028 assert_eq!(config_4.hidden_probability, PROB_HIDDEN_DEFAULT);
2029
2030 assert!(saved_networks.lookup(&id_3).await.is_empty());
2032 }
2033
2034 #[fuchsia::test]
2035 async fn test_update_scan_stats_for_single_bss() {
2036 let saved_networks = SavedNetworksManager::new_for_test().await;
2039
2040 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2041 let credential = Credential::Password(b"some_password".to_vec());
2042 assert!(
2043 saved_networks
2044 .store(id.clone(), credential.clone())
2045 .await
2046 .expect("failed to store network")
2047 .is_none()
2048 );
2049
2050 let id_detailed = types::NetworkIdentifierDetailed {
2051 ssid: id.ssid.clone(),
2052 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2053 };
2054 let scan_results = HashMap::from([(
2055 id_detailed.clone(),
2056 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2057 )]);
2058
2059 for _ in 0..5 {
2061 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2062 }
2063
2064 let is_single_bss = saved_networks
2065 .is_network_single_bss(&id, &credential)
2066 .await
2067 .expect("failed to lookup if network is single BSS");
2068 assert!(is_single_bss);
2069 }
2070
2071 #[fuchsia::test]
2072 async fn test_update_scan_stats_for_multiple_bss_at_least_once() {
2073 let saved_networks = SavedNetworksManager::new_for_test().await;
2076
2077 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2078 let credential = Credential::Password(b"some_password".to_vec());
2079 assert!(
2080 saved_networks
2081 .store(id.clone(), credential.clone())
2082 .await
2083 .expect("failed to store network")
2084 .is_none()
2085 );
2086
2087 let id_detailed = types::NetworkIdentifierDetailed {
2088 ssid: id.ssid.clone(),
2089 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2090 };
2091 let scan_results_single = HashMap::from([(
2092 id_detailed.clone(),
2093 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2094 )]);
2095
2096 let scan_results_multi = HashMap::from([(
2097 id_detailed.clone(),
2098 vec![
2099 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2100 types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() },
2101 ],
2102 )]);
2103
2104 for _ in 0..2 {
2106 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2107 }
2108
2109 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_multi).await;
2110 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results_single).await;
2111
2112 let is_single_bss = saved_networks
2115 .is_network_single_bss(&id, &credential)
2116 .await
2117 .expect("failed to lookup if network is single BSS");
2118 assert!(!is_single_bss);
2119 }
2120
2121 #[fuchsia::test]
2122 async fn test_record_scan_more_than_once_to_decide_single_bss() {
2123 let saved_networks = SavedNetworksManager::new_for_test().await;
2125
2126 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2127 let credential = Credential::Password(b"some_password".to_vec());
2128 assert!(
2129 saved_networks
2130 .store(id.clone(), credential.clone())
2131 .await
2132 .expect("failed to store network")
2133 .is_none()
2134 );
2135
2136 let id_detailed = types::NetworkIdentifierDetailed {
2137 ssid: id.ssid.clone(),
2138 security_type: types::SecurityTypeDetailed::Wpa2Personal,
2139 };
2140 let scan_results = HashMap::from([(
2141 id_detailed,
2142 vec![types::Bss { observation: ScanObservation::Passive, ..generate_random_bss() }],
2143 )]);
2144
2145 saved_networks.record_scan_result(vec![id.ssid.clone()], &scan_results).await;
2148
2149 let is_single_bss = saved_networks
2150 .is_network_single_bss(&id, &credential)
2151 .await
2152 .expect("failed to lookup if network is single BSS");
2153 assert!(!is_single_bss);
2154 }
2155
2156 #[fuchsia::test]
2157 async fn test_get_past_connections() {
2158 let saved_networks_manager = SavedNetworksManager::new_for_test().await;
2159
2160 let id = NetworkIdentifier::try_from("foo", SecurityType::Wpa).unwrap();
2161 let credential = Credential::Password(b"some_password".to_vec());
2162 let mut config = NetworkConfig::new(id.clone(), credential.clone(), true)
2163 .expect("failed to create config");
2164 let mut past_connections = HistoricalListsByBssid::new();
2165
2166 let data_1 = random_connection_data();
2168 let bssid_1 = data_1.bssid;
2169 let mut data_2 = random_connection_data();
2170 data_2.bssid = bssid_1;
2171 past_connections.add(bssid_1, data_1);
2172 past_connections.add(bssid_1, data_2);
2173
2174 let data_3 = random_connection_data();
2176 let bssid_2 = data_3.bssid;
2177 past_connections.add(bssid_2, data_3);
2178 config.perf_stats.past_connections = past_connections;
2179
2180 assert!(
2182 saved_networks_manager
2183 .saved_networks
2184 .lock()
2185 .await
2186 .insert(id.clone(), vec![config])
2187 .is_none()
2188 );
2189
2190 let mut expected_past_connections = PastConnectionList::default();
2192 expected_past_connections.add(data_1);
2193 expected_past_connections.add(data_2);
2194 let actual_past_connections =
2195 saved_networks_manager.get_past_connections(&id, &credential, &bssid_1).await;
2196 assert_eq!(actual_past_connections, expected_past_connections);
2197
2198 let mut expected_past_connections = PastConnectionList::default();
2199 expected_past_connections.add(data_3);
2200 let actual_past_connections =
2201 saved_networks_manager.get_past_connections(&id, &credential, &bssid_2).await;
2202 assert_eq!(actual_past_connections, expected_past_connections);
2203
2204 let actual_past_connections = saved_networks_manager
2207 .get_past_connections(&id, &Credential::Password(b"other-password".to_vec()), &bssid_1)
2208 .await;
2209 assert_eq!(actual_past_connections, PastConnectionList::default());
2210 }
2211
2212 fn fake_successful_connect_result() -> fidl_sme::ConnectResult {
2213 fidl_sme::ConnectResult {
2214 code: fidl_ieee80211::StatusCode::Success,
2215 is_credential_rejected: false,
2216 is_reconnect: false,
2217 }
2218 }
2219}