| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! This module contains types used for sending and receiving messages to and from the storage |
| //! agent. |
| |
| use crate::base::{SettingInfo, SettingType}; |
| use fuchsia_trace as ftrace; |
| use settings_storage::UpdateState; |
| |
| /// `Payload` wraps the request and response payloads. |
| #[derive(Clone, PartialEq, Debug)] |
| pub enum Payload { |
| Request(StorageRequest), |
| Response(StorageResponse), |
| } |
| |
| /// `StorageRequest` contains all of the requests that can be made to the storage agent. |
| #[derive(Clone, PartialEq, Debug)] |
| pub enum StorageRequest { |
| /// A read requests for the corresponding [`StorageInfo`] of this `StorageType`. |
| Read(StorageType, ftrace::Id), |
| /// A write requests for this [`StorageInfo`]. |
| Write(StorageInfo, ftrace::Id), |
| } |
| |
| #[derive(Clone, PartialEq, Debug)] |
| pub enum StorageType { |
| SettingType(SettingType), |
| } |
| |
| impl From<SettingType> for StorageType { |
| fn from(setting_type: SettingType) -> Self { |
| StorageType::SettingType(setting_type) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Debug)] |
| pub enum StorageInfo { |
| SettingInfo(SettingInfo), |
| } |
| |
| impl From<SettingInfo> for StorageInfo { |
| fn from(setting_info: SettingInfo) -> Self { |
| StorageInfo::SettingInfo(setting_info) |
| } |
| } |
| |
| /// `StorageResponse` contains the corresponding result types to the matching [`StorageRequest`] |
| /// variants of the same name. |
| #[derive(Clone, PartialEq, Debug)] |
| pub enum StorageResponse { |
| /// The storage info read from storage in response to a [`StorageRequest::Read`] |
| Read(StorageInfo), |
| /// The result of a write request with either the [`UpdateState`] after a successful write |
| /// or a formatted error describing why the write could not occur. |
| Write(Result<UpdateState, Error>), |
| } |
| |
| /// `Error` encapsulates a formatted error the occurs due to write failures. |
| #[derive(Clone, PartialEq, Debug)] |
| pub struct Error { |
| /// The error message. |
| pub message: String, |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod testing { |
| use anyhow::Error; |
| use fidl_fuchsia_stash::{ |
| StoreAccessorMarker, StoreAccessorProxy, StoreAccessorRequest, Value, |
| }; |
| use fuchsia_inspect::component; |
| use futures::lock::Mutex; |
| use futures::prelude::*; |
| use serde::{Deserialize, Serialize}; |
| use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible}; |
| use settings_storage::stash_logger::StashInspectLogger; |
| use settings_storage::storage_factory::{ |
| DefaultLoader, InitializationState, NoneT, StorageAccess, StorageFactory, |
| }; |
| use std::any::Any; |
| use std::collections::HashMap; |
| use std::sync::Arc; |
| use {fuchsia_async as fasync, fuchsia_zircon as zx}; |
| |
| #[derive(PartialEq)] |
| pub(crate) enum StashAction { |
| Get, |
| Flush, |
| Set, |
| } |
| |
| pub(crate) struct StashStats { |
| actions: Vec<StashAction>, |
| } |
| |
| impl StashStats { |
| pub(crate) fn new() -> Self { |
| StashStats { actions: Vec::new() } |
| } |
| |
| pub(crate) fn record(&mut self, action: StashAction) { |
| self.actions.push(action); |
| } |
| } |
| |
| /// Storage that does not write to disk, for testing. |
| pub(crate) struct InMemoryStorageFactory { |
| initial_data: HashMap<&'static str, String>, |
| device_storage_cache: Mutex<InitializationState<DeviceStorage>>, |
| inspect_handle: Arc<Mutex<StashInspectLogger>>, |
| } |
| |
| impl Default for InMemoryStorageFactory { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| const INITIALIZATION_ERROR: &str = |
| "Cannot initialize an already accessed device storage. Make \ |
| sure you're not retrieving a DeviceStorage before passing InMemoryStorageFactory to an \ |
| EnvironmentBuilder. That must be done after. If you need initial data, use \ |
| InMemoryStorageFactory::with_initial_data"; |
| |
| impl InMemoryStorageFactory { |
| /// Constructs a new `InMemoryStorageFactory` with the ability to create a [`DeviceStorage`] |
| /// that can only read and write to the storage keys passed in. |
| pub fn new() -> Self { |
| InMemoryStorageFactory { |
| initial_data: HashMap::new(), |
| device_storage_cache: Mutex::new(InitializationState::new()), |
| inspect_handle: Arc::new(Mutex::new(StashInspectLogger::new( |
| component::inspector().root(), |
| ))), |
| } |
| } |
| |
| /// Constructs a new `InMemoryStorageFactory` with the data written to stash. This simulates |
| /// the data existing in storage before the RestoreAgent reads it. |
| pub fn with_initial_data<T>(data: &T) -> Self |
| where |
| T: DeviceStorageCompatible, |
| { |
| let mut map = HashMap::new(); |
| let _ = map.insert(T::KEY, serde_json::to_string(data).unwrap()); |
| InMemoryStorageFactory { |
| initial_data: map, |
| device_storage_cache: Mutex::new(InitializationState::new()), |
| inspect_handle: Arc::new(Mutex::new(StashInspectLogger::new( |
| component::inspector().root(), |
| ))), |
| } |
| } |
| |
| /// Helper method to simplify setup for `InMemoryStorageFactory` in tests. |
| pub(crate) async fn initialize_storage<T>(&self) |
| where |
| T: DeviceStorageCompatible, |
| { |
| self.initialize_storage_for_key(T::KEY).await; |
| } |
| |
| async fn initialize_storage_for_key(&self, key: &'static str) { |
| match &mut *self.device_storage_cache.lock().await { |
| InitializationState::Initializing(initial_keys, _) => { |
| let _ = initial_keys.insert(key, None); |
| } |
| InitializationState::Initialized(_) => panic!("{}", INITIALIZATION_ERROR), |
| _ => unreachable!(), |
| } |
| } |
| |
| async fn initialize_storage_for_key_with_loader( |
| &self, |
| key: &'static str, |
| loader: Box<dyn Any + Send + Sync + 'static>, |
| ) { |
| match &mut *self.device_storage_cache.lock().await { |
| InitializationState::Initializing(initial_keys, _) => { |
| let _ = initial_keys.insert(key, Some(loader)); |
| } |
| InitializationState::Initialized(_) => panic!("{}", INITIALIZATION_ERROR), |
| _ => unreachable!(), |
| } |
| } |
| |
| /// Retrieve the [`DeviceStorage`] singleton. |
| pub(crate) async fn get_device_storage(&self) -> Arc<DeviceStorage> { |
| let initialization = &mut *self.device_storage_cache.lock().await; |
| match initialization { |
| InitializationState::Initializing(initial_keys, _) => { |
| let mut device_storage = DeviceStorage::with_stash_proxy( |
| initial_keys.drain(), |
| || { |
| let (stash_proxy, _) = spawn_stash_proxy(); |
| stash_proxy |
| }, |
| Arc::clone(&self.inspect_handle), |
| ); |
| device_storage.set_caching_enabled(false); |
| device_storage.set_debounce_writes(false); |
| |
| // write initial data to storage |
| for (&key, data) in &self.initial_data { |
| device_storage |
| .write_str(key, data.clone()) |
| .await |
| .expect("Failed to write initial data"); |
| } |
| |
| let device_storage = Arc::new(device_storage); |
| *initialization = InitializationState::Initialized(Arc::clone(&device_storage)); |
| device_storage |
| } |
| InitializationState::Initialized(device_storage) => Arc::clone(device_storage), |
| _ => unreachable!(), |
| } |
| } |
| } |
| |
| #[async_trait::async_trait] |
| impl StorageFactory for InMemoryStorageFactory { |
| type Storage = DeviceStorage; |
| |
| async fn initialize<T>(&self) -> Result<(), Error> |
| where |
| T: StorageAccess<Storage = DeviceStorage>, |
| { |
| self.initialize_storage_for_key(T::STORAGE_KEY).await; |
| Ok(()) |
| } |
| |
| async fn initialize_with_loader<T>( |
| &self, |
| loader: impl DefaultLoader<Result = T::Data> + Send + Sync + 'static, |
| ) -> Result<(), Error> |
| where |
| T: StorageAccess<Storage = DeviceStorage>, |
| { |
| self.initialize_storage_for_key_with_loader( |
| T::STORAGE_KEY, |
| Box::new(loader) as Box<dyn Any + Send + Sync + 'static>, |
| ) |
| .await; |
| Ok(()) |
| } |
| |
| async fn get_store(&self) -> Arc<DeviceStorage> { |
| self.get_device_storage().await |
| } |
| } |
| |
| fn spawn_stash_proxy() -> (StoreAccessorProxy, Arc<Mutex<StashStats>>) { |
| let (stash_proxy, mut stash_stream) = |
| fidl::endpoints::create_proxy_and_stream::<StoreAccessorMarker>().unwrap(); |
| let stats = Arc::new(Mutex::new(StashStats::new())); |
| let stats_clone = stats.clone(); |
| fasync::Task::spawn(async move { |
| let mut stored_value: Option<Value> = None; |
| let mut stored_key: Option<String> = None; |
| |
| while let Some(req) = stash_stream.try_next().await.unwrap() { |
| #[allow(unreachable_patterns)] |
| match req { |
| StoreAccessorRequest::GetValue { key, responder } => { |
| stats_clone.lock().await.record(StashAction::Get); |
| if let Some(key_string) = stored_key { |
| assert_eq!(key, key_string); |
| } |
| stored_key = Some(key); |
| |
| let value = stored_value.as_ref().map(|value| match value { |
| Value::Intval(v) => Value::Intval(*v), |
| Value::Floatval(v) => Value::Floatval(*v), |
| Value::Boolval(v) => Value::Boolval(*v), |
| Value::Stringval(v) => Value::Stringval(v.clone()), |
| Value::Bytesval(buffer) => { |
| let opts = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE; |
| Value::Bytesval(fidl_fuchsia_mem::Buffer { |
| vmo: buffer.vmo.create_child(opts, 0, buffer.size).unwrap(), |
| size: buffer.size, |
| }) |
| } |
| }); |
| responder.send(value).unwrap(); |
| } |
| StoreAccessorRequest::SetValue { key, val, control_handle: _ } => { |
| stats_clone.lock().await.record(StashAction::Set); |
| if let Some(key_string) = stored_key { |
| assert_eq!(key, key_string); |
| } |
| stored_key = Some(key); |
| stored_value = Some(val); |
| } |
| StoreAccessorRequest::Flush { responder } => { |
| stats_clone.lock().await.record(StashAction::Flush); |
| let _ = responder.send(Ok(())); |
| } |
| _ => {} |
| } |
| } |
| }) |
| .detach(); |
| (stash_proxy, stats) |
| } |
| |
| const VALUE0: i32 = 3; |
| const VALUE1: i32 = 33; |
| |
| #[derive(PartialEq, Clone, Serialize, Deserialize, Debug)] |
| struct TestStruct { |
| value: i32, |
| } |
| |
| impl DeviceStorageCompatible for TestStruct { |
| type Loader = NoneT; |
| const KEY: &'static str = "testkey"; |
| } |
| impl Default for TestStruct { |
| fn default() -> Self { |
| TestStruct { value: VALUE0 } |
| } |
| } |
| |
| #[fuchsia::test(allow_stalls = false)] |
| async fn test_in_memory_storage() { |
| let factory = InMemoryStorageFactory::new(); |
| factory.initialize_storage::<TestStruct>().await; |
| |
| let store_1 = factory.get_device_storage().await; |
| let store_2 = factory.get_device_storage().await; |
| |
| // Write initial data through first store. |
| let test_struct = TestStruct { value: VALUE0 }; |
| |
| // Ensure writing from store1 ends up in store2 |
| test_write_propagation(store_1.clone(), store_2.clone(), test_struct).await; |
| |
| let test_struct_2 = TestStruct { value: VALUE1 }; |
| // Ensure writing from store2 ends up in store1 |
| test_write_propagation(store_2.clone(), store_1.clone(), test_struct_2).await; |
| } |
| |
| async fn test_write_propagation( |
| store_1: Arc<DeviceStorage>, |
| store_2: Arc<DeviceStorage>, |
| data: TestStruct, |
| ) { |
| assert!(store_1.write(&data).await.is_ok()); |
| |
| // Ensure it is read in from second store. |
| let retrieved_struct = store_2.get().await; |
| assert_eq!(data, retrieved_struct); |
| } |
| } |