use crate::Result;
use async_compression::{
tokio::{bufread::GzipDecoder, write::GzipEncoder},
Level,
};
use data_encoding::HEXUPPER;
use ring::digest::{Context, Digest, SHA256};
use std::{
collections::HashMap,
io::{Cursor, Read},
path::{Path, PathBuf},
};
use tokio::{
fs::File,
io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufReader},
};
use tokio_stream::StreamExt;
use tokio_tar::Archive;
use wascap::{
jwt::{CapabilityProvider, Claims, Token},
prelude::KeyPair,
};
const CLAIMS_JWT_FILE: &str = "claims.jwt";
const WIT_WORLD_FILE: &str = "world.wasm";
const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
pub struct ProviderArchive {
libraries: HashMap<String, Vec<u8>>,
name: String,
vendor: String,
rev: Option<i32>,
ver: Option<String>,
token: Option<Token<CapabilityProvider>>,
json_schema: Option<serde_json::Value>,
wit: Option<Vec<u8>>,
}
impl ProviderArchive {
#[must_use]
pub fn new(name: &str, vendor: &str, rev: Option<i32>, ver: Option<String>) -> ProviderArchive {
ProviderArchive {
libraries: HashMap::new(),
name: name.to_string(),
vendor: vendor.to_string(),
rev,
ver,
token: None,
json_schema: None,
wit: None,
}
}
pub fn add_library(&mut self, target: &str, input: &[u8]) -> Result<()> {
self.libraries.insert(target.to_string(), input.to_vec());
Ok(())
}
pub fn add_wit_world(&mut self, world: &[u8]) -> Result<()> {
self.wit = Some(world.to_vec());
Ok(())
}
pub fn set_schema(&mut self, schema: serde_json::Value) -> Result<()> {
self.json_schema = Some(schema);
Ok(())
}
#[must_use]
pub fn targets(&self) -> Vec<String> {
self.libraries.keys().cloned().collect()
}
#[must_use]
pub fn target_bytes(&self, target: &str) -> Option<Vec<u8>> {
self.libraries.get(target).cloned()
}
#[must_use]
pub fn claims(&self) -> Option<Claims<CapabilityProvider>> {
self.token.as_ref().map(|t| t.claims.clone())
}
#[must_use]
pub fn claims_token(&self) -> Option<Token<CapabilityProvider>> {
self.token.clone()
}
#[must_use]
pub fn schema(&self) -> Option<serde_json::Value> {
self.json_schema.clone()
}
#[must_use]
pub fn wit_world(&self) -> Option<&[u8]> {
self.wit.as_deref()
}
pub async fn try_load(input: &[u8]) -> Result<ProviderArchive> {
let mut cursor = Cursor::new(input);
Self::load(&mut cursor, None).await
}
pub async fn try_load_target(input: &[u8], target: &str) -> Result<ProviderArchive> {
let mut cursor = Cursor::new(input);
Self::load(&mut cursor, Some(target)).await
}
pub async fn try_load_file(path: impl AsRef<Path>) -> Result<ProviderArchive> {
let mut file = File::open(&path).await.map_err(|e| {
std::io::Error::new(
e.kind(),
format!(
"failed to load PAR from file [{}]: {e}",
path.as_ref().display()
),
)
})?;
Self::load(&mut file, None).await
}
pub async fn try_load_target_from_file(
path: impl AsRef<Path>,
target: &str,
) -> Result<ProviderArchive> {
let mut file = File::open(&path).await.map_err(|e| {
std::io::Error::new(
e.kind(),
format!(
"failed to load target [{target}] from PAR from file [{}]: {e}",
path.as_ref().display()
),
)
})?;
Self::load(&mut file, Some(target)).await
}
pub async fn load<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
input: &mut R,
target: Option<&str>,
) -> Result<ProviderArchive> {
let mut libraries = HashMap::new();
let mut wit_world = None;
let mut magic = [0; 2];
if let Err(e) = input.read_exact(&mut magic).await {
if matches!(e.kind(), std::io::ErrorKind::UnexpectedEof) {
return Err("Not enough bytes to be a valid PAR file".into());
}
return Err(e.into());
}
input.rewind().await?;
let mut par = Archive::new(if magic == GZIP_MAGIC {
Box::new(GzipDecoder::new(BufReader::new(input)))
as Box<dyn AsyncRead + Unpin + Sync + Send>
} else {
Box::new(input) as Box<dyn AsyncRead + Unpin + Sync + Send>
});
let mut token: Option<Token<CapabilityProvider>> = None;
let mut entries = par.entries()?;
while let Some(res) = entries.next().await {
let mut entry = res?;
let mut bytes = Vec::new();
let file_target = PathBuf::from(entry.path()?)
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string();
if file_target == "claims" {
tokio::io::copy(&mut entry, &mut bytes).await?;
let jwt = std::str::from_utf8(&bytes)?;
let claims = Some(Claims::<CapabilityProvider>::decode(jwt)?);
token = claims.map(|claims| Token {
jwt: jwt.to_string(),
claims,
});
} else if file_target == "world" {
tokio::io::copy(&mut entry, &mut bytes).await?;
wit_world = Some(bytes);
} else if let Some(t) = target {
if file_target == t {
tokio::io::copy(&mut entry, &mut bytes).await?;
libraries.insert(file_target.to_string(), bytes);
}
continue;
} else {
tokio::io::copy(&mut entry, &mut bytes).await?;
libraries.insert(file_target.to_string(), bytes);
}
}
if token.is_none() || libraries.is_empty() {
libraries.clear();
return Err(
"Not enough files found in provider archive. Is this a complete archive?".into(),
);
}
if let Some(ref claims_token) = token {
let cl = &claims_token.claims;
let metadata = cl.metadata.as_ref().unwrap();
let name = cl.name();
let vendor = metadata.vendor.to_string();
let rev = metadata.rev;
let ver = metadata.ver.clone();
let json_schema = metadata.config_schema.clone();
validate_hashes(&libraries, &wit_world, cl)?;
Ok(ProviderArchive {
libraries,
name,
vendor,
rev,
ver,
token,
json_schema,
wit: wit_world,
})
} else {
Err("No claims found embedded in provider archive.".into())
}
}
pub async fn write(
&mut self,
destination: impl AsRef<Path>,
issuer: &KeyPair,
subject: &KeyPair,
compress_par: bool,
) -> Result<()> {
let file = File::create(
if compress_par && destination.as_ref().extension().unwrap_or_default() != "gz" {
let mut file_name = destination
.as_ref()
.file_name()
.ok_or("Destination is not a file")?
.to_owned();
file_name.push(".gz");
destination.as_ref().with_file_name(file_name)
} else {
destination.as_ref().to_owned()
},
)
.await?;
let mut par = tokio_tar::Builder::new(if compress_par {
Box::new(GzipEncoder::with_quality(file, Level::Best))
as Box<dyn AsyncWrite + Send + Sync + Unpin>
} else {
Box::new(file) as Box<dyn AsyncWrite + Send + Sync + Unpin>
});
let mut claims = Claims::<CapabilityProvider>::new(
self.name.to_string(),
issuer.public_key(),
subject.public_key(),
self.vendor.to_string(),
self.rev,
self.ver.clone(),
generate_hashes(&self.libraries, &self.wit),
);
if let Some(schema) = self.json_schema.clone() {
claims.metadata.as_mut().unwrap().config_schema = Some(schema);
}
let claims_jwt = claims.encode(issuer)?;
self.token = Some(Token {
jwt: claims_jwt.clone(),
claims,
});
let mut header = tokio_tar::Header::new_gnu();
header.set_path(CLAIMS_JWT_FILE)?;
header.set_size(claims_jwt.len() as u64);
header.set_cksum();
par.append_data(&mut header, CLAIMS_JWT_FILE, Cursor::new(claims_jwt))
.await?;
if let Some(world) = &self.wit {
let mut header = tokio_tar::Header::new_gnu();
header.set_path(WIT_WORLD_FILE)?;
header.set_size(world.len() as u64);
header.set_cksum();
par.append_data(&mut header, WIT_WORLD_FILE, Cursor::new(world))
.await?;
}
for (tgt, lib) in &self.libraries {
let mut header = tokio_tar::Header::new_gnu();
let path = format!("{tgt}.bin");
header.set_path(&path)?;
header.set_size(lib.len() as u64);
header.set_cksum();
par.append_data(&mut header, &path, Cursor::new(lib))
.await?;
}
let mut inner = par.into_inner().await?;
inner.flush().await?;
inner.shutdown().await?;
Ok(())
}
}
fn validate_hashes(
libraries: &HashMap<String, Vec<u8>>,
wit: &Option<Vec<u8>>,
claims: &Claims<CapabilityProvider>,
) -> Result<()> {
let file_hashes = claims.metadata.as_ref().unwrap().target_hashes.clone();
for (tgt, library) in libraries {
let file_hash = file_hashes.get(tgt).cloned().unwrap();
let check_hash = hash_bytes(library);
if file_hash != check_hash {
return Err(format!("File hash and verify hash do not match for '{tgt}'").into());
}
}
if let Some(interface) = wit {
if let Some(wit_hash) = file_hashes.get(WIT_WORLD_FILE) {
let check_hash = hash_bytes(interface);
if wit_hash != &check_hash {
return Err("WIT interface hash does not match".into());
}
} else if wit.is_some() {
return Err("WIT interface present but no hash found in claims".into());
}
}
Ok(())
}
fn generate_hashes(
libraries: &HashMap<String, Vec<u8>>,
wit: &Option<Vec<u8>>,
) -> HashMap<String, String> {
let mut hm = HashMap::new();
for (target, lib) in libraries {
let hash = hash_bytes(lib);
hm.insert(target.to_string(), hash);
}
if let Some(interface) = wit {
let hash = hash_bytes(interface);
hm.insert(WIT_WORLD_FILE.to_string(), hash);
}
hm
}
fn hash_bytes(bytes: &[u8]) -> String {
let digest = sha256_digest(bytes).unwrap();
HEXUPPER.encode(digest.as_ref())
}
fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest> {
let mut context = Context::new(&SHA256);
let mut buffer = [0; 1024];
loop {
let count = reader.read(&mut buffer)?;
if count == 0 {
break;
}
context.update(&buffer[..count]);
}
Ok(context.finish())
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::json;
use wascap::prelude::KeyPair;
#[tokio::test]
async fn write_par() -> Result<()> {
let tempdir = tempfile::tempdir()?;
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(1), Some("0.0.1".to_string()));
arch.add_library("aarch64-linux", b"blahblah")?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let outpath = tempdir.path().join("writetest.par");
arch.write(&outpath, &issuer, &subject, false).await?;
tokio::fs::metadata(outpath)
.await
.expect("Unable to locate newly created par file");
Ok(())
}
#[tokio::test]
async fn error_on_no_providers() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(2), Some("0.0.2".to_string()));
let tempdir = tempfile::tempdir()?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let outpath = tempdir.path().join("shoulderr.par");
arch.write(&outpath, &issuer, &subject, false).await?;
let mut buf2 = Vec::new();
let mut f2 = File::open(outpath).await?;
f2.read_to_end(&mut buf2).await?;
let arch2 = ProviderArchive::try_load(&buf2).await;
match arch2 {
Ok(_notok) => panic!("Loading an archive without any libraries should fail"),
Err(_e) => (),
}
Ok(())
}
#[tokio::test]
async fn round_trip() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(3), Some("0.0.3".to_string()));
arch.add_library("aarch64-linux", b"blahblah")?;
arch.add_library("x86_64-linux", b"bloobloo")?;
arch.add_library("x86_64-macos", b"blarblar")?;
arch.set_schema(json!({"property":"foo"}))?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
let firstpath = tempdir.path().join("firstarchive.par");
let secondpath = tempdir.path().join("secondarchive.par");
arch.write(&firstpath, &issuer, &subject, false).await?;
let arch2 = ProviderArchive::try_load_file(&firstpath).await?;
assert_eq!(
arch.libraries.get("aarch64-linux"),
arch2.libraries.get("aarch64-linux")
);
assert_eq!(
arch.libraries.get("x86_64-macos"),
arch2.libraries.get("x86_64-macos")
);
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
let arch2 = ProviderArchive::try_load_target_from_file(&firstpath, "aarch64-linux").await?;
assert_eq!(
arch.libraries.get("aarch64-linux"),
arch2.libraries.get("aarch64-linux")
);
assert!(
!arch2.libraries.contains_key("x86_64-macos"),
"Should have loaded only one binary"
);
assert_eq!(
arch2.claims().unwrap().subject,
subject.public_key(),
"Claims should still load"
);
let json = arch2
.claims()
.unwrap()
.metadata
.unwrap()
.config_schema
.unwrap();
assert_eq!(json, json!({"property":"foo"}));
let mut buf2 = Vec::new();
let mut f2 = File::open(&firstpath).await?;
f2.read_to_end(&mut buf2).await?;
let mut arch2 = ProviderArchive::try_load(&buf2).await?;
assert_eq!(
arch.libraries.get("aarch64-linux"),
arch2.libraries.get("aarch64-linux")
);
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
arch2.add_library("mips-linux", b"bluhbluh")?;
arch2.write(&secondpath, &issuer, &subject, false).await?;
let mut buf3 = Vec::new();
let mut f3 = File::open(&secondpath).await?;
f3.read_to_end(&mut buf3).await?;
let arch3 = ProviderArchive::try_load(&buf3).await?;
assert_eq!(
arch3.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch3.claims().unwrap().subject, subject.public_key());
assert_eq!(arch3.targets().len(), 4);
Ok(())
}
#[tokio::test]
async fn compression_roundtrip() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(4), Some("0.0.4".to_string()));
arch.add_library("aarch64-linux", b"heylookimaraspberrypi")?;
arch.add_library("x86_64-linux", b"system76")?;
arch.add_library("x86_64-macos", b"16inchmacbookpro")?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let filename = "computers";
let tempdir = tempfile::tempdir()?;
let parpath = tempdir.path().join(format!("{filename}.par"));
let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
arch.write(&parpath, &issuer, &subject, false).await?;
arch.write(&cheezypath, &issuer, &subject, true).await?;
let mut buf2 = Vec::new();
let mut f2 = File::open(&parpath).await?;
f2.read_to_end(&mut buf2).await?;
let mut buf3 = Vec::new();
let mut f3 = File::open(&cheezypath).await?;
f3.read_to_end(&mut buf3).await?;
let arch2 = ProviderArchive::try_load(&buf3).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
let arch2 = ProviderArchive::try_load_file(&cheezypath).await?;
assert_eq!(
arch.libraries.get("aarch64-linux"),
arch2.libraries.get("aarch64-linux")
);
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
Ok(())
}
#[tokio::test]
async fn wit_compression_roundtrip() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(4), Some("0.0.4".to_string()));
arch.add_library("aarch64-linux", b"heylookimaraspberrypi")?;
arch.add_library("x86_64-linux", b"system76")?;
arch.add_library("x86_64-macos", b"16inchmacbookpro")?;
arch.add_wit_world(b"interface world example { resource config {} }")?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let filename = "wit_test";
let tempdir = tempfile::tempdir()?;
let parpath = tempdir.path().join(format!("{filename}.par"));
let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
arch.write(&parpath, &issuer, &subject, false).await?;
arch.write(&cheezypath, &issuer, &subject, true).await?;
let mut buf2 = Vec::new();
let mut f2 = File::open(&parpath).await?;
f2.read_to_end(&mut buf2).await?;
let mut buf3 = Vec::new();
let mut f3 = File::open(&cheezypath).await?;
f3.read_to_end(&mut buf3).await?;
let arch2 = ProviderArchive::try_load(&buf2).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch.wit_world(), arch2.wit_world());
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
let arch3 = ProviderArchive::try_load(&buf3).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch3.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch.wit_world(), arch3.wit_world());
assert_eq!(arch.claims().unwrap().subject, subject.public_key());
let arch4 = ProviderArchive::try_load_file(&parpath).await?;
assert_eq!(arch.wit_world(), arch4.wit_world());
let arch5 = ProviderArchive::try_load_file(&cheezypath).await?;
assert_eq!(arch.wit_world(), arch5.wit_world());
let claims = arch5.claims().unwrap();
let hashes = claims.metadata.unwrap().target_hashes;
assert!(hashes.contains_key("world.wasm"));
Ok(())
}
#[tokio::test]
async fn valid_write_compressed() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(6), Some("0.0.6".to_string()));
arch.add_library("x86_64-linux", b"linux")?;
arch.add_library("arm-macos", b"macos")?;
arch.add_library("mips64-freebsd", b"freebsd")?;
let filename = "multi-os";
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
arch.write(
tempdir.path().join(format!("{filename}.par")),
&issuer,
&subject,
true,
)
.await?;
let arch2 =
ProviderArchive::try_load_file(tempdir.path().join(format!("{filename}.par.gz")))
.await?;
assert_eq!(
arch.libraries[&"x86_64-linux".to_string()],
arch2.libraries[&"x86_64-linux".to_string()]
);
assert_eq!(
arch.libraries[&"arm-macos".to_string()],
arch2.libraries[&"arm-macos".to_string()]
);
assert_eq!(
arch.libraries[&"mips64-freebsd".to_string()],
arch2.libraries[&"mips64-freebsd".to_string()]
);
assert_eq!(arch.claims(), arch2.claims());
Ok(())
}
#[tokio::test]
async fn valid_write_compressed_with_wit() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(6), Some("0.0.6".to_string()));
arch.add_library("x86_64-linux", b"linux")?;
arch.add_library("arm-macos", b"macos")?;
arch.add_library("mips64-freebsd", b"freebsd")?;
arch.add_wit_world(b"interface world capability { resource handler {} }")?;
let filename = "multi-os-wit";
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
arch.write(
tempdir.path().join(format!("{filename}.par")),
&issuer,
&subject,
true,
)
.await?;
let arch2 =
ProviderArchive::try_load_file(tempdir.path().join(format!("{filename}.par.gz")))
.await?;
assert_eq!(
arch.libraries[&"x86_64-linux".to_string()],
arch2.libraries[&"x86_64-linux".to_string()]
);
assert_eq!(
arch.libraries[&"arm-macos".to_string()],
arch2.libraries[&"arm-macos".to_string()]
);
assert_eq!(
arch.libraries[&"mips64-freebsd".to_string()],
arch2.libraries[&"mips64-freebsd".to_string()]
);
assert_eq!(arch.wit_world(), arch2.wit_world());
assert_eq!(arch.claims(), arch2.claims());
Ok(())
}
#[tokio::test]
async fn valid_write_compressed_with_suffix() -> Result<()> {
let mut arch =
ProviderArchive::new("Testing", "wasmCloud", Some(7), Some("0.0.7".to_string()));
arch.add_library("x86_64-linux", b"linux")?;
arch.add_library("arm-macos", b"macos")?;
arch.add_library("mips64-freebsd", b"freebsd")?;
let filename = "suffix-test";
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
let cheezypath = tempdir.path().join(format!("{filename}.par.gz"));
arch.write(&cheezypath, &issuer, &subject, true)
.await
.expect("Unable to write parcheezy");
let arch2 = ProviderArchive::try_load_file(&cheezypath)
.await
.expect("Unable to load parcheezy from file");
assert_eq!(
arch.libraries[&"x86_64-linux".to_string()],
arch2.libraries[&"x86_64-linux".to_string()]
);
assert_eq!(
arch.libraries[&"arm-macos".to_string()],
arch2.libraries[&"arm-macos".to_string()]
);
assert_eq!(
arch.libraries[&"mips64-freebsd".to_string()],
arch2.libraries[&"mips64-freebsd".to_string()]
);
assert_eq!(arch.claims(), arch2.claims());
Ok(())
}
#[tokio::test]
async fn preserved_claims() -> Result<()> {
let name = "Testing";
let vendor = "wasmCloud";
let rev = 8;
let ver = "0.0.8".to_string();
let mut arch = ProviderArchive::new(name, vendor, Some(rev), Some(ver.clone()));
arch.add_library("aarch64-linux", b"blahblah")?;
arch.add_library("x86_64-linux", b"bloobloo")?;
arch.add_library("x86_64-macos", b"blarblar")?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
let originalpath = tempdir.path().join("original.par.gz");
let addedpath = tempdir.path().join("linuxadded.par.gz");
arch.write(&originalpath, &issuer, &subject, true).await?;
let mut arch2 = ProviderArchive::try_load_file(&originalpath).await?;
assert_eq!(
arch.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch2.claims().unwrap().subject, subject.public_key());
assert_eq!(arch2.claims().unwrap().issuer, issuer.public_key());
assert_eq!(arch2.claims().unwrap().name(), name);
assert_eq!(arch2.claims().unwrap().metadata.unwrap().ver.unwrap(), ver);
assert_eq!(arch2.claims().unwrap().metadata.unwrap().rev.unwrap(), rev);
assert_eq!(arch2.claims().unwrap().metadata.unwrap().vendor, vendor);
arch2.add_library("mips-linux", b"bluhbluh")?;
arch2.write(&addedpath, &issuer, &subject, true).await?;
let arch3 = ProviderArchive::try_load_file(&addedpath).await?;
assert_eq!(
arch3.libraries[&"aarch64-linux".to_string()],
arch2.libraries[&"aarch64-linux".to_string()]
);
assert_eq!(arch3.claims().unwrap().subject, subject.public_key());
assert_eq!(arch3.claims().unwrap().issuer, issuer.public_key());
assert_eq!(arch3.claims().unwrap().name(), name);
assert_eq!(arch3.claims().unwrap().metadata.unwrap().ver.unwrap(), ver);
assert_eq!(arch3.claims().unwrap().metadata.unwrap().rev.unwrap(), rev);
assert_eq!(arch3.claims().unwrap().metadata.unwrap().vendor, vendor);
assert_eq!(arch3.targets().len(), 4);
Ok(())
}
#[tokio::test]
async fn witless_archive() -> Result<()> {
let mut old_arch =
ProviderArchive::new("OldStyle", "wasmCloud", Some(1), Some("0.0.1".to_string()));
old_arch.add_library("x86_64-linux", b"oldbin")?;
let issuer = KeyPair::new_account();
let subject = KeyPair::new_service();
let tempdir = tempfile::tempdir()?;
let old_path = tempdir.path().join("old_style.par");
old_arch.write(&old_path, &issuer, &subject, false).await?;
let loaded_arch = ProviderArchive::try_load_file(&old_path).await?;
assert_eq!(loaded_arch.wit_world(), None); assert_eq!(
loaded_arch.libraries.get("x86_64-linux"),
old_arch.libraries.get("x86_64-linux")
);
Ok(())
}
}