const MIN_ENVELOPE_SIZE: usize = 5;
pub fn wrap(payload: &[u8]) -> Vec<u8> {
let checksum = crc32c::crc32c(payload);
let mut envelope = Vec::with_capacity(payload.len() + 4);
envelope.extend_from_slice(payload);
envelope.extend_from_slice(&checksum.to_le_bytes());
envelope
}
pub fn unwrap(envelope: &[u8]) -> Option<Vec<u8>> {
if envelope.len() < MIN_ENVELOPE_SIZE {
return None;
}
let split = envelope.len() - 4;
let payload = &envelope[..split];
let stored_checksum = u32::from_le_bytes(
envelope[split..]
.try_into()
.expect("split guarantees 4 bytes"),
);
let computed = crc32c::crc32c(payload);
if computed != stored_checksum {
return None;
}
Some(payload.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_empty_payload() {
let wrapped = wrap(b"");
assert_eq!(wrapped.len(), 4); assert!(unwrap(&wrapped).is_none());
}
#[test]
fn roundtrip_small_payload() {
let data = b"hello world";
let wrapped = wrap(data);
assert_eq!(wrapped.len(), data.len() + 4);
let unwrapped = unwrap(&wrapped).expect("valid checksum");
assert_eq!(unwrapped, data);
}
#[test]
fn roundtrip_large_payload() {
let data = vec![0xABu8; 1_000_000];
let wrapped = wrap(&data);
let unwrapped = unwrap(&wrapped).expect("valid checksum");
assert_eq!(unwrapped, data);
}
#[test]
fn detect_bit_flip() {
let data = b"important checkpoint data";
let mut wrapped = wrap(data);
wrapped[5] ^= 0x01;
assert!(unwrap(&wrapped).is_none(), "should detect corruption");
}
#[test]
fn detect_truncation() {
let data = b"checkpoint";
let wrapped = wrap(data);
assert!(unwrap(&wrapped[..wrapped.len() - 1]).is_none());
}
#[test]
fn detect_checksum_tamper() {
let data = b"checkpoint";
let mut wrapped = wrap(data);
let len = wrapped.len();
wrapped[len - 1] ^= 0xFF;
assert!(unwrap(&wrapped).is_none());
}
#[test]
fn too_short_returns_none() {
assert!(unwrap(&[]).is_none());
assert!(unwrap(&[1, 2, 3, 4]).is_none()); }
#[test]
fn deterministic() {
let data = b"same input";
let w1 = wrap(data);
let w2 = wrap(data);
assert_eq!(w1, w2, "same input must produce same output");
}
}