krystv's picture
Upload 107 files
3374e90 verified
use bex_types::{BexError, Manifest};
use sha2::{Sha256, Digest};
pub const MAGIC: &[u8; 4] = b"BEX\x01";
pub const CONTAINER_VERSION: u16 = 1;
pub const HEADER_LEN: usize = 96;
pub struct BexPackage {
pub manifest: Manifest,
pub wasm: Vec<u8>,
}
fn sha256(data: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().into()
}
pub fn read_manifest(data: &[u8]) -> Result<Manifest, BexError> {
if data.len() < HEADER_LEN { return Err(BexError::ManifestInvalid("too short".into())); }
if &data[0..4] != MAGIC { return Err(BexError::ManifestInvalid("bad magic".into())); }
let expected_crc = u32::from_le_bytes(data[92..96].try_into().unwrap());
let actual_crc = crc32fast::hash(&data[0..92]);
if expected_crc != actual_crc { return Err(BexError::ManifestInvalid("header CRC mismatch".into())); }
let manifest_len = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize;
if HEADER_LEN.checked_add(manifest_len).map_or(true, |end| end > data.len()) {
return Err(BexError::ManifestInvalid(
format!("manifest_len {} exceeds package size {}", manifest_len, data.len())
));
}
let manifest_bytes = &data[HEADER_LEN..HEADER_LEN + manifest_len];
let expected_hash = &data[60..92];
let actual_hash = sha256(manifest_bytes);
if expected_hash != actual_hash { return Err(BexError::ManifestInvalid("manifest hash mismatch".into())); }
serde_yaml::from_slice(manifest_bytes).map_err(|e| BexError::ManifestInvalid(format!("yaml: {e}")))
}
pub fn unpack(data: &[u8]) -> Result<BexPackage, BexError> {
let manifest = read_manifest(data)?;
let flags = u16::from_le_bytes(data[6..8].try_into().unwrap());
let manifest_len = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize;
if HEADER_LEN.checked_add(manifest_len).map_or(true, |end| end > data.len()) {
return Err(BexError::ManifestInvalid(
format!("manifest_len {} exceeds package size {}", manifest_len, data.len())
));
}
let wasm_original_len = u64::from_le_bytes(data[20..28].try_into().unwrap()) as usize;
let wasm_start = HEADER_LEN + manifest_len;
let wasm = if flags & 1 != 0 {
let mut out = Vec::with_capacity(wasm_original_len);
zstd::stream::copy_decode(&data[wasm_start..], &mut out)
.map_err(|e| BexError::Internal(format!("zstd: {e}")))?;
out
} else {
data[wasm_start..].to_vec()
};
let expected = &data[28..60];
let actual = sha256(&wasm);
if expected != actual { return Err(BexError::HashMismatch { plugin_id: manifest.id.clone() }); }
Ok(BexPackage { manifest, wasm })
}
pub fn pack(manifest: &Manifest, wasm: &[u8]) -> Result<Vec<u8>, BexError> {
let yaml = serde_yaml::to_string(manifest).map_err(|e| BexError::Internal(e.to_string()))?;
let yaml_bytes = yaml.as_bytes();
let compressed = zstd::bulk::compress(wasm, 9).map_err(|e| BexError::Internal(e.to_string()))?;
let mut out = Vec::with_capacity(HEADER_LEN + yaml_bytes.len() + compressed.len());
out.extend_from_slice(MAGIC);
out.extend_from_slice(&CONTAINER_VERSION.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&(yaml_bytes.len() as u32).to_le_bytes());
out.extend_from_slice(&(compressed.len() as u64).to_le_bytes());
out.extend_from_slice(&(wasm.len() as u64).to_le_bytes());
out.extend_from_slice(&sha256(wasm));
out.extend_from_slice(&sha256(yaml_bytes));
let crc = crc32fast::hash(&out[0..92]);
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(yaml_bytes);
out.extend_from_slice(&compressed);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use bex_types::manifest::*;
fn test_manifest() -> Manifest {
Manifest {
schema: 1, id: "com.test.plugin".into(), name: "Test".into(),
version: "1.0.0".into(), authors: vec!["dev".into()], abi: ">=1.0.0,<2.0.0".into(),
provides: ProvidesSpec { search: true, info: true, ..Default::default() },
network: NetworkSpec { hosts: vec!["*".into()], concurrent: 8 },
storage: false, secrets: vec![],
allow_js: false, allow_js_fetch: false,
display: DisplaySpec { description: Some("test".into()), ..Default::default() },
}
}
#[test]
fn round_trip() {
let m = test_manifest();
let wasm = b"\x00asm\x01\x00\x00\x00";
let packed = pack(&m, wasm).unwrap();
let unpacked = unpack(&packed).unwrap();
assert_eq!(unpacked.manifest.id, "com.test.plugin");
assert_eq!(unpacked.wasm, wasm.to_vec());
}
#[test]
fn read_manifest_from_packed() {
let m = test_manifest();
let packed = pack(&m, b"test").unwrap();
let read = read_manifest(&packed).unwrap();
assert_eq!(read.id, "com.test.plugin");
}
#[test]
fn bad_magic() {
let data = vec![0u8; 96];
let r = read_manifest(&data);
assert!(r.is_err());
}
}