| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! API endpoint library for the TEE apps to communicate with Trichechus. |
| |
| use std::borrow::{Borrow, BorrowMut}; |
| use std::collections::HashMap; |
| use std::fmt::{self, Debug, Display}; |
| use std::marker::PhantomData; |
| |
| use libsirenia::storage::{self, Storable, Storage}; |
| |
| #[derive(Debug)] |
| pub enum Error { |
| /// Error reading data from storage |
| ReadData(storage::Error), |
| /// Error writing data to storage |
| WriteData(storage::Error), |
| } |
| |
| impl Display for Error { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| use self::Error::*; |
| |
| match self { |
| ReadData(e) => write!(f, "Problem reading data from storage: {}", e), |
| WriteData(e) => write!(f, "Problem writing data to storage: {}", e), |
| } |
| } |
| } |
| |
| /// The result of an operation in this crate. |
| pub type Result<T> = std::result::Result<T, storage::Error>; |
| |
| /// Represents some scoped data temporarily loaded from the backing store. |
| pub struct ScopedData<S: Storable, T: Storage, R: BorrowMut<T>> { |
| identifier: String, |
| data: S, |
| storage: R, |
| storage_phantom: PhantomData<*const T>, |
| } |
| |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> AsRef<S> for ScopedData<S, T, R> { |
| fn as_ref(&self) -> &S { |
| self.borrow() |
| } |
| } |
| |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> AsMut<S> for ScopedData<S, T, R> { |
| fn as_mut(&mut self) -> &mut S { |
| self.borrow_mut() |
| } |
| } |
| |
| /// Borrows the data read-only. |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> Borrow<S> for ScopedData<S, T, R> { |
| fn borrow(&self) -> &S { |
| &self.data |
| } |
| } |
| |
| /// Borrows a mutable reference to the data (the ScopedData must be |
| /// constructed read-write). |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> BorrowMut<S> for ScopedData<S, T, R> { |
| fn borrow_mut(&mut self) -> &mut S { |
| &mut self.data |
| } |
| } |
| |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> Drop for ScopedData<S, T, R> { |
| fn drop(&mut self) { |
| // TODO: Figure out how we want to handle errors on storing. |
| // We might want to log failures, but not necessarily crash. This will |
| // require us to bind mount the log into the sandbox though |
| // (which we should probably do anyway). |
| // |
| // One option would be set a callback. We could provide some standard |
| // callbacks like unwrap and log. |
| self.storage |
| .borrow_mut() |
| .write_data(&self.identifier, &self.data) |
| .unwrap(); |
| } |
| } |
| |
| /// Reads the data into itself then writes back on a flush. |
| impl<S: Storable, T: Storage, R: BorrowMut<T>> ScopedData<S, T, R> { |
| /// Creates and returns a new scoped data. Attempts to read the value of |
| /// the id from the backing store and uses the passed in closure to |
| /// determine the default value if the id is not found. |
| pub fn new(mut storage: R, identifier: &str, f: fn(&str) -> S) -> Result<Self> { |
| match storage.borrow_mut().read_data(identifier) { |
| Ok(data) => { |
| let id = identifier.to_string(); |
| Ok(ScopedData { |
| identifier: id, |
| data, |
| storage, |
| storage_phantom: PhantomData, |
| }) |
| } |
| Err(storage::Error::IdNotFound(_)) => { |
| let data = f(identifier); |
| let id = identifier.to_string(); |
| Ok(ScopedData { |
| identifier: id, |
| data, |
| storage, |
| storage_phantom: PhantomData, |
| }) |
| } |
| Err(e) => Err(e), |
| } |
| } |
| |
| /// Write the data back out to the backing store. |
| pub fn flush(&mut self) -> Result<()> { |
| self.storage |
| .borrow_mut() |
| .write_data(&self.identifier, &self.data) |
| .unwrap(); |
| Ok(()) |
| } |
| } |
| |
| /// A helper type for when the storage implementation doesn't need to be shared. See ScopedData. |
| pub type ExclusiveScopedData<'a, S, T> = ScopedData<S, T, &'a mut T>; |
| |
| /// Represents an entire key value store for one identifier. |
| pub type ScopedKeyValueStore<S, T, R> = ScopedData<HashMap<String, S>, T, R>; |
| |
| /// A helper type for when the storage implementation doesn't need to be shared. |
| /// See ScopedKeyValueStore. |
| pub type ExclusiveScopedKeyValueStore<'a, S, T> = ScopedKeyValueStore<S, T, &'a mut T>; |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use std::time::{SystemTime, UNIX_EPOCH}; |
| use storage::StorableMember; |
| |
| const TEST_ID: &str = "id"; |
| |
| struct MockStorage { |
| map: HashMap<String, StorableMember>, |
| } |
| |
| impl MockStorage { |
| fn new() -> Self { |
| let map = HashMap::new(); |
| MockStorage { map } |
| } |
| } |
| |
| impl Storage for MockStorage { |
| fn read_data<S: Storable>(&mut self, id: &str) -> Result<S> { |
| match self.map.get(id) { |
| Some(val) => { |
| let data = val.try_borrow::<S>().unwrap(); |
| Ok(data.to_owned()) |
| } |
| None => Err(storage::Error::IdNotFound(id.to_string())), |
| } |
| } |
| |
| fn write_data<S: Storable>(&mut self, id: &str, data: &S) -> Result<()> { |
| let store_data = StorableMember::new_deserialized::<S>(data.to_owned()); |
| self.map.insert(id.to_string(), store_data); |
| Ok(()) |
| } |
| } |
| |
| fn get_test_value() -> String { |
| SystemTime::now() |
| .duration_since(UNIX_EPOCH) |
| .unwrap() |
| .as_secs() |
| .to_string() |
| } |
| |
| fn setup_test_case(write_back: bool) -> (MockStorage, String, String) { |
| let mut store = MockStorage::new(); |
| let id = TEST_ID; |
| let s = get_test_value(); |
| if write_back { |
| store.write_data::<String>(&id, &s).unwrap(); |
| } |
| (store, id.to_string(), s) |
| } |
| |
| fn callback_id_not_found(_s: &str) -> String { |
| "Could not find id".to_string() |
| } |
| |
| fn callback_id_found(_s: &str) -> String { |
| unreachable!("This callback should not be called because the id was found in the store.") |
| } |
| |
| #[test] |
| fn write_and_read() { |
| let (mut store, id, s) = setup_test_case(/* write back */ true); |
| assert_eq!(s, store.read_data::<String>(&id).unwrap()); |
| } |
| |
| #[test] |
| fn read_id_not_found() { |
| let mut store = MockStorage::new(); |
| assert_eq!( |
| storage::Error::IdNotFound(TEST_ID.to_string()).to_string(), |
| store.read_data::<String>(TEST_ID).unwrap_err().to_string() |
| ); |
| } |
| |
| #[test] |
| fn make_new_scoped_data() { |
| let (mut store, id, _s) = setup_test_case(/* write back */ false); |
| let data: ExclusiveScopedData<String, MockStorage> = |
| ScopedData::new(&mut store, &id, callback_id_not_found).unwrap(); |
| let res: &String = data.borrow(); |
| assert_eq!("Could not find id", res); |
| } |
| |
| #[test] |
| fn make_existing_scoped_data() { |
| let (mut store, id, s) = setup_test_case(/* write back */ true); |
| |
| let data: ExclusiveScopedData<String, MockStorage> = |
| ScopedData::new(&mut store, &id, callback_id_found).unwrap(); |
| let res: &String = data.borrow(); |
| assert_eq!(&s, res); |
| } |
| |
| #[test] |
| fn mut_and_drop_scoped_data() { |
| let (mut store, id, mut s) = setup_test_case(/* write back */ true); |
| |
| { |
| let mut data: ExclusiveScopedData<String, MockStorage> = |
| ScopedData::new(&mut store, &id, callback_id_found).unwrap(); |
| let res: &mut String = data.borrow_mut(); |
| res.push_str(" New"); |
| } |
| s.push_str(" New"); |
| |
| assert_eq!(s, store.read_data::<String>(&id).unwrap()); |
| } |
| |
| #[test] |
| fn mut_and_flush_scoped_data() { |
| let (mut store, id, mut s) = setup_test_case(/* write back */ true); |
| |
| { |
| let mut data: ExclusiveScopedData<String, MockStorage> = |
| ScopedData::new(&mut store, &id, callback_id_found).unwrap(); |
| let res: &mut String = data.borrow_mut(); |
| res.push_str(" New"); |
| data.flush().unwrap(); |
| } |
| s.push_str(" New"); |
| |
| assert_eq!(s, store.read_data::<String>(&id).unwrap()); |
| } |
| |
| #[test] |
| fn mut_and_drop_kvstore() { |
| let mut store = MockStorage::new(); |
| let id = "id"; |
| let map = HashMap::new(); |
| store |
| .write_data::<HashMap<String, String>>(&id, &map) |
| .unwrap(); |
| |
| { |
| let fun = |_h: &str| panic!(); |
| let key = "key"; |
| let value = "value"; |
| let mut kvstore: ExclusiveScopedKeyValueStore<String, MockStorage> = |
| ScopedKeyValueStore::new(&mut store, &id, fun).unwrap(); |
| kvstore.as_mut().insert(key.to_string(), value.to_string()); |
| assert!(kvstore.as_mut().contains_key(key)); |
| } |
| |
| let res_map = store.read_data::<HashMap<String, String>>(&id).unwrap(); |
| assert!(res_map.contains_key("key")); |
| assert_eq!("value", res_map.get("key").unwrap()) |
| } |
| } |