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