Merge pull request #2 from Nitrokey/streaming

Add streaming API
This commit is contained in:
Robin Krahl
2023-04-26 15:05:35 +02:00
committed by GitHub
10 changed files with 1602 additions and 12 deletions
+4
View File
@@ -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 = []
+1 -1
View File
@@ -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
View File
@@ -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
+292
View File
@@ -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,
)
}
+91
View File
@@ -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
View File
@@ -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,
)
}
}
}
}
+149
View File
@@ -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(),
);
})
}
+191
View File
@@ -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(),
);
})
}