use std::io::Write as _;
use tensor_wasm_artifacts::{
ArtifactError, ArtifactStore, ContentHash, DiskArtifactStore, ARTIFACT_HEADER_LEN,
ARTIFACT_HMAC_LEN, ARTIFACT_MAGIC, ARTIFACT_VERSION, DEFAULT_ZSTD_LEVEL, MAX_DECOMPRESSED_LEN,
MAX_PAYLOAD_LEN,
};
const TEST_KEY: [u8; 32] = [0x42u8; 32];
#[test]
fn put_rejects_payload_over_cap() {
let tmp = tempfile::tempdir().expect("tempdir");
let store = DiskArtifactStore::new(tmp.path().to_path_buf(), [0x11u8; 32]);
let payload = vec![0u8; MAX_PAYLOAD_LEN + 1];
let err = store
.put(&payload)
.expect_err("must reject oversized payload");
match err {
ArtifactError::TooLarge { actual, limit } => {
assert_eq!(actual, MAX_PAYLOAD_LEN + 1, "actual size in error");
assert_eq!(limit, MAX_PAYLOAD_LEN, "limit in error");
}
other => panic!("expected TooLarge, got {other:?}"),
}
let entries: Vec<_> = std::fs::read_dir(tmp.path())
.expect("read_dir")
.flatten()
.collect();
assert!(
entries.is_empty(),
"no file should be persisted for rejected put, found {entries:?}"
);
}
fn sign(key: &[u8; 32], bytes: &[u8]) -> [u8; ARTIFACT_HMAC_LEN] {
use hmac::{Hmac, Mac};
use sha2::Sha256;
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(&key[..]).expect("hmac key");
mac.update(bytes);
let out = mac.finalize().into_bytes();
let mut tag = [0u8; ARTIFACT_HMAC_LEN];
tag.copy_from_slice(out.as_slice());
tag
}
fn key_fp_hex(key: &[u8; 32]) -> String {
let h = blake3::hash(&key[..]);
h.as_bytes()[..8]
.iter()
.map(|b| format!("{:02x}", b))
.collect()
}
#[test]
#[ignore = "allocates >1 GiB to actually trigger the decompressed-size cap; \
run with `cargo test -p tensor-wasm-artifacts -- --ignored`"]
fn get_rejects_zstd_bomb_over_cap() {
let plaintext_len = MAX_DECOMPRESSED_LEN + 1;
let plaintext = vec![0u8; plaintext_len];
let zstd_body =
zstd::encode_all(plaintext.as_slice(), DEFAULT_ZSTD_LEVEL).expect("zstd encode");
drop(plaintext);
let content_hash_field = [0u8; 32];
let mut framed: Vec<u8> =
Vec::with_capacity(ARTIFACT_HEADER_LEN + zstd_body.len() + ARTIFACT_HMAC_LEN);
framed.write_all(&ARTIFACT_MAGIC).unwrap();
framed.write_all(&ARTIFACT_VERSION.to_le_bytes()).unwrap();
framed.write_all(&content_hash_field).unwrap();
framed.write_all(&zstd_body).unwrap();
let tag = sign(&TEST_KEY, &framed);
framed.extend_from_slice(&tag);
drop(zstd_body);
let tmp = tempfile::tempdir().expect("tempdir");
let store = DiskArtifactStore::new(tmp.path().to_path_buf(), TEST_KEY);
let fake_hash = ContentHash::from_bytes(content_hash_field);
let fname = format!("{}.{}.bin", fake_hash, key_fp_hex(&TEST_KEY));
let target = tmp.path().join(&fname);
std::fs::create_dir_all(tmp.path()).expect("mkdir");
std::fs::write(&target, &framed).expect("write crafted blob");
drop(framed);
let err = store
.get(&fake_hash)
.expect_err("must reject zstd-bomb payload");
match err {
ArtifactError::TooLarge { actual, limit } => {
assert!(
actual > MAX_DECOMPRESSED_LEN,
"actual {actual} should exceed cap {MAX_DECOMPRESSED_LEN}",
);
assert_eq!(limit, MAX_DECOMPRESSED_LEN, "limit in error");
}
other => panic!("expected TooLarge, got {other:?}"),
}
}