1use crate::stash_store::StashStore;
6use crate::storage_store::StorageStore;
7use anyhow::{Context, Error, format_err};
8use fidl_fuchsia_stash as fidl_stash;
9use fuchsia_component::client::connect_to_protocol;
10use wlan_metrics_registry::StashMigrationResultsMetricDimensionMigrationResult as MigrationResult;
11
12pub use wlan_storage_constants::{
13 self, Credential, NetworkIdentifier, POLICY_STORAGE_ID, PersistentData, PersistentStorageData,
14 SecurityType,
15};
16
17const FILE_PATH_FORMAT: &str = "/data/network-data.";
19
20pub struct PolicyStorage {
23 root: StorageStore,
25 legacy_stash: Result<StashStore, Error>,
29 cobalt_proxy: Option<fidl_fuchsia_metrics::MetricEventLoggerProxy>,
30}
31
32impl PolicyStorage {
33 pub async fn new_with_id(id: &str) -> Self {
36 let path = format!("{FILE_PATH_FORMAT}{id}");
37 let root = StorageStore::new(&path);
38
39 let proxy = connect_to_protocol::<fidl_stash::SecureStoreMarker>();
40 let legacy_stash =
41 proxy.and_then(|p| StashStore::from_secure_store_proxy(id, p)).map_err(|e| e);
42
43 let cobalt_proxy = init_telemetry_channel()
44 .await
45 .inspect_err(|e| {
46 log::info!(
47 "Error accessing telemetry. Stash migration metric will not be logged: {}",
48 e
49 );
50 })
51 .ok();
52
53 Self { root, legacy_stash, cobalt_proxy }
54 }
55
56 pub fn new_with_stash_proxy_and_id(
58 stash_proxy: fidl_stash::SecureStoreProxy,
59 id: &str,
60 ) -> Self {
61 let root = StorageStore::new(id);
62 let legacy_stash = StashStore::from_secure_store_proxy(id, stash_proxy);
63 let cobalt_proxy = None;
64 Self { root, legacy_stash, cobalt_proxy }
65 }
66
67 pub async fn load(&mut self) -> Result<Vec<PersistentStorageData>, Error> {
71 let load_err = match self.root.load() {
74 Ok(networks) => {
75 self.log_load_metric(MigrationResult::AlreadyMigrated).await;
76 return Ok(networks);
77 }
78 Err(e) => e,
79 };
80 let stash_store: &mut StashStore = if let Ok(stash) = self.legacy_stash.as_mut() {
81 stash
82 } else {
83 return Err(format_err!("error accessing stash"));
84 };
85 if let Ok(config) = stash_store.load().await {
87 let mut networks_list = Vec::new();
89 for (id, legacy_configs) in config.into_iter() {
90 let mut new_configs = legacy_configs
91 .into_iter()
92 .map(|c| PersistentStorageData::new_from_legacy_data(id.clone(), c));
93 networks_list.extend(&mut new_configs);
94 }
95
96 match self.root.write(networks_list.clone()) {
98 Ok(_) => {
99 log::info!("Migrated saved networks from stash");
100 let delete_result = stash_store.delete_store().await;
102 match delete_result {
103 Ok(()) => {
104 self.log_load_metric(MigrationResult::Success).await;
105 }
106 Err(e) => {
107 log::info!(
108 "Failed to delete legacy stash data after migration: {:?}",
109 e
110 );
111 self.log_load_metric(MigrationResult::MigratedButFailedToDeleteLegacy)
112 .await;
113 }
114 }
115 }
116 Err(e) => {
117 log::info!(e:?; "Failed to write migrated saved networks");
118 self.log_load_metric(MigrationResult::FailedToWriteNewStore).await;
119 }
120 }
121 Ok(networks_list)
122 } else {
123 log::info!(load_err:?; "Failed to read saved networks from file and legacy stash, new file will be created when a network is saved",);
127 self.log_load_metric(MigrationResult::FailedToLoadLegacyData).await;
128 Ok(Vec::new())
129 }
130 }
131
132 pub fn write(&self, network_configs: Vec<PersistentStorageData>) -> Result<(), Error> {
135 self.root.write(network_configs)
136 }
137
138 pub fn clear(&mut self) -> Result<(), Error> {
141 self.root.empty_store()
142 }
143
144 async fn log_load_metric(&self, result_event_code: MigrationResult) {
145 let cobalt_proxy = match &self.cobalt_proxy {
148 Some(proxy) => proxy,
149 None => return,
150 };
151
152 let events = &[fidl_fuchsia_metrics::MetricEvent {
153 metric_id: wlan_metrics_registry::STASH_MIGRATION_RESULTS_METRIC_ID,
154 event_codes: vec![result_event_code as u32],
155 payload: fidl_fuchsia_metrics::MetricEventPayload::Count(1),
156 }];
157
158 match cobalt_proxy.log_metric_events(events).await {
160 Err(e) => {
161 log::info!(
162 "Error logging metric {:?} for migration result: {:?}",
163 result_event_code,
164 e
165 );
166 }
167 Ok(Err(e)) => {
168 log::info!(
169 "Error sending metric {:?} for migration result: {:?}",
170 result_event_code,
171 e
172 );
173 }
174 Ok(_) => (),
175 }
176 }
177}
178
179async fn init_telemetry_channel() -> Result<fidl_fuchsia_metrics::MetricEventLoggerProxy, Error> {
180 let factory_proxy = fuchsia_component::client::connect_to_protocol::<
182 fidl_fuchsia_metrics::MetricEventLoggerFactoryMarker,
183 >()?;
184
185 let (cobalt_proxy, cobalt_1dot1_server) =
186 fidl::endpoints::create_proxy::<fidl_fuchsia_metrics::MetricEventLoggerMarker>();
187
188 let project_spec = fidl_fuchsia_metrics::ProjectSpec {
189 customer_id: None, project_id: Some(wlan_metrics_registry::PROJECT_ID),
191 ..Default::default()
192 };
193
194 factory_proxy
195 .create_metric_event_logger(&project_spec, cobalt_1dot1_server)
196 .await
197 .context("failed to create metrics event logger")?
198 .map_err(|e| format_err!("failed to create metrics event logger: {:?}", e))?;
199
200 Ok(cobalt_proxy)
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::tests::{network_id, rand_string};
207 use assert_matches::assert_matches;
208 use fidl::endpoints::create_request_stream;
209 use fidl_fuchsia_stash::{SecureStoreRequest, StoreAccessorRequest};
210 use fuchsia_async as fasync;
211 use futures::task::Poll;
212 use futures::{StreamExt, TryStreamExt};
213 use ieee80211::Ssid;
214 use std::convert::TryFrom;
215 use std::io::Write;
216 use std::sync::Arc;
217 use std::sync::atomic::{AtomicBool, Ordering};
218 use wlan_storage_constants::PersistentData;
219
220 pub const PSK_BYTE_LEN: usize = 32;
224
225 #[fuchsia::test]
226 async fn write_and_read() {
227 let mut store = new_storage(&rand_string()).await;
228 let cfg = PersistentStorageData {
229 ssid: Ssid::try_from("foo").unwrap().to_vec(),
230 security_type: SecurityType::Wpa2,
231 credential: Credential::Password(b"password".to_vec()),
232 has_ever_connected: true,
233 hidden_probability: None,
234 };
235
236 store.write(vec![cfg.clone()]).expect("Failed writing to storage");
238
239 let cfgs_from_store = store.load().await.expect("Failed reading from storage");
241 assert_eq!(1, cfgs_from_store.len());
242 assert_eq!(vec![cfg.clone()], cfgs_from_store);
243
244 let cfg_2 = PersistentStorageData {
246 ssid: Ssid::try_from("foo").unwrap().to_vec(),
247 security_type: SecurityType::Wpa2,
248 credential: Credential::Password(b"other-password".to_vec()),
249 has_ever_connected: false,
250 hidden_probability: None,
251 };
252 store.write(vec![cfg.clone(), cfg_2.clone()]).expect("Failed writing to storage");
253
254 let cfgs_from_store = store.load().await.expect("Failed reading from stash");
256 assert_eq!(2, cfgs_from_store.len());
257 assert!(cfgs_from_store.contains(&cfg));
258 assert!(cfgs_from_store.contains(&cfg_2));
259 }
260
261 #[fuchsia::test]
262 async fn write_read_security_types() {
263 let mut store = new_storage(&rand_string()).await;
264 let password = Credential::Password(b"config-password".to_vec());
265
266 let cfg_open = PersistentStorageData {
268 ssid: Ssid::try_from("foo").unwrap().to_vec(),
269 security_type: SecurityType::None,
270 credential: Credential::None,
271 has_ever_connected: false,
272 hidden_probability: None,
273 };
274 let cfg_wep = PersistentStorageData {
275 ssid: Ssid::try_from("foo").unwrap().to_vec(),
276 security_type: SecurityType::Wep,
277 credential: password.clone(),
278 has_ever_connected: false,
279 hidden_probability: None,
280 };
281 let cfg_wpa = PersistentStorageData {
282 ssid: Ssid::try_from("foo").unwrap().to_vec(),
283 security_type: SecurityType::Wpa,
284 credential: password.clone(),
285 has_ever_connected: false,
286 hidden_probability: None,
287 };
288 let cfg_wpa2 = PersistentStorageData {
289 ssid: Ssid::try_from("foo").unwrap().to_vec(),
290 security_type: SecurityType::Wpa2,
291 credential: password.clone(),
292 has_ever_connected: false,
293 hidden_probability: None,
294 };
295 let cfg_wpa3 = PersistentStorageData {
296 ssid: Ssid::try_from("foo").unwrap().to_vec(),
297 security_type: SecurityType::Wpa3,
298 credential: password.clone(),
299 has_ever_connected: false,
300 hidden_probability: None,
301 };
302
303 let mut saved_networks = vec![cfg_open.clone()];
305 store.write(saved_networks.clone()).expect("failed to write config");
306 saved_networks.push(cfg_wep.clone());
307 store.write(saved_networks.clone()).expect("failed to write config");
308 saved_networks.push(cfg_wpa.clone());
309 store.write(saved_networks.clone()).expect("failed to write config");
310 saved_networks.push(cfg_wpa2.clone());
311 store.write(saved_networks.clone()).expect("failed to write config");
312 saved_networks.push(cfg_wpa3.clone());
313 store.write(saved_networks.clone()).expect("failed to write config");
314
315 let configs = store.load().await.expect("failed loading from storage");
317 assert_eq!(configs.len(), 5);
318 assert!(configs.contains(&cfg_open));
319 assert!(configs.contains(&cfg_wep));
320 assert!(configs.contains(&cfg_wpa));
321 assert!(configs.contains(&cfg_wpa2));
322 assert!(configs.contains(&cfg_wpa3));
323 }
324
325 #[fuchsia::test]
326 async fn write_read_credentials() {
327 let mut store = new_storage(&rand_string()).await;
328
329 let password = Credential::Password(b"config-password".to_vec());
331 let psk = Credential::Psk([65; PSK_BYTE_LEN].to_vec());
332
333 let cfg_none = PersistentStorageData {
334 ssid: b"bar-none".to_vec(),
335 security_type: SecurityType::None,
336 credential: Credential::None,
337 has_ever_connected: false,
338 hidden_probability: None,
339 };
340 let cfg_password = PersistentStorageData {
341 ssid: b"bar-password".to_vec(),
342 security_type: SecurityType::Wpa2,
343 credential: password,
344 has_ever_connected: false,
345 hidden_probability: None,
346 };
347 let cfg_psk = PersistentStorageData {
348 ssid: b"bar-psk".to_vec(),
349 security_type: SecurityType::Wpa2,
350 credential: psk,
351 has_ever_connected: false,
352 hidden_probability: None,
353 };
354
355 let mut saved_networks = vec![cfg_none.clone()];
357 store.write(saved_networks.clone()).expect("failed to write");
358 saved_networks.push(cfg_password.clone());
359 store.write(saved_networks.clone()).expect("failed to write");
360 saved_networks.push(cfg_psk.clone());
361 store.write(saved_networks.clone()).expect("failed to write");
362
363 let configs = store.load().await.expect("failed loading from storage");
365 assert_eq!(3, configs.len());
366 assert!(configs.contains(&cfg_none));
367 assert!(configs.contains(&cfg_password));
368 assert!(configs.contains(&cfg_psk));
369 }
370
371 #[fuchsia::test]
372 async fn write_read_hidden_probability() {
373 let mut store = new_storage(&rand_string()).await;
374
375 let cfg_zero = PersistentStorageData {
377 ssid: Ssid::try_from("foo").unwrap().to_vec(),
378 security_type: SecurityType::None,
379 credential: Credential::None,
380 has_ever_connected: false,
381 hidden_probability: Some(0.0),
382 };
383 let cfg_low = PersistentStorageData {
384 ssid: Ssid::try_from("foo").unwrap().to_vec(),
385 security_type: SecurityType::None,
386 credential: Credential::None,
387 has_ever_connected: false,
388 hidden_probability: Some(0.05),
389 };
390 let cfg_high = PersistentStorageData {
391 ssid: Ssid::try_from("foo").unwrap().to_vec(),
392 security_type: SecurityType::None,
393 credential: Credential::None,
394 has_ever_connected: false,
395 hidden_probability: Some(0.9),
396 };
397
398 let mut saved_networks = vec![cfg_zero.clone()];
400 store.write(saved_networks.clone()).expect("failed to write config");
401 saved_networks.push(cfg_low.clone());
402 store.write(saved_networks.clone()).expect("failed to write config");
403 saved_networks.push(cfg_high.clone());
404 store.write(saved_networks.clone()).expect("failed to write config");
405
406 let configs = store.load().await.expect("failed loading from storage");
408 assert_eq!(configs.len(), 3);
409 assert!(configs.contains(&cfg_zero));
410 assert!(configs.contains(&cfg_low));
411 assert!(configs.contains(&cfg_high));
412 }
413
414 #[fuchsia::test]
415 async fn write_persists() {
416 let path = rand_string();
417 let store = new_storage(&path).await;
418 let cfg = PersistentStorageData {
419 ssid: Ssid::try_from("foo").unwrap().to_vec(),
420 security_type: SecurityType::Wpa2,
421 credential: Credential::Password(b"password".to_vec()),
422 has_ever_connected: true,
423 hidden_probability: None,
424 };
425
426 store.write(vec![cfg.clone()]).expect("Failed writing to storage");
428
429 let mut store = PolicyStorage::new_with_id(&path).await;
431
432 let cfgs_from_store = store.load().await.expect("Failed reading from storage");
434 assert_eq!(1, cfgs_from_store.len());
435 assert!(cfgs_from_store.contains(&cfg));
436 }
437
438 #[fuchsia::test]
439 async fn load_storage() {
440 let mut store = new_storage(&rand_string()).await;
441 let cfg_foo = PersistentStorageData {
442 ssid: Ssid::try_from("foo").unwrap().to_vec(),
443 security_type: SecurityType::Wpa2,
444 credential: Credential::Password(b"12345678".to_vec()),
445 has_ever_connected: true,
446 hidden_probability: None,
447 };
448 let cfg_bar = PersistentStorageData {
449 ssid: Ssid::try_from("bar").unwrap().to_vec(),
450 security_type: SecurityType::Wpa2,
451 credential: Credential::Password(b"qwertyuiop".to_vec()),
452 has_ever_connected: true,
453 hidden_probability: None,
454 };
455
456 store
458 .write(vec![cfg_foo.clone(), cfg_bar.clone()])
459 .expect("Failed to save configs to stash");
460
461 let expected_cfgs = vec![cfg_foo, cfg_bar];
463 assert_eq!(expected_cfgs, store.load().await.expect("Failed to load configs from stash"));
464 }
465
466 #[fuchsia::test]
467 async fn load_empty_storage_does_loads_empty_list() {
468 let store_id = &rand_string();
469 let mut store = new_storage(&store_id).await;
470
471 store.write(vec![]).expect("failed to write value");
473
474 let loaded_configs = store.load().await.expect("failed to load store");
476 assert!(loaded_configs.is_empty());
477 }
478
479 #[fuchsia::test]
480 pub async fn load_no_file_creates_file() {
481 let store_id = &rand_string();
485 let backing_file_path = format!("{}{}", FILE_PATH_FORMAT, store_id).to_string();
486 let mut store = PolicyStorage::new_with_id(store_id).await;
487
488 std::fs::read(&backing_file_path).expect_err("The file for the store should not exist yet");
490
491 let loaded_configs = store.load().await.expect("failed to load store");
493 assert_eq!(loaded_configs, vec![]);
494
495 let file_contents = std::fs::read(&backing_file_path).expect(
498 "Failed to read file that should have been created when loading non-existant file",
499 );
500 assert!(!file_contents.is_empty());
501
502 let cfg_id = NetworkIdentifier {
504 ssid: Ssid::try_from(rand_string().as_str()).unwrap().to_vec(),
505 security_type: SecurityType::Wpa2,
506 };
507 let cfg = PersistentData {
508 credential: Credential::Password(rand_string().as_bytes().to_vec()),
509 has_ever_connected: true,
510 };
511
512 match store.legacy_stash.as_mut() {
513 Ok(stash) => {
514 stash.write(&cfg_id, &[cfg]).await.expect("Failed writing to legacy stash");
515 stash.flush().await.expect("Failed to flush legacy stash");
516 }
517 Err(e) => {
518 panic!("error initializing legacy stash: {}", e);
519 }
520 }
521 let loaded_configs = store.load().await.expect("failed to load store");
522 assert!(loaded_configs.is_empty());
523
524 let file_contents = std::fs::read(&backing_file_path).expect(
526 "Failed to read file that should have been created when loading non-existant file",
527 );
528 assert!(!file_contents.is_empty());
529 }
530
531 #[fuchsia::test]
532 async fn clear_storage() {
533 let storage_id = &rand_string();
534 let mut storage = new_storage(&storage_id).await;
535
536 let cfg_foo = PersistentStorageData {
538 ssid: Ssid::try_from("foo").unwrap().to_vec(),
539 security_type: SecurityType::Wpa2,
540 credential: Credential::Password(b"qwertyuio".to_vec()),
541 has_ever_connected: true,
542 hidden_probability: None,
543 };
544 let cfg_bar = PersistentStorageData {
545 ssid: Ssid::try_from("bar").unwrap().to_vec(),
546 security_type: SecurityType::Wpa2,
547 credential: Credential::Password(b"12345678".to_vec()),
548 has_ever_connected: false,
549 hidden_probability: None,
550 };
551 storage.write(vec![cfg_foo.clone(), cfg_bar.clone()]).expect("Failed to write to storage");
552
553 let configs_from_storage = storage.load().await.expect("Failed to read");
555 assert_eq!(2, configs_from_storage.len());
556 assert!(configs_from_storage.contains(&cfg_foo));
557 assert!(configs_from_storage.contains(&cfg_bar));
558
559 storage.clear().expect("Failed to clear storage");
561 let configs_from_storage = storage.load().await.expect("Failed to read");
563 assert_eq!(0, configs_from_storage.len());
564
565 let mut storage = PolicyStorage::new_with_id(storage_id).await;
567 let configs_from_storage = storage.load().await.expect("Failed to read");
568 assert_eq!(0, configs_from_storage.len());
569 }
570
571 #[fuchsia::test]
572 async fn test_migration() {
573 let storage_id = rand_string();
574 let stash_client = connect_to_protocol::<fidl_stash::SecureStoreMarker>()
575 .expect("failed to connect to store");
576 let ssid = "foo";
577 let security_type = SecurityType::Wpa2;
578 let credential = Credential::Password(b"password".to_vec());
579 let has_ever_connected = false;
580 let network_id = network_id(ssid, security_type);
582 let previous_data = PersistentData { credential: credential.clone(), has_ever_connected };
583
584 let network_config = vec![PersistentStorageData {
586 ssid: ssid.into(),
587 security_type: security_type,
588 credential: credential.clone(),
589 has_ever_connected,
590 hidden_probability: None,
591 }];
592
593 let stash = StashStore::from_secure_store_proxy(&storage_id, stash_client.clone())
595 .expect("failed to get stash proxy");
596 stash.write(&network_id, &vec![previous_data]).await.expect("write failed");
597
598 let mut storage = PolicyStorage::new_with_id(&storage_id).await;
600 storage.legacy_stash = Ok(stash);
601 assert_eq!(storage.load().await.expect("load failed"), network_config);
602
603 let stash_client = connect_to_protocol::<fidl_stash::SecureStoreMarker>()
606 .expect("failed to connect to store");
607 let stash = StashStore::from_secure_store_proxy(&storage_id, stash_client)
608 .expect("failed to get stash proxy");
609 assert!(stash.load().await.expect("load failed").is_empty());
610
611 let mut storage = PolicyStorage::new_with_id(&storage_id).await;
613 assert_eq!(storage.load().await.expect("load failed"), network_config);
614 }
615
616 #[fuchsia::test]
617 async fn test_migration_with_bad_stash() {
618 let store_id = rand_string();
619
620 let (client, mut request_stream) = create_request_stream::<fidl_stash::SecureStoreMarker>();
621
622 let read_from_stash = Arc::new(AtomicBool::new(false));
625
626 let _task = {
630 let read_from_stash = read_from_stash.clone();
631 fasync::Task::spawn(async move {
632 while let Some(request) = request_stream.next().await {
633 match request.unwrap() {
634 SecureStoreRequest::Identify { .. } => {}
635 SecureStoreRequest::CreateAccessor { accessor_request, .. } => {
636 let read_from_stash = read_from_stash.clone();
637 fuchsia_async::Task::spawn(async move {
638 let mut request_stream = accessor_request.into_stream();
639 while let Some(request) = request_stream.next().await {
640 match request.unwrap() {
641 StoreAccessorRequest::ListPrefix { .. } => {
642 read_from_stash.store(true, Ordering::Relaxed);
643 }
646 _ => unreachable!(),
647 }
648 }
649 })
650 .detach();
651 }
652 }
653 }
654 })
655 };
656
657 let mut store = PolicyStorage::new_with_id(&store_id).await;
659 let proxy_fn = client.into_proxy();
660 store.legacy_stash = StashStore::from_secure_store_proxy(&store_id, proxy_fn);
661
662 assert!(&store.load().await.expect("load failed").is_empty());
664
665 assert!(read_from_stash.load(Ordering::Relaxed));
667 }
668
669 pub async fn new_storage(id: &str) -> PolicyStorage {
672 let mut store = PolicyStorage::new_with_id(id).await;
673 store.clear().expect("failed to clear stash");
674 store
675 }
676
677 struct MetricsTestValues {
679 store: PolicyStorage,
680 cobalt_stream: fidl_fuchsia_metrics::MetricEventLoggerRequestStream,
681 stash_stream: fidl_stash::SecureStoreRequestStream,
682 }
683
684 fn migration_metrics_test_values() -> MetricsTestValues {
688 let (cobalt_proxy, cobalt_stream) = fidl::endpoints::create_proxy_and_stream::<
689 fidl_fuchsia_metrics::MetricEventLoggerMarker,
690 >();
691
692 let (legacy_stash, stash_stream) = stash_for_test();
693 let root = StorageStore::new(format!("{FILE_PATH_FORMAT}{}", rand_string()));
694 let store = PolicyStorage { root, legacy_stash, cobalt_proxy: Some(cobalt_proxy) };
695
696 MetricsTestValues { store, cobalt_stream, stash_stream }
697 }
698
699 fn stash_for_test() -> (Result<StashStore, Error>, fidl_stash::SecureStoreRequestStream) {
700 let (client, stash_stream) = create_request_stream::<fidl_stash::SecureStoreMarker>();
701 let proxy_fn = client.into_proxy();
702 let id = rand_string();
703 let legacy_stash = StashStore::from_secure_store_proxy(&id, proxy_fn);
704
705 (legacy_stash, stash_stream)
706 }
707
708 fn check_load_metric(
711 logged_metric: fidl_fuchsia_metrics::MetricEventLoggerRequest,
712 expected_event: MigrationResult,
713 ) {
714 assert_matches!(logged_metric, fidl_fuchsia_metrics::MetricEventLoggerRequest::LogMetricEvents {
715 mut events, responder, ..
716 } => {
717 assert_eq!(events.len(), 1);
718 let event = events.pop().unwrap();
719 assert_matches!(event, fidl_fuchsia_metrics::MetricEvent { metric_id, event_codes, payload: _payload } => {
720 assert_eq!(metric_id, wlan_metrics_registry::STASH_MIGRATION_RESULTS_METRIC_ID);
721 assert_eq!(event_codes, [expected_event as u32]);
722 });
723
724 assert!(responder.send(Ok(())).is_ok());
725 });
726 }
727
728 fn process_init_stash(
729 exec: &mut fasync::TestExecutor,
730 mut stash_stream: fidl_stash::SecureStoreRequestStream,
731 ) -> fidl_stash::StoreAccessorRequestStream {
732 assert_matches!(
733 exec.run_until_stalled(&mut stash_stream.next()),
734 Poll::Ready(Some(Ok(SecureStoreRequest::Identify { .. })))
735 );
736
737 let accessor_req_stream = assert_matches!(
738 exec.run_until_stalled(&mut stash_stream.next()),
739 Poll::Ready(Some(Ok(SecureStoreRequest::CreateAccessor { accessor_request, .. }))) =>
740 {
741 accessor_request.into_stream()
742 });
743
744 accessor_req_stream
745 }
746
747 fn respond_to_stash_list_prefix(
750 exec: &mut fasync::TestExecutor,
751 stash_server: &mut fidl_stash::StoreAccessorRequestStream,
752 ) {
753 let request = assert_matches!(exec.run_until_stalled(&mut stash_server.next()), Poll::Ready(req) => {
754 req.expect("ListPrefix stash request not recieved.")
755 });
756 match request.unwrap() {
757 StoreAccessorRequest::ListPrefix { it, .. } => {
758 let mut iter = it.into_stream();
759 assert_matches!(
760 exec.run_until_stalled(&mut iter.try_next()),
761 Poll::Ready(Ok(Some(fidl_stash::ListIteratorRequest::GetNext { responder }))) => {
762 responder.send(&[]).expect("error sending stash response");
763 });
764 }
765 _ => unreachable!(),
766 }
767 }
768
769 fn process_stash_delete(
770 exec: &mut fasync::TestExecutor,
771 stash_server: &mut fidl_stash::StoreAccessorRequestStream,
772 ) {
773 let request = assert_matches!(exec.run_until_stalled(&mut stash_server.next()), Poll::Ready(req) => {
775 req.expect("DeletePrefix stash request not recieved.")
776 });
777 match request.unwrap() {
778 StoreAccessorRequest::DeletePrefix { .. } => {}
779 _ => unreachable!(),
780 }
781
782 assert_matches!(
784 exec.run_until_stalled(&mut stash_server.try_next()),
785 Poll::Ready(Ok(Some(fidl_stash::StoreAccessorRequest::Flush{responder}))) => {
786 responder.send(Ok(())).expect("failed to send stash response");
787 }
788 );
789 }
790
791 #[fuchsia::test]
792 pub fn test_load_logs_result_success_metric() {
793 let mut exec = fasync::TestExecutor::new();
794 let mut test_values = migration_metrics_test_values();
797
798 {
800 let load_fut = test_values.store.load();
801 futures::pin_mut!(load_fut);
802 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
803
804 let mut accessor_req_stream = process_init_stash(&mut exec, test_values.stash_stream);
806 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
807
808 respond_to_stash_list_prefix(&mut exec, &mut accessor_req_stream);
810 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
811
812 process_stash_delete(&mut exec, &mut accessor_req_stream);
814 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
815
816 let mut metric_fut = test_values.cobalt_stream.next();
817 assert_matches!(exec.run_until_stalled(&mut metric_fut), Poll::Ready(Some(Ok(logged_metric))) => {
818 check_load_metric(logged_metric, MigrationResult::Success);
819 });
820 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(_)));
821 }
822
823 let load_fut = test_values.store.load();
826 futures::pin_mut!(load_fut);
827 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
828
829 let mut metric_fut = test_values.cobalt_stream.next();
830 assert_matches!(exec.run_until_stalled(&mut metric_fut), Poll::Ready(Some(Ok(logged_metric))) => {
831 check_load_metric(logged_metric, MigrationResult::AlreadyMigrated);
832 });
833
834 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(_)));
835
836 assert_matches!(exec.run_until_stalled(&mut metric_fut), Poll::Pending);
838 }
839
840 #[fuchsia::test]
841 pub fn test_load_failure_logs_result_metric() {
842 let mut exec = fuchsia_async::TestExecutor::new();
843 let mut test_values = migration_metrics_test_values();
844 let load_fut = test_values.store.load();
845 futures::pin_mut!(load_fut);
846
847 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
850
851 let mut accessor_req_stream = process_init_stash(&mut exec, test_values.stash_stream);
853 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
854
855 let request = assert_matches!(exec.run_until_stalled(&mut accessor_req_stream.next()), Poll::Ready(req) => {
857 req.expect("ListPrefix stash request not recieved.")
858 });
859 match request.unwrap() {
860 StoreAccessorRequest::ListPrefix { it: _, .. } => {
861 }
863 _ => unreachable!(),
864 }
865
866 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
868
869 assert_matches!(exec.run_until_stalled(&mut test_values.cobalt_stream.next()), Poll::Ready(Some(Ok(metric))) => {
871 check_load_metric(metric, MigrationResult::FailedToLoadLegacyData);
872 });
873
874 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(data)) => {
876 assert!(data.is_empty());
877 });
878
879 assert_matches!(
881 exec.run_until_stalled(&mut test_values.cobalt_stream.next()),
882 Poll::Pending
883 );
884 }
885
886 #[fuchsia::test]
887 pub fn test_load_delete_stash_failure_logs_result_metric() {
888 let mut exec = fuchsia_async::TestExecutor::new();
889 let mut test_values = migration_metrics_test_values();
890 let load_fut = test_values.store.load();
891 futures::pin_mut!(load_fut);
892
893 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
896
897 let mut accessor_req_stream = process_init_stash(&mut exec, test_values.stash_stream);
899 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
900
901 respond_to_stash_list_prefix(&mut exec, &mut accessor_req_stream);
903
904 drop(accessor_req_stream);
906
907 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
909
910 assert_matches!(exec.run_until_stalled(&mut test_values.cobalt_stream.next()), Poll::Ready(Some(Ok(metric))) => {
912 check_load_metric(metric, MigrationResult::MigratedButFailedToDeleteLegacy);
913 });
914
915 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(data)) => {
917 assert!(data.is_empty());
918 });
919
920 assert_matches!(
922 exec.run_until_stalled(&mut test_values.cobalt_stream.next()),
923 Poll::Pending
924 );
925 }
926
927 #[fuchsia::test]
928 pub fn test_load_logs_result_failed_to_write_metric() {
929 let mut exec = fasync::TestExecutor::new();
930 let store_id = "//";
932 let mut test_values = migration_metrics_test_values();
933
934 test_values.store.root = StorageStore::new(std::path::Path::new(store_id));
936
937 let load_fut = test_values.store.load();
939 futures::pin_mut!(load_fut);
940 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
941
942 let mut accessor_req_stream = process_init_stash(&mut exec, test_values.stash_stream);
944 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
945
946 respond_to_stash_list_prefix(&mut exec, &mut accessor_req_stream);
948 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
949
950 let mut metric_fut = test_values.cobalt_stream.next();
952 assert_matches!(exec.run_until_stalled(&mut metric_fut), Poll::Ready(Some(Ok(logged_metric))) => {
953 check_load_metric(logged_metric, MigrationResult::FailedToWriteNewStore);
954 });
955
956 assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(_)));
957
958 assert_matches!(
960 exec.run_until_stalled(&mut test_values.cobalt_stream.next()),
961 Poll::Pending
962 );
963 }
964
965 #[fuchsia::test]
966 pub fn load_wpa2_and_open_network_golden_file_test() {
967 let file_contents =
974 "{\"saved_networks\":[\
975 {\
976 \"ssid\":[115,111,109,101,45,110,101,116,119,111,114,107],\
977 \"security_type\":\"Wpa2\",\
978 \"credential\":{\"Password\":[115,111,109,101,45,112,97,115,115,119,111,114,100]},\
979 \"has_ever_connected\":false
980 },\
981 {
982 \"ssid\":[111,112,101,110,45,110,101,116,119,111,114,107],\
983 \"security_type\":\"None\",\
984 \"credential\":\"None\",\
985 \"has_ever_connected\":true\
986 }],\
987 \"version\":1\
988 }"
989 .as_bytes();
990 let store_id = &rand_string();
991
992 let network_configs = vec![
993 PersistentStorageData {
994 ssid: vec![115, 111, 109, 101, 45, 110, 101, 116, 119, 111, 114, 107],
995 security_type: SecurityType::Wpa2,
996 credential: Credential::Password(vec![100, 100, 100, 100, 100, 100]),
997 has_ever_connected: false,
998 hidden_probability: None,
999 },
1000 PersistentStorageData {
1001 ssid: vec![111, 112, 101, 110, 45, 110, 101, 116, 119, 111, 114, 107],
1002 security_type: SecurityType::None,
1003 credential: Credential::None,
1004 has_ever_connected: true,
1005 hidden_probability: None,
1006 },
1007 ];
1008
1009 let path = format!("/data/config.{}", store_id);
1011 let mut file = std::fs::File::create(&path).expect("failed to open file for writing");
1012 assert_eq!(
1013 file.write(&file_contents).expect("Failed to write to file"),
1014 file_contents.len()
1015 );
1016 file.flush().expect("failed to flush contents of file");
1017
1018 let store = StorageStore::new(&path);
1020 let loaded_configs = store.load().expect("load failed");
1021 assert_eq!(loaded_configs.len(), network_configs.len());
1022 for config in network_configs.iter() {
1023 assert!(network_configs.contains(config));
1024 }
1025 }
1026}