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

Commit 6e73119

Browse files
committed
feat(rtc_tenclave): add filesystem-based FsStore and SgxFsStore
1 parent 4c9e212 commit 6e73119

File tree

6 files changed

+299
-9
lines changed

6 files changed

+299
-9
lines changed

rtc_tenclave/src/kv_store/fs.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! [`KvStore`] implementation based on [`fs`]
2+
3+
#[cfg(not(test))]
4+
use std::prelude::v1::*;
5+
6+
use std::io;
7+
use std::io::prelude::*;
8+
use std::path::{Path, PathBuf};
9+
10+
use serde::de::DeserializeOwned;
11+
use serde::Serialize;
12+
13+
#[cfg(not(test))]
14+
use std::untrusted::{fs, fs::File};
15+
#[cfg(test)]
16+
use std::{fs, fs::File};
17+
18+
use super::{KvStore, StoreResult};
19+
20+
/// Filesystem-based [`KvStore`]
21+
pub struct FsStore {
22+
pub(crate) root_dir: PathBuf,
23+
}
24+
25+
impl FsStore {
26+
/// Validate that `root_dir` exists as a directory.
27+
#[cfg_attr(not(test), allow(dead_code))]
28+
pub fn new(root: impl AsRef<Path>) -> StoreResult<Self> {
29+
let root = root.as_ref();
30+
31+
fs::create_dir_all(root)
32+
.map_err(|err| format!("FsStore: create_dir_all({:?}) failed: {:?}", root, err))?;
33+
34+
Ok(FsStore {
35+
root_dir: root.to_path_buf(),
36+
})
37+
}
38+
39+
/// Resolve file name for the value of `key`.
40+
fn value_path(&self, key: &str) -> PathBuf {
41+
// XXX: Escaping / encoding?
42+
let file_name = Self::encode_key(key);
43+
self.root_dir.join(file_name)
44+
}
45+
46+
pub(crate) fn encode_key(key: &str) -> String {
47+
let encoded = hex::encode(key);
48+
format!("x{}", encoded)
49+
}
50+
51+
#[cfg_attr(not(test), allow(dead_code))]
52+
pub(crate) fn decode_key(file_name: &str) -> StoreResult<String> {
53+
let encoded: &str = file_name
54+
.strip_prefix("x")
55+
.ok_or_else(|| format!("FsStore::decode_key: missing x prefix for {:?}", file_name))?;
56+
// FIXME: Dodgy err.to_string()
57+
let bytes: Vec<u8> = hex::decode(encoded).map_err(|err| err.to_string())?;
58+
String::from_utf8(bytes).map_err(|err| err.into())
59+
}
60+
}
61+
62+
impl<V> KvStore<V> for FsStore
63+
where
64+
V: Serialize + DeserializeOwned,
65+
{
66+
fn load(&self, key: &str) -> StoreResult<Option<V>> {
67+
let value_file_name = self.value_path(key);
68+
69+
// TODO: Handle NotFound
70+
let value_file = File::open(&value_file_name)
71+
.map_err(|err| format!("FsStore: open {:?} failed: {}", value_file_name, err))?;
72+
73+
// Note: Read all the data into memory first, then deserialize, for efficiency.
74+
// See the docs for [`serde_json::de::from_reader`],
75+
// and https://github.com/serde-rs/json/issues/160
76+
let serialised: Vec<u8> = read_all(value_file)
77+
.map_err(|err| format!("FsStore: read from {:?} failed: {}", value_file_name, err))?;
78+
79+
let deserialized: V = serde_json::from_slice(serialised.as_slice())?;
80+
Ok(Some(deserialized))
81+
}
82+
83+
fn save(&mut self, key: &str, value: V) -> StoreResult<()> {
84+
let serialized: Vec<u8> = serde_json::to_vec(&value)?;
85+
86+
let value_file_name = self.value_path(key);
87+
88+
let mut value_file = File::create(&value_file_name)
89+
.map_err(|err| format!("open {:?} failed: {}", value_file_name, err))?;
90+
91+
value_file.write_all(serialized.as_slice()).map_err(|err| {
92+
format!(
93+
"FsStore: write_all to {:?} failed: {}",
94+
value_file_name, err
95+
)
96+
})?;
97+
Ok(())
98+
}
99+
}
100+
101+
/// Helper: Like [`fs::read`], but take an open file.
102+
fn read_all(mut file: File) -> io::Result<Vec<u8>> {
103+
let mut bytes = Vec::with_capacity(initial_buffer_size(&file));
104+
file.read_to_end(&mut bytes)?;
105+
Ok(bytes)
106+
}
107+
108+
/// Indicates how large a buffer to pre-allocate before reading the entire file.
109+
fn initial_buffer_size(file: &File) -> usize {
110+
// Allocate one extra byte so the buffer doesn't need to grow before the
111+
// final `read` call at the end of the file. Don't worry about `usize`
112+
// overflow because reading will fail regardless in that case.
113+
file.metadata().map(|m| m.len() as usize + 1).unwrap_or(0)
114+
}

