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#[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 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 65_u64.to_le_bytes(),
359 Tag::Directory,
360 56_u64.to_le_bytes(),
361 Timestamps::default(),
362 empty_hash,
363 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 5_u64.to_le_bytes(),
380 "first",
381 187_u64.to_le_bytes(),
383 Tag::Directory,
385 105_u64.to_le_bytes(),
386 Timestamps::default(),
387 dir_hash,
388 114_u64.to_le_bytes(),
390 a_hash,
391 1_u64.to_le_bytes(),
392 "a",
393
394 Tag::File,
396 0_u64.to_le_bytes(),
397 13_u64.to_le_bytes(),
398 sha256("Hello, World!"),
399 Timestamps::default(),
400
401 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 [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 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}