use crate::Lsn;
use crate::crc::crc32c;
pub(crate) const RECORD_HEADER_SIZE: usize = 20;
const REC_TYPE_FULL: u8 = 1;
const REC_TYPE_SENTINEL: u8 = 0;
const LEN_OFF: usize = 4;
const LSN_OFF: usize = 8;
const REC_TYPE_OFF: usize = 16;
const PAYLOAD_OFF: usize = RECORD_HEADER_SIZE;
const ZERO_PAD: [u8; 7] = [0u8; 7];
#[inline]
#[must_use]
pub(crate) const fn padding_for(payload_len: usize) -> usize {
(8 - ((RECORD_HEADER_SIZE + payload_len) % 8)) % 8
}
#[inline]
#[must_use]
const fn padding_for_u64(payload_len: u64) -> u64 {
(8 - ((RECORD_HEADER_SIZE as u64 % 8 + payload_len % 8) % 8)) % 8
}
#[inline]
#[must_use]
pub(crate) const fn framed_size(payload_len: usize) -> usize {
RECORD_HEADER_SIZE + payload_len + padding_for(payload_len)
}
#[inline]
#[must_use]
pub(crate) const fn framed_size_u64(payload_len: u64) -> u64 {
RECORD_HEADER_SIZE as u64 + payload_len + padding_for_u64(payload_len)
}
#[derive(Debug)]
pub(crate) enum Decoded<'a> {
Record {
lsn: Lsn,
payload: &'a [u8],
framed_len: usize,
},
Sentinel,
Incomplete,
Invalid(#[allow(dead_code)] DecodeError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DecodeError {
LengthTooLarge,
BadCrc,
UnknownRecType,
}
pub(crate) fn encode_into(buf: &mut Vec<u8>, lsn: Lsn, payload: &[u8]) -> usize {
debug_assert!(
payload.len() <= u32::MAX as usize,
"payload length must fit u32 (caller enforces max_record_size)"
);
let length = payload.len() as u32;
let pad = padding_for(payload.len());
let start = buf.len();
buf.reserve(framed_size(payload.len()));
buf.extend_from_slice(&[0u8; 4]);
buf.extend_from_slice(&length.to_le_bytes());
buf.extend_from_slice(&lsn.0.to_le_bytes());
buf.push(REC_TYPE_FULL);
buf.push(0); buf.extend_from_slice(&[0u8; 2]); debug_assert_eq!(buf.len() - start, RECORD_HEADER_SIZE);
buf.extend_from_slice(payload);
buf.extend_from_slice(&ZERO_PAD[..pad]);
let framed = buf.len() - start;
debug_assert_eq!(framed, framed_size(payload.len()));
let crc = crc32c(&buf[start + LEN_OFF..start + framed]);
buf[start..start + LEN_OFF].copy_from_slice(&crc.to_le_bytes());
framed
}
pub(crate) fn peek(buf: &[u8]) -> (Lsn, usize) {
debug_assert!(buf.len() >= RECORD_HEADER_SIZE, "peek needs a whole record");
let length = u32::from_le_bytes(buf[LEN_OFF..LEN_OFF + 4].try_into().unwrap()) as usize;
let lsn = Lsn(u64::from_le_bytes(
buf[LSN_OFF..LSN_OFF + 8].try_into().unwrap(),
));
(lsn, framed_size(length))
}
pub(crate) fn decode(buf: &[u8], max_record_size: u32) -> Decoded<'_> {
if buf.len() < RECORD_HEADER_SIZE {
return Decoded::Incomplete;
}
let rec_type = buf[REC_TYPE_OFF];
if rec_type == REC_TYPE_SENTINEL && buf[..RECORD_HEADER_SIZE].iter().all(|&b| b == 0) {
return Decoded::Sentinel;
}
let length = u32::from_le_bytes(buf[LEN_OFF..LEN_OFF + 4].try_into().unwrap());
if length > max_record_size {
return Decoded::Invalid(DecodeError::LengthTooLarge);
}
let framed_u64 =
RECORD_HEADER_SIZE as u64 + u64::from(length) + padding_for_u64(u64::from(length));
if framed_u64 > buf.len() as u64 {
return Decoded::Incomplete;
}
let length = length as usize;
let framed = framed_u64 as usize;
let stored_crc = u32::from_le_bytes(buf[0..4].try_into().unwrap());
let computed_crc = crc32c(&buf[LEN_OFF..framed]);
if computed_crc != stored_crc {
return Decoded::Invalid(DecodeError::BadCrc);
}
if rec_type != REC_TYPE_FULL {
return Decoded::Invalid(DecodeError::UnknownRecType);
}
let lsn = Lsn(u64::from_le_bytes(
buf[LSN_OFF..LSN_OFF + 8].try_into().unwrap(),
));
Decoded::Record {
lsn,
payload: &buf[PAYLOAD_OFF..PAYLOAD_OFF + length],
framed_len: framed,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(miri))]
use proptest::prelude::*;
const MAX: u32 = 1 << 20;
fn assert_roundtrip(lsn: Lsn, payload: &[u8]) {
let mut buf = Vec::new();
let framed = encode_into(&mut buf, lsn, payload);
assert_eq!(framed, framed_size(payload.len()));
assert_eq!(framed % 8, 0, "framed size must be 8-aligned");
assert_eq!(buf.len(), framed);
match decode(&buf, MAX) {
Decoded::Record {
lsn: got_lsn,
payload: got_payload,
framed_len,
} => {
assert_eq!(got_lsn, lsn);
assert_eq!(got_payload, payload);
assert_eq!(framed_len, framed);
}
other => panic!("expected Record, got {other:?}"),
}
}
#[test]
fn roundtrip_alignment_edge_sizes() {
for len in [0usize, 1, 7, 8, 9] {
let payload: Vec<u8> = (0..len).map(|i| i as u8).collect();
assert_roundtrip(Lsn(1), &payload);
}
}
#[test]
fn roundtrip_spread_and_max() {
let sizes: &[usize] = if cfg!(miri) {
&[16, 100, 1000]
} else {
&[16, 100, 1000, MAX as usize]
};
for &len in sizes {
let payload: Vec<u8> = (0..len).map(|i| (i * 31 + 7) as u8).collect();
assert_roundtrip(Lsn(42), &payload);
}
}
#[test]
fn padding_formula_and_zero_bytes() {
for len in 0usize..=64 {
let expected_pad = (8 - ((RECORD_HEADER_SIZE + len) % 8)) % 8;
assert_eq!(padding_for(len), expected_pad);
assert_eq!(framed_size(len), RECORD_HEADER_SIZE + len + expected_pad);
let payload = vec![0xABu8; len];
let mut buf = Vec::new();
let framed = encode_into(&mut buf, Lsn(1), &payload);
for &b in &buf[PAYLOAD_OFF + len..framed] {
assert_eq!(b, 0);
}
}
}
#[test]
fn padding_is_inside_crc_coverage() {
let mut buf = Vec::new();
let framed = encode_into(&mut buf, Lsn(1), &[0x55]);
let pad = padding_for(1);
assert!(pad > 0, "need a padded payload to test this");
let pad_idx = PAYLOAD_OFF + 1;
assert!(pad_idx < framed);
buf[pad_idx] ^= 0xFF;
assert!(matches!(
decode(&buf, MAX),
Decoded::Invalid(DecodeError::BadCrc)
));
}
#[test]
fn payload_and_crc_field_corruption_detected() {
let mut buf = Vec::new();
encode_into(&mut buf, Lsn(7), &[1, 2, 3, 4, 5]);
let mut a = buf.clone();
a[PAYLOAD_OFF] ^= 0x01;
assert!(matches!(
decode(&a, MAX),
Decoded::Invalid(DecodeError::BadCrc)
));
let mut b = buf.clone();
b[0] ^= 0x01;
assert!(matches!(
decode(&b, MAX),
Decoded::Invalid(DecodeError::BadCrc)
));
}
#[test]
fn length_over_max_rejected_without_oob() {
let mut buf = vec![0u8; RECORD_HEADER_SIZE];
buf[REC_TYPE_OFF] = REC_TYPE_FULL;
buf[LEN_OFF..LEN_OFF + 4].copy_from_slice(&u32::MAX.to_le_bytes());
assert!(matches!(
decode(&buf, 64),
Decoded::Invalid(DecodeError::LengthTooLarge)
));
}
#[test]
fn framed_overrun_is_incomplete_without_oob() {
let mut buf = vec![0u8; RECORD_HEADER_SIZE];
buf[REC_TYPE_OFF] = REC_TYPE_FULL;
buf[LEN_OFF..LEN_OFF + 4].copy_from_slice(&100u32.to_le_bytes());
assert!(matches!(decode(&buf, MAX), Decoded::Incomplete));
}
#[test]
fn short_buffer_is_incomplete() {
for n in 0..RECORD_HEADER_SIZE {
assert!(matches!(decode(&vec![0xFFu8; n], MAX), Decoded::Incomplete));
}
}
#[test]
fn sentinel_header_detected() {
let mut buf = Vec::new();
encode_into(&mut buf, Lsn(1), &[0xAB, 0xCD]);
buf[REC_TYPE_OFF] = REC_TYPE_SENTINEL; assert!(matches!(
decode(&buf, MAX),
Decoded::Invalid(DecodeError::BadCrc)
));
let mut garbage = vec![0xFFu8; RECORD_HEADER_SIZE];
garbage[REC_TYPE_OFF] = REC_TYPE_SENTINEL;
assert!(matches!(decode(&garbage, MAX), Decoded::Invalid(_)));
assert!(matches!(
decode(&[0u8; RECORD_HEADER_SIZE], MAX),
Decoded::Sentinel
));
}
#[test]
fn unknown_rec_type_with_valid_crc_is_invalid() {
let mut buf = Vec::new();
let framed = encode_into(&mut buf, Lsn(3), &[9, 8, 7]);
buf[REC_TYPE_OFF] = 2; let crc = crc32c(&buf[LEN_OFF..framed]);
buf[0..LEN_OFF].copy_from_slice(&crc.to_le_bytes());
assert!(matches!(
decode(&buf, MAX),
Decoded::Invalid(DecodeError::UnknownRecType)
));
}
#[test]
fn lsn_roundtrips_small_and_large() {
assert_roundtrip(Lsn(1), b"first");
assert_roundtrip(Lsn(u64::MAX), b"last");
}
#[test]
fn padding_for_u64_matches_usize_helper() {
for len in 0u64..=64 {
assert_eq!(padding_for_u64(len), padding_for(len as usize) as u64);
}
for len in [u32::MAX as u64, u32::MAX as u64 + 1, u64::MAX] {
let expected = (8 - ((20u64 + len % 8) % 8)) % 8;
assert_eq!(padding_for_u64(len), expected);
}
}
#[test]
fn framed_size_u64_matches_and_does_not_wrap() {
for len in [0usize, 1, 7, 8, 9, 20, 4096] {
assert_eq!(framed_size_u64(len as u64), framed_size(len) as u64);
}
let big = u32::MAX as u64;
let f = framed_size_u64(big);
assert!(
f >= 20 + big,
"framed_size_u64 must not wrap for a huge length"
);
assert_eq!(f % 8, 0, "framed size is always 8-aligned");
}
#[cfg(not(miri))]
proptest! {
#[test]
fn prop_roundtrip(lsn in any::<u64>(), payload in proptest::collection::vec(any::<u8>(), 0..4096)) {
let mut buf = Vec::new();
let framed = encode_into(&mut buf, Lsn(lsn), &payload);
prop_assert_eq!(framed, framed_size(payload.len()));
prop_assert_eq!(framed % 8, 0);
match decode(&buf, MAX) {
Decoded::Record { lsn: got, payload: got_p, framed_len } => {
prop_assert_eq!(got, Lsn(lsn));
prop_assert_eq!(got_p, &payload[..]);
prop_assert_eq!(framed_len, framed);
}
other => prop_assert!(false, "expected Record, got {:?}", other),
}
}
#[test]
fn prop_single_bit_flip_is_detected(
payload in proptest::collection::vec(any::<u8>(), 0..256),
bit in any::<u8>(),
) {
let mut buf = Vec::new();
let framed = encode_into(&mut buf, Lsn(1), &payload);
let idx = (bit as usize) % framed;
let bitpos = bit % 8;
buf[idx] ^= 1 << bitpos;
let resurfaced = matches!(
decode(&buf, MAX),
Decoded::Record { lsn, payload: p, .. }
if lsn == Lsn(1) && p == &payload[..]
);
prop_assert!(!resurfaced, "flip at byte {} bit {} was not detected", idx, bitpos);
}
#[test]
fn prop_decode_arbitrary_is_bounded(
bytes in proptest::collection::vec(any::<u8>(), 0..2048),
max in any::<u32>(),
) {
match decode(&bytes, max) {
Decoded::Record { payload, framed_len, .. } => {
prop_assert!(payload.len() as u64 <= max as u64);
prop_assert!(framed_len <= bytes.len());
}
Decoded::Invalid(DecodeError::LengthTooLarge)
| Decoded::Invalid(DecodeError::BadCrc)
| Decoded::Invalid(DecodeError::UnknownRecType)
| Decoded::Sentinel
| Decoded::Incomplete => {}
}
}
}
}