use alloc::vec::Vec;
use core::fmt;
use crate::error::Error;
const INDEX_HEADER: usize = core::mem::size_of::<u32>();
const LEN_BYTES: usize = core::mem::size_of::<u32>();
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BlobHeapCfg {
pub max_bytes: usize,
pub max_blob: usize,
}
impl BlobHeapCfg {
pub const fn new() -> Self {
Self {
max_bytes: usize::MAX,
max_blob: usize::MAX,
}
}
pub const fn with_max_bytes(mut self, max_bytes: usize) -> Self {
self.max_bytes = max_bytes;
self
}
pub const fn with_max_blob(mut self, max_blob: usize) -> Self {
self.max_blob = max_blob;
self
}
}
impl Default for BlobHeapCfg {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct BlobHeap<'a> {
base: &'a [u8],
tail: Vec<u8>,
index: Vec<(usize, u32)>,
cfg: BlobHeapCfg,
}
impl<'a> BlobHeap<'a> {
pub const fn new(cfg: BlobHeapCfg) -> Self {
Self {
base: &[],
tail: Vec::new(),
index: Vec::new(),
cfg,
}
}
fn pool_len(&self) -> usize {
self.base.len() + self.tail.len()
}
fn slice(&self, offset: usize, len: usize) -> &[u8] {
let base_len = self.base.len();
if offset < base_len {
&self.base[offset..offset + len]
} else {
let at = offset - base_len;
&self.tail[at..at + len]
}
}
pub fn push(&mut self, bytes: &[u8]) -> Result<BlobId, Error> {
if bytes.len() > self.cfg.max_blob {
return Err(Error::BlobTooLarge {
len: bytes.len(),
max_blob: self.cfg.max_blob,
});
}
let capacity_exceeded = Error::CapacityExceeded {
max_bytes: self.cfg.max_bytes,
};
let offset = self.pool_len();
let end = offset.checked_add(bytes.len()).ok_or(capacity_exceeded)?;
if end > self.cfg.max_bytes {
return Err(capacity_exceeded);
}
let id = u32::try_from(self.index.len())
.ok()
.filter(|&i| i != u32::MAX)
.ok_or(capacity_exceeded)?;
self.tail.extend_from_slice(bytes);
self.index.push((offset, bytes.len() as u32));
Ok(BlobId(id))
}
pub fn get(&self, id: BlobId) -> &[u8] {
let (offset, len) = self.index[id.0 as usize];
self.slice(offset, len as usize)
}
pub fn len(&self) -> usize {
self.index.len()
}
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
pub fn pool_bytes(&self) -> usize {
self.pool_len()
}
pub fn iter(&self) -> impl Iterator<Item = (BlobId, &[u8])> {
self.index
.iter()
.enumerate()
.map(|(i, &(offset, len))| (BlobId(i as u32), self.slice(offset, len as usize)))
}
pub fn dump_index(&self, out: &mut Vec<u8>) {
out.reserve(INDEX_HEADER + self.index.len() * LEN_BYTES);
out.extend_from_slice(&(self.index.len() as u32).to_le_bytes());
for &(_, len) in &self.index {
out.extend_from_slice(&len.to_le_bytes());
}
}
pub fn dump_pool(&self, out: &mut Vec<u8>) {
out.reserve(self.pool_len());
out.extend_from_slice(self.base);
out.extend_from_slice(&self.tail);
}
pub fn load(cfg: BlobHeapCfg, index: &[u8], pool: &[u8]) -> Result<Self, Error> {
let rebuilt = validate_index(cfg, index, pool.len())?;
Ok(Self {
base: &[],
tail: pool.to_vec(),
index: rebuilt,
cfg,
})
}
pub fn load_borrowed(cfg: BlobHeapCfg, index: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
let rebuilt = validate_index(cfg, index, pool.len())?;
Ok(Self {
base: pool,
tail: Vec::new(),
index: rebuilt,
cfg,
})
}
pub fn load_overlay(cfg: BlobHeapCfg, index: &[u8], pool: &'a [u8]) -> Result<Self, Error> {
Self::load_borrowed(cfg, index, pool)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlobHeapBuilder {
lens: Vec<u32>,
pool_len: usize,
cfg: BlobHeapCfg,
}
impl BlobHeapBuilder {
pub const fn new(cfg: BlobHeapCfg) -> Self {
Self {
lens: Vec::new(),
pool_len: 0,
cfg,
}
}
pub fn push_len(&mut self, len: usize) -> Result<BlobId, Error> {
if len > self.cfg.max_blob {
return Err(Error::BlobTooLarge {
len,
max_blob: self.cfg.max_blob,
});
}
let capacity_exceeded = Error::CapacityExceeded {
max_bytes: self.cfg.max_bytes,
};
let end = self.pool_len.checked_add(len).ok_or(capacity_exceeded)?;
if end > self.cfg.max_bytes {
return Err(capacity_exceeded);
}
let id = u32::try_from(self.lens.len())
.ok()
.filter(|&i| i != u32::MAX)
.ok_or(capacity_exceeded)?;
self.lens.push(len as u32);
self.pool_len = end;
Ok(BlobId(id))
}
pub fn len(&self) -> usize {
self.lens.len()
}
pub fn is_empty(&self) -> bool {
self.lens.is_empty()
}
pub fn pool_bytes(&self) -> usize {
self.pool_len
}
pub fn dump_index(&self, out: &mut Vec<u8>) {
out.reserve(INDEX_HEADER + self.lens.len() * LEN_BYTES);
out.extend_from_slice(&(self.lens.len() as u32).to_le_bytes());
for &len in &self.lens {
out.extend_from_slice(&len.to_le_bytes());
}
}
}
impl PartialEq for BlobHeap<'_> {
fn eq(&self, other: &Self) -> bool {
self.cfg == other.cfg
&& self.index == other.index
&& self
.base
.iter()
.chain(&self.tail)
.eq(other.base.iter().chain(&other.tail))
}
}
impl Eq for BlobHeap<'_> {}
fn validate_index(
cfg: BlobHeapCfg,
index: &[u8],
pool_len: usize,
) -> Result<Vec<(usize, u32)>, Error> {
if index.len() < INDEX_HEADER {
return Err(Error::Corrupt("blob index shorter than its header"));
}
let blobs = u32::from_le_bytes(index[0..4].try_into().unwrap());
if blobs == u32::MAX {
return Err(Error::Corrupt("blob count overflows the id space"));
}
if index.len() as u64 != INDEX_HEADER as u64 + u64::from(blobs) * LEN_BYTES as u64 {
return Err(Error::Corrupt("blob index length mismatch"));
}
if pool_len > cfg.max_bytes {
return Err(Error::Corrupt("blob pool exceeds the configured ceiling"));
}
let mut rebuilt = Vec::with_capacity(blobs as usize);
let mut offset: usize = 0;
for i in 0..blobs as usize {
let at = INDEX_HEADER + i * LEN_BYTES;
let len = u32::from_le_bytes(index[at..at + LEN_BYTES].try_into().unwrap());
if len as usize > cfg.max_blob {
return Err(Error::Corrupt(
"blob length exceeds the configured max_blob",
));
}
rebuilt.push((offset, len));
offset = offset
.checked_add(len as usize)
.filter(|&o| o <= pool_len)
.ok_or(Error::Corrupt("blob lengths overrun the pool"))?;
}
if offset != pool_len {
return Err(Error::Corrupt("blob lengths do not cover the pool"));
}
Ok(rebuilt)
}
impl fmt::Debug for BlobHeap<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BlobHeap")
.field("blobs", &self.index.len())
.field("pool_bytes", &self.pool_len())
.finish()
}
}