trussed-staging 0.5.0-rc.1

Work in progress trussed features
Documentation
// Copyright (C) Nitrokey GmbH
// SPDX-License-Identifier: Apache-2.0 or MIT

#![cfg(all(feature = "virt", feature = "chunked"))]

use littlefs2_core::{path, PathBuf};
use serde_byte_array::ByteArray;
use trussed::virt::StoreConfig;
use trussed_chunked::{
    utils::{self, EncryptionData},
    ChunkedClient,
};
use trussed_core::{
    syscall, try_syscall, types::Bytes, types::Location, CryptoClient, Error, FilesystemClient,
};
use trussed_staging::virt::with_client;

fn test_write_all(location: Location) {
    with_client(StoreConfig::ram(), "test chunked", |mut client| {
        let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;
        let path = PathBuf::from(path!("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_client(StoreConfig::ram(), "test chunked", |mut client| {
        let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;
        let path = PathBuf::from(path!("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_client(StoreConfig::ram(), "chunked-tests", |mut client| {
        let path = PathBuf::from(path!("test_file"));
        let key = syscall!(client.generate_secret_key(32, Location::Volatile)).key;

        assert!(
            syscall!(client.entry_metadata(Location::Internal, path.clone()))
                .metadata
                .is_none(),
        );

        let large_data = Bytes::from(&[0; 1024]);
        let large_data2 = Bytes::from(&[1; 1024]);
        let more_data = Bytes::from(&[2; 42]);
        // ======== CHUNKED WRITES ========
        syscall!(client.start_encrypted_chunked_write(
            Location::Internal,
            path.clone(),
            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, path.clone(), 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, path.clone()))
            .metadata
            .unwrap();
        assert!(metadata.is_file());

        // ======== ABORTED CHUNKED WRITES ========
        syscall!(client.start_encrypted_chunked_write(
            Location::Internal,
            path.clone(),
            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, path.clone(), 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(path!("bad_name")))
        )
        .is_err());
        let metadata = syscall!(client.entry_metadata(Location::Internal, path.clone()))
            .metadata
            .unwrap();
        assert!(metadata.is_file());

        syscall!(client.remove_file(Location::Internal, path.clone()));
        assert!(
            syscall!(client.entry_metadata(Location::Internal, path.clone()))
                .metadata
                .is_none(),
        );
    })
}