use super::error::{Error, Result};
pub const RECORD_HEADER_LEN: usize = 8;
const EMPTY_PAYLOAD_CRC: u32 = 0xFFFF_FFFF;
#[inline]
fn payload_crc(payload: &[u8]) -> u32 {
if payload.is_empty() {
EMPTY_PAYLOAD_CRC
} else {
crc32fast::hash(payload)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct RecordHeader {
pub entry_len: u32,
pub crc32: u32,
}
impl RecordHeader {
#[inline]
pub const fn new(entry_len: u32, crc32: u32) -> Self {
Self { entry_len, crc32 }
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.entry_len == 0 && self.crc32 == 0
}
#[inline]
pub const fn payload_len(&self) -> usize {
self.entry_len as usize
}
#[inline]
pub fn for_payload(payload: &[u8]) -> Self {
Self {
entry_len: payload.len() as u32,
crc32: payload_crc(payload),
}
}
#[inline]
pub fn verify(&self, payload: &[u8]) -> Result<()> {
if payload.len() != self.entry_len as usize {
return Err(Error::InvalidRecordHeader);
}
let actual = payload_crc(payload);
if actual != self.crc32 {
return Err(Error::ChecksumMismatch {
expected: self.crc32,
actual,
});
}
Ok(())
}
#[inline]
pub fn encode(&self, dest: &mut [u8]) {
debug_assert!(dest.len() >= RECORD_HEADER_LEN);
if let Some(chunk) = dest.first_chunk_mut::<RECORD_HEADER_LEN>() {
*chunk = self.to_bytes();
}
}
#[inline]
pub const fn to_bytes(&self) -> [u8; RECORD_HEADER_LEN] {
let packed = (self.entry_len as u64) | ((self.crc32 as u64) << 32);
packed.to_le_bytes()
}
#[inline]
pub const fn from_bytes(src: &[u8; RECORD_HEADER_LEN]) -> Self {
let packed = u64::from_le_bytes(*src);
Self {
entry_len: packed as u32,
crc32: (packed >> 32) as u32,
}
}
#[inline]
pub fn decode(src: &[u8]) -> Result<Self> {
let Some(chunk) = src.first_chunk::<RECORD_HEADER_LEN>() else {
return Err(Error::InvalidRecordHeader);
};
Ok(Self::from_bytes(chunk))
}
}