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    v3::{self, write::FileEntry},
9    AbstractVolume, Metadata, PathSegment, PathSegments,
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
224impl WasmerPackageVolume for MemoryVolume {
225    fn as_directory_tree(
226        &self,
227        strictness: Strictness,
228    ) -> Result<webc::v3::write::Directory<'_>, anyhow::Error> {
229        let res = self.node.as_directory_tree(strictness);
230        res
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use sha2::{Digest, Sha256};
237    use v3::{
238        write::Writer, Checksum, ChecksumAlgorithm, Index, IndexEntry, Signature,
239        SignatureAlgorithm, Span, Tag, Timestamps,
240    };
241    use webc::metadata::Manifest;
242
243    use super::*;
244
245    fn sha256(data: impl AsRef<[u8]>) -> [u8; 32] {
246        let mut state = Sha256::default();
247        state.update(data.as_ref());
248        state.finalize().into()
249    }
250
251    #[test]
252    fn volume_metadata() -> anyhow::Result<()> {
253        let file_modified = SystemTime::now();
254        let file_data = String::from("Hello, world!").as_bytes().to_vec();
255        let file_data_len = file_data.len();
256
257        let file = MemoryFile {
258            modified: file_modified,
259            data: file_data,
260        };
261
262        let mut nodes = BTreeMap::new();
263        nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
264
265        let dir_modified = SystemTime::now();
266        let dir = MemoryDir {
267            modified: dir_modified,
268            nodes,
269        };
270
271        let volume = MemoryVolume { node: dir };
272
273        let file_metadata = volume.metadata(&PathSegments::from_str("hello.txt")?);
274        assert!(file_metadata.is_some());
275
276        let file_metadata = file_metadata.unwrap();
277        assert!(file_metadata.is_file());
278
279        let (length, timestamps) = match file_metadata {
280            Metadata::File { length, timestamps } => (length, timestamps),
281            _ => unreachable!(),
282        };
283
284        assert_eq!(
285            timestamps.unwrap().modified(),
286            file_modified.duration_since(UNIX_EPOCH)?.as_nanos() as u64
287        );
288
289        assert_eq!(length, file_data_len);
290
291        let dir_metadata = volume.metadata(&PathSegments::from_str("/")?);
292        assert!(dir_metadata.is_some());
293
294        let dir_metadata = dir_metadata.unwrap();
295        assert!(dir_metadata.is_dir());
296
297        let timestamps = match dir_metadata {
298            Metadata::Dir { timestamps } => timestamps,
299            _ => unreachable!(),
300        };
301
302        assert_eq!(
303            timestamps.unwrap().modified(),
304            dir_modified.duration_since(UNIX_EPOCH)?.as_nanos() as u64
305        );
306
307        Ok(())
308    }
309
310    #[test]
311    fn create_webc_file_from_memory() -> Result<(), Box<dyn std::error::Error>> {
312        let manifest = Manifest::default();
313
314        let mut writer = Writer::new(ChecksumAlgorithm::Sha256)
315            .write_manifest(&manifest)?
316            .write_atoms(BTreeMap::new())?;
317
318        let file_contents = "Hello, World!";
319        let file = MemoryFile {
320            modified: SystemTime::UNIX_EPOCH,
321            data: file_contents.as_bytes().to_vec(),
322        };
323        let mut nodes = BTreeMap::new();
324        nodes.insert(String::from("a"), MemoryNode::File(file));
325
326        let dir_modified = std::time::SystemTime::UNIX_EPOCH;
327        let dir = MemoryDir {
328            modified: dir_modified,
329            nodes,
330        };
331
332        let volume = MemoryVolume { node: dir };
333
334        writer.write_volume(
335            "first",
336            dbg!(WasmerPackageVolume::as_directory_tree(
337                &volume,
338                Strictness::Strict,
339            )?),
340        )?;
341
342        let webc = writer.finish(SignatureAlgorithm::None)?;
343
344        let mut data = vec![];
345        ciborium::into_writer(&manifest, &mut data).unwrap();
346        let manifest_hash: [u8; 32] = sha2::Sha256::digest(data).into();
347        let manifest_section = bytes! {
348            Tag::Manifest,
349            manifest_hash,
350            1_u64.to_le_bytes(),
351            [0xa0],
352        };
353
354        let empty_hash: [u8; 32] = sha2::Sha256::new().finalize().into();
355
356        let atoms_header_and_data = bytes! {
357            // header section
358            65_u64.to_le_bytes(),
359            Tag::Directory,
360            56_u64.to_le_bytes(),
361            Timestamps::default(),
362            empty_hash,
363            // data section (empty)
364            0_u64.to_le_bytes(),
365        };
366
367        let atoms_hash: [u8; 32] = sha2::Sha256::digest(&atoms_header_and_data).into();
368        let atoms_section = bytes! {
369            Tag::Atoms,
370            atoms_hash,
371            81_u64.to_le_bytes(),
372            atoms_header_and_data,
373        };
374
375        let a_hash: [u8; 32] = sha2::Sha256::digest(file_contents).into();
376        let dir_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
377        let volume_header_and_data = bytes! {
378            // ==== Name ====
379            5_u64.to_le_bytes(),
380            "first",
381            // ==== Header Section ====
382            187_u64.to_le_bytes(),
383            // ---- root directory ----
384            Tag::Directory,
385            105_u64.to_le_bytes(),
386            Timestamps::default(),
387            dir_hash,
388            // first entry
389            114_u64.to_le_bytes(),
390            a_hash,
391            1_u64.to_le_bytes(),
392            "a",
393
394            // ---- first item ----
395            Tag::File,
396            0_u64.to_le_bytes(),
397            13_u64.to_le_bytes(),
398            sha256("Hello, World!"),
399            Timestamps::default(),
400
401            // ==== Data Section ====
402            13_u64.to_le_bytes(),
403            file_contents,
404        };
405        let volume_hash: [u8; 32] = sha2::Sha256::digest(&volume_header_and_data).into();
406        let first_volume_section = bytes! {
407            Tag::Volume,
408            volume_hash,
409            229_u64.to_le_bytes(),
410            volume_header_and_data,
411        };
412
413        let index = Index::new(
414            IndexEntry::new(
415                Span::new(437, 42),
416                Checksum::sha256(sha256(&manifest_section[41..])),
417            ),
418            IndexEntry::new(
419                Span::new(479, 122),
420                Checksum::sha256(sha256(&atoms_section[41..])),
421            ),
422            [(
423                "first".to_string(),
424                IndexEntry::new(
425                    Span::new(601, 270),
426                    Checksum::sha256(sha256(&first_volume_section[41..])),
427                ),
428            )]
429            .into_iter()
430            .collect(),
431            Signature::none(),
432        );
433
434        let mut serialized_index = vec![];
435        ciborium::into_writer(&index, &mut serialized_index).unwrap();
436        let index_section = bytes! {
437            Tag::Index,
438            420_u64.to_le_bytes(),
439            serialized_index,
440            // padding bytes to compensate for an unknown index length
441            // NOTE: THIS VALUE IS COMPLETELY RANDOM AND YOU SHOULD GUESS WHAT VALUE
442            // WILL WORK.
443            [0_u8; 75],
444        };
445
446        assert_bytes_eq!(
447            &webc,
448            bytes! {
449                webc::MAGIC,
450                webc::Version::V3,
451                index_section,
452                manifest_section,
453                atoms_section,
454                first_volume_section,
455            }
456        );
457
458        // make sure the index is accurate
459        assert_bytes_eq!(&webc[index.manifest.span], manifest_section);
460        assert_bytes_eq!(&webc[index.atoms.span], atoms_section);
461        assert_bytes_eq!(&webc[index.volumes["first"].span], first_volume_section);
462
463        Ok(())
464    }
465}