use crate::core::{Error, ErrorKind, Result};
pub const SLOT_MAGIC: u32 = 0xFB57_0001;
pub const CACHE_LINE: usize = 64;
pub const HEADER_SIZE: usize = 32;
pub const FLAG_VALID: u16 = 0x0001;
pub const FLAG_SUSPECT: u16 = 0x0002;
pub fn slot_size(max_payload: usize) -> Result<usize> {
let raw = HEADER_SIZE
.checked_add(max_payload)
.ok_or_else(|| Error::config("slot_size overflow: max_payload too large"))?;
let rounded = raw
.checked_add(CACHE_LINE - 1)
.ok_or_else(|| Error::config("slot_size overflow while aligning"))?
& !(CACHE_LINE - 1);
Ok(rounded)
}
pub const fn slot_size_const(max_payload: usize) -> usize {
let raw = HEADER_SIZE + max_payload;
(raw + CACHE_LINE - 1) & !(CACHE_LINE - 1)
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlotHeader {
pub magic: u32,
pub schema_id: u16,
pub flags: u16,
pub sequence: u64,
pub timestamp: u64,
pub payload_len: u32,
pub(crate) _reserved: u32,
}
const _: () = assert!(
core::mem::size_of::<SlotHeader>() == HEADER_SIZE,
"SlotHeader must be exactly 32 bytes"
);
impl SlotHeader {
pub fn new(
schema_id: u16,
flags: u16,
sequence: u64,
timestamp: u64,
payload_len: u32,
) -> Self {
Self {
magic: SLOT_MAGIC,
schema_id,
flags,
sequence,
timestamp,
payload_len,
_reserved: 0,
}
}
pub fn is_valid(&self) -> bool {
self.magic == SLOT_MAGIC && (self.flags & FLAG_VALID) != 0
}
}
pub fn encode(header: &SlotHeader, payload: &[u8], dst: &mut [u8]) -> Result<()> {
let needed = HEADER_SIZE + payload.len();
if dst.len() < needed {
return Err(Error::new(
ErrorKind::Encode,
"destination buffer too small for slot",
));
}
unsafe {
core::ptr::copy_nonoverlapping(
header as *const SlotHeader as *const u8,
dst.as_mut_ptr(),
HEADER_SIZE,
);
}
dst[HEADER_SIZE..needed].copy_from_slice(payload);
Ok(())
}
pub fn decode(src: &[u8]) -> Result<(SlotHeader, &[u8])> {
if src.len() < HEADER_SIZE {
return Err(Error::new(
ErrorKind::Decode,
"buffer too small for slot header",
));
}
let header: SlotHeader = unsafe {
let mut h = core::mem::MaybeUninit::<SlotHeader>::uninit();
core::ptr::copy_nonoverlapping(src.as_ptr(), h.as_mut_ptr() as *mut u8, HEADER_SIZE);
h.assume_init()
};
if header.magic != SLOT_MAGIC {
return Err(Error::new(ErrorKind::Decode, "invalid slot magic"));
}
let payload_end = HEADER_SIZE + header.payload_len as usize;
if src.len() < payload_end {
return Err(Error::new(
ErrorKind::Decode,
"buffer too small for slot payload",
));
}
Ok((header, &src[HEADER_SIZE..payload_end]))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_header(seq: u64) -> SlotHeader {
SlotHeader::new(1, FLAG_VALID, seq, 1_000_000_000, 4)
}
#[test]
fn header_size_is_32() {
assert_eq!(core::mem::size_of::<SlotHeader>(), HEADER_SIZE);
}
#[test]
fn slot_size_rounds_to_cache_line() {
assert_eq!(slot_size(0).unwrap(), CACHE_LINE);
assert_eq!(slot_size(32).unwrap(), CACHE_LINE);
assert_eq!(slot_size(33).unwrap(), 2 * CACHE_LINE);
assert_eq!(slot_size(96).unwrap(), 2 * CACHE_LINE);
assert_eq!(slot_size(97).unwrap(), 3 * CACHE_LINE);
assert_eq!(slot_size_const(33), 2 * CACHE_LINE);
}
#[test]
fn encode_decode_roundtrip() {
let header = make_header(42);
let payload = b"tick";
let mut buf = vec![0u8; HEADER_SIZE + payload.len()];
encode(&header, payload, &mut buf).unwrap();
let (decoded_header, decoded_payload) = decode(&buf).unwrap();
assert_eq!(decoded_header, header);
assert_eq!(decoded_payload, payload);
}
#[test]
fn encode_rejects_short_dst() {
let header = make_header(1);
let mut buf = vec![0u8; HEADER_SIZE - 1];
assert!(encode(&header, &[], &mut buf).is_err());
}
#[test]
fn decode_rejects_short_src() {
let buf = vec![0u8; HEADER_SIZE - 1];
assert!(decode(&buf).is_err());
}
#[test]
fn decode_rejects_bad_magic() {
let header = make_header(1);
let mut buf = vec![0u8; HEADER_SIZE];
encode(&header, &[], &mut buf).unwrap();
buf[0] = 0xFF; assert!(decode(&buf).is_err());
}
#[test]
fn decode_rejects_payload_overrun() {
let header = SlotHeader::new(1, FLAG_VALID, 0, 0, 50);
let mut buf = vec![0u8; HEADER_SIZE + 50];
encode(&header, &[0u8; 50], &mut buf).unwrap();
let len_offset = core::mem::offset_of!(SlotHeader, payload_len);
buf[len_offset..len_offset + 4].copy_from_slice(&100u32.to_ne_bytes());
assert!(decode(&buf).is_err());
}
#[test]
fn flag_valid_and_suspect() {
let h = SlotHeader::new(1, FLAG_VALID | FLAG_SUSPECT, 0, 0, 0);
assert!(h.is_valid());
assert_eq!(h.flags & FLAG_SUSPECT, FLAG_SUSPECT);
}
#[test]
fn flag_missing_valid() {
let h = SlotHeader::new(1, 0, 0, 0, 0);
assert!(!h.is_valid());
}
}