wasmer_package/package/volume/
in_memory.rs

1use std::{
2    collections::BTreeMap,
3    str::FromStr,
4    time::{SystemTime, UNIX_EPOCH},
5};
6
7use webc::{
8    AbstractVolume, Metadata, PathSegment, PathSegments,
9    v3::{self, write::FileEntry},
10};
11
12use crate::package::Strictness;
13
14use super::WasmerPackageVolume;
15
16/// An in-memory representation of a volume.
17#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
18pub struct MemoryVolume {
19    /// The internal node
20    pub node: MemoryDir,
21}
22
23impl MemoryVolume {
24    /// The name of the volume used to store metadata files.
25    pub(crate) const METADATA: &'static str = "metadata";
26}
27
28/// An in-memory representation of a filesystem node.
29#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
30pub enum MemoryNode {
31    /// A file
32    File(MemoryFile),
33
34    /// A directory
35    Dir(MemoryDir),
36}
37
38impl MemoryNode {
39    /// Try to return a [`MemoryDir`] out of `self`.
40    pub fn as_dir(&self) -> Option<&MemoryDir> {
41        match self {
42            MemoryNode::Dir(d) => Some(d),
43            _ => None,
44        }
45    }
46
47    /// Try to return a [`MemoryFile`] out of `self`.
48    pub fn as_file(&self) -> Option<&MemoryFile> {
49        match self {
50            MemoryNode::File(f) => Some(f),
51            _ => None,
52        }
53    }
54
55    fn as_dir_entry(&self) -> anyhow::Result<webc::v3::write::DirEntry<'_>> {
56        match self {
57            MemoryNode::File(f) => f.as_dir_entry(),
58            MemoryNode::Dir(d) => d.as_dir_entry(),
59        }
60    }
61
62    fn metadata(&self) -> Metadata {
63        match self {
64            MemoryNode::File(f) => f.metadata(),
65            MemoryNode::Dir(d) => d.metadata(),
66        }
67    }
68}
69
70/// An in-memory file.
71#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
72pub struct MemoryFile {
73    /// When the file was last modified.
74    pub modified: SystemTime,
75    /// Raw data  
76    pub data: Vec<u8>,
77}
78impl MemoryFile {
79    fn as_dir_entry(&self) -> anyhow::Result<v3::write::DirEntry<'_>> {
80        Ok(v3::write::DirEntry::File(FileEntry::owned(
81            self.data.clone(),
82            v3::Timestamps {
83                modified: self.modified,
84            },
85        )))
86    }
87
88    fn metadata(&self) -> Metadata {
89        let modified = self.modified.duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
90        Metadata::File {
91            length: self.data.len(),
92            timestamps: Some(webc::Timestamps::from_modified(modified)),
93        }
94    }
95}
96
97/// An in-memory directory.
98#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
99pub struct MemoryDir {
100    /// When the directory or its contents were last modified.
101    pub modified: SystemTime,
102    /// List of nodes in the directory
103    pub nodes: BTreeMap<String, MemoryNode>,
104}
105
106impl MemoryDir {
107    fn metadata(&self) -> Metadata {
108        let modified = self.modified.duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
109        Metadata::Dir {
110            timestamps: Some(webc::Timestamps::from_modified(modified)),
111        }
112    }
113
114    // Can't return a reference to MemoryNode as it can return itself.
115    fn find_node(&self, path: &PathSegments) -> Option<MemoryNode> {
116        let mut segments = path.iter().collect::<Vec<_>>();
117        if segments.is_empty() {
118            return Some(MemoryNode::Dir(self.clone()));
119        }
120
121        let mut dir = self;
122
123        while !segments.is_empty() {
124            let next = (*segments.first().unwrap()).clone();
125            segments.remove(0);
126
127            if let Some(next_node) = dir.nodes.get(&next.to_string()) {
128                if segments.is_empty() {
129                    return Some(next_node.clone());
130                } else {
131                    match next_node {
132                        MemoryNode::File(_) => break,
133                        MemoryNode::Dir(d) => dir = d,
134                    }
135                }
136            }
137        }
138
139        None
140    }
141
142    fn read_file(&self, path: &PathSegments) -> Option<shared_buffer::OwnedBuffer> {
143        self.find_node(path).and_then(|n| {
144            if let MemoryNode::File(f) = n {
145                Some(shared_buffer::OwnedBuffer::from_bytes(f.data.clone()))
146            } else {
147                None
148            }
149        })
150    }
151
152    #[allow(clippy::type_complexity)]
153    fn read_dir(
154        &self,
155        path: &PathSegments,
156    ) -> Option<Vec<(PathSegment, Option<[u8; 32]>, Metadata)>> {
157        self.find_node(path).and_then(|n| {
158            if let MemoryNode::Dir(d) = n {
159                let mut ret = vec![];
160
161                for (name, node) in &d.nodes {
162                    let meta = node.metadata();
163                    ret.push((PathSegment::from_str(name).ok()?, None, meta))
164                }
165
166                Some(ret)
167            } else {
168                None
169            }
170        })
171    }
172
173    fn find_meta(&self, path: &PathSegments) -> Option<Metadata> {
174        self.find_node(path).map(|n| n.metadata())
175    }
176
177    fn as_directory_tree(
178        &self,
179        _strictness: Strictness,
180    ) -> Result<webc::v3::write::Directory<'_>, anyhow::Error> {
181        let mut children = BTreeMap::new();
182
183        for (key, value) in self.nodes.iter() {
184            children.insert(PathSegment::from_str(key)?, value.as_dir_entry()?);
185        }
186
187        let dir = v3::write::Directory::new(
188            children,
189            v3::Timestamps {
190                modified: self.modified,
191            },
192        );
193
194        Ok(dir)
195    }
196
197    fn as_dir_entry(&self) -> anyhow::Result<v3::write::DirEntry<'_>> {
198        Ok(v3::write::DirEntry::Dir(
199            self.as_directory_tree(Strictness::default())?,
200        ))
201    }
202}
203
204impl AbstractVolume for MemoryVolume {
205    fn read_file(
206        &self,
207        path: &PathSegments,
208    ) -> Option<(shared_buffer::OwnedBuffer, Option<[u8; 32]>)> {
209        self.node.read_file(path).map(|c| (c, None))
210    }
211
212    fn read_dir(
213        &self,
214        path: &PathSegments,
215    ) -> Option<Vec<(PathSegment, Option<[u8; 32]>, Metadata)>> {
216        self.node.read_dir(path)
217    }
218
219    fn metadata(&self, path: &PathSegments) -> Option<Metadata> {
220        self.node.find_meta(path)
221    }
222
223    fn read_link(&self, _path: &PathSegments) -> Option<(String, Option<[u8; 32]>)> {
224        None
225    }
226}
227
228impl WasmerPackageVolume for MemoryVolume {
229    fn as_directory_tree(
230        &self,
231        strictness: Strictness,
232    ) -> Result<webc::v3::write::Directory<'_>, anyhow::Error> {
233        self.node.as_directory_tree(strictness)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use sha2::{Digest, Sha256};
240    use v3::{
241        Checksum, ChecksumAlgorithm, Index, IndexEntry, Signature, SignatureAlgorithm, Span, Tag,
242        Timestamps, write::Writer,
243    };
244    use webc::metadata::Manifest;
245
246    use super::*;
247
248    fn sha256(data: impl AsRef<[u8]>) -> [u8; 32] {
249        let mut state = Sha256::default();
250        state.update(data.as_ref());
251        state.finalize().into()
252    }
253
254    #[test]
255    fn volume_metadata() -> anyhow::Result<()> {
256        let file_modified = SystemTime::now();
257        let file_data = String::from("Hello, world!").as_bytes().to_vec();
258        let file_data_len = file_data.len();
259
260        let file = MemoryFile {
261            modified: file_modified,
262            data: file_data,
263        };
264
265        let mut nodes = BTreeMap::new();
266        nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
267
268        let dir_modified = SystemTime::now();
269        let dir = MemoryDir {
270            modified: dir_modified,
271            nodes,
272        };
273
274        let volume = MemoryVolume { node: dir };
275
276        let file_metadata = volume.metadata(&PathSegments::from_str("hello.txt")?);
277        assert!(file_metadata.is_some());
278
279        let file_metadata = file_metadata.unwrap();
280        assert!(file_metadata.is_file());
281
282        let (length, timestamps) = match file_metadata {
283            Metadata::File { length, timestamps } => (length, timestamps),
284            _ => unreachable!(),
285        };
286
287        assert_eq!(
288            timestamps.unwrap().modified(),
289            file_modified.duration_since(UNIX_EPOCH)?.as_nanos() as u64
290        );
291
292        assert_eq!(length, file_data_len);
293
294        let dir_metadata = volume.metadata(&PathSegments::from_str("/")?);
295        assert!(dir_metadata.is_some());
296
297        let dir_metadata = dir_metadata.unwrap();
298        assert!(dir_metadata.is_dir());
299
300        let timestamps = match dir_metadata {
301            Metadata::Dir { timestamps } => timestamps,
302            _ => unreachable!(),
303        };
304
305        assert_eq!(
306            timestamps.unwrap().modified(),
307            dir_modified.duration_since(UNIX_EPOCH)?.as_nanos() as u64
308        );
309
310        Ok(())
311    }
312
313    #[test]
314    fn create_webc_file_from_memory() -> Result<(), Box<dyn std::error::Error>> {
315        let manifest = Manifest::default();
316
317        let mut writer = Writer::new(ChecksumAlgorithm::Sha256)
318            .write_manifest(&manifest)?
319            .write_atoms(BTreeMap::new())?;
320
321        let file_contents = "Hello, World!";
322        let file = MemoryFile {
323            modified: SystemTime::UNIX_EPOCH,
324            data: file_contents.as_bytes().to_vec(),
325        };
326        let mut nodes = BTreeMap::new();
327        nodes.insert(String::from("a"), MemoryNode::File(file));
328
329        let dir_modified = std::time::SystemTime::UNIX_EPOCH;
330        let dir = MemoryDir {
331            modified: dir_modified,
332            nodes,
333        };
334
335        let volume = MemoryVolume { node: dir };
336
337        writer.write_volume(
338            "first",
339            dbg!(WasmerPackageVolume::as_directory_tree(
340                &volume,
341                Strictness::Strict,
342            )?),
343        )?;
344
345        let webc = writer.finish(SignatureAlgorithm::None)?;
346
347        let mut data = vec![];
348        ciborium::into_writer(&manifest, &mut data).unwrap();
349        let manifest_hash: [u8; 32] = sha2::Sha256::digest(data).into();
350        let manifest_section = bytes! {
351            Tag::Manifest,
352            manifest_hash,
353            1_u64.to_le_bytes(),
354            [0xa0],
355        };
356
357        let empty_hash: [u8; 32] = sha2::Sha256::new().finalize().into();
358
359        let atoms_header_and_data = bytes! {
360            // header section
361            65_u64.to_le_bytes(),
362            Tag::Directory,
363            56_u64.to_le_bytes(),
364            Timestamps::default(),
365            empty_hash,
366            // data section (empty)
367            0_u64.to_le_bytes(),
368        };
369
370        let atoms_hash: [u8; 32] = sha2::Sha256::digest(&atoms_header_and_data).into();
371        let atoms_section = bytes! {
372            Tag::Atoms,
373            atoms_hash,
374            81_u64.to_le_bytes(),
375            atoms_header_and_data,
376        };
377
378        let a_hash: [u8; 32] = sha2::Sha256::digest(file_contents).into();
379        let dir_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
380        let volume_header_and_data = bytes! {
381            // ==== Name ====
382            5_u64.to_le_bytes(),
383            "first",
384            // ==== Header Section ====
385            187_u64.to_le_bytes(),
386            // ---- root directory ----
387            Tag::Directory,
388            105_u64.to_le_bytes(),
389            Timestamps::default(),
390            dir_hash,
391            // first entry
392            114_u64.to_le_bytes(),
393            a_hash,
394            1_u64.to_le_bytes(),
395            "a",
396
397            // ---- first item ----
398            Tag::File,
399            0_u64.to_le_bytes(),
400            13_u64.to_le_bytes(),
401            sha256("Hello, World!"),
402            Timestamps::default(),
403
404            // ==== Data Section ====
405            13_u64.to_le_bytes(),
406            file_contents,
407        };
408        let volume_hash: [u8; 32] = sha2::Sha256::digest(&volume_header_and_data).into();
409        let first_volume_section = bytes! {
410            Tag::Volume,
411            volume_hash,
412            229_u64.to_le_bytes(),
413            volume_header_and_data,
414        };
415
416        let index = Index::new(
417            IndexEntry::new(
418                Span::new(437, 42),
419                Checksum::sha256(sha256(&manifest_section[41..])),
420            ),
421            IndexEntry::new(
422                Span::new(479, 122),
423                Checksum::sha256(sha256(&atoms_section[41..])),
424            ),
425            [(
426                "first".to_string(),
427                IndexEntry::new(
428                    Span::new(601, 270),
429                    Checksum::sha256(sha256(&first_volume_section[41..])),
430                ),
431            )]
432            .into_iter()
433            .collect(),
434            Signature::none(),
435        );
436
437        let mut serialized_index = vec![];
438        ciborium::into_writer(&index, &mut serialized_index).unwrap();
439        let index_section = bytes! {
440            Tag::Index,
441            420_u64.to_le_bytes(),
442            serialized_index,
443            // padding bytes to compensate for an unknown index length
444            // NOTE: THIS VALUE IS COMPLETELY RANDOM AND YOU SHOULD GUESS WHAT VALUE
445            // WILL WORK.
446            [0_u8; 75],
447        };
448
449        assert_bytes_eq!(
450            &webc,
451            bytes! {
452                webc::MAGIC,
453                webc::Version::V3,
454                index_section,
455                manifest_section,
456                atoms_section,
457                first_volume_section,
458            }
459        );
460
461        // make sure the index is accurate
462        assert_bytes_eq!(&webc[index.manifest.span], manifest_section);
463        assert_bytes_eq!(&webc[index.atoms.span], atoms_section);
464        assert_bytes_eq!(&webc[index.volumes["first"].span], first_volume_section);
465
466        Ok(())
467    }
468}