Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hostenv file storage #489

Merged
merged 10 commits into from
Jun 8, 2022
3 changes: 3 additions & 0 deletions libraries/persistent_store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ edition = "2018"

[dependencies]

[dev-dependencies]
tempfile = "3"

[features]
std = []
189 changes: 189 additions & 0 deletions libraries/persistent_store/src/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! File-backed persistent flash storage for virtual authenticator.
//!
//! [`FileStorage`] implements the flash [`Storage`] interface but doesn't interface with an
//! actual flash storage. Instead it uses a host-based file to persist the storage state.

use crate::{Storage, StorageIndex, StorageResult};
use alloc::borrow::Cow;
use core::cell::RefCell;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;

/// Simulates a flash storage using a host-based file.
///
/// This is usable for emulating authenticator hardware on VM hypervisor's host OS.
pub struct FileStorage {
// Options of the storage.
options: FileOptions,

/// File for persisting contents of the storage.
///
/// Reading data from File requires mutable reference, as seeking and reading data
/// changes file's current position.
///
/// All operations on backing file internally always first seek to needed position,
/// so it's safe to borrow mutable reference to backing file for the time of operation.
file: RefCell<File>,
}

/// Options for file-backed storage.
pub struct FileOptions {
/// Size of a word in bytes.
pub word_size: usize,

/// Size of a page in bytes.
pub page_size: usize,

/// Number of pages in storage.
pub num_pages: usize,
}

impl FileStorage {
pub fn new(path: &Path, options: FileOptions) -> StorageResult<FileStorage> {
let mut file_ref = RefCell::new(
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?,
);
let file = file_ref.get_mut();
let file_len = file.metadata()?.len();
let store_len: u64 = (options.page_size * options.num_pages) as u64;

if file_len == 0 {
file.seek(SeekFrom::Start(0))?;
let buf = vec![0xffu8; options.page_size];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have generally used 0xFF. Is the u8 annotation is required?

Other instances of this exist below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, for consistency with the rest of the library let's use 0xff if Rust is able to infer the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

for _ in 0..options.num_pages {
file.write(&buf)?;
}
} else if file_len != store_len {
// FileStorage buffer should be of fixed size, opening previously saved file
// from storage of different size is not supported
panic!("Invalid file size {}, should be {}", file_len, store_len);
}
Ok(FileStorage {
options,
file: file_ref,
})
}
}

impl Storage for FileStorage {
fn word_size(&self) -> usize {
self.options.word_size
}

fn page_size(&self) -> usize {
self.options.page_size
}

fn num_pages(&self) -> usize {
self.options.num_pages
}

fn max_word_writes(&self) -> usize {
usize::MAX
}

fn max_page_erases(&self) -> usize {
usize::MAX
}

fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<Cow<[u8]>> {
let mut file = self.file.borrow_mut();
file.seek(SeekFrom::Start(index.range(length, self)?.start as u64))?;
let mut buf = vec![0u8; length];
file.read_exact(&mut buf)?;
Ok(Cow::Owned(buf))
}

fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
let mut file = self.file.borrow_mut();
file.seek(SeekFrom::Start(
index.range(value.len(), self)?.start as u64,
))?;
file.write_all(value)?;
Ok(())
}

fn erase_page(&mut self, page: usize) -> StorageResult<()> {
let mut file = self.file.borrow_mut();
let index = StorageIndex { page, byte: 0 };
file.seek(SeekFrom::Start(
index.range(self.page_size(), self)?.start as u64,
))?;
file.write_all(&vec![0xffu8; self.page_size()][..])?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::TempDir;

const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff];
const DATA_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77];

const FILE_NAME: &str = "opensk_storage.bin";

const OPTIONS: FileOptions = FileOptions {
word_size: 4,
page_size: 0x1000,
num_pages: 20,
};

fn make_tmp_dir() -> PathBuf {
let tmp_dir = TempDir::new().unwrap();
tmp_dir.into_path()
}

fn remove_tmp_dir(tmp_dir: PathBuf) {
std::fs::remove_dir_all(tmp_dir).unwrap();
}

fn temp_storage(tmp_dir: &PathBuf) -> FileStorage {
let mut tmp_file = tmp_dir.clone();
tmp_file.push(FILE_NAME);
FileStorage::new(&tmp_file, OPTIONS).unwrap()
}

#[test]
fn read_write_persist_ok() {
let index = StorageIndex { page: 0, byte: 0 };
let next_index = StorageIndex { page: 0, byte: 4 };

let tmp_dir = make_tmp_dir();
{
let mut file_storage = temp_storage(&tmp_dir);
assert_eq!(file_storage.read_slice(index, 4).unwrap(), BLANK_WORD);
file_storage.write_slice(index, DATA_WORD).unwrap();
assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
}
// Reload and check the data from previously persisted storage
{
let file_storage = temp_storage(&tmp_dir);
assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
}
remove_tmp_dir(tmp_dir);
}
}
4 changes: 4 additions & 0 deletions libraries/persistent_store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ extern crate alloc;
mod buffer;
#[cfg(feature = "std")]
mod driver;
#[cfg(feature = "std")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this std condition correctly interact with the rest of the code? We use std for tests and fuzzing only so far. Therefore, I was wondering if you can just compile the OpenSK library with std later for non-testing purposes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, std means that we compile with the standard library (and thus an OS). We use std for tests and fuzzing because that's the only reason we need to compile on an OS. And this example is another reason now.

Note that once CTAP is a library we will compile it with std for non-testing purposes too. This library is just one step ahead because it's already a library and doesn't depend on Tock.

mod file;
mod format;
pub mod fragment;
#[cfg(feature = "std")]
Expand All @@ -381,6 +383,8 @@ pub use self::driver::{
StoreDriver, StoreDriverOff, StoreDriverOn, StoreInterruption, StoreInvariant,
};
#[cfg(feature = "std")]
pub use self::file::{FileOptions, FileStorage};
#[cfg(feature = "std")]
pub use self::model::{StoreModel, StoreOperation};
pub use self::storage::{Storage, StorageError, StorageIndex, StorageResult};
pub use self::store::{
Expand Down
7 changes: 7 additions & 0 deletions libraries/persistent_store/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ pub enum StorageError {
CustomError,
}

#[cfg(feature = "std")]
impl From<std::io::Error> for StorageError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, another use of std that is actually necessary outside of tests.

fn from(_: std::io::Error) -> Self {
Self::CustomError
}
}

pub type StorageResult<T> = Result<T, StorageError>;

/// Abstracts a flash storage.
Expand Down