corevm_host/
fs.rs

1use alloc::vec::Vec;
2use codec::{ConstEncodedLen, Decode, Encode, Input, MaxEncodedLen};
3use jam_types::{Hash, ServiceId, MAX_PREIMAGE_BLOB_LEN};
4
5/// Maximum allowed total size of all file blocks excluding the hashes.
6pub const MAX_FILE_LEN: u64 = 100 * 1024 * 1024;
7
8pub const MAX_FILE_BLOCK_LEN: u64 = MAX_PREIMAGE_BLOB_LEN as u64;
9pub const MIN_FILE_BLOCK_LEN: u64 = 32;
10
11/// Virtual file system block.
12///
13/// The first 32 bytes are occupied by the hash of the next block.
14/// If this is the last block the hash is zero.
15///
16/// `[ hash(32) | data(MAX_PREIMAGE_LEN - 32) ]`
17#[derive(Clone, Encode, Debug)]
18pub struct FileBlock(Vec<u8>);
19
20impl FileBlock {
21	pub fn new(v: Vec<u8>) -> Option<Self> {
22		if v.len() < 32 {
23			// File block must be at least 32 bytes long
24			return None;
25		}
26		Some(Self(v))
27	}
28	pub fn next(&self) -> Hash {
29		self.0[..32].try_into().expect("File block is always at least 32 bytes long")
30	}
31
32	pub fn data(&self) -> &[u8] {
33		&self.0[32..]
34	}
35
36	pub fn into_inner(self) -> Vec<u8> {
37		self.0
38	}
39}
40
41impl core::ops::Deref for FileBlock {
42	type Target = [u8];
43	fn deref(&self) -> &Self::Target {
44		self.0.as_slice()
45	}
46}
47
48impl AsRef<[u8]> for FileBlock {
49	fn as_ref(&self) -> &[u8] {
50		&self.0[..]
51	}
52}
53
54impl Decode for FileBlock {
55	fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
56		let bytes: Vec<u8> = Decode::decode(input)?;
57		if !(MIN_FILE_BLOCK_LEN..=MAX_FILE_BLOCK_LEN).contains(&(bytes.len() as u64)) {
58			return Err("Invalid file block".into());
59		}
60		Ok(Self(bytes))
61	}
62}
63
64impl MaxEncodedLen for FileBlock {
65	fn max_encoded_len() -> usize {
66		MAX_PREIMAGE_BLOB_LEN
67	}
68}
69
70/// Virtual file.
71///
72/// The file is stored as a series of file blocks, each block is a preimage.
73#[derive(Clone, Debug)]
74pub struct File {
75	blocks: Vec<(Hash, FileBlock)>,
76}
77
78impl File {
79	pub fn new(data: &[u8]) -> Self {
80		let iter = data.chunks(MAX_DATA_LEN);
81		let mut blocks = Vec::with_capacity(iter.len());
82		// Hash of the next file block.
83		let mut next_hash = [0_u8; 32];
84		for data in iter.rev() {
85			let mut block = Vec::with_capacity(data.len() + 32);
86			block.extend_from_slice(&next_hash[..]);
87			block.extend_from_slice(data);
88			next_hash = hash_raw(&block);
89			blocks.push((next_hash, FileBlock(block)));
90		}
91		blocks.reverse();
92		Self { blocks }
93	}
94
95	/// Read the file from the storage via the provided `lookup` function.
96	///
97	/// `make_error` is used to create custom error when the file or one of its blocks is invalid
98	/// (too small or too large).
99	pub fn read<F1, F2, E>(file: &FileRef, mut lookup: F1, make_error: F2) -> Result<Self, E>
100	where
101		F1: FnMut(ServiceId, &Hash) -> Result<Vec<u8>, E>,
102		F2: FnOnce() -> E,
103	{
104		let mut blocks = Vec::new();
105		let mut next_hash = file.hash;
106		let mut file_len = 0;
107		while next_hash != [0; 32] {
108			let block = lookup(file.service_id, &next_hash)?;
109			if !(MIN_FILE_BLOCK_LEN..=MAX_FILE_BLOCK_LEN).contains(&(block.len() as u64)) {
110				// The file block is too large/small.
111				return Err(make_error());
112			}
113			if file_len + block.len() > MAX_FILE_LEN as usize {
114				// The file is too large.
115				return Err(make_error());
116			}
117			let next_next_hash = block[..32].try_into().expect("The length is 32");
118			file_len += block.len();
119			blocks.push((next_hash, FileBlock(block)));
120			next_hash = next_next_hash;
121		}
122		Ok(Self { blocks })
123	}
124
125	pub fn from_blocks(blocks: Vec<(Hash, FileBlock)>) -> Self {
126		Self { blocks }
127	}
128
129	/// Hash of the first block.
130	pub fn first_hash(&self) -> Hash {
131		self.blocks[0].0
132	}
133
134	pub fn into_inner(self) -> Vec<(Hash, FileBlock)> {
135		self.blocks
136	}
137}
138
139impl core::ops::Deref for File {
140	type Target = [(Hash, FileBlock)];
141	fn deref(&self) -> &Self::Target {
142		self.blocks.as_slice()
143	}
144}
145
146fn hash_raw(data: &[u8]) -> Hash {
147	let h = blake2b_simd::Params::new().hash_length(32).hash(data);
148	h.as_bytes().try_into().expect("Hash length set to 32")
149}
150
151const MAX_DATA_LEN: usize = MAX_PREIMAGE_BLOB_LEN - 32;
152
153/// File location in the virtual file system.
154#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode, MaxEncodedLen, Debug)]
155pub struct FileRef {
156	/// Id of the service where all the file blocks are stored.
157	pub service_id: ServiceId,
158	/// Hash of the first file block.
159	pub hash: Hash,
160}
161
162impl ConstEncodedLen for FileRef {}