rtc_tenclave/src/kv_store/inspect.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
//! Support for inspecting [`KvStore`] instances (for testing and debugging)
22
3-
use serde::de::DeserializeOwned;
4-
use serde::Serialize;
3+
#[cfg(not(test))]
4+
use std::prelude::v1::*;
5+
56
use std::borrow::ToOwned;
67
use std::collections::HashMap;
7-
use std::iter::Iterator;
8+
9+
use serde::de::DeserializeOwned;
10+
use serde::Serialize;
811

912
use super::in_memory::{InMemoryJsonStore, InMemoryStore};
1013
use super::KvStore;
@@ -30,10 +33,52 @@ where
3033
self.map
3134
.keys()
3235
.map(|k| {
33-
let loaded: Option<V> = self.load(&k).expect(&format!("load {:?} failed!", k));
34-
let v: V = loaded.expect(&format!("key missing! {:?}", k));
36+
let loaded: Option<V> = self
37+
.load(k)
38+
.unwrap_or_else(|_| panic!("load {:?} failed!", k));
39+
let v: V = loaded.unwrap_or_else(|| panic!("key missing! {:?}", k));
3540
(k.to_owned(), v)
3641
})
3742
.collect()
3843
}
3944
}
45+
46+
// sgx_tstd (v1.1.3) does not support `fs::read_dir`, so limit the following to tests, for now.
47+
//
48+
// See: https://github.com/apache/incubator-teaclave-sgx-sdk/blob/v1.1.3/release_notes.md#partially-supported-modstraits-in-sgx_tstd
49+
50+
#[cfg(test)]
51+
use std::{ffi::OsStr, fs::DirEntry, io, iter::Iterator};
52+
53+
#[cfg(test)]
54+
use super::fs::FsStore;
55+
56+
#[cfg(test)]
57+
impl<V> InspectStore<V> for FsStore
58+
where
59+
V: Serialize + DeserializeOwned,
60+
{
61+
fn as_map(&self) -> HashMap<String, V> {
62+
let entries: impl Iterator<Item = io::Result<DirEntry>> = self
63+
.root_dir
64+
.read_dir()
65+
.expect(&format!("read_dir {:?} failed", self.root_dir));
66+
67+
let keys: impl Iterator<Item = String> = entries.map(|entry: io::Result<DirEntry>| {
68+
let entry: DirEntry = entry.expect("read_dir entry failed");
69+
let file_path = entry.path();
70+
let os_file_name: &OsStr = file_path
71+
.file_name()
72+
.expect(&format!("directory entry lacks file_name: {:?}", file_path));
73+
let file_name: &str = os_file_name.to_str().expect("OsStr.to_str failed");
74+
FsStore::decode_key(file_name).expect("FsStore::decode_key failed")
75+
});
76+
77+
keys.map(|k| {
78+
let loaded: Option<V> = self.load(&k).expect(&format!("load {:?} failed!", k));
79+
let v: V = loaded.expect(&format!("key missing! {:?}", k));
80+
(k, v)
81+
})
82+
.collect()
83+
}
84+
}

rtc_tenclave/src/kv_store/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ pub trait KvStore<V> {
2727
// TODO: add update()
2828
}
2929

30-
#[cfg(test)]
30+
mod fs;
3131
mod in_memory;
32-
33-
#[cfg(test)]
3432
mod inspect;
33+
#[cfg(not(test))]
34+
mod sgxfs;
3535

3636
#[cfg(test)]
3737
mod tests;

