blob: 49ba3108abdfd1735767436a986771397788d9ab [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.
//! Defines the messages and abstracts out communication for storage between
//! TEE apps, Trichechus, and Dugong.
use std::sync::Arc;
use libsirenia::communication::persistence::Status;
use libsirenia::communication::tee_api::TeeApi;
use libsirenia::communication::tee_api::TeeApiClient;
use libsirenia::storage::to_read_data_error;
use libsirenia::storage::to_remove_error;
use libsirenia::storage::to_write_data_error;
use libsirenia::storage::Error;
use libsirenia::storage::Result;
use libsirenia::storage::Storage;
use libsirenia::transport::Transport;
use sync::Mutex;
use crate::rpc_handle;
/// Holds the rpc client for the specific instance of the TEE App.
#[derive(Clone)]
pub struct TrichechusStorage {
rpc: Arc<Mutex<TeeApiClient>>,
}
impl TrichechusStorage {
/// Initialize the storage related middleware.
///
/// This uses the TEE API over RPC which is protected by a mutex so keep that in mind when
/// planning deadlock avoidance.
pub fn new() -> Self {
TrichechusStorage { rpc: rpc_handle() }
}
}
impl Default for TrichechusStorage {
fn default() -> Self {
Self::new()
}
}
impl From<Transport> for TrichechusStorage {
fn from(transport: Transport) -> Self {
TrichechusStorage {
rpc: Arc::new(Mutex::new(TeeApiClient::new(transport))),
}
}
}
impl Storage for TrichechusStorage {
/// Read without deserializing.
fn read_raw(&mut self, id: &str) -> Result<Vec<u8>> {
// TODO: Log the rpc error.
match self.rpc.lock().read_data(id.to_string()) {
Ok((Status::Success, res)) => Ok(res),
Ok((Status::IdNotFound, _)) => Err(Error::IdNotFound(id.to_string())),
Ok((Status::Failure, _)) | Ok((Status::CryptoFailure, _)) => Err(Error::ReadData(None)),
Err(err) => Err(to_read_data_error(err)),
}
}
fn remove(&mut self, id: &str) -> Result<()> {
match self.rpc.lock().remove(id.to_string()) {
Ok(Status::Success) => Ok(()),
Ok(_) => Err(Error::Remove(None)),
Err(err) => Err(to_remove_error(err)),
}
}
/// Write without serializing.
fn write_raw(&mut self, id: &str, data: &[u8]) -> Result<()> {
match self.rpc.lock().write_data(id.to_string(), data.to_vec()) {
Ok(Status::Success) => Ok(()),
Ok(_) => Err(Error::WriteData(None)),
Err(err) => Err(to_write_data_error(err)),
}
}
}
#[cfg(test)]
pub mod tests {
use std::cell::RefCell;
use std::collections::BTreeMap as Map;
use std::rc::Rc;
use std::thread::spawn;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use anyhow::anyhow;
use anyhow::Error;
use anyhow::Result;
use assert_matches::assert_matches;
use libsirenia::communication::tee_api::TeeApiServer;
use libsirenia::rpc::RpcDispatcher;
use libsirenia::storage::Error as StorageError;
use libsirenia::transport::create_transport_from_pipes;
use super::*;
const TEST_ID: &str = "id";
#[derive(Clone)]
struct TeeApiServerImpl {
map: Rc<RefCell<Map<String, Vec<u8>>>>,
}
impl TeeApi<Error> for TeeApiServerImpl {
fn read_data(&mut self, id: String) -> Result<(Status, Vec<u8>)> {
match self.map.borrow().get(&id) {
Some(val) => Ok((Status::Success, val.to_vec())),
None => Err(anyhow!("id missing")),
}
}
fn remove(&mut self, id: String) -> Result<Status> {
Ok(match self.map.borrow_mut().remove(&id) {
Some(_) => Status::Success,
None => Status::IdNotFound,
})
}
fn write_data(&mut self, id: String, data: Vec<u8>) -> Result<Status> {
self.map.borrow_mut().insert(id, data);
Ok(Status::Success)
}
}
fn get_test_value() -> String {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string()
}
fn setup() -> (RpcDispatcher<Box<dyn TeeApiServer>>, TrichechusStorage) {
let (server_transport, client_transport) = create_transport_from_pipes().unwrap();
let handler: Box<dyn TeeApiServer> = Box::new(TeeApiServerImpl {
map: Rc::new(RefCell::new(Map::new())),
});
let dispatcher = RpcDispatcher::new(handler, server_transport);
(dispatcher, TrichechusStorage::from(client_transport))
}
#[test]
fn write_and_read() {
let (mut dispatcher, mut trichechus_storage) = setup();
let client_thread = spawn(move || {
let data = get_test_value();
trichechus_storage.write_data(TEST_ID, &data).unwrap();
let retrieved_data = trichechus_storage.read_data::<String>(TEST_ID).unwrap();
assert_eq!(retrieved_data, data);
});
let sleep_for = None;
assert_matches!(dispatcher.read_complete_message(sleep_for), Ok(None));
assert_matches!(dispatcher.read_complete_message(sleep_for), Ok(None));
client_thread.join().unwrap();
}
#[test]
fn read_id_not_found() {
let (mut dispatcher, mut trichechus_storage) = setup();
let client_thread = spawn(move || {
let error = trichechus_storage.read_data::<String>(TEST_ID).unwrap_err();
println!("Client thread: {}", error);
assert_matches!(error, StorageError::ReadData(_));
});
let sleep_for = None;
assert_matches!(dispatcher.read_complete_message(sleep_for), Ok(Some(_)));
// Explicitly call drop to close the pipe so the client thread gets the hang up since the return
// value should be a RemoveFd mutator.
drop(dispatcher);
client_thread.join().unwrap();
}
}