Skip to content
This repository has been archived by the owner on May 9, 2022. It is now read-only.

Commit

Permalink
feat(rtc_tenclave): add filesystem-based FsStore and SgxFsStore
Browse files Browse the repository at this point in the history
  • Loading branch information
PiDelport committed May 18, 2021
1 parent 4c9e212 commit 6e73119
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 9 deletions.
114 changes: 114 additions & 0 deletions rtc_tenclave/src/kv_store/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! [`KvStore`] implementation based on [`fs`]

#[cfg(not(test))]
use std::prelude::v1::*;

use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use serde::de::DeserializeOwned;
use serde::Serialize;

#[cfg(not(test))]
use std::untrusted::{fs, fs::File};
#[cfg(test)]
use std::{fs, fs::File};

use super::{KvStore, StoreResult};

/// Filesystem-based [`KvStore`]
pub struct FsStore {
pub(crate) root_dir: PathBuf,
}

impl FsStore {
/// Validate that `root_dir` exists as a directory.
#[cfg_attr(not(test), allow(dead_code))]
pub fn new(root: impl AsRef<Path>) -> StoreResult<Self> {
let root = root.as_ref();

fs::create_dir_all(root)
.map_err(|err| format!("FsStore: create_dir_all({:?}) failed: {:?}", root, err))?;

Ok(FsStore {
root_dir: root.to_path_buf(),
})
}

/// Resolve file name for the value of `key`.
fn value_path(&self, key: &str) -> PathBuf {
// XXX: Escaping / encoding?
let file_name = Self::encode_key(key);
self.root_dir.join(file_name)
}

pub(crate) fn encode_key(key: &str) -> String {
let encoded = hex::encode(key);
format!("x{}", encoded)
}

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn decode_key(file_name: &str) -> StoreResult<String> {
let encoded: &str = file_name
.strip_prefix("x")
.ok_or_else(|| format!("FsStore::decode_key: missing x prefix for {:?}", file_name))?;
// FIXME: Dodgy err.to_string()
let bytes: Vec<u8> = hex::decode(encoded).map_err(|err| err.to_string())?;
String::from_utf8(bytes).map_err(|err| err.into())
}
}

impl<V> KvStore<V> for FsStore
where
V: Serialize + DeserializeOwned,
{
fn load(&self, key: &str) -> StoreResult<Option<V>> {
let value_file_name = self.value_path(key);

// TODO: Handle NotFound
let value_file = File::open(&value_file_name)
.map_err(|err| format!("FsStore: open {:?} failed: {}", value_file_name, err))?;

// Note: Read all the data into memory first, then deserialize, for efficiency.
// See the docs for [`serde_json::de::from_reader`],
// and https://github.com/serde-rs/json/issues/160
let serialised: Vec<u8> = read_all(value_file)
.map_err(|err| format!("FsStore: read from {:?} failed: {}", value_file_name, err))?;

let deserialized: V = serde_json::from_slice(serialised.as_slice())?;
Ok(Some(deserialized))
}

fn save(&mut self, key: &str, value: V) -> StoreResult<()> {
let serialized: Vec<u8> = serde_json::to_vec(&value)?;

let value_file_name = self.value_path(key);

let mut value_file = File::create(&value_file_name)
.map_err(|err| format!("open {:?} failed: {}", value_file_name, err))?;

value_file.write_all(serialized.as_slice()).map_err(|err| {
format!(
"FsStore: write_all to {:?} failed: {}",
value_file_name, err
)
})?;
Ok(())
}
}

/// Helper: Like [`fs::read`], but take an open file.
fn read_all(mut file: File) -> io::Result<Vec<u8>> {
let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
file.read_to_end(&mut bytes)?;
Ok(bytes)
}

/// Indicates how large a buffer to pre-allocate before reading the entire file.
fn initial_buffer_size(file: &File) -> usize {
// Allocate one extra byte so the buffer doesn't need to grow before the
// final `read` call at the end of the file. Don't worry about `usize`
// overflow because reading will fail regardless in that case.
file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
}
55 changes: 50 additions & 5 deletions rtc_tenclave/src/kv_store/inspect.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
//! Support for inspecting [`KvStore`] instances (for testing and debugging)

