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 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 65_u64.to_le_bytes(),
362 Tag::Directory,
363 56_u64.to_le_bytes(),
364 Timestamps::default(),
365 empty_hash,
366 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 5_u64.to_le_bytes(),
383 "first",
384 187_u64.to_le_bytes(),
386 Tag::Directory,
388 105_u64.to_le_bytes(),
389 Timestamps::default(),
390 dir_hash,
391 114_u64.to_le_bytes(),
393 a_hash,
394 1_u64.to_le_bytes(),
395 "a",
396
397 Tag::File,
399 0_u64.to_le_bytes(),
400 13_u64.to_le_bytes(),
401 sha256("Hello, World!"),
402 Timestamps::default(),
403
404 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 [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 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}