use crc32c::crc32c;
pub const WIRE_ENVELOPE_MAGIC: &[u8; 4] = b"WMBT";
pub const WIRE_ENVELOPE_VERSION: u32 = 1;
pub const WIRE_ENVELOPE_BYTES: usize = 16;
const _: () = assert!(WIRE_ENVELOPE_BYTES == 16);
const _: () = assert!(
WIRE_ENVELOPE_BYTES
== std::mem::size_of::<[u8; 4]>() + std::mem::size_of::<u32>() + std::mem::size_of::<u32>() + std::mem::size_of::<u32>() );
const _: () = assert!(WIRE_ENVELOPE_VERSION == 1);
const _: () = assert!(WIRE_ENVELOPE_MAGIC.len() == 4);
#[derive(Debug)]
pub enum EnvelopeError {
TooShort { got: usize, need: usize },
BadMagic { got: [u8; 4] },
BadVersion { got: u32, want: u32 },
BadLength { header_len: u32, actual_body_bytes: usize },
BadCrc { header_crc: u32, computed_crc: u32 },
}
impl std::fmt::Display for EnvelopeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EnvelopeError::TooShort { got, need } => {
write!(f, "envelope too short: got {got} bytes, need at least {need}")
}
EnvelopeError::BadMagic { got } => {
write!(f, "envelope bad magic: got {got:?}, expected {WIRE_ENVELOPE_MAGIC:?}")
}
EnvelopeError::BadVersion { got, want } => {
write!(
f,
"envelope unsupported version {got} (want {want}); pre-launch \
breaking-window applies, daemons and clients must upgrade together"
)
}
EnvelopeError::BadLength { header_len, actual_body_bytes } => {
write!(
f,
"envelope length mismatch: header says {header_len} body bytes, \
actually got {actual_body_bytes}"
)
}
EnvelopeError::BadCrc { header_crc, computed_crc } => {
write!(
f,
"envelope CRC32C mismatch: header={header_crc:08x} computed={computed_crc:08x}"
)
}
}
}
}
impl std::error::Error for EnvelopeError {}
#[must_use]
pub fn encode_envelope(body: &[u8]) -> Vec<u8> {
let body_len = u32::try_from(body.len()).expect("body fits u32 (4 GiB)");
let crc = crc32c(body);
let mut out = Vec::with_capacity(WIRE_ENVELOPE_BYTES + body.len());
out.extend_from_slice(WIRE_ENVELOPE_MAGIC);
out.extend_from_slice(&WIRE_ENVELOPE_VERSION.to_le_bytes());
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&body_len.to_le_bytes());
out.extend_from_slice(body);
out
}
pub fn decode_envelope(bytes: &[u8]) -> Result<&[u8], EnvelopeError> {
if bytes.len() < WIRE_ENVELOPE_BYTES {
return Err(EnvelopeError::TooShort { got: bytes.len(), need: WIRE_ENVELOPE_BYTES });
}
let header_bytes: [u8; WIRE_ENVELOPE_BYTES] =
bytes[..WIRE_ENVELOPE_BYTES].try_into().expect("len checked");
let header = decode_envelope_header(&header_bytes)?;
let body_end =
WIRE_ENVELOPE_BYTES.checked_add(header.len as usize).ok_or(EnvelopeError::BadLength {
header_len: header.len,
actual_body_bytes: bytes.len().saturating_sub(WIRE_ENVELOPE_BYTES),
})?;
if bytes.len() != body_end {
return Err(EnvelopeError::BadLength {
header_len: header.len,
actual_body_bytes: bytes.len() - WIRE_ENVELOPE_BYTES,
});
}
let body = &bytes[WIRE_ENVELOPE_BYTES..body_end];
verify_envelope_crc(&header, body)?;
Ok(body)
}
pub struct EnvelopeHeader {
pub crc: u32,
pub len: u32,
}
pub fn decode_envelope_header(
header: &[u8; WIRE_ENVELOPE_BYTES],
) -> Result<EnvelopeHeader, EnvelopeError> {
let magic: [u8; 4] = header[0..4].try_into().expect("4-byte slice");
if &magic != WIRE_ENVELOPE_MAGIC {
return Err(EnvelopeError::BadMagic { got: magic });
}
let version = u32::from_le_bytes(header[4..8].try_into().expect("4-byte slice"));
if version != WIRE_ENVELOPE_VERSION {
return Err(EnvelopeError::BadVersion { got: version, want: WIRE_ENVELOPE_VERSION });
}
let crc = u32::from_le_bytes(header[8..12].try_into().expect("4-byte slice"));
let len = u32::from_le_bytes(header[12..16].try_into().expect("4-byte slice"));
Ok(EnvelopeHeader { crc, len })
}
pub fn verify_envelope_crc(header: &EnvelopeHeader, body: &[u8]) -> Result<(), EnvelopeError> {
if body.len() != header.len as usize {
return Err(EnvelopeError::BadLength {
header_len: header.len,
actual_body_bytes: body.len(),
});
}
let computed = crc32c(body);
if computed != header.crc {
return Err(EnvelopeError::BadCrc { header_crc: header.crc, computed_crc: computed });
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_small() {
let body = b"hello world";
let wire = encode_envelope(body);
assert_eq!(wire.len(), WIRE_ENVELOPE_BYTES + body.len());
let decoded = decode_envelope(&wire).unwrap();
assert_eq!(decoded, body);
}
#[test]
fn roundtrip_empty() {
let body: &[u8] = &[];
let wire = encode_envelope(body);
assert_eq!(wire.len(), WIRE_ENVELOPE_BYTES);
let decoded = decode_envelope(&wire).unwrap();
assert_eq!(decoded, body);
}
#[test]
fn roundtrip_large() {
let body = vec![0xa5u8; 1 << 20]; let wire = encode_envelope(&body);
assert_eq!(wire.len(), WIRE_ENVELOPE_BYTES + body.len());
let decoded = decode_envelope(&wire).unwrap();
assert_eq!(decoded, body.as_slice());
}
#[test]
#[allow(non_snake_case)]
fn pinned_layout_v1_DO_NOT_UPDATE() {
let body = b"x";
let wire = encode_envelope(body);
assert_eq!(wire.len(), 17);
assert_eq!(&wire[0..4], b"WMBT", "magic");
assert_eq!(&wire[4..8], &1u32.to_le_bytes(), "version=1 LE");
let crc_x = crc32c(b"x");
assert_eq!(&wire[8..12], &crc_x.to_le_bytes(), "crc32c('x') LE");
assert_eq!(&wire[12..16], &1u32.to_le_bytes(), "body_len=1 LE");
assert_eq!(wire[16], b'x', "body byte 0");
}
#[test]
fn header_decode_rejects_oversized_len_without_body_alloc() {
let mut header = [0u8; WIRE_ENVELOPE_BYTES];
header[0..4].copy_from_slice(WIRE_ENVELOPE_MAGIC);
header[4..8].copy_from_slice(&WIRE_ENVELOPE_VERSION.to_le_bytes());
header[8..12].copy_from_slice(&0xDEADBEEFu32.to_le_bytes()); header[12..16].copy_from_slice(&u32::MAX.to_le_bytes()); let h = decode_envelope_header(&header).expect("header itself parses");
assert_eq!(h.len, u32::MAX);
}
#[test]
fn rejects_bad_magic() {
let mut wire = encode_envelope(b"hi");
wire[0] = b'X';
assert!(matches!(decode_envelope(&wire), Err(EnvelopeError::BadMagic { .. })));
}
#[test]
fn rejects_bad_version() {
let mut wire = encode_envelope(b"hi");
wire[4] = 99;
assert!(matches!(decode_envelope(&wire), Err(EnvelopeError::BadVersion { .. })));
}
#[test]
fn rejects_bad_crc() {
let mut wire = encode_envelope(b"hi");
wire[16] = b'X'; assert!(matches!(decode_envelope(&wire), Err(EnvelopeError::BadCrc { .. })));
}
#[test]
fn rejects_truncated_body() {
let wire = encode_envelope(b"hello");
let truncated = &wire[..wire.len() - 1];
assert!(matches!(decode_envelope(truncated), Err(EnvelopeError::BadLength { .. })));
}
#[test]
fn rejects_extra_bytes() {
let mut wire = encode_envelope(b"hi");
wire.push(0);
assert!(matches!(decode_envelope(&wire), Err(EnvelopeError::BadLength { .. })));
}
#[test]
fn header_only_decode() {
let body = b"streaming reader needs len";
let wire = encode_envelope(body);
let header_bytes: [u8; WIRE_ENVELOPE_BYTES] =
wire[..WIRE_ENVELOPE_BYTES].try_into().unwrap();
let header = decode_envelope_header(&header_bytes).unwrap();
assert_eq!(header.len as usize, body.len());
verify_envelope_crc(&header, body).unwrap();
}
}