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 entry_type: None,
39 },
40);
41
42pub const DUMMY_DOT_DOT_ENTRY: (DirEntryIndex, DirEntry) = (
43 DUMMY_DOT_DOT_ENTRY_INDEX,
44 DirEntry {
45 name: FileName {
46 length: 2,
47 bytes: {
48 let mut arr = [0u8; 255];
49 arr[0] = b'.';
50 arr[1] = b'.';
51 arr
52 },
53 },
54 node: 0,
55 entry_type: None,
56 },
57);
58
59pub type Node = u64;
62
63pub type FileSize = u64;
65
66pub type FileChunkIndex = u32;
68
69pub type FileChunkPtr = u64;
71
72pub static ZEROES: [u8; MAX_FILE_CHUNK_SIZE_V2] = [0u8; MAX_FILE_CHUNK_SIZE_V2];
74
75#[derive(Debug, PartialEq, Eq)]
77pub(crate) struct ChunkHandle {
78 pub index: FileChunkIndex,
79 pub offset: FileSize,
80 pub len: FileSize,
81}
82
83#[derive(Clone, Debug, PartialEq)]
85pub struct FileChunk {
86 pub bytes: [u8; FILE_CHUNK_SIZE_V1],
87}
88
89impl Default for FileChunk {
90 fn default() -> Self {
91 Self {
92 bytes: [0; FILE_CHUNK_SIZE_V1],
93 }
94 }
95}
96
97impl ic_stable_structures::Storable for FileChunk {
98 fn to_bytes(&'_ self) -> std::borrow::Cow<'_, [u8]> {
99 std::borrow::Cow::Borrowed(&self.bytes)
100 }
101
102 fn into_bytes(self) -> Vec<u8> {
103 self.bytes.to_vec()
104 }
105
106 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
107 Self {
108 bytes: bytes.as_ref().try_into().unwrap(),
109 }
110 }
111
112 const BOUND: Bound = Bound::Bounded {
113 max_size: FILE_CHUNK_SIZE_V1 as u32,
114 is_fixed_size: true,
115 };
116}
117
118#[derive(Clone, Debug, Default, Serialize, Deserialize)]
119pub struct Header {
120 pub version: u32,
121 pub next_node: Node,
122}
123
124impl ic_stable_structures::Storable for Header {
125 fn to_bytes(&'_ self) -> std::borrow::Cow<'_, [u8]> {
126 let mut buf = vec![];
127 ciborium::ser::into_writer(&self, &mut buf).unwrap();
128 std::borrow::Cow::Owned(buf)
129 }
130
131 fn into_bytes(self) -> Vec<u8> {
132 let mut buf = vec![];
133 ciborium::ser::into_writer(&self, &mut buf).unwrap();
134 buf
135 }
136
137 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
138 ciborium::de::from_reader(bytes.as_ref()).unwrap()
139 }
140
141 const BOUND: Bound = Bound::Unbounded;
142}
143
144#[repr(C, align(8))]
145#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
146pub struct Metadata {
147 pub node: Node,
148 pub file_type: FileType,
149 pub link_count: u64,
150 pub size: FileSize,
151 pub times: Times,
152 pub first_dir_entry: Option<DirEntryIndex>, pub last_dir_entry: Option<DirEntryIndex>, pub chunk_type: Option<ChunkType>,
155 pub maximum_size_allowed: Option<FileSize>,
156}
157
158impl ic_stable_structures::Storable for Metadata {
159 fn to_bytes(&'_ self) -> std::borrow::Cow<'_, [u8]> {
160 let mut buf = vec![];
161 ciborium::ser::into_writer(&self, &mut buf).unwrap();
162 std::borrow::Cow::Owned(buf)
163 }
164
165 fn into_bytes(self) -> Vec<u8> {
166 let mut buf = vec![];
167 ciborium::ser::into_writer(&self, &mut buf).unwrap();
168 buf
169 }
170
171 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
172 ciborium::de::from_reader(bytes.as_ref()).unwrap()
173 }
174
175 const BOUND: Bound = Bound::Unbounded;
176}
177
178#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
180pub enum FileType {
181 Directory = 3,
182 #[default]
183 RegularFile = 4,
184 SymbolicLink = 7,
185}
186
187impl TryFrom<u8> for FileType {
188 type Error = Error;
189
190 fn try_from(value: u8) -> Result<Self, Self::Error> {
191 match value {
192 3 => Ok(FileType::Directory),
193 4 => Ok(FileType::RegularFile),
194 7 => Ok(FileType::SymbolicLink),
195 _ => Err(Error::InvalidArgument),
196 }
197 }
198}
199
200impl From<FileType> for u8 {
201 fn from(val: FileType) -> Self {
202 match val {
203 FileType::Directory => 3,
204 FileType::RegularFile => 4,
205 FileType::SymbolicLink => 7,
206 }
207 }
208}
209
210#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
212pub struct Times {
213 pub accessed: u64,
214 pub modified: u64,
215 pub created: u64,
216}
217
218use std::cmp::{Ord, Ordering, PartialOrd};
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
223pub struct FileName {
224 pub length: u8,
225 #[serde(
226 deserialize_with = "deserialize_file_name",
227 serialize_with = "serialize_file_name"
228 )]
229 pub bytes: [u8; MAX_FILE_NAME],
230}
231
232impl Eq for FileName {}
233
234impl PartialEq for FileName {
235 fn eq(&self, other: &Self) -> bool {
236 self.length == other.length
237 && self.bytes[..self.length as usize] == other.bytes[..other.length as usize]
238 }
239}
240
241impl PartialOrd for FileName {
242 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
243 Some(self.cmp(other))
244 }
245}
246
247impl Ord for FileName {
248 fn cmp(&self, other: &Self) -> Ordering {
249 let min_len = self.length.min(other.length) as usize;
250
251 match self.bytes[..min_len].cmp(&other.bytes[..min_len]) {
252 Ordering::Equal => self.length.cmp(&other.length),
253 ord => ord,
254 }
255 }
256}
257
258use ic_stable_structures::Storable;
259
260impl Storable for FileName {
261 fn to_bytes(&'_ self) -> std::borrow::Cow<'_, [u8]> {
262 let mut buf = [0u8; MAX_FILE_NAME + 1];
263
264 buf[0] = self.length;
265 buf[1..256].copy_from_slice(&self.bytes);
266
267 std::borrow::Cow::Owned(buf.to_vec())
268 }
269
270 fn into_bytes(self) -> Vec<u8> {
271 let mut buf = [0u8; MAX_FILE_NAME + 1];
272
273 buf[0] = self.length;
274 buf[1..256].copy_from_slice(&self.bytes);
275
276 buf.to_vec()
277 }
278
279 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
280 let mut arr = [0u8; MAX_FILE_NAME];
281 arr.copy_from_slice(&bytes[1..MAX_FILE_NAME + 1]);
282
283 FileName {
284 length: bytes[0],
285 bytes: arr,
286 }
287 }
288
289 const BOUND: ic_stable_structures::storable::Bound =
290 ic_stable_structures::storable::Bound::Bounded {
291 max_size: 256,
292 is_fixed_size: true,
293 };
294}
295
296fn serialize_file_name<S>(bytes: &[u8; MAX_FILE_NAME], serializer: S) -> Result<S::Ok, S::Error>
297where
298 S: serde::Serializer,
299{
300 serde_bytes::Bytes::new(bytes).serialize(serializer)
301}
302
303fn deserialize_file_name<'de, D>(deserializer: D) -> Result<[u8; MAX_FILE_NAME], D::Error>
304where
305 D: serde::Deserializer<'de>,
306{
307 let bytes: Vec<u8> = serde_bytes::deserialize(deserializer).unwrap();
308 let len = bytes.len();
309 let bytes_array: [u8; MAX_FILE_NAME] = bytes
310 .try_into()
311 .map_err(|_| serde::de::Error::invalid_length(len, &"expected MAX_FILE_NAME bytes"))?;
312 Ok(bytes_array)
313}
314
315impl Default for FileName {
316 fn default() -> Self {
317 Self {
318 length: 0,
319 bytes: [0; MAX_FILE_NAME],
320 }
321 }
322}
323
324impl FileName {
325 pub fn new(name: &[u8]) -> Result<Self, Error> {
326 let len = name.len();
327 if len > MAX_FILE_NAME {
328 return Err(Error::FilenameTooLong);
329 }
330
331 let mut bytes = [0; MAX_FILE_NAME];
332 bytes[0..len].copy_from_slice(name);
333 Ok(Self {
334 length: len as u8,
335 bytes,
336 })
337 }
338}
339
340impl std::fmt::Display for FileName {
341 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
342 write!(f, "{}", unsafe {
343 std::str::from_utf8_unchecked(&self.bytes[..(self.length as usize)])
344 })
345 }
346}
347
348pub type DirEntryIndex = u32;
350
351#[derive(Clone, Debug, Default, Serialize, Deserialize)]
354pub struct DirEntry {
355 pub name: FileName,
356 pub node: Node,
357 pub entry_type: Option<FileType>,
358}
359
360impl ic_stable_structures::Storable for DirEntry {
361 fn to_bytes(&'_ self) -> std::borrow::Cow<'_, [u8]> {
362 let mut buf = vec![];
363 ciborium::ser::into_writer(&self, &mut buf).unwrap();
364 std::borrow::Cow::Owned(buf)
365 }
366
367 fn into_bytes(self) -> Vec<u8> {
368 let mut buf = vec![];
369 ciborium::ser::into_writer(&self, &mut buf).unwrap();
370 buf
371 }
372
373 fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
374 ciborium::de::from_reader(bytes.as_ref()).unwrap()
375 }
376
377 const BOUND: ic_stable_structures::storable::Bound = Bound::Unbounded;
378}
379
380pub enum MountedFileSizePolicy {
382 PreviousOrZero,
384 PreviousOrMemoryPages,
386 Explicit(FileSize),
388 MemoryPages,
390}
391
392impl MountedFileSizePolicy {
393 pub fn get_mounted_file_size(
394 &self,
395 previous_size: Option<FileSize>,
396 current_memory_pages: FileSize,
397 ) -> FileSize {
398 let current_size = current_memory_pages * ic_cdk::stable::WASM_PAGE_SIZE_IN_BYTES;
399
400 match self {
401 MountedFileSizePolicy::PreviousOrZero => {
402 if let Some(old_size) = previous_size
403 && old_size <= current_size
404 {
405 old_size
406 } else {
407 0
408 }
409 }
410 MountedFileSizePolicy::PreviousOrMemoryPages => {
411 if let Some(old_size) = previous_size
412 && old_size <= current_size
413 {
414 old_size
415 } else {
416 current_size
417 }
418 }
419 MountedFileSizePolicy::Explicit(size) => *size,
420 MountedFileSizePolicy::MemoryPages => current_size,
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use crate::{fs::ChunkType, storage::types::MountedFileSizePolicy};
428
429 use super::{DirEntryIndex, FileSize, FileType, Node, Times};
430 use serde::{Deserialize, Serialize};
431
432 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
434 pub struct MetadataOld {
435 pub node: Node,
436 pub file_type: FileType,
437 pub link_count: u64,
438 pub size: FileSize,
439 pub times: Times,
440 pub first_dir_entry: Option<DirEntryIndex>,
441 pub last_dir_entry: Option<DirEntryIndex>,
442 }
443
444 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
446 pub struct MetadataNew {
447 pub node: Node,
448 pub file_type: FileType,
449 pub link_count: u64,
450 pub size: FileSize,
451 pub times: Times,
452 pub _first_dir_entry: Option<DirEntryIndex>, pub _last_dir_entry: Option<DirEntryIndex>, pub chunk_type: Option<ChunkType>,
455 }
456
457 fn meta_to_bytes(meta: &'_ MetadataOld) -> std::borrow::Cow<'_, [u8]> {
458 let mut buf = vec![];
459 ciborium::ser::into_writer(meta, &mut buf).unwrap();
460 std::borrow::Cow::Owned(buf)
461 }
462
463 fn meta_from_bytes(bytes: std::borrow::Cow<[u8]>) -> MetadataNew {
464 ciborium::de::from_reader(bytes.as_ref()).unwrap()
465 }
466
467 #[test]
468 fn store_old_load_new() {
469 let meta_old = MetadataOld {
470 node: 23,
471 file_type: FileType::RegularFile,
472 link_count: 3,
473 size: 123,
474 times: Times::default(),
475 first_dir_entry: Some(23),
476 last_dir_entry: Some(35),
477 };
478
479 let bytes = meta_to_bytes(&meta_old);
480
481 let meta_new = meta_from_bytes(bytes);
482
483 assert_eq!(meta_new.node, meta_old.node);
484 assert_eq!(meta_new.file_type, meta_old.file_type);
485 assert_eq!(meta_new.link_count, meta_old.link_count);
486 assert_eq!(meta_new.size, meta_old.size);
487 assert_eq!(meta_new.times, meta_old.times);
488 assert_eq!(meta_new.chunk_type, None);
489 }
490
491 #[test]
492 fn store_old_load_new_both_none() {
493 let meta_old = MetadataOld {
494 node: 23,
495 file_type: FileType::RegularFile,
496 link_count: 3,
497 size: 123,
498 times: Times::default(),
499 first_dir_entry: None,
500 last_dir_entry: None,
501 };
502
503 let bytes = meta_to_bytes(&meta_old);
504
505 let meta_new = meta_from_bytes(bytes);
506
507 assert_eq!(meta_new.node, meta_old.node);
508 assert_eq!(meta_new.file_type, meta_old.file_type);
509 assert_eq!(meta_new.link_count, meta_old.link_count);
510 assert_eq!(meta_new.size, meta_old.size);
511 assert_eq!(meta_new.times, meta_old.times);
512 assert_eq!(meta_new.chunk_type, None);
513 }
514
515 #[test]
516 fn store_old_load_new_first_none() {
517 let meta_old = MetadataOld {
518 node: 23,
519 file_type: FileType::RegularFile,
520 link_count: 3,
521 size: 123,
522 times: Times::default(),
523 first_dir_entry: None,
524 last_dir_entry: Some(23),
525 };
526
527 let bytes = meta_to_bytes(&meta_old);
528
529 let meta_new = meta_from_bytes(bytes);
530
531 assert_eq!(meta_new.node, meta_old.node);
532 assert_eq!(meta_new.file_type, meta_old.file_type);
533 assert_eq!(meta_new.link_count, meta_old.link_count);
534 assert_eq!(meta_new.size, meta_old.size);
535 assert_eq!(meta_new.times, meta_old.times);
536 assert_eq!(meta_new.chunk_type, None);
537 }
538
539 #[test]
540 fn size_policy_test() {
541 use ic_cdk::stable::WASM_PAGE_SIZE_IN_BYTES as PAGE_SIZE;
542
543 let p = MountedFileSizePolicy::PreviousOrZero;
544 assert_eq!(p.get_mounted_file_size(Some(100000), 1), 0);
545 assert_eq!(p.get_mounted_file_size(Some(100000), 2), 100000);
546 assert_eq!(p.get_mounted_file_size(None, 2), 0);
547
548 let p = MountedFileSizePolicy::PreviousOrMemoryPages;
549 assert_eq!(p.get_mounted_file_size(Some(100000), 1), PAGE_SIZE);
550 assert_eq!(p.get_mounted_file_size(Some(100000), 2), 100000);
551 assert_eq!(p.get_mounted_file_size(None, 2), PAGE_SIZE * 2);
552
553 let p = MountedFileSizePolicy::Explicit(3000);
554 assert_eq!(p.get_mounted_file_size(Some(100000), 1), 3000);
555 assert_eq!(p.get_mounted_file_size(Some(100000), 2), 3000);
556 assert_eq!(p.get_mounted_file_size(None, 2), 3000);
557
558 let p = MountedFileSizePolicy::MemoryPages;
559 assert_eq!(p.get_mounted_file_size(Some(100000), 1), PAGE_SIZE);
560 assert_eq!(p.get_mounted_file_size(Some(100000), 2), PAGE_SIZE * 2);
561 assert_eq!(p.get_mounted_file_size(None, 2), PAGE_SIZE * 2);
562 }
563}