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