use serde::de::DeserializeOwned;
use serde::Serialize;
#[cfg(not(test))]
use std::prelude::v1::*;

use std::borrow::ToOwned;
use std::collections::HashMap;
use std::iter::Iterator;

use serde::de::DeserializeOwned;
use serde::Serialize;

use super::in_memory::{InMemoryJsonStore, InMemoryStore};
use super::KvStore;
Expand All @@ -30,10 +33,52 @@ where
self.map
.keys()
.map(|k| {
let loaded: Option<V> = self.load(&k).expect(&format!("load {:?} failed!", k));
let v: V = loaded.expect(&format!("key missing! {:?}", k));
let loaded: Option<V> = self
.load(k)
.unwrap_or_else(|_| panic!("load {:?} failed!", k));
let v: V = loaded.unwrap_or_else(|| panic!("key missing! {:?}", k));
(k.to_owned(), v)
})
.collect()
}
}

// sgx_tstd (v1.1.3) does not support `fs::read_dir`, so limit the following to tests, for now.
//
// See: https://github.com/apache/incubator-teaclave-sgx-sdk/blob/v1.1.3/release_notes.md#partially-supported-modstraits-in-sgx_tstd

#[cfg(test)]
use std::{ffi::OsStr, fs::DirEntry, io, iter::Iterator};

#[cfg(test)]
use super::fs::FsStore;

#[cfg(test)]
impl<V> InspectStore<V> for FsStore
where
V: Serialize + DeserializeOwned,
{
fn as_map(&self) -> HashMap<String, V> {
let entries: impl Iterator<Item = io::Result<DirEntry>> = self
.root_dir
.read_dir()
.expect(&format!("read_dir {:?} failed", self.root_dir));

let keys: impl Iterator<Item = String> = entries.map(|entry: io::Result<DirEntry>| {
let entry: DirEntry = entry.expect("read_dir entry failed");
let file_path = entry.path();
let os_file_name: &OsStr = file_path
.file_name()
.expect(&format!("directory entry lacks file_name: {:?}", file_path));
let file_name: &str = os_file_name.to_str().expect("OsStr.to_str failed");
FsStore::decode_key(file_name).expect("FsStore::decode_key failed")
});

keys.map(|k| {
let loaded: Option<V> = self.load(&k).expect(&format!("load {:?} failed!", k));
let v: V = loaded.expect(&format!("key missing! {:?}", k));
(k, v)
})
.collect()
}
}
6 changes: 3 additions & 3 deletions rtc_tenclave/src/kv_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ pub trait KvStore<V> {
// TODO: add update()
}

#[cfg(test)]
mod fs;
mod in_memory;

#[cfg(test)]
mod inspect;
#[cfg(not(test))]
mod sgxfs;

#[cfg(test)]
mod tests;
102 changes: 102 additions & 0 deletions rtc_tenclave/src/kv_store/sgxfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! [`KvStore`] implementation based on [`sgx_tstd::sgxfs`] (using the Intel SGX Protected FS Library)

use std::prelude::v1::*;

use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use serde::de::DeserializeOwned;
use serde::Serialize;
use sgx_tstd::sgxfs::SgxFile;

use super::{KvStore, StoreResult};

/// Filesystem-based [`KvStore`], using [`SgxFile`]
///
/// TODO: Document security guarantees.
///
struct SgxFsStore {
pub(crate) root_dir: PathBuf,
}

impl SgxFsStore {
/// Validate that `root_dir` exists as a directory.
pub fn new(root: impl AsRef<Path>) -> StoreResult<Self> {
let root = root.as_ref();

// XXX: no create_dir_all()

Ok(SgxFsStore {
root_dir: root.to_path_buf(),
})
}

/// Resolve file name for the value of `key`.
fn value_path(&self, key: &str) -> PathBuf {
// XXX: Escaping / encoding?
let file_name = Self::encode_key(key);
self.root_dir.join(file_name)
}

pub(crate) fn encode_key(key: &str) -> String {
let encoded = hex::encode(key);
format!("x{}", encoded)
}
pub(crate) fn decode_key(file_name: &str) -> StoreResult<String> {
let encoded: &str = file_name
.strip_prefix("x")
.ok_or_else(|| format!("FsStore::decode_key: missing x prefix for {:?}", file_name))?;
// FIXME: Dodgy err.to_string()
let bytes: Vec<u8> = hex::decode(encoded).map_err(|err| err.to_string())?;
String::from_utf8(bytes).map_err(|err| err.into())
}
}

