blob: 9976f1436489879b8aba403a794712e1d04c0602 [file] [log] [blame]
// 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())
}
}