1use anyhow::Error;
6use fidl_fuchsia_stash::{StoreAccessorMarker, StoreAccessorProxy, StoreAccessorRequest, Value};
7use fuchsia_inspect::component;
8use futures::lock::Mutex;
9use futures::TryStreamExt;
10use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
11use settings_storage::fidl_storage::{FidlStorage, FidlStorageConvertible};
12use settings_storage::stash_logger::StashInspectLogger;
13use settings_storage::storage_factory::{
14 DefaultLoader, InitializationState, StorageAccess, StorageFactory,
15};
16use std::any::Any;
17use std::collections::HashMap;
18use std::rc::Rc;
19use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
20
21#[derive(PartialEq)]
22pub(crate) enum StashAction {
23 Get,
24 Flush,
25 Set,
26}
27
28pub(crate) struct StashStats {
29 actions: Vec<StashAction>,
30}
31
32impl StashStats {
33 pub(crate) fn new() -> Self {
34 StashStats { actions: Vec::new() }
35 }
36
37 pub(crate) fn record(&mut self, action: StashAction) {
38 self.actions.push(action);
39 }
40}
41
42pub struct InMemoryStorageFactory {
44 initial_data: HashMap<&'static str, String>,
45 device_storage_cache: Mutex<InitializationState<DeviceStorage>>,
46 inspect_handle: Rc<Mutex<StashInspectLogger>>,
47}
48
49impl Default for InMemoryStorageFactory {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55const INITIALIZATION_ERROR: &str = "Cannot initialize an already accessed device storage. Make \
56 sure you're not retrieving a DeviceStorage before passing InMemoryStorageFactory to an \
57 EnvironmentBuilder. That must be done after. If you need initial data, use \
58 InMemoryStorageFactory::with_initial_data";
59
60impl InMemoryStorageFactory {
61 pub fn new() -> Self {
64 InMemoryStorageFactory {
65 initial_data: HashMap::new(),
66 device_storage_cache: Mutex::new(InitializationState::new()),
67 inspect_handle: Rc::new(Mutex::new(StashInspectLogger::new(
68 component::inspector().root(),
69 ))),
70 }
71 }
72
73 pub fn with_initial_data<T>(data: &T) -> Self
76 where
77 T: DeviceStorageCompatible,
78 {
79 let mut map = HashMap::new();
80 let _ = map.insert(T::KEY, serde_json::to_string(data).unwrap());
81 InMemoryStorageFactory {
82 initial_data: map,
83 device_storage_cache: Mutex::new(InitializationState::new()),
84 inspect_handle: Rc::new(Mutex::new(StashInspectLogger::new(
85 component::inspector().root(),
86 ))),
87 }
88 }
89
90 pub async fn initialize_storage<T>(&self)
92 where
93 T: DeviceStorageCompatible,
94 {
95 self.initialize_storage_for_key(T::KEY).await;
96 }
97
98 async fn initialize_storage_for_key(&self, key: &'static str) {
99 match &mut *self.device_storage_cache.lock().await {
100 InitializationState::Initializing(initial_keys, _) => {
101 let _ = initial_keys.insert(key, None);
102 }
103 InitializationState::Initialized(_) => panic!("{}", INITIALIZATION_ERROR),
104 _ => unreachable!(),
105 }
106 }
107
108 async fn initialize_storage_for_key_with_loader(
109 &self,
110 key: &'static str,
111 loader: Box<dyn Any>,
112 ) {
113 match &mut *self.device_storage_cache.lock().await {
114 InitializationState::Initializing(initial_keys, _) => {
115 let _ = initial_keys.insert(key, Some(loader));
116 }
117 InitializationState::Initialized(_) => panic!("{}", INITIALIZATION_ERROR),
118 _ => unreachable!(),
119 }
120 }
121
122 pub async fn get_device_storage(&self) -> Rc<DeviceStorage> {
124 let initialization = &mut *self.device_storage_cache.lock().await;
125 match initialization {
126 InitializationState::Initializing(initial_keys, _) => {
127 let mut device_storage = DeviceStorage::with_stash_proxy(
128 initial_keys.drain(),
129 || {
130 let (stash_proxy, _) = spawn_stash_proxy();
131 stash_proxy
132 },
133 Rc::clone(&self.inspect_handle),
134 );
135 device_storage.set_caching_enabled(false);
136 device_storage.set_debounce_writes(false);
137
138 for (&key, data) in &self.initial_data {
140 device_storage
141 .write_str(key, data.clone())
142 .await
143 .expect("Failed to write initial data");
144 }
145
146 let device_storage = Rc::new(device_storage);
147 *initialization = InitializationState::Initialized(Rc::clone(&device_storage));
148 device_storage
149 }
150 InitializationState::Initialized(device_storage) => Rc::clone(device_storage),
151 _ => unreachable!(),
152 }
153 }
154}
155
156impl StorageFactory for InMemoryStorageFactory {
157 type Storage = DeviceStorage;
158
159 async fn initialize<T>(&self) -> Result<(), Error>
160 where
161 T: StorageAccess<Storage = DeviceStorage>,
162 {
163 self.initialize_storage_for_key(T::STORAGE_KEY).await;
164 Ok(())
165 }
166
167 async fn initialize_with_loader<T, L>(&self, loader: L) -> Result<(), Error>
168 where
169 T: StorageAccess<Storage = DeviceStorage>,
170 L: DefaultLoader<Result = T::Data> + 'static,
171 {
172 self.initialize_storage_for_key_with_loader(
173 T::STORAGE_KEY,
174 Box::new(loader) as Box<dyn Any>,
175 )
176 .await;
177 Ok(())
178 }
179
180 async fn get_store(&self) -> Rc<DeviceStorage> {
181 self.get_device_storage().await
182 }
183}
184
185fn spawn_stash_proxy() -> (StoreAccessorProxy, Rc<Mutex<StashStats>>) {
186 let (stash_proxy, mut stash_stream) =
187 fidl::endpoints::create_proxy_and_stream::<StoreAccessorMarker>();
188 let stats = Rc::new(Mutex::new(StashStats::new()));
189 let stats_clone = stats.clone();
190 fasync::Task::local(async move {
191 let mut stored_value: Option<Value> = None;
192 let mut stored_key: Option<String> = None;
193
194 while let Some(req) = stash_stream.try_next().await.unwrap() {
195 #[allow(unreachable_patterns)]
196 match req {
197 StoreAccessorRequest::GetValue { key, responder } => {
198 stats_clone.lock().await.record(StashAction::Get);
199 if let Some(key_string) = stored_key {
200 assert_eq!(key, key_string);
201 }
202 stored_key = Some(key);
203
204 let value = stored_value.as_ref().map(|value| match value {
205 Value::Intval(v) => Value::Intval(*v),
206 Value::Floatval(v) => Value::Floatval(*v),
207 Value::Boolval(v) => Value::Boolval(*v),
208 Value::Stringval(v) => Value::Stringval(v.clone()),
209 Value::Bytesval(buffer) => {
210 let opts = zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE;
211 Value::Bytesval(fidl_fuchsia_mem::Buffer {
212 vmo: buffer.vmo.create_child(opts, 0, buffer.size).unwrap(),
213 size: buffer.size,
214 })
215 }
216 });
217 responder.send(value).unwrap();
218 }
219 StoreAccessorRequest::SetValue { key, val, control_handle: _ } => {
220 stats_clone.lock().await.record(StashAction::Set);
221 if let Some(key_string) = stored_key {
222 assert_eq!(key, key_string);
223 }
224 stored_key = Some(key);
225 stored_value = Some(val);
226 }
227 StoreAccessorRequest::Flush { responder } => {
228 stats_clone.lock().await.record(StashAction::Flush);
229 let _ = responder.send(Ok(()));
230 }
231 _ => {}
232 }
233 }
234 })
235 .detach();
236 (stash_proxy, stats)
237}
238
239pub struct InMemoryFidlStorageFactory {
241 initial_data: HashMap<&'static str, Vec<u8>>,
242 fidl_storage_cache: Mutex<InitializationState<(Rc<FidlStorage>, tempfile::TempDir)>>,
243}
244
245impl Default for InMemoryFidlStorageFactory {
246 fn default() -> Self {
247 Self::new()
248 }
249}
250
251const FIDL_INITIALIZATION_ERROR: &str =
252 "Cannot initialize an already accessed device storage. Make \
253 sure you're not retrieving a FidlStorage before passing InMemoryFidlStorageFactory to an \
254 EnvironmentBuilder. That must be done after.";
255
256fn open_tempdir(tempdir: &tempfile::TempDir) -> fio::DirectoryProxy {
257 fuchsia_fs::directory::open_in_namespace(
258 tempdir.path().to_str().expect("tempdir path is not valid UTF-8"),
259 fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_WRITABLE,
260 )
261 .expect("failed to open connection to tempdir")
262}
263
264impl InMemoryFidlStorageFactory {
265 pub fn new() -> Self {
268 InMemoryFidlStorageFactory {
269 initial_data: HashMap::new(),
270 fidl_storage_cache: Mutex::new(InitializationState::new()),
271 }
272 }
273
274 pub async fn initialize_storage<T>(&self)
276 where
277 T: FidlStorageConvertible,
278 {
279 self.initialize_storage_for_key(T::KEY).await;
280 }
281
282 async fn initialize_storage_for_key(&self, key: &'static str) {
283 match &mut *self.fidl_storage_cache.lock().await {
284 InitializationState::Initializing(initial_keys, _) => {
285 let _ = initial_keys.insert(key, None);
286 }
287 InitializationState::Initialized(_) => panic!("{}", FIDL_INITIALIZATION_ERROR),
288 _ => unreachable!(),
289 }
290 }
291
292 async fn initialize_storage_for_key_with_loader(
293 &self,
294 key: &'static str,
295 loader: Box<dyn Any>,
296 ) {
297 match &mut *self.fidl_storage_cache.lock().await {
298 InitializationState::Initializing(initial_keys, _) => {
299 let _ = initial_keys.insert(key, Some(loader));
300 }
301 InitializationState::Initialized(_) => panic!("{}", INITIALIZATION_ERROR),
302 _ => unreachable!(),
303 }
304 }
305
306 pub(crate) async fn get_fidl_storage(&self) -> Rc<FidlStorage> {
308 let initialization = &mut *self.fidl_storage_cache.lock().await;
309 match initialization {
310 InitializationState::Initializing(initial_keys, _) => {
311 let tempdir = tempfile::tempdir().expect("failed to create tempdir");
312 let directory = open_tempdir(&tempdir);
313
314 let (mut fidl_storage, tasks) =
315 FidlStorage::with_file_proxy(initial_keys.drain(), directory, move |key| {
316 let temp_file_name = format!("{key}.tmp");
317 let file_name = format!("{key}.pfidl");
318 Ok((temp_file_name, file_name))
319 })
320 .await
321 .unwrap();
322
323 for task in tasks {
324 task.detach();
325 }
326 fidl_storage.set_caching_enabled(false);
327 fidl_storage.set_debounce_writes(false);
328
329 for (&key, data) in &self.initial_data {
331 fidl_storage
332 .write_test_bytes(key, data.clone())
333 .await
334 .expect("Failed to write initial data");
335 }
336
337 let fidl_storage = Rc::new(fidl_storage);
338 *initialization =
339 InitializationState::Initialized(Rc::new((Rc::clone(&fidl_storage), tempdir)));
340 fidl_storage
341 }
342 InitializationState::Initialized(initialized) => Rc::clone(&initialized.as_ref().0),
343 _ => unreachable!(),
344 }
345 }
346}
347
348impl StorageFactory for InMemoryFidlStorageFactory {
349 type Storage = FidlStorage;
350
351 async fn initialize<T>(&self) -> Result<(), Error>
352 where
353 T: StorageAccess<Storage = FidlStorage>,
354 {
355 self.initialize_storage_for_key(T::STORAGE_KEY).await;
356 Ok(())
357 }
358
359 async fn initialize_with_loader<T, L>(&self, loader: L) -> Result<(), Error>
360 where
361 T: StorageAccess<Storage = FidlStorage>,
362 L: DefaultLoader<Result = T::Data> + 'static,
363 {
364 self.initialize_storage_for_key_with_loader(
365 T::STORAGE_KEY,
366 Box::new(loader) as Box<dyn Any>,
367 )
368 .await;
369 Ok(())
370 }
371
372 async fn get_store(&self) -> Rc<FidlStorage> {
373 self.get_fidl_storage().await
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use serde::{Deserialize, Serialize};
381 use settings_storage::storage_factory::NoneT;
382
383 const VALUE0: i32 = 3;
384 const VALUE1: i32 = 33;
385
386 #[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
387 struct TestStruct {
388 value: i32,
389 }
390
391 impl DeviceStorageCompatible for TestStruct {
392 type Loader = NoneT;
393 const KEY: &'static str = "testkey";
394 }
395
396 impl Default for TestStruct {
397 fn default() -> Self {
398 TestStruct { value: VALUE0 }
399 }
400 }
401
402 #[fuchsia::test(allow_stalls = false)]
403 async fn test_in_memory_storage() {
404 let factory = InMemoryStorageFactory::new();
405 factory.initialize_storage::<TestStruct>().await;
406
407 let store_1 = factory.get_device_storage().await;
408 let store_2 = factory.get_device_storage().await;
409
410 let test_struct = TestStruct { value: VALUE0 };
412
413 test_write_propagation(store_1.clone(), store_2.clone(), test_struct).await;
415
416 let test_struct_2 = TestStruct { value: VALUE1 };
417 test_write_propagation(store_2.clone(), store_1.clone(), test_struct_2).await;
419 }
420
421 async fn test_write_propagation(
422 store_1: Rc<DeviceStorage>,
423 store_2: Rc<DeviceStorage>,
424 data: TestStruct,
425 ) {
426 assert!(store_1.write(&data).await.is_ok());
427
428 let retrieved_struct = store_2.get::<TestStruct>().await;
430 assert_eq!(data, retrieved_struct);
431 }
432}