1use crate::Result;
2use async_compression::{
3 tokio::{bufread::GzipDecoder, write::GzipEncoder},
4 Level,
5};
6use data_encoding::HEXUPPER;
7use ring::digest::{Context, Digest, SHA256};
8use std::{
9 collections::HashMap,
10 io::{Cursor, Read},
11 path::{Path, PathBuf},
12};
13use tokio::{
14 fs::File,
15 io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufReader},
16};
17use tokio_stream::StreamExt;
18use tokio_tar::Archive;
19use wascap::{
20 jwt::{CapabilityProvider, Claims, Token},
21 prelude::KeyPair,
22};
23
24const CLAIMS_JWT_FILE: &str = "claims.jwt";
25const WIT_WORLD_FILE: &str = "world.wasm";
26
27const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
28
29pub struct ProviderArchive {
33 libraries: HashMap<String, Vec<u8>>,
34 name: String,
35 vendor: String,
36 rev: Option<i32>,
37 ver: Option<String>,
38 token: Option<Token<CapabilityProvider>>,
39 json_schema: Option<serde_json::Value>,
40 wit: Option<Vec<u8>>,
41}
42
43impl ProviderArchive {
44 #[must_use]
46 pub fn new(name: &str, vendor: &str, rev: Option<i32>, ver: Option<String>) -> ProviderArchive {
47 ProviderArchive {
48 libraries: HashMap::new(),
49 name: name.to_string(),
50 vendor: vendor.to_string(),
51 rev,
52 ver,
53 token: None,
54 json_schema: None,
55 wit: None,
56 }
57 }
58
59 pub fn add_library(&mut self, target: &str, input: &[u8]) -> Result<()> {
61 self.libraries.insert(target.to_string(), input.to_vec());
62
63 Ok(())
64 }
65
66 pub fn add_wit_world(&mut self, world: &[u8]) -> Result<()> {
68 self.wit = Some(world.to_vec());
69
70 Ok(())
71 }
72
73 pub fn set_schema(&mut self, schema: serde_json::Value) -> Result<()> {
77 self.json_schema = Some(schema);
78
79 Ok(())
80 }
81
82 #[must_use]
84 pub fn targets(&self) -> Vec<String> {
85 self.libraries.keys().cloned().collect()
86 }
87
88 #[must_use]
90 pub fn target_bytes(&self, target: &str) -> Option<Vec<u8>> {
91 self.libraries.get(target).cloned()
92 }
93
94 #[must_use]
98 pub fn claims(&self) -> Option<Claims<CapabilityProvider>> {
99 self.token.as_ref().map(|t| t.claims.clone())
100 }
101
102 #[must_use]
104 pub fn claims_token(&self) -> Option<Token<CapabilityProvider>> {
105 self.token.clone()
106 }
107
108 #[must_use]
111 pub fn schema(&self) -> Option<serde_json::Value> {
112 self.json_schema.clone()
113 }
114
115 #[must_use]
117 pub fn wit_world(&self) -> Option<&[u8]> {
118 self.wit.as_deref()
119 }
120
121 pub async fn try_load(input: &[u8]) -> Result<ProviderArchive> {
132 let mut cursor = Cursor::new(input);
133 Self::load(&mut cursor, None).await
134 }
135
136 pub async fn try_load_target(input: &[u8], target: &str) -> Result<ProviderArchive> {
149 let mut cursor = Cursor::new(input);
150 Self::load(&mut cursor, Some(target)).await
151 }
152
153 pub async fn try_load_file(path: impl AsRef<Path>) -> Result<ProviderArchive> {
162 let mut file = File::open(&path).await.map_err(|e| {
163 std::io::Error::new(
164 e.kind(),
165 format!(
166 "failed to load PAR from file [{}]: {e}",
167 path.as_ref().display()
168 ),
169 )
170 })?;
171 Self::load(&mut file, None).await
172 }
173
174 pub async fn try_load_target_from_file(
184 path: impl AsRef<Path>,
185 target: &str,
186 ) -> Result<ProviderArchive> {
187 let mut file = File::open(&path).await.map_err(|e| {
188 std::io::Error::new(
189 e.kind(),
190 format!(
191 "failed to load target [{target}] from PAR from file [{}]: {e}",
192 path.as_ref().display()
193 ),
194 )
195 })?;
196 Self::load(&mut file, Some(target)).await
197 }
198
199 pub async fn load<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
210 input: &mut R,
211 target: Option<&str>,
212 ) -> Result<ProviderArchive> {
213 let mut libraries = HashMap::new();
214 let mut wit_world = None;
215
216 let mut magic = [0; 2];
217 if let Err(e) = input.read_exact(&mut magic).await {
218 if matches!(e.kind(), std::io::ErrorKind::UnexpectedEof) {
220 return Err("Not enough bytes to be a valid PAR file".into());
221 }
222 return Err(e.into());
223 }
224
225 input.rewind().await?;
227
228 let mut par = Archive::new(if magic == GZIP_MAGIC {
229 Box::new(GzipDecoder::new(BufReader::new(input)))
230 as Box<dyn AsyncRead + Unpin + Sync + Send>
231 } else {
232 Box::new(input) as Box<dyn AsyncRead + Unpin + Sync + Send>
233 });
234
235 let mut token: Option<Token<CapabilityProvider>> = None;
236
237 let mut entries = par.entries()?;
238
239 while let Some(res) = entries.next().await {
240 let mut entry = res?;
241 let mut bytes = Vec::new();
242 let file_target = PathBuf::from(entry.path()?)
243 .file_stem()
244 .unwrap()
245 .to_str()
246 .unwrap()
247 .to_string();
248 if file_target == "claims" {
249 tokio::io::copy(&mut entry, &mut bytes).await?;
250 let jwt = std::str::from_utf8(&bytes)?;
251 let claims = Some(Claims::<CapabilityProvider>::decode(jwt)?);
252 token = claims.map(|claims| Token {
253 jwt: jwt.to_string(),
254 claims,
255 });
256 } else if file_target == "world" {
257 tokio::io::copy(&mut entry, &mut bytes).await?;
258 wit_world = Some(bytes);
259 } else if let Some(t) = target {
260 if file_target == t {
263 tokio::io::copy(&mut entry, &mut bytes).await?;
264 libraries.insert(file_target.to_string(), bytes);
265 }
266 continue;
267 } else {
268 tokio::io::copy(&mut entry, &mut bytes).await?;
269 libraries.insert(file_target.to_string(), bytes);
270 }
271 }
272
273 if token.is_none() || libraries.is_empty() {
274 libraries.clear();
276 return Err(
277 "Not enough files found in provider archive. Is this a complete archive?".into(),
278 );
279 }
280
281 if let Some(ref claims_token) = token {
282 let cl = &claims_token.claims;
283 let metadata = cl.metadata.as_ref().unwrap();
284 let name = cl.name();
285 let vendor = metadata.vendor.to_string();
286 let rev = metadata.rev;
287 let ver = metadata.ver.clone();
288 let json_schema = metadata.config_schema.clone();
289
290 validate_hashes(&libraries, &wit_world, cl)?;
291
292 Ok(ProviderArchive {
293 libraries,
294 name,
295 vendor,
296 rev,
297 ver,
298 token,
299 json_schema,
300 wit: wit_world,
301 })
302 } else {
303 Err("No claims found embedded in provider archive.".into())
304 }
305 }
306
307 pub async fn write(
309 &mut self,
310 destination: impl AsRef<Path>,
311 issuer: &KeyPair,
312 subject: &KeyPair,
313 compress_par: bool,
314 ) -> Result<()> {
315 let file = File::create(
316 if compress_par && destination.as_ref().extension().unwrap_or_default() != "gz" {
317 let mut file_name = destination
318 .as_ref()
319 .file_name()
320 .ok_or("Destination is not a file")?
321 .to_owned();
322 file_name.push(".gz");
323 destination.as_ref().with_file_name(file_name)
324 } else {
325 destination.as_ref().to_owned()
326 },
327 )
328 .await?;
329
330 let mut par = tokio_tar::Builder::new(if compress_par {
331 Box::new(GzipEncoder::with_quality(file, Level::Best))
332 as Box<dyn AsyncWrite + Send + Sync + Unpin>
333 } else {
334 Box::new(file) as Box<dyn AsyncWrite + Send + Sync + Unpin>
335 });
336
337 let mut claims = Claims::<CapabilityProvider>::new(
338 self.name.to_string(),
339 issuer.public_key(),
340 subject.public_key(),
341 self.vendor.to_string(),
342 self.rev,
343 self.ver.clone(),
344 generate_hashes(&self.libraries, &self.wit),
345 );
346 if let Some(schema) = self.json_schema.clone() {
347 claims.metadata.as_mut().unwrap().config_schema = Some(schema);
348 }
349
350 let claims_jwt = claims.encode(issuer)?;
351 self.token = Some(Token {
352 jwt: claims_jwt.clone(),
353 claims,
354 });
355
356 let mut header = tokio_tar::Header::new_gnu();
357 header.set_path(CLAIMS_JWT_FILE)?;
358 header.set_size(claims_jwt.len() as u64);
359 header.set_cksum();
360 par.append_data(&mut header, CLAIMS_JWT_FILE, Cursor::new(claims_jwt))
361 .await?;
362
363 if let Some(world) = &self.wit {
364 let mut header = tokio_tar::Header::new_gnu();
365 header.set_path(WIT_WORLD_FILE)?;
366 header.set_size(world.len() as u64);
367 header.set_cksum();
368 par.append_data(&mut header, WIT_WORLD_FILE, Cursor::new(world))
369 .await?;
370 }
371
372 for (tgt, lib) in &self.libraries {
373 let mut header = tokio_tar::Header::new_gnu();
374 let path = format!("{tgt}.bin");
375 header.set_path(&path)?;
376 header.set_size(lib.len() as u64);
377 header.set_cksum();
378 par.append_data(&mut header, &path, Cursor::new(lib))
379 .await?;
380 }
381
382 let mut inner = par.into_inner().await?;
384 inner.flush().await?;
386 inner.shutdown().await?;
387
388 Ok(())
389 }
390}
391
392fn validate_hashes(
393 libraries: &HashMap<String, Vec<u8>>,
394 wit: &Option<Vec<u8>>,
395 claims: &Claims<CapabilityProvider>,
396) -> Result<()> {
397 let file_hashes = claims.metadata.as_ref().unwrap().target_hashes.clone();
398
399 for (tgt, library) in libraries {
400 let file_hash = file_hashes.get(tgt).cloned().unwrap();
401 let check_hash = hash_bytes(library);
402 if file_hash != check_hash {
403 return Err(format!("File hash and verify hash do not match for '{tgt}'").into());
404 }
405 }
406
407 if let Some(interface) = wit {
408 if let Some(wit_hash) = file_hashes.get(WIT_WORLD_FILE) {
409 let check_hash = hash_bytes(interface);
410 if wit_hash != &check_hash {
411 return Err("WIT interface hash does not match".into());
412 }
413 } else if wit.is_some() {
414 return Err("WIT interface present but no hash found in claims".into());
415 }
416 }
417 Ok(())
418}
419
420fn generate_hashes(
421 libraries: &HashMap<String, Vec<u8>>,
422 wit: &Option<Vec<u8>>,
423) -> HashMap<String, String> {
424 let mut hm = HashMap::new();
425 for (target, lib) in libraries {
426 let hash = hash_bytes(lib);
427 hm.insert(target.to_string(), hash);
428 }
429
430 if let Some(interface) = wit {
431 let hash = hash_bytes(interface);
432 hm.insert(WIT_WORLD_FILE.to_string(), hash);
433 }
434
435 hm
436}
437
438fn hash_bytes(bytes: &[u8]) -> String {
439 let digest = sha256_digest(bytes).unwrap();
440 HEXUPPER.encode(digest.as_ref())
441}
442
443fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> {
444 let mut context = Context::new(&SHA256);
445 let mut buffer = [0; 1024];
446
447 loop {
448 let count = reader.read(&mut buffer)?;
449 if count == 0 {
450 break;
451 }
452 context.update(&buffer[..count]);
453 }
454
455 Ok(context.finish())
456}
457
458#[cfg(test)]
459mod test {
460 use super::*;
461 use serde_json::json;
462 use wascap::prelude::KeyPair;
463
464 #[tokio::test]
465 async fn write_par() -> Result<()> {
466 let tempdir = tempfile::tempdir()?;
467 let mut arch =
468 ProviderArchive::new("Testing", "wasmCloud", Some(1), Some("0.0.1".to_string()));
469 arch.add_library("aarch64-linux", b"blahblah")?;
470
471 let issuer = KeyPair::new_account();
472 let subject = KeyPair::new_service();
473
474 let outpath = tempdir.path().join("writetest.par");
475 arch.write(&outpath, &issuer, &subject, false).await?;
476 tokio::fs::metadata(outpath)
477 .await
478 .expect("Unable to locate newly created par file");
479
480 Ok(())
481 }
482
483 #[tokio::test]
484 async fn error_on_no_providers() -> Result<()> {
485 let mut arch =
486 ProviderArchive::new("Testing", "wasmCloud", Some(2), Some("0.0.2".to_string()));
487
488 let tempdir = tempfile::tempdir()?;
489
490 let issuer = KeyPair::new_account();
491 let subject = KeyPair::new_service();
492
493 let outpath = tempdir.path().join("shoulderr.par");
494 arch.write(&outpath, &issuer, &subject, false).await?;
495
496 let mut buf2 = Vec::new();
497 let mut f2 = File::open(outpath).await?;
498 f2.read_to_end(&mut buf2).await?;
499
500 let arch2 = ProviderArchive::try_load(&buf2).await;
501
502 match arch2 {
503 Ok(_notok) => panic!("Loading an archive without any libraries should fail"),
504 Err(_e) => (),
505 }
506
507 Ok(())
508 }
509
510 #[tokio::test]
511 async fn round_trip() -> Result<()> {
512 let mut arch =
514 ProviderArchive::new("Testing", "wasmCloud", Some(3), Some("0.0.3".to_string()));
515 arch.add_library("aarch64-linux", b"blahblah")?;
516 arch.add_library("x86_64-linux", b"bloobloo")?;
517 arch.add_library("x86_64-macos", b"blarblar")?;
518 arch.set_schema(json!({"property":"foo"}))?;
519
520 let issuer = KeyPair::new_account();
521 let subject = KeyPair::new_service();
522
523 let tempdir = tempfile::tempdir()?;
524
525 let firstpath = tempdir.path().join("firstarchive.par");
526 let secondpath = tempdir.path().join("secondarchive.par");
527
528 arch.write(&firstpath, &issuer, &subject, false).await?;
530
531 let arch2 = ProviderArchive::try_load_file(&firstpath).await?;
533 assert_eq!(
534 arch.libraries.get("aarch64-linux"),
535 arch2.libraries.get("aarch64-linux")
536 );
537 assert_eq!(
538 arch.libraries.get("x86_64-macos"),
539 arch2.libraries.get("x86_64-macos")
540 );
541 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
542
543 let arch2 = ProviderArchive::try_load_target_from_file(&firstpath, "aarch64-linux").await?;
545 assert_eq!(
546 arch.libraries.get("aarch64-linux"),
547 arch2.libraries.get("aarch64-linux")
548 );
549 assert!(
550 !arch2.libraries.contains_key("x86_64-macos"),
551 "Should have loaded only one binary"
552 );
553 assert_eq!(
554 arch2.claims().unwrap().subject,
555 subject.public_key(),
556 "Claims should still load"
557 );
558
559 let json = arch2
560 .claims()
561 .unwrap()
562 .metadata
563 .unwrap()
564 .config_schema
565 .unwrap();
566 assert_eq!(json, json!({"property":"foo"}));
567
568 let mut buf2 = Vec::new();
569 let mut f2 = File::open(&firstpath).await?;
570 f2.read_to_end(&mut buf2).await?;
571
572 let mut arch2 = ProviderArchive::try_load(&buf2).await?;
574 assert_eq!(
575 arch.libraries.get("aarch64-linux"),
576 arch2.libraries.get("aarch64-linux")
577 );
578 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
579
580 arch2.add_library("mips-linux", b"bluhbluh")?;
582 arch2.write(&secondpath, &issuer, &subject, false).await?;
583
584 let mut buf3 = Vec::new();
585 let mut f3 = File::open(&secondpath).await?;
586 f3.read_to_end(&mut buf3).await?;
587
588 let arch3 = ProviderArchive::try_load(&buf3).await?;
590 assert_eq!(
591 arch3.libraries[&"aarch64-linux".to_string()],
592 arch2.libraries[&"aarch64-linux".to_string()]
593 );
594 assert_eq!(arch3.claims().unwrap().subject, subject.public_key());
595 assert_eq!(arch3.targets().len(), 4);
596
597 Ok(())
598 }
599
600 #[tokio::test]
601 async fn compression_roundtrip() -> Result<()> {
602 let mut arch =
603 ProviderArchive::new("Testing", "wasmCloud", Some(4), Some("0.0.4".to_string()));
604 arch.add_library("aarch64-linux", b"heylookimaraspberrypi")?;
605 arch.add_library("x86_64-linux", b"system76")?;
606 arch.add_library("x86_64-macos", b"16inchmacbookpro")?;
607
608 let issuer = KeyPair::new_account();
609 let subject = KeyPair::new_service();
610
611 let filename = "computers";
612
613 let tempdir = tempfile::tempdir()?;
614
615 let parpath = tempdir.path().join(format!("{filename}.par"));
616 let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
617
618 arch.write(&parpath, &issuer, &subject, false).await?;
619 arch.write(&cheezypath, &issuer, &subject, true).await?;
620
621 let mut buf2 = Vec::new();
622 let mut f2 = File::open(&parpath).await?;
623 f2.read_to_end(&mut buf2).await?;
624
625 let mut buf3 = Vec::new();
626 let mut f3 = File::open(&cheezypath).await?;
627 f3.read_to_end(&mut buf3).await?;
628
629 let arch2 = ProviderArchive::try_load(&buf3).await?;
631 assert_eq!(
632 arch.libraries[&"aarch64-linux".to_string()],
633 arch2.libraries[&"aarch64-linux".to_string()]
634 );
635 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
636
637 let arch2 = ProviderArchive::try_load_file(&cheezypath).await?;
639 assert_eq!(
640 arch.libraries.get("aarch64-linux"),
641 arch2.libraries.get("aarch64-linux")
642 );
643 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
644
645 Ok(())
646 }
647
648 #[tokio::test]
649 async fn wit_compression_roundtrip() -> Result<()> {
650 let mut arch =
651 ProviderArchive::new("Testing", "wasmCloud", Some(4), Some("0.0.4".to_string()));
652
653 arch.add_library("aarch64-linux", b"heylookimaraspberrypi")?;
655 arch.add_library("x86_64-linux", b"system76")?;
656 arch.add_library("x86_64-macos", b"16inchmacbookpro")?;
657 arch.add_wit_world(b"interface world example { resource config {} }")?;
658
659 let issuer = KeyPair::new_account();
660 let subject = KeyPair::new_service();
661
662 let filename = "wit_test";
663 let tempdir = tempfile::tempdir()?;
664
665 let parpath = tempdir.path().join(format!("{filename}.par"));
666 let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
667
668 arch.write(&parpath, &issuer, &subject, false).await?;
670 arch.write(&cheezypath, &issuer, &subject, true).await?;
671
672 let mut buf2 = Vec::new();
674 let mut f2 = File::open(&parpath).await?;
675 f2.read_to_end(&mut buf2).await?;
676
677 let mut buf3 = Vec::new();
678 let mut f3 = File::open(&cheezypath).await?;
679 f3.read_to_end(&mut buf3).await?;
680
681 let arch2 = ProviderArchive::try_load(&buf2).await?;
683 assert_eq!(
684 arch.libraries[&"aarch64-linux".to_string()],
685 arch2.libraries[&"aarch64-linux".to_string()]
686 );
687 assert_eq!(arch.wit_world(), arch2.wit_world());
688 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
689
690 let arch3 = ProviderArchive::try_load(&buf3).await?;
692 assert_eq!(
693 arch.libraries[&"aarch64-linux".to_string()],
694 arch3.libraries[&"aarch64-linux".to_string()]
695 );
696 assert_eq!(arch.wit_world(), arch3.wit_world());
697 assert_eq!(arch.claims().unwrap().subject, subject.public_key());
698
699 let arch4 = ProviderArchive::try_load_file(&parpath).await?;
701 assert_eq!(arch.wit_world(), arch4.wit_world());
702
703 let arch5 = ProviderArchive::try_load_file(&cheezypath).await?;
704 assert_eq!(arch.wit_world(), arch5.wit_world());
705
706 let claims = arch5.claims().unwrap();
708 let hashes = claims.metadata.unwrap().target_hashes;
709 assert!(hashes.contains_key("world.wasm"));
710
711 Ok(())
712 }
713
714 #[tokio::test]
715 async fn valid_write_compressed() -> Result<()> {
716 let mut arch =
717 ProviderArchive::new("Testing", "wasmCloud", Some(6), Some("0.0.6".to_string()));
718 arch.add_library("x86_64-linux", b"linux")?;
719 arch.add_library("arm-macos", b"macos")?;
720 arch.add_library("mips64-freebsd", b"freebsd")?;
721
722 let filename = "multi-os";
723
724 let issuer = KeyPair::new_account();
725 let subject = KeyPair::new_service();
726
727 let tempdir = tempfile::tempdir()?;
728
729 arch.write(
730 tempdir.path().join(format!("{filename}.par")),
731 &issuer,
732 &subject,
733 true,
734 )
735 .await?;
736
737 let arch2 =
738 ProviderArchive::try_load_file(tempdir.path().join(format!("{filename}.par.gz")))
739 .await?;
740
741 assert_eq!(
742 arch.libraries[&"x86_64-linux".to_string()],
743 arch2.libraries[&"x86_64-linux".to_string()]
744 );
745 assert_eq!(
746 arch.libraries[&"arm-macos".to_string()],
747 arch2.libraries[&"arm-macos".to_string()]
748 );
749 assert_eq!(
750 arch.libraries[&"mips64-freebsd".to_string()],
751 arch2.libraries[&"mips64-freebsd".to_string()]
752 );
753 assert_eq!(arch.claims(), arch2.claims());
754
755 Ok(())
756 }
757
758 #[tokio::test]
759 async fn valid_write_compressed_with_wit() -> Result<()> {
760 let mut arch =
761 ProviderArchive::new("Testing", "wasmCloud", Some(6), Some("0.0.6".to_string()));
762
763 arch.add_library("x86_64-linux", b"linux")?;
765 arch.add_library("arm-macos", b"macos")?;
766 arch.add_library("mips64-freebsd", b"freebsd")?;
767 arch.add_wit_world(b"interface world capability { resource handler {} }")?;
768
769 let filename = "multi-os-wit";
770 let issuer = KeyPair::new_account();
771 let subject = KeyPair::new_service();
772
773 let tempdir = tempfile::tempdir()?;
774 arch.write(
775 tempdir.path().join(format!("{filename}.par")),
776 &issuer,
777 &subject,
778 true,
779 )
780 .await?;
781
782 let arch2 =
783 ProviderArchive::try_load_file(tempdir.path().join(format!("{filename}.par.gz")))
784 .await?;
785
786 assert_eq!(
788 arch.libraries[&"x86_64-linux".to_string()],
789 arch2.libraries[&"x86_64-linux".to_string()]
790 );
791 assert_eq!(
792 arch.libraries[&"arm-macos".to_string()],
793 arch2.libraries[&"arm-macos".to_string()]
794 );
795 assert_eq!(
796 arch.libraries[&"mips64-freebsd".to_string()],
797 arch2.libraries[&"mips64-freebsd".to_string()]
798 );
799
800 assert_eq!(arch.wit_world(), arch2.wit_world());
802 assert_eq!(arch.claims(), arch2.claims());
803
804 Ok(())
805 }
806
807 #[tokio::test]
808 async fn valid_write_compressed_with_suffix() -> Result<()> {
809 let mut arch =
810 ProviderArchive::new("Testing", "wasmCloud", Some(7), Some("0.0.7".to_string()));
811 arch.add_library("x86_64-linux", b"linux")?;
812 arch.add_library("arm-macos", b"macos")?;
813 arch.add_library("mips64-freebsd", b"freebsd")?;
814
815 let filename = "suffix-test";
816
817 let issuer = KeyPair::new_account();
818 let subject = KeyPair::new_service();
819
820 let tempdir = tempfile::tempdir()?;
821 let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
822
823 arch.write(&cheezypath, &issuer, &subject, true)
825 .await
826 .expect("Unable to write parcheezy");
827
828 let arch2 = ProviderArchive::try_load_file(&cheezypath)
829 .await
830 .expect("Unable to load parcheezy from file");
831
832 assert_eq!(
833 arch.libraries[&"x86_64-linux".to_string()],
834 arch2.libraries[&"x86_64-linux".to_string()]
835 );
836 assert_eq!(
837 arch.libraries[&"arm-macos".to_string()],
838 arch2.libraries[&"arm-macos".to_string()]
839 );
840 assert_eq!(
841 arch.libraries[&"mips64-freebsd".to_string()],
842 arch2.libraries[&"mips64-freebsd".to_string()]
843 );
844 assert_eq!(arch.claims(), arch2.claims());
845
846 Ok(())
847 }
848
849 #[tokio::test]
850 async fn preserved_claims() -> Result<()> {
851 let name = "Testing";
853 let vendor = "wasmCloud";
854 let rev = 8;
855 let ver = "0.0.8".to_string();
856 let mut arch = ProviderArchive::new(name, vendor, Some(rev), Some(ver.clone()));
857 arch.add_library("aarch64-linux", b"blahblah")?;
858 arch.add_library("x86_64-linux", b"bloobloo")?;
859 arch.add_library("x86_64-macos", b"blarblar")?;
860
861 let issuer = KeyPair::new_account();
862 let subject = KeyPair::new_service();
863
864 let tempdir = tempfile::tempdir()?;
865 let originalpath = tempdir.path().join("original.par.gz");
866 let addedpath = tempdir.path().join("linuxadded.par.gz");
867
868 arch.write(&originalpath, &issuer, &subject, true).await?;
869
870 let mut arch2 = ProviderArchive::try_load_file(&originalpath).await?;
872
873 assert_eq!(
874 arch.libraries[&"aarch64-linux".to_string()],
875 arch2.libraries[&"aarch64-linux".to_string()]
876 );
877 assert_eq!(arch2.claims().unwrap().subject, subject.public_key());
878 assert_eq!(arch2.claims().unwrap().issuer, issuer.public_key());
879 assert_eq!(arch2.claims().unwrap().name(), name);
880 assert_eq!(arch2.claims().unwrap().metadata.unwrap().ver.unwrap(), ver);
881 assert_eq!(arch2.claims().unwrap().metadata.unwrap().rev.unwrap(), rev);
882 assert_eq!(arch2.claims().unwrap().metadata.unwrap().vendor, vendor);
883
884 arch2.add_library("mips-linux", b"bluhbluh")?;
886 arch2.write(&addedpath, &issuer, &subject, true).await?;
887
888 let arch3 = ProviderArchive::try_load_file(&addedpath).await?;
890 assert_eq!(
891 arch3.libraries[&"aarch64-linux".to_string()],
892 arch2.libraries[&"aarch64-linux".to_string()]
893 );
894 assert_eq!(arch3.claims().unwrap().subject, subject.public_key());
895 assert_eq!(arch3.claims().unwrap().issuer, issuer.public_key());
896 assert_eq!(arch3.claims().unwrap().name(), name);
897 assert_eq!(arch3.claims().unwrap().metadata.unwrap().ver.unwrap(), ver);
898 assert_eq!(arch3.claims().unwrap().metadata.unwrap().rev.unwrap(), rev);
899 assert_eq!(arch3.claims().unwrap().metadata.unwrap().vendor, vendor);
900 assert_eq!(arch3.targets().len(), 4);
901
902 Ok(())
903 }
904
905 #[tokio::test]
907 async fn witless_archive() -> Result<()> {
908 let mut old_arch =
910 ProviderArchive::new("OldStyle", "wasmCloud", Some(1), Some("0.0.1".to_string()));
911 old_arch.add_library("x86_64-linux", b"oldbin")?;
912
913 let issuer = KeyPair::new_account();
914 let subject = KeyPair::new_service();
915
916 let tempdir = tempfile::tempdir()?;
917 let old_path = tempdir.path().join("old_style.par");
918
919 old_arch.write(&old_path, &issuer, &subject, false).await?;
921
922 let loaded_arch = ProviderArchive::try_load_file(&old_path).await?;
923 assert_eq!(loaded_arch.wit_world(), None); assert_eq!(
925 loaded_arch.libraries.get("x86_64-linux"),
926 old_arch.libraries.get("x86_64-linux")
927 );
928
929 Ok(())
930 }
931}