use super::compact_encoding::{read_varint_from_slice, varint_size, write_varint_to_vec};
use super::disk_manager::BLOCK_SIZE;
use super::PersistentARTrieError;
type Result<T> = std::result::Result<T, PersistentARTrieError>;
pub const ARENA_MAGIC: u64 = 0x414E4152_41545942;
pub const ARENA_MAGIC_V2: u64 = 0x32564152_41545942;
pub const ARENA_VERSION: u16 = 1;
pub const ARENA_VERSION_V2: u16 = 2;
pub const HEADER_SIZE: usize = 64;
pub const SLOT_SIZE: usize = 8;
pub const MIN_FREE_SPACE: usize = 64;
pub const FLAG_VARINT_DIRECTORY: u16 = 0x0001;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct ArenaHeader {
pub magic: u64,
pub version: u16,
pub flags: u16,
pub node_count: u32,
pub free_offset: u32,
pub directory_start: u32,
pub checksum: u32,
pub reserved: [u8; 28],
}
impl ArenaHeader {
pub fn new(block_size: usize) -> Self {
Self {
magic: ARENA_MAGIC,
version: ARENA_VERSION,
flags: 0,
node_count: 0,
free_offset: HEADER_SIZE as u32,
directory_start: block_size as u32,
checksum: 0,
reserved: [0u8; 28],
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() < HEADER_SIZE {
return Err(PersistentARTrieError::corrupted("Arena header too small"));
}
let magic = u64::from_le_bytes(bytes[0..8].try_into().expect("8 bytes"));
if magic != ARENA_MAGIC && magic != ARENA_MAGIC_V2 {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid arena magic: expected {:016x} or {:016x}, got {:016x}",
ARENA_MAGIC, ARENA_MAGIC_V2, magic
)));
}
let version = u16::from_le_bytes(bytes[8..10].try_into().expect("2 bytes"));
if version != ARENA_VERSION && version != ARENA_VERSION_V2 {
return Err(PersistentARTrieError::corrupted(&format!(
"Unsupported arena version: {}",
version
)));
}
let flags = u16::from_le_bytes(bytes[10..12].try_into().expect("2 bytes"));
let node_count = u32::from_le_bytes(bytes[12..16].try_into().expect("4 bytes"));
let free_offset = u32::from_le_bytes(bytes[16..20].try_into().expect("4 bytes"));
let directory_start = u32::from_le_bytes(bytes[20..24].try_into().expect("4 bytes"));
let checksum = u32::from_le_bytes(bytes[24..28].try_into().expect("4 bytes"));
let mut reserved = [0u8; 28];
reserved.copy_from_slice(&bytes[28..56]);
Ok(Self {
magic,
version,
flags,
node_count,
free_offset,
directory_start,
checksum,
reserved,
})
}
pub fn to_bytes(&self, out: &mut [u8]) {
out[0..8].copy_from_slice(&self.magic.to_le_bytes());
out[8..10].copy_from_slice(&self.version.to_le_bytes());
out[10..12].copy_from_slice(&self.flags.to_le_bytes());
out[12..16].copy_from_slice(&self.node_count.to_le_bytes());
out[16..20].copy_from_slice(&self.free_offset.to_le_bytes());
out[20..24].copy_from_slice(&self.directory_start.to_le_bytes());
out[24..28].copy_from_slice(&self.checksum.to_le_bytes());
out[28..56].copy_from_slice(&self.reserved);
}
pub fn available_space(&self) -> usize {
if self.directory_start <= self.free_offset {
0
} else {
(self.directory_start - self.free_offset) as usize
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SlotEntry {
pub offset: u32,
pub len: u32,
}
impl SlotEntry {
pub fn new(offset: u32, len: u32) -> Self {
Self { offset, len }
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let offset = u32::from_le_bytes(bytes[0..4].try_into().expect("4 bytes"));
let len = u32::from_le_bytes(bytes[4..8].try_into().expect("4 bytes"));
Self { offset, len }
}
pub fn to_bytes(&self, out: &mut [u8]) {
out[0..4].copy_from_slice(&self.offset.to_le_bytes());
out[4..8].copy_from_slice(&self.len.to_le_bytes());
}
}
pub struct ByteNodeArena {
data: Vec<u8>,
header: ArenaHeader,
pub block_id: Option<u32>,
dirty: bool,
max_data_offset: u32,
}
impl ByteNodeArena {
pub fn new(size: usize) -> Self {
let mut data = vec![0u8; size];
let header = ArenaHeader::new(size);
header.to_bytes(&mut data[0..HEADER_SIZE]);
Self {
data,
header,
block_id: None,
dirty: true,
max_data_offset: HEADER_SIZE as u32,
}
}
pub fn new_default() -> Self {
Self::new(BLOCK_SIZE)
}
pub fn from_bytes(bytes: &[u8], block_id: u32) -> Result<Self> {
if bytes.len() < HEADER_SIZE {
return Err(PersistentARTrieError::corrupted("Arena data too small"));
}
let header = ArenaHeader::from_bytes(bytes)?;
let data = bytes.to_vec();
let max_data_offset = header.free_offset;
Ok(Self {
data,
header,
block_id: Some(block_id),
dirty: false,
max_data_offset,
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
self.dirty = true;
&mut self.data
}
pub fn can_allocate(&self, size: usize) -> bool {
let needed = size + SLOT_SIZE + MIN_FREE_SPACE;
self.header.available_space() >= needed
}
pub fn allocate(&mut self, data: &[u8]) -> Option<u32> {
let len = data.len();
if !self.can_allocate(len) {
return None;
}
let data_offset = self.header.free_offset;
self.data[data_offset as usize..(data_offset as usize + len)].copy_from_slice(data);
self.header.free_offset += len as u32;
if self.header.free_offset > self.max_data_offset {
self.max_data_offset = self.header.free_offset;
}
self.header.directory_start -= SLOT_SIZE as u32;
let slot_offset = self.header.directory_start as usize;
let slot = SlotEntry::new(data_offset, len as u32);
slot.to_bytes(&mut self.data[slot_offset..slot_offset + SLOT_SIZE]);
let slot_id = self.header.node_count;
self.header.node_count += 1;
self.header.to_bytes(&mut self.data[0..HEADER_SIZE]);
self.dirty = true;
Some(slot_id)
}
pub fn read(&self, slot_id: u32) -> Result<&[u8]> {
if slot_id >= self.header.node_count {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id, self.header.node_count
)));
}
let slot_offset = self.data.len() - ((slot_id as usize + 1) * SLOT_SIZE);
let slot = SlotEntry::from_bytes(&self.data[slot_offset..slot_offset + SLOT_SIZE]);
let start = slot.offset as usize;
let end = start + slot.len as usize;
if end > self.data.len() {
return Err(PersistentARTrieError::corrupted(&format!(
"Slot {} points outside arena: offset={}, len={}",
slot_id, slot.offset, slot.len
)));
}
Ok(&self.data[start..end])
}
pub fn update(&mut self, slot_id: u32, new_data: &[u8]) -> Result<()> {
if slot_id >= self.header.node_count {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id, self.header.node_count
)));
}
let slot_offset = self.data.len() - ((slot_id as usize + 1) * SLOT_SIZE);
let slot = SlotEntry::from_bytes(&self.data[slot_offset..slot_offset + SLOT_SIZE]);
let start = slot.offset as usize;
let original_len = slot.len as usize;
if new_data.len() != original_len {
return Err(PersistentARTrieError::internal(&format!(
"Update size mismatch: original={}, new={}",
original_len,
new_data.len()
)));
}
let end = start + original_len;
if end > self.data.len() {
return Err(PersistentARTrieError::corrupted(&format!(
"Slot {} points outside arena: offset={}, len={}",
slot_id, slot.offset, slot.len
)));
}
self.data[start..end].copy_from_slice(new_data);
self.dirty = true;
Ok(())
}
pub fn node_count(&self) -> u32 {
self.header.node_count
}
pub fn available_space(&self) -> usize {
self.header.available_space()
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_clean(&mut self) {
self.dirty = false;
}
pub fn size(&self) -> usize {
self.data.len()
}
pub fn set_block_id(&mut self, block_id: u32) {
self.block_id = Some(block_id);
}
pub fn max_data_offset(&self) -> u32 {
self.max_data_offset
}
pub fn free_offset(&self) -> u32 {
self.header.free_offset
}
pub fn slot_data_range(&self, slot_id: u32) -> Result<(usize, usize)> {
if slot_id >= self.header.node_count {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id, self.header.node_count
)));
}
let slot_offset = self.data.len() - ((slot_id as usize + 1) * SLOT_SIZE);
let slot = SlotEntry::from_bytes(&self.data[slot_offset..slot_offset + SLOT_SIZE]);
Ok((slot.offset as usize, slot.len as usize))
}
pub fn slot_bytes(&self, slot_id: u32) -> Result<&[u8]> {
let (offset, len) = self.slot_data_range(slot_id)?;
if offset + len > self.data.len() {
return Err(PersistentARTrieError::corrupted(&format!(
"Slot {} points outside arena: offset={}, len={}",
slot_id, offset, len
)));
}
Ok(&self.data[offset..offset + len])
}
pub fn slot_directory_entry_range(&self, slot_id: u32) -> Result<(usize, usize)> {
if slot_id >= self.header.node_count {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id, self.header.node_count
)));
}
let slot_offset = self.data.len() - ((slot_id as usize + 1) * SLOT_SIZE);
Ok((slot_offset, SLOT_SIZE))
}
#[inline]
pub fn header_range(&self) -> (usize, usize) {
(0, HEADER_SIZE)
}
#[inline]
pub fn directory_range(&self) -> (usize, usize) {
let start = self.header.directory_start as usize;
(start, self.data.len() - start)
}
#[inline]
pub fn slot_count(&self) -> u32 {
self.header.node_count
}
}
#[derive(Debug, Clone, Copy)]
pub struct VarintSlotEntry {
pub offset: u64,
pub len: u64,
}
impl VarintSlotEntry {
pub fn new(offset: u64, len: u64) -> Self {
Self { offset, len }
}
pub fn encoded_size(&self) -> usize {
varint_size(self.offset) + varint_size(self.len)
}
pub fn write_to_vec(&self, out: &mut Vec<u8>) {
write_varint_to_vec(self.offset, out);
write_varint_to_vec(self.len, out);
}
pub fn read_from_slice(data: &[u8]) -> (Self, usize) {
let (offset, consumed1) = read_varint_from_slice(data);
let (len, consumed2) = read_varint_from_slice(&data[consumed1..]);
(Self { offset, len }, consumed1 + consumed2)
}
}
pub struct ByteNodeArenaV2 {
data: Vec<u8>,
header: ArenaHeader,
slots: Vec<VarintSlotEntry>,
pub block_id: Option<u32>,
dirty: bool,
max_data_offset: u32,
data_end: usize,
}
impl ByteNodeArenaV2 {
pub fn new(size: usize) -> Self {
let mut data = vec![0u8; size];
let mut header = ArenaHeader::new(size);
header.magic = ARENA_MAGIC_V2;
header.version = ARENA_VERSION_V2;
header.flags = FLAG_VARINT_DIRECTORY;
header.to_bytes(&mut data[0..HEADER_SIZE]);
Self {
data,
header,
slots: Vec::new(),
block_id: None,
dirty: true,
max_data_offset: HEADER_SIZE as u32,
data_end: HEADER_SIZE,
}
}
pub fn new_default() -> Self {
Self::new(BLOCK_SIZE)
}
pub fn can_allocate(&self, size: usize) -> bool {
let slot_overhead = 18 + MIN_FREE_SPACE;
let needed = size + slot_overhead;
let available = self.data.len() - self.data_end;
available >= needed
}
pub fn allocate(&mut self, node_data: &[u8]) -> Option<u32> {
let len = node_data.len();
if !self.can_allocate(len) {
return None;
}
let offset = self.data_end;
self.data[offset..offset + len].copy_from_slice(node_data);
self.data_end += len;
if self.data_end as u32 > self.max_data_offset {
self.max_data_offset = self.data_end as u32;
}
let slot_id = self.slots.len() as u32;
self.slots
.push(VarintSlotEntry::new(offset as u64, len as u64));
self.header.node_count = self.slots.len() as u32;
self.header.free_offset = self.data_end as u32;
self.dirty = true;
Some(slot_id)
}
pub fn read(&self, slot_id: u32) -> Result<&[u8]> {
let slot = self.slots.get(slot_id as usize).ok_or_else(|| {
PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id,
self.slots.len()
))
})?;
let start = slot.offset as usize;
let end = start + slot.len as usize;
if end > self.data.len() {
return Err(PersistentARTrieError::corrupted(&format!(
"Slot {} points outside arena: offset={}, len={}",
slot_id, slot.offset, slot.len
)));
}
Ok(&self.data[start..end])
}
pub fn finalize(&mut self) {
let mut directory = Vec::new();
for slot in &self.slots {
slot.write_to_vec(&mut directory);
}
let dir_start = self.data_end;
let dir_end = dir_start + directory.len();
if dir_end > self.data.len() {
return;
}
self.data[dir_start..dir_end].copy_from_slice(&directory);
self.header.directory_start = dir_start as u32;
self.header.to_bytes(&mut self.data[0..HEADER_SIZE]);
}
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
pub fn from_bytes(bytes: &[u8], block_id: u32) -> Result<Self> {
if bytes.len() < HEADER_SIZE {
return Err(PersistentARTrieError::corrupted("Arena data too small"));
}
let header = ArenaHeader::from_bytes(bytes)?;
if header.magic != ARENA_MAGIC_V2 && header.magic != ARENA_MAGIC {
return Err(PersistentARTrieError::corrupted(&format!(
"Invalid V2 arena magic: {:016x}",
header.magic
)));
}
let mut slots = Vec::with_capacity(header.node_count as usize);
let mut offset = header.directory_start as usize;
for _ in 0..header.node_count {
if offset >= bytes.len() {
break;
}
let (entry, consumed) = VarintSlotEntry::read_from_slice(&bytes[offset..]);
slots.push(entry);
offset += consumed;
}
let data_end = header.free_offset as usize;
let max_data_offset = header.free_offset;
Ok(Self {
data: bytes.to_vec(),
header,
slots,
block_id: Some(block_id),
dirty: false,
max_data_offset,
data_end,
})
}
pub fn node_count(&self) -> u32 {
self.slots.len() as u32
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn mark_clean(&mut self) {
self.dirty = false;
}
pub fn size(&self) -> usize {
self.data.len()
}
pub fn set_block_id(&mut self, block_id: u32) {
self.block_id = Some(block_id);
}
pub fn max_data_offset(&self) -> u32 {
self.max_data_offset
}
pub fn available_space(&self) -> usize {
if self.data_end >= self.data.len() {
0
} else {
self.data.len() - self.data_end - MIN_FREE_SPACE
}
}
pub fn directory_savings(&self) -> (usize, usize) {
let fixed_size = self.slots.len() * SLOT_SIZE;
let varint_size: usize = self.slots.iter().map(|s| s.encoded_size()).sum();
(fixed_size, varint_size)
}
pub fn slot_data_range(&self, slot_id: u32) -> Result<(usize, usize)> {
let slot = self.slots.get(slot_id as usize).ok_or_else(|| {
PersistentARTrieError::corrupted(&format!(
"Invalid slot ID {} (arena has {} nodes)",
slot_id,
self.slots.len()
))
})?;
Ok((slot.offset as usize, slot.len as usize))
}
pub fn slot_bytes(&self, slot_id: u32) -> Result<&[u8]> {
let (offset, len) = self.slot_data_range(slot_id)?;
if offset + len > self.data.len() {
return Err(PersistentARTrieError::corrupted(&format!(
"Slot {} points outside arena: offset={}, len={}",
slot_id, offset, len
)));
}
Ok(&self.data[offset..offset + len])
}
#[inline]
pub fn header_range(&self) -> (usize, usize) {
(0, HEADER_SIZE)
}
#[inline]
pub fn directory_range(&self) -> (usize, usize) {
let start = self.header.directory_start as usize;
let dir_len: usize = self.slots.iter().map(|s| s.encoded_size()).sum();
(start, dir_len)
}
#[inline]
pub fn slot_count(&self) -> u32 {
self.slots.len() as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn byte_key_arena_magic_matches_canonical_source() {
use crate::persistent_artrie_core::key_encoding::{ByteKey, KeyEncoding};
assert_eq!(ByteKey::ARENA_MAGIC, ARENA_MAGIC);
assert_eq!(ByteKey::ARENA_MAGIC_V2, ARENA_MAGIC_V2);
assert_eq!(&ByteKey::FILE_MAGIC, b"PART");
}
#[test]
fn test_arena_creation() {
let arena = ByteNodeArena::new(4096);
assert_eq!(arena.node_count(), 0);
assert!(arena.available_space() > 0);
assert!(arena.is_dirty());
}
#[test]
fn test_arena_allocation() {
let mut arena = ByteNodeArena::new(4096);
let data1 = b"hello world";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
assert_eq!(slot1, 0);
assert_eq!(arena.node_count(), 1);
let read1 = arena.read(slot1).expect("read should succeed");
assert_eq!(read1, data1);
let data2 = b"goodbye world";
let slot2 = arena.allocate(data2).expect("allocation should succeed");
assert_eq!(slot2, 1);
assert_eq!(arena.node_count(), 2);
let read1 = arena.read(slot1).expect("read should succeed");
let read2 = arena.read(slot2).expect("read should succeed");
assert_eq!(read1, data1);
assert_eq!(read2, data2);
}
#[test]
fn test_arena_serialization() {
let mut arena = ByteNodeArena::new(4096);
let data1 = b"test data 1";
let data2 = b"test data 2 longer";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
let slot2 = arena.allocate(data2).expect("allocation should succeed");
let bytes = arena.as_bytes().to_vec();
let loaded = ByteNodeArena::from_bytes(&bytes, 0).expect("load should succeed");
assert_eq!(loaded.node_count(), 2);
assert_eq!(loaded.read(slot1).expect("read should succeed"), data1);
assert_eq!(loaded.read(slot2).expect("read should succeed"), data2);
}
#[test]
fn test_arena_full() {
let mut arena = ByteNodeArena::new(256);
let mut allocated = 0;
while arena.can_allocate(10) {
arena
.allocate(&[0u8; 10])
.expect("allocation should succeed");
allocated += 1;
}
assert!(allocated > 0);
assert!(!arena.can_allocate(10));
}
#[test]
fn test_arena_v2_creation() {
let arena = ByteNodeArenaV2::new(4096);
assert_eq!(arena.node_count(), 0);
assert!(arena.available_space() > 0);
assert!(arena.is_dirty());
}
#[test]
fn test_arena_v2_allocation() {
let mut arena = ByteNodeArenaV2::new(4096);
let data1 = b"hello world";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
assert_eq!(slot1, 0);
assert_eq!(arena.node_count(), 1);
let read1 = arena.read(slot1).expect("read should succeed");
assert_eq!(read1, data1);
let data2 = b"goodbye world";
let slot2 = arena.allocate(data2).expect("allocation should succeed");
assert_eq!(slot2, 1);
assert_eq!(arena.node_count(), 2);
let read2 = arena.read(slot2).expect("read should succeed");
assert_eq!(read2, data2);
}
#[test]
fn test_arena_v2_serialization() {
let mut arena = ByteNodeArenaV2::new(4096);
let data1 = b"test data 1";
let data2 = b"test data 2 longer";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
let slot2 = arena.allocate(data2).expect("allocation should succeed");
arena.finalize();
let bytes = arena.as_bytes().to_vec();
let loaded = ByteNodeArenaV2::from_bytes(&bytes, 0).expect("load should succeed");
assert_eq!(loaded.node_count(), 2);
assert_eq!(loaded.read(slot1).expect("read should succeed"), data1);
assert_eq!(loaded.read(slot2).expect("read should succeed"), data2);
}
#[test]
fn test_arena_v2_directory_savings() {
let mut arena = ByteNodeArenaV2::new(4096);
for i in 0..50 {
let data = format!("entry{}", i);
arena
.allocate(data.as_bytes())
.expect("allocation should succeed");
}
let (fixed_size, varint_size) = arena.directory_savings();
assert_eq!(fixed_size, 400);
assert!(varint_size < fixed_size);
assert!(varint_size < 200);
let savings = 100.0 * (1.0 - varint_size as f64 / fixed_size as f64);
assert!(savings > 50.0); }
#[test]
fn test_varint_slot_entry_roundtrip() {
let test_cases = [
(0u64, 0u64),
(1, 1),
(100, 50),
(247, 247), (248, 248), (1000, 500),
(65535, 1024),
(0xFFFFFF, 0xFFFF),
];
for (offset, len) in test_cases {
let entry = VarintSlotEntry::new(offset, len);
let mut buf = Vec::new();
entry.write_to_vec(&mut buf);
let (decoded, consumed) = VarintSlotEntry::read_from_slice(&buf);
assert_eq!(decoded.offset, offset);
assert_eq!(decoded.len, len);
assert_eq!(consumed, buf.len());
}
}
#[test]
fn test_slot_data_range() {
let mut arena = ByteNodeArena::new(4096);
let data1 = b"hello world";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
let data2 = b"goodbye world";
let slot2 = arena.allocate(data2).expect("allocation should succeed");
let (offset1, len1) = arena.slot_data_range(slot1).expect("should get range");
assert_eq!(len1, data1.len());
assert_eq!(&arena.as_bytes()[offset1..offset1 + len1], data1);
let (offset2, len2) = arena.slot_data_range(slot2).expect("should get range");
assert_eq!(len2, data2.len());
assert_eq!(&arena.as_bytes()[offset2..offset2 + len2], data2);
}
#[test]
fn test_slot_bytes() {
let mut arena = ByteNodeArena::new(4096);
let data = b"test data for slot";
let slot = arena.allocate(data).expect("allocation should succeed");
let bytes = arena.slot_bytes(slot).expect("should get bytes");
assert_eq!(bytes, data);
}
#[test]
fn test_slot_directory_entry_range() {
let mut arena = ByteNodeArena::new(4096);
let slot = arena.allocate(b"data").expect("allocation should succeed");
let (offset, len) = arena
.slot_directory_entry_range(slot)
.expect("should get range");
assert_eq!(len, SLOT_SIZE); assert!(offset > 0);
assert!(offset < arena.size());
}
#[test]
fn test_header_range() {
let arena = ByteNodeArena::new(4096);
let (offset, len) = arena.header_range();
assert_eq!(offset, 0);
assert_eq!(len, HEADER_SIZE);
}
#[test]
fn test_directory_range() {
let mut arena = ByteNodeArena::new(4096);
let (start1, len1) = arena.directory_range();
assert_eq!(start1, 4096); assert_eq!(len1, 0);
arena.allocate(b"data").expect("allocation should succeed");
let (start2, _len2) = arena.directory_range();
assert!(start2 < 4096); }
#[test]
fn test_slot_count() {
let mut arena = ByteNodeArena::new(4096);
assert_eq!(arena.slot_count(), 0);
arena.allocate(b"data1").expect("allocation should succeed");
assert_eq!(arena.slot_count(), 1);
arena.allocate(b"data2").expect("allocation should succeed");
assert_eq!(arena.slot_count(), 2);
}
#[test]
fn test_invalid_slot_id_errors() {
let arena = ByteNodeArena::new(4096);
assert!(arena.slot_data_range(0).is_err());
assert!(arena.slot_bytes(0).is_err());
assert!(arena.slot_directory_entry_range(0).is_err());
}
#[test]
fn test_v2_slot_data_range() {
let mut arena = ByteNodeArenaV2::new(4096);
let data1 = b"hello world";
let slot1 = arena.allocate(data1).expect("allocation should succeed");
let (offset, len) = arena.slot_data_range(slot1).expect("should get range");
assert_eq!(len, data1.len());
assert!(offset >= HEADER_SIZE);
}
#[test]
fn test_v2_slot_bytes() {
let mut arena = ByteNodeArenaV2::new(4096);
let data = b"test data for v2";
let slot = arena.allocate(data).expect("allocation should succeed");
let bytes = arena.slot_bytes(slot).expect("should get bytes");
assert_eq!(bytes, data);
}
#[test]
fn test_v2_slot_count() {
let mut arena = ByteNodeArenaV2::new(4096);
assert_eq!(arena.slot_count(), 0);
arena.allocate(b"data").expect("allocation should succeed");
assert_eq!(arena.slot_count(), 1);
}
}