impl<V> KvStore<V> for SgxFsStore
where
V: Serialize + DeserializeOwned,
{
fn load(&self, key: &str) -> StoreResult<Option<V>> {
let value_file_name = self.value_path(key);

// TODO: Handle NotFound
// TODO: open_ex() with key
let value_file = SgxFile::open(&value_file_name)
.map_err(|err| format!("FsStore: open {:?} failed: {}", value_file_name, err))?;

// Note: Read all the data into memory first, then deserialize, for efficiency.
// See the docs for [`serde_json::de::from_reader`],
// and https://github.com/serde-rs/json/issues/160
let serialised: Vec<u8> = read_all(value_file)
.map_err(|err| format!("FsStore: read from {:?} failed: {}", value_file_name, err))?;

let deserialized: V = serde_json::from_slice(serialised.as_slice())?;
Ok(Some(deserialized))
}

fn save(&mut self, key: &str, value: V) -> StoreResult<()> {
let serialized: Vec<u8> = serde_json::to_vec(&value)?;

let value_file_name = self.value_path(key);

let mut value_file = SgxFile::create(&value_file_name)
.map_err(|err| format!("open {:?} failed: {}", value_file_name, err))?;

value_file.write_all(serialized.as_slice()).map_err(|err| {
format!(
"FsStore: write_all to {:?} failed: {}",
value_file_name, err
)
})?;
Ok(())
}
}

/// Helper: Like [`fs::read`], but take an open file.
fn read_all(mut file: SgxFile) -> io::Result<Vec<u8>> {
// XXX: No metadata for initial_buffer_size in sgxfs
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(bytes)
}
30 changes: 29 additions & 1 deletion rtc_tenclave/src/kv_store/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
//! Tests for [`rtc_tenclave::kv_store`]

#[cfg(not(test))]
use std::prelude::v1::*;

use std::collections::HashMap;
use std::fs::remove_dir_all;
use std::path::Path;

use proptest::prelude::*;

use super::fs::FsStore;
use super::in_memory::{InMemoryJsonStore, InMemoryStore};
use super::inspect::InspectStore;
use super::KvStore;
Expand All @@ -23,18 +29,40 @@ fn prop_store_ops_match_model() {
.prop_shuffle()
};

// XXX: hacky clearing
pub fn clear_dir(path: &Path) {
if path.is_dir() {
remove_dir_all(path).expect("remove_dir_all failed");
};
}

proptest!(|(store_ops_vec in store_ops_strategy)| {
// FIXME: This value type parameter needs better handling.
type V = String;

// Init the models
let mut store_model: InMemoryStore<V> = InMemoryStore::default();
let mut store_model_json: InMemoryJsonStore = InMemoryJsonStore::default();
// TODO: FS-based store

// Init the store under test
let path = Path::new("store_test");
clear_dir(path); // Clear before each test
let mut store_fs: FsStore = FsStore::new(path).expect("FsStore::new failed");

for (k, v) in store_ops_vec {
store_model.save(&k, v.clone()).expect("InMemoryStore save failed!");
store_model_json.save(&k, v.clone()).expect("InMemoryJsonStore save failed!");
store_fs.save(&k, v.clone()).expect("FsStore save failed!");

// Models match each other
prop_assert_eq!(store_model.as_map(), store_model_json.as_map());
// Models match store_fs
prop_assert_eq!(store_model.as_map(), store_fs.as_map());
// FIXME: explicit coercion for as_map()
prop_assert_eq!(store_model_json.as_map() as HashMap<String, V>, store_fs.as_map());
}

clear_dir(path); // Clear after successful tests, just to keep the workdir clean
});
}

Expand Down
1 change: 1 addition & 0 deletions rtc_tenclave/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#![feature(const_evaluatable_checked)]
#![deny(clippy::mem_forget)]
#![cfg_attr(not(test), no_std)]
#![feature(impl_trait_in_bindings)]

#[cfg(not(test))]
#[macro_use]
Expand Down

0 comments on commit 6e73119

Please sign in to comment.