Skip to main content

wlan_storage/
policy.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
17/// If the store ID is saved_networks, the file will be saved at /data/network-data.saved_networks
18const FILE_PATH_FORMAT: &str = "/data/network-data.";
19
20/// Manages access to the persistent storage. This layer on top of storage just migrates the legacy
21/// persisted data if it hasn't been migrated yet.
22pub struct PolicyStorage {
23    /// This is the actual store.
24    root: StorageStore,
25    /// This is used to get legacy data, and should only ever be used once, when migrating the data
26    /// the first time. The result is carried here because we only care about any errors if we end
27    /// up migrating the data.
28    legacy_stash: Result<StashStore, Error>,
29    cobalt_proxy: Option<fidl_fuchsia_metrics::MetricEventLoggerProxy>,
30}
31
32impl PolicyStorage {
33    /// Initialize new store with the ID provided by the Saved Networks Manager. The ID will
34    /// identify stored values as being part of the same persistent storage.
35    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    /// Initializer for tests outside of this module
57    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    /// Initialize the storage wrapper and load all saved network configs from persistent storage.
68    /// If there is an error loading from local storage, that may mean stash data hasn't been
69    /// migrated yet. If so, the function will try to load from legacy stash data.
70    pub async fn load(&mut self) -> Result<Vec<PersistentStorageData>, Error> {
71        // If there is an error loading from the new version of storage, it means it hasn't been
72        // create and should be loaded from stash.
73        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        // Try and read from Stash since store doesn't exist yet
86        if let Ok(config) = stash_store.load().await {
87            // Read the stash data and convert it to a flattened list of config data.
88            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            // Write the data to the new storage.
97            match self.root.write(networks_list.clone()) {
98                Ok(_) => {
99                    log::info!("Migrated saved networks from stash");
100                    // Delete from stash if writing was successful.
101                    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            // The backing file is only actually created when a write happens, but we
124            // don't want to intentionally create a file if migrating stash fails since
125            // then we will never try to read from stash again.
126            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    /// Update the network configs of a given network identifier to persistent storage, deleting
133    /// the key entirely if the new list of configs is empty.
134    pub fn write(&self, network_configs: Vec<PersistentStorageData>) -> Result<(), Error> {
135        self.root.write(network_configs)
136    }
137
138    /// Remove all saved values from the stash. It will delete everything under the root node,
139    /// and anything else in the same stash but not under the root node would be ignored.
140    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        // No need to log an error if the cobalt proxy is none, errors would have been logged on
146        // failed initialization of the channel.
147        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        // The error type of this inner result is a fidl_fuchsia_metrics defined error.
159        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    // Get channel for logging cobalt 1.1 metrics.
181    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, // defaults to fuchsia
190        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    /// The PSK provided must be the bytes form of the 64 hexadecimal character hash. This is a
221    /// duplicate of a definition in wlan/wlancfg/src, since I don't think there's a good way to
222    /// import just that constant.
223    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        // Save a network config to storage
237        store.write(vec![cfg.clone()]).expect("Failed writing to storage");
238
239        // Expect to read the same value back with the same key
240        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        // Overwrite the list of configs saved in storage
245        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        // Expect to read the saved value back
255        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        // create and write configs with each security type
267        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        // Write the saved network list as it would be each time a new network was added.
304        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        // Load storage and expect each config that we wrote.
316        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        // Create and write configs with each type credential.
330        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        // Write configs to storage as they would be when saved.
356        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        // Check that the configs are loaded correctly.
364        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        // create and write configs with each hidden probability
376        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        // Write the saved network list as it would be each time a new network was added.
399        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        // Load storage and expect each config that we wrote.
407        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        // Save a network config to the stash
427        store.write(vec![cfg.clone()]).expect("Failed writing to storage");
428
429        // Create the storage again with same id
430        let mut store = PolicyStorage::new_with_id(&path).await;
431
432        // Expect to read the same value back with the same key, should exist in new stash
433        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 two networks in our stash.
457        store
458            .write(vec![cfg_foo.clone(), cfg_bar.clone()])
459            .expect("Failed to save configs to stash");
460
461        // load should give us the two networks we saved
462        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        // write to storage an empty saved networks list
472        store.write(vec![]).expect("failed to write value");
473
474        // recreate the storage to load it
475        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        // Test what would happen if policy persistent storage is loaded twice - the first attempt
482        // should initialize the backing file. The second attempt should not attempt to load from
483        // the legacy stash.
484        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        // The file should not exist yet, so reading it would give an error.
489        std::fs::read(&backing_file_path).expect_err("The file for the store should not exist yet");
490
491        // Load the store.
492        let loaded_configs = store.load().await.expect("failed to load store");
493        assert_eq!(loaded_configs, vec![]);
494
495        // Check that the file is created. It should have some JSON structure even though there
496        // are no saved networks.
497        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        // Load the store again, but with some values in the legacy stash which should be ignored.
503        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        // The file should still exist.
525        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        // add some configs to the storage
537        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        // verify that the configs are found in storage
554        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        // clear the storage
560        storage.clear().expect("Failed to clear storage");
561        // verify that the configs are no longer in the storage
562        let configs_from_storage = storage.load().await.expect("Failed to read");
563        assert_eq!(0, configs_from_storage.len());
564
565        // recreate storage and verify that clearing the storage persists
566        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        // This is the version used by the previous storage mechanism.
581        let network_id = network_id(ssid, security_type);
582        let previous_data = PersistentData { credential: credential.clone(), has_ever_connected };
583
584        // This is the version used by the new storage mechanism.
585        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        // Write the config to stash that storage will migrate from.
594        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        // Initialize storage, and give it the stash with the saved network data.
599        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        // The config should have been deleted from stash.
604        // The stash connection can't be reused, or the stash store will fail to access stash.
605        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        // And once more, but this time there should be no migration.
612        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        // This will be set to true if stash is accessed, so that the test can check whether stash
623        // was read by the migration code.
624        let read_from_stash = Arc::new(AtomicBool::new(false));
625
626        // This responds in the background to any stash requests for initializing the connection to
627        // stash (identify or create accessor), and responds to requests for reading data with an
628        // an error by dropping the responder.
629        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                                            // If we just drop the iterator, it should trigger a
644                                            // read error.
645                                        }
646                                        _ => unreachable!(),
647                                    }
648                                }
649                            })
650                            .detach();
651                        }
652                    }
653                }
654            })
655        };
656
657        // Initialize the store but switch out with the stash we made to act corrupted.
658        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        // Try and load the config. It should provide empty config.
663        assert!(&store.load().await.expect("load failed").is_empty());
664
665        // Make sure there was an attempt to actually read from stash.
666        assert!(read_from_stash.load(Ordering::Relaxed));
667    }
668
669    /// Creates a new persistent storage with a file bath based on the given ID and clears any
670    /// values saved in the store.
671    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    /// Metrics tests need to be able to control stash behavior and check what is sent to cobalt.
678    struct MetricsTestValues {
679        store: PolicyStorage,
680        cobalt_stream: fidl_fuchsia_metrics::MetricEventLoggerRequestStream,
681        stash_stream: fidl_stash::SecureStoreRequestStream,
682    }
683
684    /// This initializes PolicyStorage with a cobalt channel and stash channel that is
685    /// controlled by the test. It is for tests that want to control stash responses and read
686    /// messages send to cobalt.
687    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    // Checks that the metric event is correct and acks the metric event so that the load fut
709    // can continue.
710    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    /// Respond to the ListPrefix request with empty data, which matches the scenario where nothing
748    /// is saved in stash.
749    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        // Respond to stash delete.
774        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        // Respond to stash flush.
783        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        // Use a PolicyStorage with the default stash proxy for this test, but switch out the
795        // cobalt proxy to intercept metric events.
796        let mut test_values = migration_metrics_test_values();
797
798        // Load for the first time successfully.
799        {
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            // Respond to stash initialization requests.
805            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 read requests with empty data.
809            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 and flush.
813            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        // Load again, an AlreadyMigrated metric event code should be logged. Stash do not need
824        // handling because the stash wrapper internally stores the data.
825        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        // Check that nothing else was logged
837        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        // Start running the future to load and trigger migration. It should halt waiting on a
848        // stash request.
849        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
850
851        // Respond to stash initialization requests.
852        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        // Drop the request to read stash so that loading stash fails.
856        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                // If we just drop the iterator without responding, it should trigger a read error.
862            }
863            _ => unreachable!(),
864        }
865
866        // Continue the load fut, it should wait on a response to sending a metric.
867        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
868
869        // Check for the correct metric and ack.
870        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        // The load should finish this time.
875        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(data)) => {
876            assert!(data.is_empty());
877        });
878
879        // Verify that nothing else was send through the cobalt channel.
880        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        // Start running the future to load and trigger migration. It should halt waiting on a
894        // stash request.
895        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
896
897        // Respond to stash initialization requests.
898        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 requests as if loading empty stash data.
902        respond_to_stash_list_prefix(&mut exec, &mut accessor_req_stream);
903
904        // Drop the accessor request stream to trigger an error when deleting stash.
905        drop(accessor_req_stream);
906
907        // Continue the load fut, it should wait on a response to sending a metric.
908        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Pending);
909
910        // Check for the correct metric and ack.
911        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        // The load should finish this time.
916        assert_matches!(exec.run_until_stalled(&mut load_fut), Poll::Ready(Ok(data)) => {
917            assert!(data.is_empty());
918        });
919
920        // Verify that nothing else was sent through the cobalt channel.
921        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        // Use a path that is invalid so that writing to it fails.
931        let store_id = "//";
932        let mut test_values = migration_metrics_test_values();
933
934        // Switch out StorageStore to one using the invalid path.
935        test_values.store.root = StorageStore::new(std::path::Path::new(store_id));
936
937        // Start loading to migrate stash data.
938        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        // Respond to stash initialization requests.
943        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 requests as if loading empty stash data.
947        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        // Check that the metric is logged for the failure to write.
951        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        // Verify that nothing else was sent through the cobalt channel.
959        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        // This tests that this example persisted file with one WPA2 network and one open network
968        // can still be read by the code, even if the corresponding write logic changes.
969        // This test should NOT change even if the way data is saved changes, unless there has been
970        // a stepping stone version that migrates this format, since the purpose is to test that
971        // this format can be read after an update.
972
973        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        // Write the data to the StorageStore's backing file.
1010        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        // Load the file data and check that the expected networks are there.
1019        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}