use crate::error::{Error, Result};
pub(crate) const MAGIC: [u8; 4] = *b"MRNE";
pub(crate) const ENCODING_VERSION: u8 = 0;
pub(crate) const HEADER_LEN: usize = MAGIC.len() + 1;
pub(crate) fn header_buf(payload_len: usize) -> Vec<u8> {
let mut framed = Vec::with_capacity(HEADER_LEN + payload_len);
framed.extend_from_slice(&MAGIC);
framed.push(ENCODING_VERSION);
framed
}
#[cfg(test)]
pub(crate) fn frame(payload: &[u8]) -> Vec<u8> {
let mut framed = header_buf(payload.len());
framed.extend_from_slice(payload);
framed
}
pub(crate) fn unframe(bytes: &[u8]) -> Result<&[u8]> {
let (header, payload) = bytes
.split_at_checked(HEADER_LEN)
.ok_or_else(|| Error::Corruption("value: truncated framing header".to_string()))?;
if header[..MAGIC.len()] != MAGIC {
return Err(Error::Corruption("value: bad magic".to_string()));
}
let version = header[MAGIC.len()];
if version > ENCODING_VERSION {
return Err(Error::Corruption(format!(
"value: encoding version {version} is newer than supported {ENCODING_VERSION}"
)));
}
Ok(payload)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
#[test]
fn frame_prepends_magic_and_version() {
let framed = frame(b"payload");
assert_eq!(&framed[..4], b"MRNE");
assert_eq!(framed[4], ENCODING_VERSION);
assert_eq!(&framed[5..], b"payload");
}
#[test]
fn unframe_rejects_corrupt_magic() {
let mut framed = frame(b"payload");
framed[0] = b'X';
let err = unframe(&framed).unwrap_err();
assert!(matches!(err, Error::Corruption(_)));
}
#[test]
fn unframe_rejects_truncated_header() {
for len in 0..5 {
let framed = frame(b"payload");
assert!(
unframe(&framed[..len]).is_err(),
"header truncated to {len} bytes must not decode"
);
}
}
#[test]
fn unframe_rejects_newer_encoding_version() {
let mut framed = frame(b"payload");
framed[4] = ENCODING_VERSION + 1;
let err = unframe(&framed).unwrap_err();
assert!(matches!(err, Error::Corruption(_)));
}
#[test]
fn empty_payload_roundtrips() {
assert_eq!(unframe(&frame(b"")).unwrap(), b"");
}
proptest! {
#[test]
fn roundtrip(payload in proptest::collection::vec(any::<u8>(), 0..1024)) {
let framed = frame(&payload);
prop_assert_eq!(unframe(&framed).unwrap(), payload.as_slice());
}
#[test]
fn unframe_arbitrary_bytes_never_panics(
bytes in proptest::collection::vec(any::<u8>(), 0..64),
) {
let _ = unframe(&bytes);
}
}
}