rtc_tenclave/src/kv_store/sgxfs.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! [`KvStore`] implementation based on [`sgx_tstd::sgxfs`] (using the Intel SGX Protected FS Library)
2+
3+
use std::prelude::v1::*;
4+
5+
use std::io;
6+
use std::io::prelude::*;
7+
use std::path::{Path, PathBuf};
8+
9+
use serde::de::DeserializeOwned;
10+
use serde::Serialize;
11+
use sgx_tstd::sgxfs::SgxFile;
12+
13+
use super::{KvStore, StoreResult};
14+
15+
/// Filesystem-based [`KvStore`], using [`SgxFile`]
16+
///
17+
/// TODO: Document security guarantees.
18+
///
19+
struct SgxFsStore {
20+
pub(crate) root_dir: PathBuf,
21+
}
22+
23+
impl SgxFsStore {
24+
/// Validate that `root_dir` exists as a directory.
25+
pub fn new(root: impl AsRef<Path>) -> StoreResult<Self> {
26+
let root = root.as_ref();
27+
28+
// XXX: no create_dir_all()
29+
30+
Ok(SgxFsStore {
31+
root_dir: root.to_path_buf(),
32+
})
33+
}
34+
35+
/// Resolve file name for the value of `key`.
36+
fn value_path(&self, key: &str) -> PathBuf {
37+
// XXX: Escaping / encoding?
38+
let file_name = Self::encode_key(key);
39+
self.root_dir.join(file_name)
40+
}
41+
42+
pub(crate) fn encode_key(key: &str) -> String {
43+
let encoded = hex::encode(key);
44+
format!("x{}", encoded)
45+
}
46+
pub(crate) fn decode_key(file_name: &str) -> StoreResult<String> {
47+
let encoded: &str = file_name
48+
.strip_prefix("x")
49+
.ok_or_else(|| format!("FsStore::decode_key: missing x prefix for {:?}", file_name))?;
50+
// FIXME: Dodgy err.to_string()
51+
let bytes: Vec<u8> = hex::decode(encoded).map_err(|err| err.to_string())?;
52+
String::from_utf8(bytes).map_err(|err| err.into())
53+
}
54+
}
55+
56+
impl<V> KvStore<V> for SgxFsStore
57+
where
58+
V: Serialize + DeserializeOwned,
59+
{
60+
fn load(&self, key: &str) -> StoreResult<Option<V>> {
61+
let value_file_name = self.value_path(key);
62+
63+
// TODO: Handle NotFound
64+
// TODO: open_ex() with key
65+
let value_file = SgxFile::open(&value_file_name)
66+
.map_err(|err| format!("FsStore: open {:?} failed: {}", value_file_name, err))?;
67+
68+
// Note: Read all the data into memory first, then deserialize, for efficiency.
69+
// See the docs for [`serde_json::de::from_reader`],
70+
// and https://github.com/serde-rs/json/issues/160
71+
let serialised: Vec<u8> = read_all(value_file)
72+
.map_err(|err| format!("FsStore: read from {:?} failed: {}", value_file_name, err))?;
73+
74+
let deserialized: V = serde_json::from_slice(serialised.as_slice())?;
75+
Ok(Some(deserialized))
76+
}
77+
78+
fn save(&mut self, key: &str, value: V) -> StoreResult<()> {
79+
let serialized: Vec<u8> = serde_json::to_vec(&value)?;
80+
81+
let value_file_name = self.value_path(key);
82+
83+
let mut value_file = SgxFile::create(&value_file_name)
84+
.map_err(|err| format!("open {:?} failed: {}", value_file_name, err))?;
85+
86+
value_file.write_all(serialized.as_slice()).map_err(|err| {
87+
format!(
88+
"FsStore: write_all to {:?} failed: {}",
89+
value_file_name, err
90+
)
91+
})?;
92+
Ok(())
93+
}
94+
}
95+
96+
/// Helper: Like [`fs::read`], but take an open file.
97+
fn read_all(mut file: SgxFile) -> io::Result<Vec<u8>> {
98+
// XXX: No metadata for initial_buffer_size in sgxfs
99+
let mut bytes = Vec::new();
100+
file.read_to_end(&mut bytes)?;
101+
Ok(bytes)
102+
}

rtc_tenclave/src/kv_store/tests.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
//! Tests for [`rtc_tenclave::kv_store`]
22
3+
#[cfg(not(test))]
4+
use std::prelude::v1::*;
5+
36
use std::collections::HashMap;
7+
use std::fs::remove_dir_all;
8+
use std::path::Path;
49

510
use proptest::prelude::*;
611

12+
use super::fs::FsStore;
713
use super::in_memory::{InMemoryJsonStore, InMemoryStore};
814
use super::inspect::InspectStore;
915
use super::KvStore;
@@ -23,18 +29,40 @@ fn prop_store_ops_match_model() {
2329
.prop_shuffle()
2430
};
2531

32+
// XXX: hacky clearing
33+
pub fn clear_dir(path: &Path) {
34+
if path.is_dir() {
35+
remove_dir_all(path).expect("remove_dir_all failed");
36+
};
37+
}
38+
2639
proptest!(|(store_ops_vec in store_ops_strategy)| {
40+
// FIXME: This value type parameter needs better handling.
2741
type V = String;
42+
43+
// Init the models
2844
let mut store_model: InMemoryStore<V> = InMemoryStore::default();
2945
let mut store_model_json: InMemoryJsonStore = InMemoryJsonStore::default();
30-
// TODO: FS-based store
46+
47+
// Init the store under test
48+
let path = Path::new("store_test");
49+
clear_dir(path); // Clear before each test
50+
let mut store_fs: FsStore = FsStore::new(path).expect("FsStore::new failed");
3151

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

57+
// Models match each other
3658
prop_assert_eq!(store_model.as_map(), store_model_json.as_map());
59+
// Models match store_fs
60+
prop_assert_eq!(store_model.as_map(), store_fs.as_map());
61+
// FIXME: explicit coercion for as_map()
62+
prop_assert_eq!(store_model_json.as_map() as HashMap<String, V>, store_fs.as_map());
3763
}
64+
65+
clear_dir(path); // Clear after successful tests, just to keep the workdir clean
3866
});
3967
}
4068

rtc_tenclave/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#![feature(const_evaluatable_checked)]
77
#![deny(clippy::mem_forget)]
88
#![cfg_attr(not(test), no_std)]
9+
#![feature(impl_trait_in_bindings)]
910

1011
#[cfg(not(test))]
1112
#[macro_use]

0 commit comments

Comments
 (0)