use alloc::vec::Vec;
use core::fmt;
use crate::error::Error;
use crate::paged::Paged;
pub const CHUNK_BYTES: usize = 64;
const LINK_BYTES: usize = core::mem::size_of::<u32>();
pub const CHUNK_PAYLOAD: usize = CHUNK_BYTES - LINK_BYTES;
const META_HEADER: usize = 2 * core::mem::size_of::<u32>();
const NONE: u32 = u32::MAX;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ChunkPoolCfg {
pub max_bytes: usize,
}
impl ChunkPoolCfg {
pub const fn new() -> Self {
Self {
max_bytes: usize::MAX,
}
}
pub const fn with_max_bytes(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
}
impl Default for ChunkPoolCfg {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ListHandle {
head: u32,
tail: u32,
len: u32,
}
impl ListHandle {
pub const EMPTY: Self = Self {
head: NONE,
tail: NONE,
len: 0,
};
pub fn len(&self) -> u32 {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn to_bytes(self) -> [u8; 12] {
let mut out = [0u8; 12];
out[0..4].copy_from_slice(&self.head.to_be_bytes());
out[4..8].copy_from_slice(&self.tail.to_be_bytes());
out[8..12].copy_from_slice(&self.len.to_be_bytes());
out
}
pub fn from_bytes(bytes: [u8; 12]) -> Self {
Self {
head: u32::from_be_bytes(bytes[0..4].try_into().unwrap()),
tail: u32::from_be_bytes(bytes[4..8].try_into().unwrap()),
len: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),
}
}
}
impl Default for ListHandle {
fn default() -> Self {
Self::EMPTY
}
}
pub struct ChunkPool<'a> {
pool: Paged<'a, CHUNK_BYTES>,
used: Vec<u8>,
free_head: u32,
cfg: ChunkPoolCfg,
}
impl<'a> ChunkPool<'a> {
pub const fn new(cfg: ChunkPoolCfg) -> Self {
Self {
pool: Paged::owned_empty(),
used: Vec::new(),
free_head: NONE,
cfg,
}
}
pub fn push(&mut self, list: &mut ListHandle, value: &[u8]) -> Result<(), Error> {
if value.len() > CHUNK_PAYLOAD {
return Err(Error::ValueTooLarge { len: value.len() });
}
if !value.is_empty() {
let tail_fits = list.tail != NONE
&& self.used[list.tail as usize] as usize + value.len() <= CHUNK_PAYLOAD;
if !tail_fits {
let chunk = self.alloc_chunk()?;
if list.tail == NONE {
list.head = chunk;
} else {
self.set_link(list.tail, chunk);
}
list.tail = chunk;
}
let tail = list.tail as usize;
let rel = LINK_BYTES + self.used[tail] as usize;
let len = value.len();
self.pool.page_mut(tail as u32)[rel..rel + len].copy_from_slice(value);
self.used[tail] += len as u8;
}
list.len += 1;
Ok(())
}
pub fn free(&mut self, list: &mut ListHandle) {
if list.head != NONE {
self.set_link(list.tail, self.free_head);
self.free_head = list.head;
}
*list = ListHandle::EMPTY;
}
pub fn iter<'s>(&'s self, list: &ListHandle) -> ChunkIter<'s> {
ChunkIter {
pool: self,
chunk: list.head,
}
}
pub fn pool_bytes(&self) -> usize {
self.pool.len()
}
fn alloc_chunk(&mut self) -> Result<u32, Error> {
let chunk = if self.free_head != NONE {
let chunk = self.free_head;
self.free_head = self.link(chunk);
self.used[chunk as usize] = 0;
chunk
} else {
let capacity_exceeded = Error::CapacityExceeded {
max_bytes: self.cfg.max_bytes,
};
let new_len = self
.pool
.len()
.checked_add(CHUNK_BYTES)
.ok_or(capacity_exceeded)?;
if new_len > self.cfg.max_bytes {
return Err(capacity_exceeded);
}
let chunk = self.used.len();
let chunk = u32::try_from(chunk)
.ok()
.filter(|&c| c != NONE)
.ok_or(capacity_exceeded)?;
let tail = self.pool.grown_tail_mut();
let tail_len = tail.len() + CHUNK_BYTES;
tail.resize(tail_len, 0);
self.used.push(0);
chunk
};
self.set_link(chunk, NONE);
Ok(chunk)
}
fn link(&self, chunk: u32) -> u32 {
u32::from_le_bytes(self.pool.page(chunk)[..LINK_BYTES].try_into().unwrap())
}
fn set_link(&mut self, chunk: u32, to: u32) {
self.pool.page_mut(chunk)[..LINK_BYTES].copy_from_slice(&to.to_le_bytes());
}
pub fn chunks(&self) -> usize {
self.used.len()
}
pub fn validate_chain(&self, list: &ListHandle, visited: &mut [bool]) -> Result<(), Error> {
debug_assert_eq!(visited.len(), self.chunks());
if list.head == NONE || list.tail == NONE {
if list.head != list.tail {
return Err(Error::Corrupt("chunk chain head/tail disagree"));
}
return Ok(());
}
let mut chunk = list.head;
loop {
let c = chunk as usize;
if c >= visited.len() {
return Err(Error::Corrupt("chunk chain reaches out of bounds"));
}
if core::mem::replace(&mut visited[c], true) {
return Err(Error::Corrupt("chunk claimed by two chains"));
}
if chunk == list.tail {
if self.link(chunk) != NONE {
return Err(Error::Corrupt("chunk chain tail links onward"));
}
return Ok(());
}
let next = self.link(chunk);
if next == NONE {
return Err(Error::Corrupt("chunk chain ends before its tail"));
}
chunk = next;
}
}
pub fn orphan_count(&self, claimed: &[bool]) -> usize {
let free = self.free_map();
(0..self.used.len())
.filter(|&c| !claimed[c] && !free[c])
.count()
}
fn free_map(&self) -> Vec<bool> {
let mut free = alloc::vec![false; self.used.len()];
let mut chunk = self.free_head;
while chunk != NONE {
free[chunk as usize] = true;
chunk = self.link(chunk);
}
free
}
pub fn dump_meta(&self, out: &mut Vec<u8>) {
let free = self.free_map();
out.reserve(META_HEADER + self.used.len());
out.extend_from_slice(&(self.used.len() as u32).to_le_bytes());
out.extend_from_slice(&self.free_head.to_le_bytes());
for (chunk, &used) in self.used.iter().enumerate() {
out.push(if free[chunk] { 0 } else { used });
}
}
pub fn dump_pool(&self, out: &mut Vec<u8>) {
let free = self.free_map();
out.reserve(self.pool.len());
for (chunk, &used) in self.used.iter().enumerate() {
let used = if free[chunk] { 0 } else { used as usize };
out.extend_from_slice(&self.pool.page(chunk as u32)[..LINK_BYTES + used]);
out.resize(out.len() + (CHUNK_PAYLOAD - used), 0);
}
}
pub fn load(cfg: ChunkPoolCfg, meta: &[u8], pool: &[u8]) -> Result<Self, Error> {
let (used, free_head) = validate_chunks(cfg, meta, pool)?;
Ok(Self {
pool: Paged::owned_from(pool.to_vec()),
used,
free_head,
cfg,
})
}
pub fn load_borrowed(cfg: ChunkPoolCfg, meta: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
let (used, free_head) = validate_chunks(cfg, meta, pool)?;
Ok(Self {
pool: Paged::borrowed(pool),
used,
free_head,
cfg,
})
}
pub fn load_overlay(cfg: ChunkPoolCfg, meta: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
let (used, free_head) = validate_chunks(cfg, meta, pool)?;
Ok(Self {
pool: Paged::borrowed(pool),
used,
free_head,
cfg,
})
}
}
fn validate_chunks(cfg: ChunkPoolCfg, meta: &[u8], pool: &[u8]) -> Result<(Vec<u8>, u32), Error> {
if meta.len() < META_HEADER {
return Err(Error::Corrupt("chunk meta shorter than its header"));
}
let chunks = u32::from_le_bytes(meta[0..4].try_into().unwrap());
let free_head = u32::from_le_bytes(meta[4..META_HEADER].try_into().unwrap());
if chunks == NONE {
return Err(Error::Corrupt("chunk count overflows the index space"));
}
if meta.len() as u64 != META_HEADER as u64 + u64::from(chunks) {
return Err(Error::Corrupt("chunk meta length mismatch"));
}
if pool.len() as u64 != u64::from(chunks) * CHUNK_BYTES as u64 {
return Err(Error::Corrupt("chunk pool length mismatch"));
}
if pool.len() > cfg.max_bytes {
return Err(Error::Corrupt("chunk pool exceeds the configured ceiling"));
}
let used: Vec<u8> = meta[META_HEADER..].to_vec();
if used.iter().any(|&u| u as usize > CHUNK_PAYLOAD) {
return Err(Error::Corrupt("chunk used bytes exceed the payload size"));
}
let link_of = |chunk: usize| {
let at = chunk * CHUNK_BYTES;
u32::from_le_bytes(pool[at..at + LINK_BYTES].try_into().unwrap())
};
for chunk in 0..chunks as usize {
let link = link_of(chunk);
if link != NONE && link >= chunks {
return Err(Error::Corrupt("chunk link out of bounds"));
}
}
let mut seen = alloc::vec![false; chunks as usize];
let mut chunk = free_head;
while chunk != NONE {
let c = chunk as usize;
if c >= chunks as usize {
return Err(Error::Corrupt("chunk free-list head out of bounds"));
}
if core::mem::replace(&mut seen[c], true) {
return Err(Error::Corrupt("chunk free-list contains a cycle"));
}
if used[c] != 0 {
return Err(Error::Corrupt("free chunk has nonzero used bytes"));
}
chunk = link_of(c);
}
Ok((used, free_head))
}
impl fmt::Debug for ChunkPool<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChunkPool")
.field("chunks", &self.used.len())
.field("pool_bytes", &self.pool.len())
.finish()
}
}
pub struct ChunkIter<'a> {
pool: &'a ChunkPool<'a>,
chunk: u32,
}
impl<'a> Iterator for ChunkIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if self.chunk == NONE {
return None;
}
let chunk = self.chunk;
self.chunk = self.pool.link(chunk);
let used = self.pool.used[chunk as usize] as usize;
Some(&self.pool.pool.page(chunk)[LINK_BYTES..LINK_BYTES + used])
}
}