mirror of
https://github.com/trussed-dev/trussed-staging.git
synced 2026-06-20 04:16:23 -07:00
@@ -16,6 +16,8 @@ chacha20poly1305 = { version = "0.10", default-features = false, features = ["he
|
||||
serde = { version = "1.0.160", default-features = false, features = ["derive"] }
|
||||
rand_core = { version = "0.6.4", default-features = false }
|
||||
delog = "0.1.6"
|
||||
littlefs2 = "0.4.0"
|
||||
serde-byte-array = "0.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
trussed = { version = "0.1.0", default-features = false, features = ["serde-extensions", "virt"] }
|
||||
@@ -24,6 +26,8 @@ trussed = { version = "0.1.0", default-features = false, features = ["serde-exte
|
||||
default = []
|
||||
|
||||
wrap-key-to-file = ["chacha20poly1305"]
|
||||
chunked = []
|
||||
encrypted-chunked = ["chunked", "chacha20poly1305/stream"]
|
||||
|
||||
virt = ["std", "trussed/virt"]
|
||||
std = []
|
||||
|
||||
@@ -9,7 +9,7 @@ check:
|
||||
lint:
|
||||
cargo clippy --all-features --all-targets -- --deny warnings
|
||||
cargo fmt -- --check
|
||||
RUSTDOCFLAGS='-Dwarnings' cargo doc --no-deps
|
||||
RUSTDOCFLAGS='-Dwarnings' cargo doc --no-deps --all-features
|
||||
reuse lint
|
||||
|
||||
.PHONY: test
|
||||
|
||||
+9
-10
@@ -2,14 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![cfg_attr(not(any(test, feature = "std")), no_std)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
// missing_docs,
|
||||
non_ascii_idents,
|
||||
trivial_casts,
|
||||
unused,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![warn(non_ascii_idents, trivial_casts, unused, unused_qualifications)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
delog::generate_macros!();
|
||||
@@ -22,6 +15,9 @@ pub mod virt;
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
pub mod wrap_key_to_file;
|
||||
|
||||
#[cfg(feature = "chunked")]
|
||||
pub mod streaming;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct StagingBackend {}
|
||||
@@ -32,9 +28,12 @@ impl StagingBackend {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct StagingContext {}
|
||||
pub struct StagingContext {
|
||||
#[cfg(feature = "chunked")]
|
||||
chunked_io_state: Option<streaming::ChunkedIoState>,
|
||||
}
|
||||
|
||||
impl Backend for StagingBackend {
|
||||
type Context = StagingContext;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,292 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
use littlefs2::driver::Storage as LfsStorage;
|
||||
use littlefs2::fs::{File, Filesystem};
|
||||
use littlefs2::io::{SeekFrom, Write};
|
||||
|
||||
use trussed::store::{create_directories, Store};
|
||||
use trussed::types::{Bytes, Location, Path, PathBuf};
|
||||
use trussed::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Enumeration of possible methods to seek within an file that was just opened
|
||||
/// Used in the [`read_chunk`](crate::store::read_chunk) and [`write_chunk`](crate::store::write_chunk) calls,
|
||||
/// Where [`SeekFrom::Current`](littlefs2::io::SeekFrom::Current) would not make sense.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum OpenSeekFrom {
|
||||
Start(u32),
|
||||
End(i32),
|
||||
}
|
||||
|
||||
impl From<OpenSeekFrom> for SeekFrom {
|
||||
fn from(value: OpenSeekFrom) -> Self {
|
||||
match value {
|
||||
OpenSeekFrom::Start(o) => Self::Start(o),
|
||||
OpenSeekFrom::End(o) => Self::End(o),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fs_read_chunk<Storage: LfsStorage, const N: usize>(
|
||||
fs: &Filesystem<Storage>,
|
||||
path: &Path,
|
||||
pos: OpenSeekFrom,
|
||||
) -> Result<(Bytes<N>, usize), Error> {
|
||||
let mut contents = Bytes::default();
|
||||
contents.resize_default(contents.capacity()).unwrap();
|
||||
let file_len = File::open_and_then(fs, path, |file| {
|
||||
file.seek(pos.into())?;
|
||||
let read_n = file.read(&mut contents)?;
|
||||
contents.truncate(read_n);
|
||||
file.len()
|
||||
})
|
||||
.map_err(|_| Error::FilesystemReadFailure)?;
|
||||
Ok((contents, file_len))
|
||||
}
|
||||
/// Reads contents from path in location of store.
|
||||
#[inline(never)]
|
||||
pub fn read_chunk<const N: usize>(
|
||||
store: impl Store,
|
||||
location: Location,
|
||||
path: &Path,
|
||||
pos: OpenSeekFrom,
|
||||
) -> Result<(Bytes<N>, usize), Error> {
|
||||
debug_now!("reading chunk {},{:?}", &path, pos);
|
||||
match location {
|
||||
Location::Internal => fs_read_chunk(store.ifs(), path, pos),
|
||||
Location::External => fs_read_chunk(store.efs(), path, pos),
|
||||
Location::Volatile => fs_read_chunk(store.vfs(), path, pos),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fs_write_chunk<Storage: LfsStorage>(
|
||||
fs: &Filesystem<Storage>,
|
||||
path: &Path,
|
||||
contents: &[u8],
|
||||
pos: OpenSeekFrom,
|
||||
) -> Result<(), Error> {
|
||||
File::<Storage>::with_options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open_and_then(fs, path, |file| {
|
||||
file.seek(pos.into())?;
|
||||
file.write_all(contents)
|
||||
})
|
||||
.map_err(|_| Error::FilesystemReadFailure)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes contents to path in location of store.
|
||||
#[inline(never)]
|
||||
pub fn write_chunk(
|
||||
store: impl Store,
|
||||
location: Location,
|
||||
path: &Path,
|
||||
contents: &[u8],
|
||||
pos: OpenSeekFrom,
|
||||
) -> Result<(), Error> {
|
||||
debug_now!("writing {}", &path);
|
||||
match location {
|
||||
Location::Internal => fs_write_chunk(store.ifs(), path, contents, pos),
|
||||
Location::External => fs_write_chunk(store.efs(), path, contents, pos),
|
||||
Location::Volatile => fs_write_chunk(store.vfs(), path, contents, pos),
|
||||
}
|
||||
.map_err(|_| Error::FilesystemWriteFailure)
|
||||
}
|
||||
|
||||
pub fn move_file(
|
||||
store: impl Store,
|
||||
from_location: Location,
|
||||
from_path: &Path,
|
||||
to_location: Location,
|
||||
to_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
debug_now!(
|
||||
"Moving {:?}({}) to {:?}({})",
|
||||
from_location,
|
||||
from_path,
|
||||
to_location,
|
||||
to_path
|
||||
);
|
||||
|
||||
match to_location {
|
||||
Location::Internal => create_directories(store.ifs(), to_path),
|
||||
Location::External => create_directories(store.efs(), to_path),
|
||||
Location::Volatile => create_directories(store.vfs(), to_path),
|
||||
}
|
||||
.map_err(|_err| {
|
||||
error!("Failed to create directories chunks: {:?}", _err);
|
||||
Error::FilesystemWriteFailure
|
||||
})?;
|
||||
|
||||
let on_fail = |_err| {
|
||||
error!("Failed to rename file: {:?}", _err);
|
||||
Error::FilesystemWriteFailure
|
||||
};
|
||||
// Fast path for same-filesystem
|
||||
match (from_location, to_location) {
|
||||
(Location::Internal, Location::Internal) => {
|
||||
return store.ifs().rename(from_path, to_path).map_err(on_fail)
|
||||
}
|
||||
(Location::External, Location::External) => {
|
||||
return store.efs().rename(from_path, to_path).map_err(on_fail)
|
||||
}
|
||||
(Location::Volatile, Location::Volatile) => {
|
||||
return store.vfs().rename(from_path, to_path).map_err(on_fail)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match from_location {
|
||||
Location::Internal => {
|
||||
move_file_step1(store, &**store.ifs(), from_path, to_location, to_path)
|
||||
}
|
||||
Location::External => {
|
||||
move_file_step1(store, &**store.efs(), from_path, to_location, to_path)
|
||||
}
|
||||
Location::Volatile => {
|
||||
move_file_step1(store, &**store.vfs(), from_path, to_location, to_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separate generic function to avoid having 9 times the same code because the filesystem types are not the same.
|
||||
fn move_file_step1<S: LfsStorage>(
|
||||
store: impl Store,
|
||||
from_fs: &Filesystem<S>,
|
||||
from_path: &Path,
|
||||
to_location: Location,
|
||||
to_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
match to_location {
|
||||
Location::Internal => move_file_step2(from_fs, from_path, &**store.ifs(), to_path),
|
||||
Location::External => move_file_step2(from_fs, from_path, &**store.efs(), to_path),
|
||||
Location::Volatile => move_file_step2(from_fs, from_path, &**store.vfs(), to_path),
|
||||
}
|
||||
}
|
||||
|
||||
// Separate generic function to avoid having 9 times the same code because the filesystem types are not the same.
|
||||
fn move_file_step2<S1: LfsStorage, S2: LfsStorage>(
|
||||
from_fs: &Filesystem<S1>,
|
||||
from_path: &Path,
|
||||
to_fs: &Filesystem<S2>,
|
||||
to_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
File::open_and_then(from_fs, from_path, |from_file| {
|
||||
File::create_and_then(to_fs, to_path, |to_file| copy_file_data(from_file, to_file))
|
||||
})
|
||||
.map_err(|_err| {
|
||||
error!("Failed to flush chunks: {:?}", _err);
|
||||
Error::FilesystemWriteFailure
|
||||
})
|
||||
}
|
||||
|
||||
fn copy_file_data<S1: LfsStorage, S2: LfsStorage>(
|
||||
from: &File<S1>,
|
||||
to: &File<S2>,
|
||||
) -> Result<(), littlefs2::io::Error> {
|
||||
let mut buf = [0; 1024];
|
||||
loop {
|
||||
let read = from.read(&mut buf)?;
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
to.write_all(&buf[..read])?;
|
||||
}
|
||||
}
|
||||
|
||||
fn chunks_path(client_id: &Path, client_path: &Path, location: Location) -> Result<PathBuf, Error> {
|
||||
// Clients must not escape their namespace
|
||||
if client_path.as_ref().contains("..") {
|
||||
return Err(Error::InvalidPath);
|
||||
}
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path.push(client_id);
|
||||
match location {
|
||||
Location::Volatile => path.push(&PathBuf::from("vfs-part")),
|
||||
Location::External => path.push(&PathBuf::from("efs-part")),
|
||||
Location::Internal => path.push(&PathBuf::from("ifs-part")),
|
||||
}
|
||||
path.push(client_path);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn actual_path(client_id: &Path, client_path: &Path) -> Result<PathBuf, Error> {
|
||||
// Clients must not escape their namespace
|
||||
if client_path.as_ref().contains("..") {
|
||||
return Err(Error::InvalidPath);
|
||||
}
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
path.push(client_id);
|
||||
path.push(&PathBuf::from("dat"));
|
||||
path.push(client_path);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn start_chunked_write(
|
||||
store: impl Store,
|
||||
client_id: &Path,
|
||||
path: &PathBuf,
|
||||
location: Location,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let path = chunks_path(client_id, path, location)?;
|
||||
trussed::store::store(store, Location::Volatile, &path, data)
|
||||
}
|
||||
|
||||
pub fn filestore_write_chunk(
|
||||
store: impl Store,
|
||||
client_id: &Path,
|
||||
path: &Path,
|
||||
location: Location,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let path = chunks_path(client_id, path, location)?;
|
||||
write_chunk(store, Location::Volatile, &path, data, OpenSeekFrom::End(0))
|
||||
}
|
||||
|
||||
pub fn filestore_read_chunk<const N: usize>(
|
||||
store: impl Store,
|
||||
client_id: &Path,
|
||||
path: &PathBuf,
|
||||
location: Location,
|
||||
pos: OpenSeekFrom,
|
||||
) -> Result<(Bytes<N>, usize), Error> {
|
||||
let path = actual_path(client_id, path)?;
|
||||
|
||||
read_chunk(store, location, &path, pos)
|
||||
}
|
||||
|
||||
pub fn abort_chunked_write(
|
||||
store: impl Store,
|
||||
client_id: &Path,
|
||||
path: &PathBuf,
|
||||
location: Location,
|
||||
) -> bool {
|
||||
let Ok(path) = chunks_path(client_id,path, location) else {
|
||||
return false;
|
||||
};
|
||||
trussed::store::delete(store, Location::Volatile, &path)
|
||||
}
|
||||
|
||||
pub fn flush_chunks(
|
||||
store: impl Store,
|
||||
client_id: &Path,
|
||||
path: &PathBuf,
|
||||
location: Location,
|
||||
) -> Result<(), Error> {
|
||||
let chunk_path = chunks_path(client_id, path, location)?;
|
||||
let client_path = actual_path(client_id, path)?;
|
||||
move_file(
|
||||
store,
|
||||
Location::Volatile,
|
||||
&chunk_path,
|
||||
location,
|
||||
&client_path,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
use littlefs2::path::PathBuf;
|
||||
use serde_byte_array::ByteArray;
|
||||
|
||||
use trussed::{
|
||||
syscall, try_syscall,
|
||||
types::{KeyId, Location, Message, UserAttribute},
|
||||
Error,
|
||||
};
|
||||
|
||||
use super::{ChunkedClient, CHACHA8_STREAM_NONCE_LEN};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EncryptionData {
|
||||
pub key: KeyId,
|
||||
pub nonce: Option<ByteArray<CHACHA8_STREAM_NONCE_LEN>>,
|
||||
}
|
||||
|
||||
/// Write a large file (can be larger than 1KiB)
|
||||
///
|
||||
/// This is a wrapper around the [chunked writes api](ChunkedClient)
|
||||
pub fn write_all(
|
||||
client: &mut impl ChunkedClient,
|
||||
location: Location,
|
||||
path: PathBuf,
|
||||
data: &[u8],
|
||||
user_attribute: Option<UserAttribute>,
|
||||
encryption: Option<EncryptionData>,
|
||||
) -> Result<(), Error> {
|
||||
if let (Ok(msg), None) = (Message::from_slice(data), encryption) {
|
||||
// Fast path for small files
|
||||
try_syscall!(client.write_file(location, path, msg, user_attribute))?;
|
||||
Ok(())
|
||||
} else {
|
||||
write_chunked(client, location, path, data, user_attribute, encryption)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_chunked(
|
||||
client: &mut impl ChunkedClient,
|
||||
location: Location,
|
||||
path: PathBuf,
|
||||
data: &[u8],
|
||||
user_attribute: Option<UserAttribute>,
|
||||
encryption: Option<EncryptionData>,
|
||||
) -> Result<(), Error> {
|
||||
let res = write_chunked_inner(client, location, path, data, user_attribute, encryption);
|
||||
if res.is_err() {
|
||||
syscall!(client.abort_chunked_write());
|
||||
return res;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_chunked_inner(
|
||||
client: &mut impl ChunkedClient,
|
||||
location: Location,
|
||||
path: PathBuf,
|
||||
data: &[u8],
|
||||
user_attribute: Option<UserAttribute>,
|
||||
encryption: Option<EncryptionData>,
|
||||
) -> Result<(), Error> {
|
||||
let msg = Message::new();
|
||||
let chunk_size = msg.capacity();
|
||||
let chunks = data.chunks(chunk_size).map(|chunk| {
|
||||
Message::from_slice(chunk).expect("Iteration over chunks yields maximum of chunk_size")
|
||||
});
|
||||
if let Some(encryption_data) = encryption {
|
||||
try_syscall!(client.start_encrypted_chunked_write(
|
||||
location,
|
||||
path,
|
||||
encryption_data.key,
|
||||
encryption_data.nonce,
|
||||
user_attribute,
|
||||
))?;
|
||||
} else {
|
||||
try_syscall!(client.start_chunked_write(location, path, user_attribute))?;
|
||||
}
|
||||
let mut written = 0;
|
||||
for chunk in chunks {
|
||||
written += chunk.len();
|
||||
try_syscall!(client.write_file_chunk(chunk))?;
|
||||
}
|
||||
|
||||
if { written % chunk_size } == 0 {
|
||||
try_syscall!(client.write_file_chunk(Message::new()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+29
-1
@@ -8,6 +8,9 @@ use crate::wrap_key_to_file::WrapKeyToFileExtension;
|
||||
|
||||
use crate::{StagingBackend, StagingContext};
|
||||
|
||||
#[cfg(feature = "chunked")]
|
||||
use crate::streaming::ChunkedExtension;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Dispatcher {
|
||||
backend: StagingBackend,
|
||||
@@ -22,6 +25,8 @@ pub enum BackendIds {
|
||||
pub enum ExtensionIds {
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
WrapKeyToFile,
|
||||
#[cfg(feature = "chunked")]
|
||||
Chunked,
|
||||
}
|
||||
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
@@ -30,11 +35,19 @@ impl ExtensionId<WrapKeyToFileExtension> for Dispatcher {
|
||||
const ID: ExtensionIds = ExtensionIds::WrapKeyToFile;
|
||||
}
|
||||
|
||||
#[cfg(feature = "chunked")]
|
||||
impl ExtensionId<ChunkedExtension> for Dispatcher {
|
||||
type Id = ExtensionIds;
|
||||
const ID: ExtensionIds = ExtensionIds::Chunked;
|
||||
}
|
||||
|
||||
impl From<ExtensionIds> for u8 {
|
||||
fn from(value: ExtensionIds) -> Self {
|
||||
match value {
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
ExtensionIds::WrapKeyToFile => 0,
|
||||
#[cfg(feature = "chunked")]
|
||||
ExtensionIds::Chunked => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +58,8 @@ impl TryFrom<u8> for ExtensionIds {
|
||||
match value {
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
0 => Ok(Self::WrapKeyToFile),
|
||||
#[cfg(feature = "chunked")]
|
||||
1 => Ok(Self::Chunked),
|
||||
_ => Err(Error::FunctionNotSupported),
|
||||
}
|
||||
}
|
||||
@@ -81,12 +96,25 @@ impl ExtensionDispatch for Dispatcher {
|
||||
// See https://github.com/rust-lang/rust/issues/78123#
|
||||
match *extension {
|
||||
#[cfg(feature = "wrap-key-to-file")]
|
||||
ExtensionIds::WrapKeyToFile => self.backend.extension_request_serialized(
|
||||
ExtensionIds::WrapKeyToFile => <StagingBackend as ExtensionImpl<
|
||||
WrapKeyToFileExtension,
|
||||
>>::extension_request_serialized(
|
||||
&mut self.backend,
|
||||
&mut ctx.core,
|
||||
&mut ctx.backends,
|
||||
request,
|
||||
resources,
|
||||
),
|
||||
#[cfg(feature = "chunked")]
|
||||
ExtensionIds::Chunked => {
|
||||
<StagingBackend as ExtensionImpl<ChunkedExtension>>::extension_request_serialized(
|
||||
&mut self.backend,
|
||||
&mut ctx.core,
|
||||
&mut ctx.backends,
|
||||
request,
|
||||
resources,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![cfg(all(feature = "virt", feature = "chunked"))]
|
||||
|
||||
use littlefs2::path::PathBuf;
|
||||
use trussed::{client::FilesystemClient, syscall, try_syscall, types::Location, Bytes};
|
||||
use trussed_staging::{
|
||||
streaming::{utils, ChunkedClient},
|
||||
virt::with_ram_client,
|
||||
};
|
||||
fn test_write_all(location: Location) {
|
||||
with_ram_client("test chunked", |mut client| {
|
||||
let path = PathBuf::from("foo");
|
||||
utils::write_all(&mut client, location, path.clone(), &[48; 1234], None, None).unwrap();
|
||||
|
||||
let data = syscall!(client.start_chunked_read(location, path)).data;
|
||||
assert_eq!(&data, &[48; 1024]);
|
||||
let data = syscall!(client.read_file_chunk()).data;
|
||||
assert_eq!(&data, &[48; 1234 - 1024]);
|
||||
});
|
||||
}
|
||||
|
||||
fn test_write_all_small(location: Location) {
|
||||
with_ram_client("test chunked", |mut client| {
|
||||
let path = PathBuf::from("foo2");
|
||||
utils::write_all(&mut client, location, path.clone(), &[48; 1023], None, None).unwrap();
|
||||
|
||||
let data = syscall!(client.start_chunked_read(location, path)).data;
|
||||
assert_eq!(&data, &[48; 1023]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_volatile() {
|
||||
test_write_all(Location::Volatile);
|
||||
test_write_all_small(Location::Volatile);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_external() {
|
||||
test_write_all(Location::External);
|
||||
test_write_all_small(Location::External);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_internal() {
|
||||
test_write_all(Location::Internal);
|
||||
test_write_all_small(Location::Internal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filesystem() {
|
||||
with_ram_client("chunked-tests", |mut client| {
|
||||
assert!(
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.is_none(),
|
||||
);
|
||||
|
||||
let data = Bytes::from_slice(b"test data").unwrap();
|
||||
syscall!(client.write_file(
|
||||
Location::Internal,
|
||||
PathBuf::from("test_file"),
|
||||
data.clone(),
|
||||
None,
|
||||
));
|
||||
|
||||
let recv_data =
|
||||
syscall!(client.read_file(Location::Internal, PathBuf::from("test_file"))).data;
|
||||
assert_eq!(data, recv_data);
|
||||
|
||||
// ======== CHUNKED READS ========
|
||||
let first_data =
|
||||
syscall!(client.start_chunked_read(Location::Internal, PathBuf::from("test_file"),));
|
||||
assert_eq!(&first_data.data, &data);
|
||||
assert_eq!(first_data.len, data.len());
|
||||
|
||||
let empty_data = syscall!(client.read_file_chunk());
|
||||
assert!(empty_data.data.is_empty());
|
||||
assert_eq!(empty_data.len, data.len());
|
||||
|
||||
let large_data = Bytes::from_slice(&[0; 1024]).unwrap();
|
||||
let large_data2 = Bytes::from_slice(&[1; 1024]).unwrap();
|
||||
let more_data = Bytes::from_slice(&[2; 42]).unwrap();
|
||||
// ======== CHUNKED WRITES ========
|
||||
syscall!(client.start_chunked_write(Location::Internal, PathBuf::from("test_file"), None));
|
||||
|
||||
syscall!(client.write_file_chunk(large_data.clone()));
|
||||
syscall!(client.write_file_chunk(large_data2.clone()));
|
||||
syscall!(client.write_file_chunk(more_data.clone()));
|
||||
|
||||
// ======== CHUNKED READS ========
|
||||
let full_len = large_data.len() + large_data2.len() + more_data.len();
|
||||
let first_data =
|
||||
syscall!(client.start_chunked_read(Location::Internal, PathBuf::from("test_file"),));
|
||||
assert_eq!(&first_data.data, &large_data);
|
||||
assert_eq!(first_data.len, full_len);
|
||||
|
||||
let second_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&second_data.data, &large_data2);
|
||||
assert_eq!(second_data.len, full_len);
|
||||
|
||||
let third_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&third_data.data, &more_data);
|
||||
assert_eq!(third_data.len, full_len);
|
||||
|
||||
let empty_data = syscall!(client.read_file_chunk());
|
||||
assert!(empty_data.data.is_empty());
|
||||
assert_eq!(empty_data.len, full_len);
|
||||
|
||||
let metadata =
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.unwrap();
|
||||
assert!(metadata.is_file());
|
||||
|
||||
// ======== ABORTED CHUNKED WRITES ========
|
||||
syscall!(client.start_chunked_write(Location::Internal, PathBuf::from("test_file"), None));
|
||||
|
||||
syscall!(client.write_file_chunk(large_data.clone()));
|
||||
syscall!(client.write_file_chunk(large_data2));
|
||||
syscall!(client.abort_chunked_write());
|
||||
|
||||
// Old data is still there after abort
|
||||
let partial_data =
|
||||
syscall!(client.start_chunked_read(Location::Internal, PathBuf::from("test_file")));
|
||||
assert_eq!(&partial_data.data, &large_data);
|
||||
assert_eq!(partial_data.len, full_len);
|
||||
|
||||
// This returns an error because the name doesn't exist
|
||||
assert!(
|
||||
try_syscall!(client.remove_file(Location::Internal, PathBuf::from("bad_name")))
|
||||
.is_err()
|
||||
);
|
||||
let metadata =
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.unwrap();
|
||||
assert!(metadata.is_file());
|
||||
|
||||
syscall!(client.remove_file(Location::Internal, PathBuf::from("test_file")));
|
||||
assert!(
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.is_none(),
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![cfg(all(feature = "virt", feature = "encrypted-chunked"))]
|
||||
|
||||
use littlefs2::path::PathBuf;
|
||||
use serde_byte_array::ByteArray;
|
||||
use trussed::{
|
||||
client::CryptoClient, client::FilesystemClient, syscall, try_syscall, types::Location, Bytes,
|
||||
Error,
|
||||
};
|
||||
use trussed_staging::{
|
||||
streaming::{
|
||||
utils::{self, EncryptionData},
|
||||
ChunkedClient,
|
||||
},
|
||||
virt::with_ram_client,
|
||||
};
|
||||
|
||||
fn test_write_all(location: Location) {
|
||||
with_ram_client("test chunked", |mut client| {
|
||||
let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;
|
||||
let path = PathBuf::from("foo");
|
||||
utils::write_all(
|
||||
&mut client,
|
||||
location,
|
||||
path.clone(),
|
||||
&[48; 1234],
|
||||
None,
|
||||
Some(EncryptionData { key, nonce: None }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
syscall!(client.start_encrypted_chunked_read(location, path, key));
|
||||
let data = syscall!(client.read_file_chunk()).data;
|
||||
assert_eq!(&data, &[48; 1024]);
|
||||
let data = syscall!(client.read_file_chunk()).data;
|
||||
assert_eq!(&data, &[48; 1234 - 1024]);
|
||||
});
|
||||
}
|
||||
|
||||
fn test_write_all_small(location: Location) {
|
||||
with_ram_client("test chunked", |mut client| {
|
||||
let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;
|
||||
let path = PathBuf::from("foo2");
|
||||
utils::write_all(
|
||||
&mut client,
|
||||
location,
|
||||
path.clone(),
|
||||
&[48; 1023],
|
||||
None,
|
||||
Some(EncryptionData { key, nonce: None }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
syscall!(client.start_encrypted_chunked_read(location, path, key));
|
||||
let data = syscall!(client.read_file_chunk()).data;
|
||||
assert_eq!(&data, &[48; 1023]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_volatile() {
|
||||
test_write_all(Location::Volatile);
|
||||
test_write_all_small(Location::Volatile);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_external() {
|
||||
test_write_all(Location::External);
|
||||
test_write_all_small(Location::External);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_all_internal() {
|
||||
test_write_all(Location::Internal);
|
||||
test_write_all_small(Location::Internal);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypted_filesystem() {
|
||||
with_ram_client("chunked-tests", |mut client| {
|
||||
let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;
|
||||
|
||||
assert!(
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.is_none(),
|
||||
);
|
||||
|
||||
let large_data = Bytes::from_slice(&[0; 1024]).unwrap();
|
||||
let large_data2 = Bytes::from_slice(&[1; 1024]).unwrap();
|
||||
let more_data = Bytes::from_slice(&[2; 42]).unwrap();
|
||||
// ======== CHUNKED WRITES ========
|
||||
syscall!(client.start_encrypted_chunked_write(
|
||||
Location::Internal,
|
||||
PathBuf::from("test_file"),
|
||||
key,
|
||||
Some(ByteArray::from([0; 8])),
|
||||
None
|
||||
));
|
||||
|
||||
syscall!(client.write_file_chunk(large_data.clone()));
|
||||
syscall!(client.write_file_chunk(large_data2.clone()));
|
||||
syscall!(client.write_file_chunk(more_data.clone()));
|
||||
|
||||
// ======== CHUNKED READS ========
|
||||
let full_len = large_data.len() + large_data2.len() + more_data.len();
|
||||
syscall!(client.start_encrypted_chunked_read(
|
||||
Location::Internal,
|
||||
PathBuf::from("test_file"),
|
||||
key
|
||||
));
|
||||
let first_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&first_data.data, &large_data);
|
||||
assert_eq!(first_data.len, full_len);
|
||||
|
||||
let second_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&second_data.data, &large_data2);
|
||||
assert_eq!(second_data.len, full_len);
|
||||
|
||||
let third_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&third_data.data, &more_data);
|
||||
assert_eq!(third_data.len, full_len);
|
||||
|
||||
assert_eq!(
|
||||
try_syscall!(client.read_file_chunk()),
|
||||
Err(Error::MechanismNotAvailable)
|
||||
);
|
||||
|
||||
let metadata =
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.unwrap();
|
||||
assert!(metadata.is_file());
|
||||
|
||||
// ======== ABORTED CHUNKED WRITES ========
|
||||
syscall!(client.start_encrypted_chunked_write(
|
||||
Location::Internal,
|
||||
PathBuf::from("test_file"),
|
||||
key,
|
||||
Some(ByteArray::from([1; 8])),
|
||||
None
|
||||
));
|
||||
|
||||
syscall!(client.write_file_chunk(large_data.clone()));
|
||||
syscall!(client.write_file_chunk(large_data2.clone()));
|
||||
syscall!(client.abort_chunked_write());
|
||||
|
||||
// Old data is still there after abort
|
||||
syscall!(client.start_encrypted_chunked_read(
|
||||
Location::Internal,
|
||||
PathBuf::from("test_file"),
|
||||
key
|
||||
));
|
||||
let first_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&first_data.data, &large_data);
|
||||
assert_eq!(first_data.len, full_len);
|
||||
|
||||
let second_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&second_data.data, &large_data2);
|
||||
assert_eq!(second_data.len, full_len);
|
||||
|
||||
let third_data = syscall!(client.read_file_chunk());
|
||||
assert_eq!(&third_data.data, &more_data);
|
||||
assert_eq!(third_data.len, full_len);
|
||||
|
||||
assert_eq!(
|
||||
try_syscall!(client.read_file_chunk()),
|
||||
Err(Error::MechanismNotAvailable)
|
||||
);
|
||||
|
||||
// This returns an error because the name doesn't exist
|
||||
assert!(
|
||||
try_syscall!(client.remove_file(Location::Internal, PathBuf::from("bad_name")))
|
||||
.is_err()
|
||||
);
|
||||
let metadata =
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.unwrap();
|
||||
assert!(metadata.is_file());
|
||||
|
||||
syscall!(client.remove_file(Location::Internal, PathBuf::from("test_file")));
|
||||
assert!(
|
||||
syscall!(client.entry_metadata(Location::Internal, PathBuf::from("test_file")))
|
||||
.metadata
|
||||
.is_none(),
|
||||
);
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user