omaha_client/storage.rs
1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use crate::time::system_time_conversion::{
10 checked_system_time_to_micros_from_epoch, micros_from_epoch_to_system_time,
11};
12use futures::future::{BoxFuture, FutureExt, TryFutureExt};
13use log::error;
14use std::time::SystemTime;
15
16mod memory;
17pub use memory::MemStorage;
18
19#[cfg(test)]
20mod stub;
21#[cfg(test)]
22pub use stub::StubStorage;
23
24/// The Storage trait is used to access typed key=value storage, for persisting protocol state and
25/// other data between runs of the update check process.
26///
27/// Implementations of this trait should cache values until commit() is called, and then perform
28/// an atomic committing of all outstanding value changes. On a given instance of Storage, a
29/// get() following a set(), but before a commit() should also return the set() value (not the
30/// previous value).
31///
32/// However, the expected usage of this trait within the library is to perform a series of get()'s
33/// at startup, and then only set()+commit() after that. The expectation being that this is being
34/// used to persist state that needs to be maintained for continuity over a reboot (or power
35/// outage).
36///
37/// A note on using the wrong type with a key: the result should be as if there is no value for
38/// the key. This is so that the Result::uwrap_or(<...default...>) pattern will work.
39///
40/// ```
41/// # futures::executor::block_on(async {
42/// use omaha_client::storage::{MemStorage, Storage};
43/// use omaha_client::unless::Unless;
44/// let mut storage = MemStorage::new();
45/// storage.set_int("key", 345);
46///
47/// // value should be None:
48/// let value: Option<String> = storage.get_string("key").await;
49/// assert_eq!(None, value);
50///
51///
52/// // value should be "default":
53/// let value: String = storage.get_string("key").await.unwrap_or("default".to_string());
54/// assert_eq!("default", value);
55/// # });
56/// ```
57pub trait Storage {
58 type Error: std::error::Error;
59
60 /// Get a string from the backing store. Returns None if there is no value for the given key,
61 /// or if the value for the key has a different type.
62 fn get_string<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<String>>;
63
64 /// Get an int from the backing store. Returns None if there is no value for the given key,
65 /// or if the value for the key has a different type.
66 fn get_int<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<i64>>;
67
68 /// Get a boolean from the backing store. Returns None if there is no value for the given key,
69 /// or if the value for the key has a different type.
70 fn get_bool<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<bool>>;
71
72 /// Set a value to be stored in the backing store. The implementation should cache the value
73 /// until the |commit()| fn is called, and then persist all cached values at that time.
74 fn set_string<'a>(
75 &'a mut self,
76 key: &'a str,
77 value: &'a str,
78 ) -> BoxFuture<'a, Result<(), Self::Error>>;
79
80 /// Set a value to be stored in the backing store. The implementation should cache the value
81 /// until the |commit()| fn is called, and then persist all cached values at that time.
82 fn set_int<'a>(
83 &'a mut self,
84 key: &'a str,
85 value: i64,
86 ) -> BoxFuture<'a, Result<(), Self::Error>>;
87
88 /// Set a value to be stored in the backing store. The implementation should cache the value
89 /// until the |commit()| fn is called, and then persist all cached values at that time.
90 fn set_bool<'a>(
91 &'a mut self,
92 key: &'a str,
93 value: bool,
94 ) -> BoxFuture<'a, Result<(), Self::Error>>;
95
96 /// Remove the value for |key| from the backing store. The implementation should cache that
97 /// the value has been removed until the |commit()| fn is called, and then persist all changes
98 /// at that time.
99 ///
100 /// If there is no value for the key, this should return without error.
101 fn remove<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, Result<(), Self::Error>>;
102
103 /// Persist all cached values to storage.
104 fn commit(&mut self) -> BoxFuture<'_, Result<(), Self::Error>>;
105}
106
107/// Extension trait that adds some features to Storage that can be implemented using the base
108/// `Storage` implementation.
109pub trait StorageExt: Storage {
110 /// Set an Option<i64> to be stored in the backing store. The implementation should cache the
111 /// value until the |commit()| fn is called, and then persist all cached values at that time.
112 /// If the Option is None, the implementation should call |remove()| for the key.
113 fn set_option_int<'a>(
114 &'a mut self,
115 key: &'a str,
116 value: Option<i64>,
117 ) -> BoxFuture<'a, Result<(), Self::Error>> {
118 match value {
119 Some(value) => self.set_int(key, value),
120 None => self.remove(key),
121 }
122 }
123
124 /// Get a SystemTime from the backing store. Returns None if there is no value for the given
125 /// key, or if the value for the key has a different type.
126 fn get_time<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Option<SystemTime>> {
127 self.get_int(key)
128 .map(|option| option.map(micros_from_epoch_to_system_time))
129 .boxed()
130 }
131
132 /// Set a SystemTime to be stored in the backing store. The implementation should cache the
133 /// value until the |commit()| fn is called, and then persist all cached values at that time.
134 /// Note that submicrosecond will be dropped.
135 fn set_time<'a>(
136 &'a mut self,
137 key: &'a str,
138 value: impl Into<SystemTime>,
139 ) -> BoxFuture<'a, Result<(), Self::Error>> {
140 self.set_option_int(key, checked_system_time_to_micros_from_epoch(value.into()))
141 }
142
143 /// Remove the value for |key| from the backing store, log an error message on error.
144 fn remove_or_log<'a>(&'a mut self, key: &'a str) -> BoxFuture<'a, ()> {
145 self.remove(key)
146 .unwrap_or_else(move |e| error!("Unable to remove {}: {}", key, e))
147 .boxed()
148 }
149
150 /// Persist all cached values to storage, log an error message on error.
151 fn commit_or_log(&mut self) -> BoxFuture<'_, ()> {
152 self.commit()
153 .unwrap_or_else(|e| error!("Unable to commit persisted data: {}", e))
154 .boxed()
155 }
156}
157
158impl<T> StorageExt for T where T: Storage {}
159
160/// The storage::tests module contains test vectors that implementations of the Storage trait should
161/// pass. These can be called with a Storage implementation as part of a test.
162///
163/// These are public so that implementors of the Storage trait (in other libraries or binaries) can
164/// call them.
165pub mod tests {
166 use super::*;
167 use pretty_assertions::assert_eq;
168 use std::time::Duration;
169
170 /// These are tests for verifying that a given Storage implementation acts as expected.
171 /// Test that the implementation stores, retrieves, and clears String values correctly.
172 pub async fn do_test_set_get_remove_string<S: Storage>(storage: &mut S) {
173 assert_eq!(None, storage.get_string("some key").await);
174
175 storage.set_string("some key", "some value").await.unwrap();
176 storage.commit().await.unwrap();
177 assert_eq!(
178 Some("some value".to_string()),
179 storage.get_string("some key").await
180 );
181
182 storage
183 .set_string("some key", "some other value")
184 .await
185 .unwrap();
186 storage.commit().await.unwrap();
187 assert_eq!(
188 Some("some other value".to_string()),
189 storage.get_string("some key").await
190 );
191
192 storage.remove("some key").await.unwrap();
193 storage.commit().await.unwrap();
194 assert_eq!(None, storage.get_string("some key").await);
195 }
196
197 /// Test that the implementation stores, retrieves, and clears int values correctly.
198 pub async fn do_test_set_get_remove_int<S: Storage>(storage: &mut S) {
199 assert_eq!(None, storage.get_int("some int key").await);
200
201 storage.set_int("some int key", 42).await.unwrap();
202 storage.commit().await.unwrap();
203 assert_eq!(Some(42), storage.get_int("some int key").await);
204
205 storage.set_int("some int key", 1).await.unwrap();
206 storage.commit().await.unwrap();
207 assert_eq!(Some(1), storage.get_int("some int key").await);
208
209 storage.remove("some int key").await.unwrap();
210 storage.commit().await.unwrap();
211 assert_eq!(None, storage.get_int("some int key").await);
212 }
213
214 /// Test that the implementation stores, retrieves, and clears Option<i64> values correctly.
215 pub async fn do_test_set_option_int<S: Storage>(storage: &mut S) {
216 assert_eq!(None, storage.get_int("some int key").await);
217
218 storage
219 .set_option_int("some int key", Some(42))
220 .await
221 .unwrap();
222 storage.commit().await.unwrap();
223 assert_eq!(Some(42), storage.get_int("some int key").await);
224
225 storage.set_option_int("some int key", None).await.unwrap();
226 storage.commit().await.unwrap();
227 assert_eq!(None, storage.get_int("some int key").await);
228 }
229
230 /// Test that the implementation stores, retrieves, and clears bool values correctly.
231 pub async fn do_test_set_get_remove_bool<S: Storage>(storage: &mut S) {
232 assert_eq!(None, storage.get_bool("some bool key").await);
233
234 storage.set_bool("some bool key", false).await.unwrap();
235 storage.commit().await.unwrap();
236 assert_eq!(Some(false), storage.get_bool("some bool key").await);
237
238 storage.set_bool("some bool key", true).await.unwrap();
239 storage.commit().await.unwrap();
240 assert_eq!(Some(true), storage.get_bool("some bool key").await);
241
242 storage.remove("some bool key").await.unwrap();
243 storage.commit().await.unwrap();
244 assert_eq!(None, storage.get_bool("some bool key").await);
245 }
246
247 /// Test that the implementation stores, retrieves, and clears bool values correctly.
248 pub async fn do_test_set_get_remove_time<S: Storage>(storage: &mut S) {
249 assert_eq!(None, storage.get_time("some time key").await);
250
251 storage
252 .set_time("some time key", SystemTime::UNIX_EPOCH)
253 .await
254 .unwrap();
255 storage.commit().await.unwrap();
256 assert_eq!(
257 Some(SystemTime::UNIX_EPOCH),
258 storage.get_time("some time key").await
259 );
260
261 let time = SystemTime::UNIX_EPOCH + Duration::from_secs(1234);
262 storage.set_time("some time key", time).await.unwrap();
263 storage.commit().await.unwrap();
264 assert_eq!(Some(time), storage.get_time("some time key").await);
265
266 storage.remove("some time key").await.unwrap();
267 storage.commit().await.unwrap();
268 assert_eq!(None, storage.get_time("some time key").await);
269 }
270
271 /// Test that the implementation returns None for a mismatch between value types
272 pub async fn do_return_none_for_wrong_value_type<S: Storage>(storage: &mut S) {
273 storage.set_int("some int key", 42).await.unwrap();
274 assert_eq!(None, storage.get_string("some int key").await);
275 }
276
277 /// Test that a remove of a non-existent key causes no errors
278 pub async fn do_ensure_no_error_remove_nonexistent_key<S: Storage>(storage: &mut S) {
279 storage.set_string("some key", "some value").await.unwrap();
280 storage.commit().await.unwrap();
281
282 storage.remove("some key").await.unwrap();
283 storage.remove("some key").await.unwrap();
284 }
285}