use crate::opcode::OPCODE_SIZE;
pub const MAGIC: u32 = 0x504C_504C;
pub const PROTOCOL_VERSION: u16 = 1;
pub const DEFAULT_SLOT_COUNT: usize = 4096;
pub const DEFAULT_ARENA_BYTES: usize = 1 << 20; pub const DEFAULT_EVENT_ARENA_BYTES: usize = 1 << 20;
pub const OFF_MAGIC: usize = 0x00;
pub const OFF_VERSION: usize = 0x04;
pub const OFF_RING_OFFSET: usize = 0x06;
pub const OFF_RING_BYTES: usize = 0x0A;
pub const OFF_SLOT_COUNT: usize = 0x0E;
pub const OFF_ARENA_OFFSET: usize = 0x12;
pub const OFF_ARENA_BYTES: usize = 0x16;
pub const OFF_ARENA_CURSOR: usize = 0x1A;
pub const OFF_READ_CURSOR: usize = 0x1E;
pub const OFF_WRITE_CURSOR: usize = 0x22;
pub const OFF_FRAME_COUNT: usize = 0x26;
pub const OFF_EVENT_RING_OFFSET: usize = 0x2A;
pub const OFF_EVENT_RING_BYTES: usize = 0x2E;
pub const OFF_EVENT_SLOT_COUNT: usize = 0x32;
pub const OFF_EVENT_READ_CURSOR: usize = 0x36;
pub const OFF_EVENT_WRITE_CURSOR: usize = 0x3A;
pub const OFF_EVENT_ARENA_OFFSET: usize = 0x40;
pub const OFF_EVENT_ARENA_BYTES: usize = 0x44;
pub const OFF_EVENT_ARENA_CURSOR: usize = 0x48;
pub const HEADER_SIZE: usize = 0x50;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemoryLayout {
pub slot_count: usize,
pub arena_bytes: usize,
pub event_arena_bytes: usize,
}
impl Default for MemoryLayout {
fn default() -> Self {
Self {
slot_count: DEFAULT_SLOT_COUNT,
arena_bytes: DEFAULT_ARENA_BYTES,
event_arena_bytes: DEFAULT_EVENT_ARENA_BYTES,
}
}
}
impl MemoryLayout {
#[inline]
pub fn ring_bytes(&self) -> usize {
self.slot_count * OPCODE_SIZE
}
#[inline]
pub fn event_ring_bytes(&self) -> usize {
self.slot_count * OPCODE_SIZE
}
pub fn total_bytes(&self) -> usize {
HEADER_SIZE
+ self.ring_bytes()
+ self.event_ring_bytes()
+ self.arena_bytes
+ self.event_arena_bytes
}
#[inline]
pub fn ring_offset(&self) -> usize {
HEADER_SIZE
}
#[inline]
pub fn event_ring_offset(&self) -> usize {
self.ring_offset() + self.ring_bytes()
}
#[inline]
pub fn arena_offset(&self) -> usize {
self.event_ring_offset() + self.event_ring_bytes()
}
#[inline]
pub fn event_arena_offset(&self) -> usize {
self.arena_offset() + self.arena_bytes
}
#[inline]
pub fn ring_mask(&self) -> usize {
debug_assert!(self.slot_count.is_power_of_two());
self.slot_count - 1
}
}
pub(crate) mod header {
use super::*;
#[inline]
pub(crate) fn get_u32(buf: &[u8], off: usize) -> u32 {
u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
}
#[inline]
pub(crate) fn set_u32(buf: &mut [u8], off: usize, value: u32) {
buf[off..off + 4].copy_from_slice(&value.to_le_bytes());
}
#[inline]
pub(crate) fn get_u16(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes(buf[off..off + 2].try_into().unwrap())
}
pub(crate) fn init(buf: &mut [u8], layout: &MemoryLayout) {
buf[..HEADER_SIZE].fill(0);
set_u32(buf, OFF_MAGIC, MAGIC);
buf[OFF_VERSION..OFF_VERSION + 2].copy_from_slice(&PROTOCOL_VERSION.to_le_bytes());
set_u32(buf, OFF_RING_OFFSET, layout.ring_offset() as u32);
set_u32(buf, OFF_RING_BYTES, layout.ring_bytes() as u32);
set_u32(buf, OFF_SLOT_COUNT, layout.slot_count as u32);
set_u32(buf, OFF_ARENA_OFFSET, layout.arena_offset() as u32);
set_u32(buf, OFF_ARENA_BYTES, layout.arena_bytes as u32);
set_u32(buf, OFF_ARENA_CURSOR, 0);
set_u32(buf, OFF_READ_CURSOR, 0);
set_u32(buf, OFF_WRITE_CURSOR, 0);
set_u32(buf, OFF_FRAME_COUNT, 0);
set_u32(buf, OFF_EVENT_RING_OFFSET, layout.event_ring_offset() as u32);
set_u32(buf, OFF_EVENT_RING_BYTES, layout.event_ring_bytes() as u32);
set_u32(buf, OFF_EVENT_SLOT_COUNT, layout.slot_count as u32);
set_u32(buf, OFF_EVENT_READ_CURSOR, 0);
set_u32(buf, OFF_EVENT_WRITE_CURSOR, 0);
set_u32(buf, OFF_EVENT_ARENA_OFFSET, layout.event_arena_offset() as u32);
set_u32(buf, OFF_EVENT_ARENA_BYTES, layout.event_arena_bytes as u32);
set_u32(buf, OFF_EVENT_ARENA_CURSOR, 0);
}
#[allow(dead_code)]
pub(crate) fn validate(buf: &[u8], layout: &MemoryLayout) -> bool {
if buf.len() < HEADER_SIZE {
return false;
}
let magic = get_u32(buf, OFF_MAGIC);
if magic != MAGIC {
return false;
}
let version = get_u16(buf, OFF_VERSION);
if version != PROTOCOL_VERSION {
return false;
}
get_u32(buf, OFF_SLOT_COUNT) as usize == layout.slot_count
&& get_u32(buf, OFF_ARENA_BYTES) as usize == layout.arena_bytes
&& get_u32(buf, OFF_EVENT_RING_OFFSET) as usize == layout.event_ring_offset()
&& get_u32(buf, OFF_EVENT_RING_BYTES) as usize == layout.event_ring_bytes()
&& get_u32(buf, OFF_EVENT_SLOT_COUNT) as usize == layout.slot_count
&& get_u32(buf, OFF_EVENT_ARENA_OFFSET) as usize == layout.event_arena_offset()
&& get_u32(buf, OFF_EVENT_ARENA_BYTES) as usize == layout.event_arena_bytes
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
#[test]
fn header_size_is_80() {
assert_eq!(HEADER_SIZE, 80);
}
#[test]
fn validate_rejects_wrong_magic() {
let layout = MemoryLayout::default();
let mut buf = vec![0u8; layout.total_bytes()];
header::init(&mut buf, &layout);
buf[OFF_MAGIC] ^= 0xFF;
assert!(!header::validate(&buf, &layout));
}
#[test]
fn validate_rejects_wrong_version() {
let layout = MemoryLayout::default();
let mut buf = vec![0u8; layout.total_bytes()];
header::init(&mut buf, &layout);
buf[OFF_VERSION] = (PROTOCOL_VERSION + 1) as u8;
assert!(!header::validate(&buf, &layout));
}
#[test]
fn validate_rejects_mismatched_layout() {
let layout = MemoryLayout::default();
let mut buf = vec![0u8; layout.total_bytes()];
header::init(&mut buf, &layout);
let other = MemoryLayout {
slot_count: layout.slot_count,
arena_bytes: layout.arena_bytes + 4096,
event_arena_bytes: layout.event_arena_bytes,
};
assert!(!header::validate(&buf, &other));
let other = MemoryLayout {
slot_count: layout.slot_count,
arena_bytes: layout.arena_bytes,
event_arena_bytes: layout.event_arena_bytes + 4096,
};
assert!(!header::validate(&buf, &other));
let other = MemoryLayout {
slot_count: layout.slot_count / 2,
arena_bytes: layout.arena_bytes,
event_arena_bytes: layout.event_arena_bytes,
};
assert!(!header::validate(&buf, &other));
}
#[test]
fn validate_rejects_short_buffer() {
let layout = MemoryLayout::default();
let tiny = vec![0u8; HEADER_SIZE - 1];
assert!(!header::validate(&tiny, &layout));
}
#[test]
fn default_layout_is_contiguous() {
let layout = MemoryLayout::default();
assert_eq!(layout.ring_offset(), HEADER_SIZE);
assert_eq!(
layout.event_ring_offset(),
HEADER_SIZE + layout.ring_bytes()
);
assert_eq!(
layout.arena_offset(),
HEADER_SIZE + layout.ring_bytes() + layout.event_ring_bytes()
);
assert_eq!(
layout.event_arena_offset(),
HEADER_SIZE + layout.ring_bytes() + layout.event_ring_bytes() + layout.arena_bytes
);
assert_eq!(
layout.total_bytes(),
HEADER_SIZE
+ layout.ring_bytes()
+ layout.event_ring_bytes()
+ layout.arena_bytes
+ layout.event_arena_bytes
);
assert!(layout.slot_count.is_power_of_two());
}
#[test]
fn header_round_trips() {
let layout = MemoryLayout::default();
let mut buf = vec![0u8; layout.total_bytes()];
header::init(&mut buf, &layout);
assert!(header::validate(&buf, &layout));
assert_eq!(header::get_u32(&buf, OFF_SLOT_COUNT), layout.slot_count as u32);
assert_eq!(header::get_u32(&buf, OFF_ARENA_BYTES), layout.arena_bytes as u32);
assert_eq!(header::get_u32(&buf, OFF_ARENA_CURSOR), 0);
assert_eq!(header::get_u32(&buf, OFF_READ_CURSOR), 0);
assert_eq!(header::get_u32(&buf, OFF_WRITE_CURSOR), 0);
assert_eq!(header::get_u32(&buf, OFF_FRAME_COUNT), 0);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_RING_OFFSET),
layout.event_ring_offset() as u32
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_RING_BYTES),
layout.event_ring_bytes() as u32
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_READ_CURSOR),
0
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_WRITE_CURSOR),
0
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_ARENA_OFFSET),
layout.event_arena_offset() as u32
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_ARENA_BYTES),
layout.event_arena_bytes as u32
);
assert_eq!(
header::get_u32(&buf, OFF_EVENT_ARENA_CURSOR),
0
);
}
}