mod error;
mod operations;
#[cfg(test)]
mod tests;
#[cfg(any(feature = "std", test))]
mod fs_std;
#[cfg(any(feature = "std", test))]
pub use self::fs_std::*;
pub use self::{error::*, operations::*};
use alloc::{borrow::Borrow, collections::VecDeque, ffi::CString, vec, vec::Vec};
use bytes::{Buf, Bytes};
use codec::{Compact, CompactLen, ConstEncodedLen, Decode, Encode, MaxEncodedLen};
use core::{ffi::CStr, ops::Deref};
use jam_types::{ServiceId, VecMap, VecSet, MAX_PREIMAGE_BLOB_LEN};
pub const MIN_BLOCK_SIZE: usize = 64 * 1024;
pub const MAX_BLOCK_SIZE: usize = MAX_PREIMAGE_BLOB_LEN;
pub const MAX_FILE_NAME_LEN: usize = 4096;
#[derive(Clone, Debug)]
pub struct FileBlock(Bytes);
impl FileBlock {
pub fn new(data: Bytes) -> Result<Self, InvalidBlock> {
if data.len() > MAX_BLOCK_SIZE {
return Err(InvalidBlock);
}
Ok(Self(data))
}
pub fn into_inner(self) -> Bytes {
self.0
}
}
impl Deref for FileBlock {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0[..]
}
}
impl AsRef<[u8]> for FileBlock {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
#[derive(
Encode, Decode, MaxEncodedLen, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug,
)]
pub enum NodeKind {
File,
Dir,
}
impl ConstEncodedLen for NodeKind {}
#[derive(Debug)]
pub struct MainBlock {
kind: NodeKind,
file_size: u64,
block_size: u64,
block_refs: Vec<BlockRef>,
first_block: FileBlock,
}
impl MainBlock {
pub fn decode(mut input: Bytes) -> Result<Self, InvalidBlock> {
let mut slice = &input[..];
let kind = NodeKind::decode(&mut slice).map_err(|_| InvalidBlock)?;
let file_size = Compact::<u64>::decode(&mut slice).map_err(|_| InvalidBlock)?.0;
let block_size = Compact::<u64>::decode(&mut slice).map_err(|_| InvalidBlock)?.0;
let block_refs = Vec::<BlockRef>::decode(&mut slice).map_err(|_| InvalidBlock)?;
let remaining_len = slice.len();
input.advance(input.len() - remaining_len);
let first_block = FileBlock::new(input)?;
validate_main_block(file_size, block_size, &block_refs, &first_block)?;
Ok(Self { kind, file_size, block_size, block_refs, first_block })
}
pub fn encode_to(&self, output: &mut Vec<u8>) {
self.kind.encode_to(output);
Compact(self.file_size).encode_to(output);
Compact(self.block_size).encode_to(output);
self.block_refs.encode_to(output);
output.extend_from_slice(self.first_block.as_ref());
}
pub const fn kind(&self) -> NodeKind {
self.kind
}
pub const fn file_size(&self) -> u64 {
self.file_size
}
pub const fn block_size(&self) -> u64 {
self.block_size
}
pub fn first_block(&self) -> &FileBlock {
&self.first_block
}
pub fn block_refs(&self) -> &[BlockRef] {
&self.block_refs
}
}
fn validate_main_block(
file_size: u64,
block_size: u64,
block_refs: &[BlockRef],
first_block: &FileBlock,
) -> Result<(), InvalidBlock> {
if !(MIN_BLOCK_SIZE as u64..=MAX_BLOCK_SIZE as u64).contains(&block_size) {
log::trace!("Invalid block size: {block_size}");
return Err(InvalidBlock);
}
let first_block_size = first_block.len();
if first_block_size < MIN_BLOCK_SIZE && !block_refs.is_empty() {
log::trace!(
"Invalid first block size: first block size = {first_block_size}, \
min. block size = {MIN_BLOCK_SIZE}, no. of hashes = {}",
block_refs.len()
);
return Err(InvalidBlock);
}
let max = first_block_size as u64 + block_refs.len() as u64 * block_size;
let min = if block_refs.is_empty() { first_block_size as u64 } else { max - block_size + 1 };
if !(min..=max).contains(&file_size) {
log::trace!("Invalid file size: {file_size} not in {min}..={max}");
return Err(InvalidBlock);
}
let encoded_len = main_block_metadata_encoded_len(file_size, block_size, block_refs.len())
.ok_or(InvalidBlock)? +
first_block.len();
if encoded_len > MAX_BLOCK_SIZE {
log::trace!(
"Encoded len is greater than the block size: {encoded_len} vs. {MAX_BLOCK_SIZE}"
);
return Err(InvalidBlock);
}
Ok(())
}
fn main_block_metadata_encoded_len(
file_size: u64,
block_size: u64,
num_blocks: usize,
) -> Option<usize> {
(NodeKind::max_encoded_len() +
Compact::<u64>::compact_len(&file_size) +
Compact::<u64>::compact_len(&block_size))
.checked_add(vec_encoded_len::<BlockRef>(num_blocks))
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct FileName(CString);
impl FileName {
pub fn new(name: CString) -> Result<Self, InvalidPath> {
validate_file_name(&name)?;
Ok(Self(name))
}
pub fn into_inner(self) -> CString {
self.0
}
}
impl Encode for FileName {
fn encode_to<O: codec::Output + ?Sized>(&self, output: &mut O) {
self.0.to_bytes().encode_to(output)
}
}
impl Decode for FileName {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let mut bytes: Vec<u8> = Decode::decode(input)?;
bytes.push(0_u8);
let name = CString::from_vec_with_nul(bytes).map_err(|_| "Invalid C-string")?;
validate_file_name(&name).map_err(|_| "Invalid file name")?;
Ok(Self(name))
}
}
impl MaxEncodedLen for FileName {
fn max_encoded_len() -> usize {
MAX_FILE_NAME_LEN - 1 + Compact::<u32>(MAX_FILE_NAME_LEN as u32).encoded_size()
}
}
impl AsRef<CStr> for FileName {
fn as_ref(&self) -> &CStr {
self.0.as_c_str()
}
}
impl Deref for FileName {
type Target = CStr;
fn deref(&self) -> &Self::Target {
self.0.as_c_str()
}
}
impl Borrow<CStr> for FileName {
fn borrow(&self) -> &CStr {
self.0.as_c_str()
}
}
impl Borrow<[u8]> for FileName {
fn borrow(&self) -> &[u8] {
self.0.to_bytes()
}
}
fn validate_file_name(name: &CStr) -> Result<(), InvalidPath> {
let bytes = name.to_bytes_with_nul();
if !(2..=MAX_FILE_NAME_LEN).contains(&bytes.len()) || bytes.contains(&b'/') {
return Err(InvalidPath);
}
Ok(())
}
#[derive(
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode, MaxEncodedLen, Debug,
)]
pub struct BlockRef {
pub service_id: ServiceId,
pub hash: Hash,
}
impl ConstEncodedLen for BlockRef {}
impl core::fmt::Display for BlockRef {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:x}:", self.service_id)?;
for byte in self.hash.0.iter() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(
Encode, Decode, MaxEncodedLen, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug, Default,
)]
pub struct Hash(pub jam_types::Hash);
impl Hash {
pub const PREFIX: &str = "fs-";
pub fn digest(data: &[u8]) -> Self {
let h = blake2b_simd::Params::new().hash_length(32).hash(data);
Self(h.as_bytes().try_into().expect("Hash length set to 32"))
}
pub fn is_zero(&self) -> bool {
self.0.iter().all(|b| *b == 0)
}
}
impl ConstEncodedLen for Hash {}
impl core::fmt::Display for Hash {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(Hash::PREFIX)?;
for byte in self.0.iter() {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
pub trait ReadBlock {
fn read_block(&mut self, block_ref: &BlockRef) -> Result<Bytes, IoError>;
}
impl<R: ReadBlock + ?Sized> ReadBlock for &mut R {
fn read_block(&mut self, block_ref: &BlockRef) -> Result<Bytes, IoError> {
ReadBlock::read_block(*self, block_ref)
}
}
impl ReadBlock for VecMap<BlockRef, Bytes> {
fn read_block(&mut self, block_ref: &BlockRef) -> Result<Bytes, IoError> {
Ok(self.get(block_ref).ok_or(IoError)?.clone())
}
}
pub trait WriteBlock {
fn write_block(&mut self, service_id: ServiceId, buf: &[u8]) -> Result<(), IoError>;
}
impl<W: WriteBlock + ?Sized> WriteBlock for &mut W {
fn write_block(&mut self, service_id: ServiceId, buf: &[u8]) -> Result<(), IoError> {
WriteBlock::write_block(*self, service_id, buf)
}
}
impl WriteBlock for VecMap<BlockRef, Bytes> {
fn write_block(&mut self, service_id: ServiceId, buf: &[u8]) -> Result<(), IoError> {
let block_ref = BlockRef { service_id, hash: Hash::digest(buf) };
self.insert(block_ref, buf.to_vec().into());
Ok(())
}
}
pub trait HostFileRead {
fn remaining_len(&mut self) -> Result<u64, IoError>;
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), IoError>;
}
pub trait HostFileWrite {
fn write_all(&mut self, buf: &[u8]) -> Result<(), IoError>;
}
pub trait HostDirRead<F: HostFileRead> {
type Id: core::hash::Hash + core::cmp::Eq + core::cmp::Ord;
fn next_entry(&mut self) -> Option<Result<HostDirEntry, IoError>>;
fn open_file(&mut self, name: &FileName) -> Result<F, IoError>;
fn open_dir(&mut self, name: &FileName) -> Result<(Self, Option<Self::Id>), IoError>
where
Self: Sized;
}
#[derive(Debug)]
pub struct HostDirEntry {
pub kind: NodeKind,
pub file_name: FileName,
}
pub trait HostDirWrite {
type FileWrite: HostFileWrite;
fn create_file(&mut self, name: &FileName) -> Result<Self::FileWrite, IoError>;
fn create_dir(&mut self, name: &FileName) -> Result<Self, IoError>
where
Self: Sized;
}
pub trait HostWrite {
type FileWrite: HostFileWrite;
type DirWrite: HostDirWrite;
fn into_file_writer(self) -> Result<Self::FileWrite, IoError>;
fn into_dir_writer(self) -> Result<Self::DirWrite, IoError>;
}
fn vec_encoded_len<T: ConstEncodedLen>(len: usize) -> usize {
T::max_encoded_len() * len + Compact::<u64>::compact_len(&(len as u64))
}