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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
18pub struct MemoryVolume {
19 pub node: MemoryDir,
21}
22
23impl MemoryVolume {
24 pub(crate) const METADATA: &'static str = "metadata";
26}
27
28#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
30pub enum MemoryNode {
31 File(MemoryFile),
33
34 Dir(MemoryDir),
36}
37
38impl MemoryNode {
39 pub fn as_dir(&self) -> Option<&MemoryDir> {
41 match self {
42 MemoryNode::Dir(d) => Some(d),
43 _ => None,
44 }
45 }
46
47 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
72pub struct MemoryFile {
73 pub modified: SystemTime,
75 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#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
99pub struct MemoryDir {
100 pub modified: SystemTime,
102 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 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 65_u64.to_le_bytes(),
358 Tag::Directory,
359 56_u64.to_le_bytes(),
360 Timestamps::default(),
361 empty_hash,
362 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 5_u64.to_le_bytes(),
379 "first",
380 187_u64.to_le_bytes(),
382 Tag::Directory,
384 105_u64.to_le_bytes(),
385 Timestamps::default(),
386 dir_hash,
387 114_u64.to_le_bytes(),
389 a_hash,
390 1_u64.to_le_bytes(),
391 "a",
392
393 Tag::File,
395 0_u64.to_le_bytes(),
396 13_u64.to_le_bytes(),
397 sha256("Hello, World!"),
398 Timestamps::default(),
399
400 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 [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 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}