#![allow(dead_code)]
use crate::{Error, Result};
pub(crate) const FRAME_MAGIC_V1: u32 = 0x4653_5901;
pub(crate) const FRAME_OVERHEAD: usize = 12;
pub(crate) const FRAME_MAX_PAYLOAD: u32 = (1 << 28) - 1;
#[inline]
pub(crate) fn crc32c(bytes: &[u8]) -> u32 {
::crc32c::crc32c(bytes)
}
pub(crate) struct Crc32cBuilder {
crc: u32,
}
impl Crc32cBuilder {
#[inline]
pub(crate) fn new() -> Self {
Self { crc: 0 }
}
#[inline]
pub(crate) fn update(&mut self, bytes: &[u8]) {
self.crc = ::crc32c::crc32c_append(self.crc, bytes);
}
#[inline]
pub(crate) fn finalize(self) -> u32 {
self.crc
}
}
#[inline]
pub(crate) fn encode_frame_into(payload: &[u8], buf: &mut [u8]) -> Result<usize> {
let total = payload.len().saturating_add(FRAME_OVERHEAD);
if buf.len() < total {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"encode_frame_into: buffer too small",
)));
}
if (payload.len() as u64) > (FRAME_MAX_PAYLOAD as u64) {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"journal record exceeds FRAME_MAX_PAYLOAD (256 MiB)",
)));
}
let len = payload.len() as u32;
buf[0..4].copy_from_slice(&FRAME_MAGIC_V1.to_be_bytes());
buf[4..8].copy_from_slice(&len.to_le_bytes());
buf[8..8 + payload.len()].copy_from_slice(payload);
let crc = crc32c(&buf[..8 + payload.len()]);
buf[8 + payload.len()..total].copy_from_slice(&crc.to_le_bytes());
Ok(total)
}
#[inline]
pub(crate) fn encode_frame_owned(payload: &[u8]) -> Result<Vec<u8>> {
let total = payload.len().saturating_add(FRAME_OVERHEAD);
let mut buf = vec![0u8; total];
let _ = encode_frame_into(payload, &mut buf)?;
Ok(buf)
}
#[derive(Debug)]
pub(crate) enum FrameDecode {
Ok {
consumed: usize,
payload_start: usize,
payload_end: usize,
},
Truncated,
BadMagic,
LengthOverflow,
ChecksumMismatch,
}
#[inline]
pub(crate) fn decode_frame(bytes: &[u8]) -> FrameDecode {
if bytes.len() < 8 {
return FrameDecode::Truncated;
}
let magic = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
if magic != FRAME_MAGIC_V1 {
return FrameDecode::BadMagic;
}
let length = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
if length > FRAME_MAX_PAYLOAD {
return FrameDecode::LengthOverflow;
}
let length = length as usize;
let total = 8 + length + 4; if bytes.len() < total {
return FrameDecode::Truncated;
}
let computed = crc32c(&bytes[..8 + length]);
let stored_crc = u32::from_le_bytes([
bytes[8 + length],
bytes[8 + length + 1],
bytes[8 + length + 2],
bytes[8 + length + 3],
]);
if computed != stored_crc {
return FrameDecode::ChecksumMismatch;
}
FrameDecode::Ok {
consumed: total,
payload_start: 8,
payload_end: 8 + length,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc32c_known_answer_vectors() {
assert_eq!(crc32c(b""), 0);
let zeros = [0u8; 32];
assert_eq!(crc32c(&zeros), 0x8a9136aa);
let ones = [0xffu8; 32];
assert_eq!(crc32c(&ones), 0x62a8ab43);
let seq: [u8; 32] = std::array::from_fn(|i| i as u8);
assert_eq!(crc32c(&seq), 0x46dd794e);
}
#[test]
fn streaming_crc_matches_one_shot() {
let data = b"the quick brown fox jumps over the lazy dog";
let one_shot = crc32c(data);
let mut builder = Crc32cBuilder::new();
builder.update(&data[..10]);
builder.update(&data[10..25]);
builder.update(&data[25..]);
let streamed = builder.finalize();
assert_eq!(one_shot, streamed);
}
#[test]
fn frame_roundtrip_simple() {
let payload = b"hello, journal!";
let buf = encode_frame_owned(payload).expect("encode");
assert_eq!(buf.len(), FRAME_OVERHEAD + payload.len());
match decode_frame(&buf) {
FrameDecode::Ok {
consumed,
payload_start,
payload_end,
} => {
assert_eq!(consumed, buf.len());
assert_eq!(&buf[payload_start..payload_end], payload);
}
other => panic!("expected Ok, got {other:?}"),
}
}
#[test]
fn frame_roundtrip_empty_payload() {
let buf = encode_frame_owned(b"").expect("encode");
assert_eq!(buf.len(), FRAME_OVERHEAD);
match decode_frame(&buf) {
FrameDecode::Ok {
payload_start,
payload_end,
..
} => {
assert_eq!(payload_start, payload_end);
}
other => panic!("expected Ok empty, got {other:?}"),
}
}
#[test]
fn frame_roundtrip_4kib() {
let payload = vec![0xCDu8; 4096];
let buf = encode_frame_owned(&payload).expect("encode");
match decode_frame(&buf) {
FrameDecode::Ok {
payload_start,
payload_end,
..
} => {
assert_eq!(&buf[payload_start..payload_end], payload.as_slice());
}
other => panic!("expected Ok, got {other:?}"),
}
}
#[test]
fn truncated_header_returns_truncated() {
let buf = [0u8; 5]; assert!(matches!(decode_frame(&buf), FrameDecode::Truncated));
}
#[test]
fn truncated_payload_returns_truncated() {
let payload = b"this won't fit";
let mut buf = encode_frame_owned(payload).expect("encode");
buf.truncate(buf.len() - 5); assert!(matches!(decode_frame(&buf), FrameDecode::Truncated));
}
#[test]
fn bad_magic_returns_bad_magic() {
let mut buf = encode_frame_owned(b"hi").expect("encode");
buf[0] = 0xDE; assert!(matches!(decode_frame(&buf), FrameDecode::BadMagic));
}
#[test]
fn checksum_mismatch_returns_mismatch() {
let mut buf = encode_frame_owned(b"original").expect("encode");
buf[8] ^= 0xFF;
assert!(matches!(decode_frame(&buf), FrameDecode::ChecksumMismatch));
}
#[test]
fn length_overflow_rejected_at_encode() {
let mut buf = vec![0u8; 8];
buf[0..4].copy_from_slice(&FRAME_MAGIC_V1.to_be_bytes());
let bad_len = (FRAME_MAX_PAYLOAD + 1).to_le_bytes();
buf[4..8].copy_from_slice(&bad_len);
assert!(matches!(decode_frame(&buf), FrameDecode::LengthOverflow));
}
#[test]
fn frame_overhead_is_exactly_12_bytes() {
assert_eq!(FRAME_OVERHEAD, 12);
}
#[test]
fn frame_max_payload_fits_in_u32() {
assert!((FRAME_MAX_PAYLOAD as u64) < (u32::MAX as u64));
}
#[test]
fn property_random_round_trips() {
let mut state: u64 = 0xCAFEBABE_DEADBEEF;
let mut next = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
state
};
for iteration in 0..10_000u32 {
let size_class = (next() & 0xFF) as usize;
let size = match size_class {
0..=10 => 0, 11..=50 => (next() % 64) as usize, 51..=150 => (next() % 1024) as usize, 151..=220 => (next() % (16 * 1024)) as usize, _ => (next() % (64 * 1024)) as usize, };
let payload: Vec<u8> = (0..size)
.map(|i| ((next() >> 16) as u8) ^ (iteration as u8) ^ (i as u8))
.collect();
let encoded = encode_frame_owned(&payload).expect("encode");
assert_eq!(
encoded.len(),
FRAME_OVERHEAD + payload.len(),
"encoded length wrong on iteration {iteration} (size {size})"
);
match decode_frame(&encoded) {
FrameDecode::Ok {
consumed,
payload_start,
payload_end,
} => {
assert_eq!(
consumed,
encoded.len(),
"consumed mismatch on iteration {iteration}"
);
assert_eq!(
&encoded[payload_start..payload_end],
payload.as_slice(),
"payload mismatch on iteration {iteration} (size {size})"
);
}
other => panic!("decode failed on iteration {iteration} (size {size}): {other:?}"),
}
}
}
#[test]
fn property_single_bit_flip_detected() {
let payloads: &[&[u8]] = &[
b"a",
b"hello",
b"the quick brown fox jumps over the lazy dog",
&[0xFF; 256],
&[0; 1024],
];
for payload in payloads {
let encoded = encode_frame_owned(payload).expect("encode");
for byte_idx in 0..encoded.len() {
for bit in 0..8 {
let mut corrupted = encoded.clone();
corrupted[byte_idx] ^= 1 << bit;
match decode_frame(&corrupted) {
FrameDecode::Ok {
payload_start,
payload_end,
..
} if &corrupted[payload_start..payload_end] == *payload => {
panic!(
"single bit flip at byte {byte_idx} bit {bit} \
produced a 'valid' decode with the original payload — \
CRC-32C failed to detect corruption"
);
}
FrameDecode::Ok {
payload_start,
payload_end,
..
} => {
panic!(
"single bit flip at byte {byte_idx} bit {bit} \
produced a 'valid' decode with payload {:?}",
&corrupted[payload_start..payload_end]
);
}
_ => {}
}
}
}
}
}
#[test]
fn forward_iteration_over_concatenated_frames() {
let mut all = Vec::new();
let payloads: &[&[u8]] = &[b"first", b"second record", b"", b"fourth!"];
for p in payloads {
all.extend_from_slice(&encode_frame_owned(p).unwrap());
}
let mut cursor = 0;
let mut decoded: Vec<&[u8]> = Vec::new();
while cursor < all.len() {
match decode_frame(&all[cursor..]) {
FrameDecode::Ok {
consumed,
payload_start,
payload_end,
} => {
decoded.push(&all[cursor + payload_start..cursor + payload_end]);
cursor += consumed;
}
other => panic!("frame {} unexpected: {other:?}", decoded.len()),
}
}
assert_eq!(decoded, payloads);
}
}