use structured_zstd::decoding::workspace::{
DecodeLimits, StepState, WorkBudget, WorkspaceDecodeError, WorkspaceDecoder, workspace_bytes,
};
use structured_zstd::encoding::{CompressionLevel, compress_slice_to_vec};
struct Manifest {
size: u64,
digest: u64,
}
#[derive(Debug)]
enum Rejected {
Memory,
Decode(WorkspaceDecodeError),
Mismatch,
}
impl core::fmt::Display for Rejected {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Rejected::Memory => write!(f, "workspace not admitted"),
Rejected::Decode(e) => write!(f, "decode refused: {e}"),
Rejected::Mismatch => write!(f, "size or digest differs from the manifest"),
}
}
}
fn outcome(result: Result<(), Rejected>) -> String {
match result {
Ok(()) => "accepted".to_string(),
Err(e) => format!("rejected ({e})"),
}
}
struct Fnv1a(u64);
impl Fnv1a {
fn new() -> Self {
Self(0xcbf2_9ce4_8422_2325)
}
fn update(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3);
}
}
}
fn verify(compressed: &[u8], manifest: &Manifest, max_window: u64) -> Result<(), Rejected> {
let limits = DecodeLimits::new(max_window).with_max_output(manifest.size);
let needed = workspace_bytes(limits.max_window_size).map_err(|_| Rejected::Memory)?;
let mut workspace = Vec::new();
workspace
.try_reserve_exact(needed)
.map_err(|_| Rejected::Memory)?;
workspace.resize(needed, 0);
let mut decoder =
WorkspaceDecoder::new(&mut workspace, limits).map_err(|_| Rejected::Memory)?;
let mut work = WorkBudget::new(16 * (compressed.len() as u64 + manifest.size) + (1 << 20));
let mut out = [0u8; 16 * 1024];
let mut hasher = Fnv1a::new();
let mut produced = 0u64;
let mut input = compressed;
loop {
let chunk = &input[..input.len().min(64 * 1024)];
let last = chunk.len() == input.len();
let progress = decoder
.step(chunk, last, &mut out, &mut work)
.map_err(Rejected::Decode)?;
hasher.update(&out[..progress.produced]);
produced += progress.produced as u64;
input = &input[progress.consumed..];
if progress.state == StepState::Done {
break;
}
}
if produced != manifest.size || hasher.0 != manifest.digest {
return Err(Rejected::Mismatch);
}
Ok(())
}
fn main() {
let content: Vec<u8> = (0..2_000_000u32)
.map(|i| (i % 251) as u8 ^ (i >> 11) as u8)
.collect();
let compressed = compress_slice_to_vec(&content, CompressionLevel::Default);
let mut hasher = Fnv1a::new();
hasher.update(&content);
let manifest = Manifest {
size: content.len() as u64,
digest: hasher.0,
};
verify(&compressed, &manifest, 8 << 20).expect("the blob verifies");
println!(
"verified {} bytes from {} compressed in a {}-byte workspace",
manifest.size,
compressed.len(),
workspace_bytes(8 << 20).unwrap()
);
let mut corrupt = compressed.clone();
let middle = corrupt.len() / 2;
corrupt[middle] ^= 0x40;
println!(
"corrupted blob: {}",
outcome(verify(&corrupt, &manifest, 8 << 20))
);
let truncated = &compressed[..compressed.len() - 10];
println!(
"truncated blob: {}",
outcome(verify(truncated, &manifest, 8 << 20))
);
}