1use crate::{error::Error, fs::ChunkType};
2use ic_stable_structures::storable::Bound;
3use serde::{Deserialize, Serialize};
4
5pub const FILE_CHUNK_SIZE_V1: usize = 4096;
6
7pub const DEFAULT_FILE_CHUNK_SIZE_V2: usize = 16384;
8pub const MAX_FILE_CHUNK_SIZE_V2: usize = 65536;
9
10pub const MAX_FILE_NAME: usize = 255;
11
12pub const MAX_FILE_CHUNK_COUNT: u32 = u32::MAX - 10;
14
15pub const MAX_FILE_SIZE: u64 = (MAX_FILE_CHUNK_COUNT as u64) * FILE_CHUNK_SIZE_V1 as u64;
17
18pub const MAX_FILE_ENTRY_INDEX: u32 = u32::MAX - 10;
20
21pub const DUMMY_DOT_ENTRY_INDEX: u32 = u32::MAX - 5;
23pub const DUMMY_DOT_DOT_ENTRY_INDEX: u32 = u32::MAX - 4;
25
26pub const DUMMY_DOT_ENTRY: (DirEntryIndex, DirEntry) = (
27 DUMMY_DOT_ENTRY_INDEX,
28 DirEntry {
29 name: FileName {
30 length: 1,
31 bytes: {
32 let mut arr = [0u8; 255];
33 arr[0] = b'.';
34 arr
35 },
36 },
37 node: 0,
38 next_entry: None,
39 prev_entry: None,
40 },
41);
42
43pub const DUMMY_DOT_DOT_ENTRY: (DirEntryIndex, DirEntry) = (
44 DUMMY_DOT_DOT_ENTRY_INDEX,
45 DirEntry {
46 name: FileName {
47 length: 2,
48 bytes: {
49 let mut arr = [0u8; 255];
50 arr[0] = b'.';
51 arr[1] = b'.';
52 arr
53 },
54 },
55 node: 0,
56 next_entry: None,
57 prev_entry: None,
58 },
59);
60
61pub type Node = u64;
64
65pub type FileSize = u64;
67
68pub type FileChunkIndex = u32;
70
71pub type FileChunkPtr = u64;
73
74pub static ZEROES: [u8; MAX_FILE_CHUNK_SIZE_V2] = [0u8; MAX_FILE_CHUNK_SIZE_V2];
76
77#[derive(Debug, PartialEq, Eq)]
79pub(crate) struct ChunkHandle {
80 pub index: FileChunkIndex,
81 pub offset: FileSize,
82 pub len: FileSize,
83}
84
85#[derive(Clone, Debug, PartialEq)]
87pub struct FileChunk {
88 pub bytes: [u8; FILE_CHUNK_SIZE_V1],
89}
90
91impl Default for FileChunk {
92 fn default() -> Self {
93 Self {
94 bytes: [0; FILE_CHUNK_SIZE_V1],
95 }
96 }
97}
98
99impl ic_stable_structures::Storable for FileChunk {
100 fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
101 std::borrow::Cow::Borrowed(&self.bytes)
102 }
103
104 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
105 Self {
106 bytes: bytes.as_ref().try_into().unwrap(),
107 }
108 }
109
110 const BOUND: Bound = Bound::Bounded {
111 max_size: FILE_CHUNK_SIZE_V1 as u32,
112 is_fixed_size: true,
113 };
114}
115
116#[derive(Clone, Debug, Default, Serialize, Deserialize)]
117pub struct Header {
118 pub version: u32,
119 pub next_node: Node,
120}
121
122impl ic_stable_structures::Storable for Header {
123 fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
124 let mut buf = vec![];
125 ciborium::ser::into_writer(&self, &mut buf).unwrap();
126 std::borrow::Cow::Owned(buf)
127 }
128
129 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
130 ciborium::de::from_reader(bytes.as_ref()).unwrap()
131 }
132
133 const BOUND: Bound = Bound::Unbounded;
134}
135
136#[repr(C, align(8))]
137#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
138pub struct Metadata {
139 pub node: Node,
140 pub file_type: FileType,
141 pub link_count: u64,
142 pub size: FileSize,
143 pub times: Times,
144 pub first_dir_entry: Option<DirEntryIndex>,
145 pub last_dir_entry: Option<DirEntryIndex>,
146 pub chunk_type: Option<ChunkType>,
147 pub maximum_size_allowed: Option<FileSize>,
148}
149
150impl ic_stable_structures::Storable for Metadata {
151 fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
152 let mut buf = vec![];
153 ciborium::ser::into_writer(&self, &mut buf).unwrap();
154 std::borrow::Cow::Owned(buf)
155 }
156
157 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
158 ciborium::de::from_reader(bytes.as_ref()).unwrap()
159 }
160
161 const BOUND: Bound = Bound::Unbounded;
162}
163
164#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub enum FileType {
167 Directory = 3,
168 #[default]
169 RegularFile = 4,
170 SymbolicLink = 7,
171}
172
173impl TryFrom<u8> for FileType {
174 type Error = Error;
175
176 fn try_from(value: u8) -> Result<Self, Self::Error> {
177 match value {
178 3 => Ok(FileType::Directory),
179 4 => Ok(FileType::RegularFile),
180 7 => Ok(FileType::SymbolicLink),
181 _ => Err(Error::InvalidArgument),
182 }
183 }
184}
185
186impl From<FileType> for u8 {
187 fn from(val: FileType) -> Self {
188 match val {
189 FileType::Directory => 3,
190 FileType::RegularFile => 4,
191 FileType::SymbolicLink => 7,
192 }
193 }
194}
195
196#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
198pub struct Times {
199 pub accessed: u64,
200 pub modified: u64,
201 pub created: u64,
202}
203
204#[derive(Clone, Debug, Serialize, Deserialize)]
207pub struct FileName {
208 pub length: u8,
209 #[serde(
210 deserialize_with = "deserialize_file_name",
211 serialize_with = "serialize_file_name"
212 )]
213 pub bytes: [u8; MAX_FILE_NAME],
214}
215
216fn serialize_file_name<S>(bytes: &[u8; MAX_FILE_NAME], serializer: S) -> Result<S::Ok, S::Error>
217where
218 S: serde::Serializer,
219{
220 serde_bytes::Bytes::new(bytes).serialize(serializer)
221}
222
223fn deserialize_file_name<'de, D>(deserializer: D) -> Result<[u8; MAX_FILE_NAME], D::Error>
224where
225 D: serde::Deserializer<'de>,
226{
227 let bytes: Vec<u8> = serde_bytes::deserialize(deserializer).unwrap();
228 let len = bytes.len();
229 let bytes_array: [u8; MAX_FILE_NAME] = bytes
230 .try_into()
231 .map_err(|_| serde::de::Error::invalid_length(len, &"expected MAX_FILE_NAME bytes"))?;
232 Ok(bytes_array)
233}
234
235impl Default for FileName {
236 fn default() -> Self {
237 Self {
238 length: 0,
239 bytes: [0; MAX_FILE_NAME],
240 }
241 }
242}
243
244impl FileName {
245 pub fn new(name: &[u8]) -> Result<Self, Error> {
246 let len = name.len();
247 if len > MAX_FILE_NAME {
248 return Err(Error::FilenameTooLong);
249 }
250 let mut bytes = [0; MAX_FILE_NAME];
251 bytes[0..len].copy_from_slice(name);
252 Ok(Self {
253 length: len as u8,
254 bytes,
255 })
256 }
257}
258
259pub type DirEntryIndex = u32;
261
262#[derive(Clone, Debug, Default, Serialize, Deserialize)]
265pub struct DirEntry {
266 pub name: FileName,
267 pub node: Node,
268 pub next_entry: Option<DirEntryIndex>,
269 pub prev_entry: Option<DirEntryIndex>,
270}
271
272impl ic_stable_structures::Storable for DirEntry {
273 fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
274 let mut buf = vec![];
275 ciborium::ser::into_writer(&self, &mut buf).unwrap();
276 std::borrow::Cow::Owned(buf)
277 }
278
279 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
280 ciborium::de::from_reader(bytes.as_ref()).unwrap()
281 }
282
283 const BOUND: ic_stable_structures::storable::Bound = Bound::Unbounded;
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::fs::ChunkType;
289
290 use super::{DirEntryIndex, FileSize, FileType, Node, Times};
291 use serde::{Deserialize, Serialize};
292
293 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
295 pub struct MetadataOld {
296 pub node: Node,
297 pub file_type: FileType,
298 pub link_count: u64,
299 pub size: FileSize,
300 pub times: Times,
301 pub first_dir_entry: Option<DirEntryIndex>,
302 pub last_dir_entry: Option<DirEntryIndex>,
303 }
304
305 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
307 pub struct MetadataNew {
308 pub node: Node,
309 pub file_type: FileType,
310 pub link_count: u64,
311 pub size: FileSize,
312 pub times: Times,
313 pub first_dir_entry: Option<DirEntryIndex>,
314 pub last_dir_entry: Option<DirEntryIndex>,
315 pub chunk_type: Option<ChunkType>,
316 }
317
318 fn meta_to_bytes(meta: &MetadataOld) -> std::borrow::Cow<[u8]> {
319 let mut buf = vec![];
320 ciborium::ser::into_writer(meta, &mut buf).unwrap();
321 std::borrow::Cow::Owned(buf)
322 }
323
324 fn meta_from_bytes(bytes: std::borrow::Cow<[u8]>) -> MetadataNew {
325 ciborium::de::from_reader(bytes.as_ref()).unwrap()
326 }
327
328 #[test]
329 fn store_old_load_new() {
330 let meta_old = MetadataOld {
331 node: 23,
332 file_type: FileType::RegularFile,
333 link_count: 3,
334 size: 123,
335 times: Times::default(),
336 first_dir_entry: Some(23),
337 last_dir_entry: Some(35),
338 };
339
340 let bytes = meta_to_bytes(&meta_old);
341
342 let meta_new = meta_from_bytes(bytes);
343
344 assert_eq!(meta_new.node, meta_old.node);
345 assert_eq!(meta_new.file_type, meta_old.file_type);
346 assert_eq!(meta_new.link_count, meta_old.link_count);
347 assert_eq!(meta_new.size, meta_old.size);
348 assert_eq!(meta_new.times, meta_old.times);
349 assert_eq!(meta_new.first_dir_entry, meta_old.first_dir_entry);
350 assert_eq!(meta_new.last_dir_entry, meta_old.last_dir_entry);
351 assert_eq!(meta_new.chunk_type, None);
352 }
353
354 #[test]
355 fn store_old_load_new_both_none() {
356 let meta_old = MetadataOld {
357 node: 23,
358 file_type: FileType::RegularFile,
359 link_count: 3,
360 size: 123,
361 times: Times::default(),
362 first_dir_entry: None,
363 last_dir_entry: None,
364 };
365
366 let bytes = meta_to_bytes(&meta_old);
367
368 let meta_new = meta_from_bytes(bytes);
369
370 assert_eq!(meta_new.node, meta_old.node);
371 assert_eq!(meta_new.file_type, meta_old.file_type);
372 assert_eq!(meta_new.link_count, meta_old.link_count);
373 assert_eq!(meta_new.size, meta_old.size);
374 assert_eq!(meta_new.times, meta_old.times);
375 assert_eq!(meta_new.first_dir_entry, meta_old.first_dir_entry);
376 assert_eq!(meta_new.last_dir_entry, meta_old.last_dir_entry);
377 assert_eq!(meta_new.chunk_type, None);
378 }
379
380 #[test]
381 fn store_old_load_new_first_none() {
382 let meta_old = MetadataOld {
383 node: 23,
384 file_type: FileType::RegularFile,
385 link_count: 3,
386 size: 123,
387 times: Times::default(),
388 first_dir_entry: None,
389 last_dir_entry: Some(23),
390 };
391
392 let bytes = meta_to_bytes(&meta_old);
393
394 let meta_new = meta_from_bytes(bytes);
395
396 assert_eq!(meta_new.node, meta_old.node);
397 assert_eq!(meta_new.file_type, meta_old.file_type);
398 assert_eq!(meta_new.link_count, meta_old.link_count);
399 assert_eq!(meta_new.size, meta_old.size);
400 assert_eq!(meta_new.times, meta_old.times);
401 assert_eq!(meta_new.first_dir_entry, meta_old.first_dir_entry);
402 assert_eq!(meta_new.last_dir_entry, meta_old.last_dir_entry);
403 assert_eq!(meta_new.chunk_type, None);
404 }
405}