use super::{BStackSlice, BStackSliceAllocator};
use std::fmt;
use std::io;
const HEADER_LEN: u64 = 16;
pub struct BStackByteVec<'a, A: BStackSliceAllocator> {
slice: BStackSlice<'a, A>,
}
impl<'a, A: BStackSliceAllocator> fmt::Debug for BStackByteVec<'a, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.read_header() {
Ok((len, cap)) => f
.debug_struct("BStackByteVec")
.field("len", &len)
.field("capacity", &cap)
.finish_non_exhaustive(),
Err(e) => write!(f, "BStackByteVec(error reading header: {e})"),
}
}
}
impl<'a, A: BStackSliceAllocator> BStackByteVec<'a, A> {
fn block_size(capacity: u64) -> io::Result<u64> {
capacity.checked_add(HEADER_LEN).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"BStackByteVec: block size overflows u64",
)
})
}
fn byte_offset(index: u64) -> u64 {
HEADER_LEN + index
}
fn read_header(&self) -> io::Result<(u64, u64)> {
let mut hdr = [0u8; 16];
self.slice.read_range_into(0, &mut hdr)?;
let len = u64::from_le_bytes(hdr[..8].try_into().unwrap());
let cap = u64::from_le_bytes(hdr[8..].try_into().unwrap());
Ok((len, cap))
}
fn write_len_field(&self, len: u64) -> io::Result<()> {
self.slice.write_range(0, len.to_le_bytes())
}
fn write_cap_field(&self, cap: u64) -> io::Result<()> {
self.slice.write_range(8, cap.to_le_bytes())
}
fn write_header(&self, len: u64, cap: u64) -> io::Result<()> {
let mut hdr = [0u8; 16];
hdr[0..8].copy_from_slice(&len.to_le_bytes());
hdr[8..16].copy_from_slice(&cap.to_le_bytes());
self.slice.write_range(0, hdr)
}
fn read_byte_at(&self, index: u64) -> io::Result<u8> {
let start = Self::byte_offset(index);
let mut byte = [0u8; 1];
self.slice.read_range_into(start, &mut byte)?;
Ok(byte[0])
}
fn write_byte_at(&self, index: u64, value: u8) -> io::Result<()> {
let start = Self::byte_offset(index);
self.slice.write_range(start, [value])
}
fn write_bytes_at(&self, start_index: u64, values: &[u8]) -> io::Result<()> {
let start = Self::byte_offset(start_index);
self.slice.write_range(start, values)
}
fn zero_byte_at(&self, index: u64) -> io::Result<()> {
self.slice.zero_range(Self::byte_offset(index), 1)
}
fn grow_to(&mut self, new_cap: u64) -> io::Result<()> {
let new_size = Self::block_size(new_cap)?;
let new_slice = self.slice.allocator().realloc(self.slice, new_size)?;
self.slice = new_slice;
Ok(())
}
}
impl<'a, A: BStackSliceAllocator> BStackByteVec<'a, A> {
pub fn new(alloc: &'a A) -> io::Result<Self> {
let slice = alloc.alloc(HEADER_LEN)?;
Ok(Self { slice })
}
pub fn with_capacity(capacity: u64, alloc: &'a A) -> io::Result<Self> {
let slice = alloc.alloc(Self::block_size(capacity)?)?;
let vec = Self { slice };
vec.write_cap_field(capacity)?;
Ok(vec)
}
pub fn from_slice(data: &[u8], alloc: &'a A) -> io::Result<Self> {
let len = data.len() as u64;
let slice = alloc.alloc(Self::block_size(len)?)?;
let vec = Self { slice };
if len > 0 {
vec.write_header(len, len)?;
vec.write_bytes_at(0, data)?;
}
Ok(vec)
}
pub unsafe fn from_raw_block(slice: BStackSlice<'a, A>) -> Self {
Self { slice }
}
pub fn len(&self) -> io::Result<u64> {
Ok(self.read_header()?.0)
}
pub fn capacity(&self) -> io::Result<u64> {
Ok(self.read_header()?.1)
}
pub fn is_empty(&self) -> io::Result<bool> {
Ok(self.len()? == 0)
}
pub fn get(&self, index: u64) -> io::Result<Option<u8>> {
let (len, _) = self.read_header()?;
if index >= len {
return Ok(None);
}
Ok(Some(self.read_byte_at(index)?))
}
pub fn read_bytes(&self) -> io::Result<Vec<u8>> {
let (len, _) = self.read_header()?;
if len == 0 {
return Ok(Vec::new());
}
self.slice.read_range(HEADER_LEN, HEADER_LEN + len)
}
pub fn as_slice(&self) -> io::Result<BStackSlice<'a, A>> {
let (len, _) = self.read_header()?;
Ok(self.slice.subslice(HEADER_LEN, HEADER_LEN + len))
}
pub fn push(&mut self, value: u8) -> io::Result<()> {
let (len, cap) = self.read_header()?;
if len == cap {
let new_cap = cap.saturating_mul(2).max(4);
self.grow_to(new_cap)?;
self.write_cap_field(new_cap)?;
}
self.write_byte_at(len, value)?;
self.write_len_field(len + 1)
}
pub fn pop(&mut self) -> io::Result<Option<u8>> {
let (len, _) = self.read_header()?;
if len == 0 {
return Ok(None);
}
let value = self.read_byte_at(len - 1)?;
self.write_len_field(len - 1)?;
self.zero_byte_at(len - 1)?;
Ok(Some(value))
}
pub fn truncate(&mut self, new_len: u64) -> io::Result<()> {
let (len, _) = self.read_header()?;
if new_len >= len {
return Ok(());
}
let start = Self::byte_offset(new_len);
let removed = len - new_len;
self.write_len_field(new_len)?;
self.slice.zero_range(start, removed)
}
pub fn clear(&mut self) -> io::Result<()> {
self.truncate(0)
}
pub fn reserve(&mut self, additional: u64) -> io::Result<()> {
let (len, cap) = self.read_header()?;
let needed = len.checked_add(additional).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"BStackByteVec::reserve: capacity overflow",
)
})?;
if needed <= cap {
return Ok(());
}
let new_cap = needed.max(cap.saturating_mul(2));
self.grow_to(new_cap)?;
self.write_cap_field(new_cap)?;
Ok(())
}
pub fn resize(&mut self, new_len: u64, value: u8) -> io::Result<()> {
let (len, _) = self.read_header()?;
if new_len <= len {
return self.truncate(new_len);
}
let additional = new_len - len;
self.reserve(additional)?;
let count = usize::try_from(additional).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"BStackByteVec::resize: growth exceeds usize",
)
})?;
let fill: Vec<u8> = std::iter::repeat_n(value, count).collect();
self.write_bytes_at(len, &fill)?;
self.write_len_field(new_len)
}
pub fn iter(&self) -> io::Result<BStackByteVecIter<'_, 'a, A>> {
let (len, _) = self.read_header()?;
Ok(BStackByteVecIter {
vec: self,
index: 0,
len,
})
}
pub unsafe fn raw_block(&self) -> BStackSlice<'a, A> {
self.slice
}
pub fn into_raw_block(self) -> BStackSlice<'a, A> {
self.slice
}
pub fn dealloc(self) -> io::Result<()> {
self.slice.allocator().dealloc(self.slice)
}
}
pub struct BStackByteVecIter<'b, 'a: 'b, A: BStackSliceAllocator> {
vec: &'b BStackByteVec<'a, A>,
index: u64,
len: u64,
}
impl<'b, 'a: 'b, A: BStackSliceAllocator> fmt::Debug for BStackByteVecIter<'b, 'a, A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BStackByteVecIter")
.field("index", &self.index)
.field("len", &self.len)
.finish_non_exhaustive()
}
}
impl<'b, 'a: 'b, A: BStackSliceAllocator> Iterator for BStackByteVecIter<'b, 'a, A> {
type Item = io::Result<u8>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.len {
return None;
}
let result = self.vec.read_byte_at(self.index);
self.index += 1;
Some(result)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.len - self.index).min(usize::MAX as u64) as usize;
(remaining, Some(remaining))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BStack;
use crate::alloc::{BStackAllocator, LinearBStackAllocator};
use std::sync::atomic::{AtomicU64, Ordering};
fn temp_path() -> std::path::PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
let pid = std::process::id();
std::env::temp_dir().join(format!("bstack_bytevec_test_{pid}_{id}.bin"))
}
struct Guard(std::path::PathBuf);
impl Drop for Guard {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
fn make_alloc() -> (LinearBStackAllocator, std::path::PathBuf) {
let path = temp_path();
let alloc = LinearBStackAllocator::new(BStack::open(&path).unwrap());
(alloc, path)
}
#[test]
fn new_is_empty_with_zero_cap() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::new(&alloc).unwrap();
assert_eq!(v.len().unwrap(), 0);
assert_eq!(v.capacity().unwrap(), 0);
assert!(v.is_empty().unwrap());
}
#[test]
fn with_capacity_has_zero_len_and_correct_cap() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::with_capacity(8, &alloc).unwrap();
assert_eq!(v.len().unwrap(), 0);
assert_eq!(v.capacity().unwrap(), 8);
assert!(v.is_empty().unwrap());
}
#[test]
fn from_slice_roundtrip() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let src = [10u8, 20, 30, 40, 50];
let v = BStackByteVec::from_slice(&src, &alloc).unwrap();
assert_eq!(v.len().unwrap(), 5);
assert_eq!(v.capacity().unwrap(), 5);
for (i, &expected) in src.iter().enumerate() {
assert_eq!(v.get(i as u64).unwrap(), Some(expected));
}
assert_eq!(v.get(5).unwrap(), None);
}
#[test]
fn from_slice_empty() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::from_slice(&[], &alloc).unwrap();
assert_eq!(v.len().unwrap(), 0);
assert_eq!(v.capacity().unwrap(), 0);
}
#[test]
fn raw_block_roundtrip() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
v.push(2).unwrap();
v.push(3).unwrap();
let block = v.into_raw_block();
let v2 = unsafe { BStackByteVec::from_raw_block(block) };
assert_eq!(v2.len().unwrap(), 3);
assert_eq!(v2.get(0).unwrap(), Some(1u8));
assert_eq!(v2.get(1).unwrap(), Some(2u8));
assert_eq!(v2.get(2).unwrap(), Some(3u8));
}
#[test]
fn reopen_header_recovery() {
let path = temp_path();
let _g = Guard(path.clone());
let block_bytes = {
let alloc = LinearBStackAllocator::new(BStack::open(&path).unwrap());
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(111).unwrap();
v.push(222).unwrap();
v.push(33).unwrap(); let bytes: [u8; 16] = v.into_raw_block().into();
bytes
};
let alloc = LinearBStackAllocator::new(BStack::open(&path).unwrap());
let block = unsafe { crate::alloc::BStackSlice::from_bytes(&alloc, block_bytes) };
let v = unsafe { BStackByteVec::from_raw_block(block) };
assert_eq!(v.len().unwrap(), 3);
assert_eq!(v.get(0).unwrap(), Some(111u8));
assert_eq!(v.get(1).unwrap(), Some(222u8));
assert_eq!(v.get(2).unwrap(), Some(33u8));
}
#[test]
fn push_pop_lifo() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
for i in 0..10u8 {
v.push(i * 11).unwrap();
}
assert_eq!(v.len().unwrap(), 10);
for i in (0..10u8).rev() {
assert_eq!(v.pop().unwrap(), Some(i * 11));
}
assert_eq!(v.pop().unwrap(), None);
assert!(v.is_empty().unwrap());
}
#[test]
fn pop_zeros_vacated_slot() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(0xABu8).unwrap();
let block = unsafe { v.raw_block() };
v.pop().unwrap();
let slot_bytes = block.read_range(16, 17).unwrap();
assert_eq!(
slot_bytes, [0u8; 1],
"vacated slot must be zeroed after pop"
);
}
#[test]
fn get_out_of_bounds_returns_none() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(42).unwrap();
assert_eq!(v.get(0).unwrap(), Some(42u8));
assert_eq!(v.get(1).unwrap(), None);
assert_eq!(v.get(u64::MAX).unwrap(), None);
}
#[test]
fn read_bytes_returns_all_elements() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let src = [7u8, 11, 13, 17];
let v = BStackByteVec::from_slice(&src, &alloc).unwrap();
assert_eq!(v.read_bytes().unwrap(), src);
}
#[test]
fn push_triggers_growth_from_zero() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
assert!(v.capacity().unwrap() >= 4);
assert_eq!(v.len().unwrap(), 1);
}
#[test]
fn push_doubles_capacity_on_overflow() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::with_capacity(2, &alloc).unwrap();
v.push(1).unwrap();
v.push(2).unwrap();
let cap_before = v.capacity().unwrap();
assert_eq!(cap_before, 2);
v.push(3).unwrap(); assert!(v.capacity().unwrap() >= 4);
assert_eq!(v.len().unwrap(), 3);
}
#[test]
fn truncate_shortens_and_zeros_slots() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(0xAAu8).unwrap();
v.push(0xBBu8).unwrap();
v.push(0xCCu8).unwrap();
let block = unsafe { v.raw_block() };
v.truncate(1).unwrap();
assert_eq!(v.len().unwrap(), 1);
assert_eq!(v.capacity().unwrap(), 4);
let removed = block.read_range(17, 19).unwrap();
assert_eq!(removed, [0u8; 2], "truncated slots must be zeroed");
}
#[test]
fn truncate_noop_when_new_len_ge_len() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(7).unwrap();
v.truncate(5).unwrap(); assert_eq!(v.len().unwrap(), 1);
assert_eq!(v.get(0).unwrap(), Some(7u8));
}
#[test]
fn clear_zeros_all_byte_slots() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
v.push(2).unwrap();
v.push(3).unwrap();
let block = unsafe { v.raw_block() };
v.clear().unwrap();
assert_eq!(v.len().unwrap(), 0);
let elems = block.read_range(16, 19).unwrap();
assert_eq!(elems, vec![0u8; 3]);
}
#[test]
fn reserve_noop_when_capacity_sufficient() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::with_capacity(10, &alloc).unwrap();
v.push(1).unwrap();
v.reserve(5).unwrap(); assert_eq!(v.capacity().unwrap(), 10);
}
#[test]
fn reserve_grows_when_needed() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
v.reserve(100).unwrap();
assert!(v.capacity().unwrap() >= 101);
}
#[test]
fn reserve_overflow_returns_error() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap(); let err = v.reserve(u64::MAX).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
fn resize_grow_fills_with_value() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
v.resize(5, 99u8).unwrap();
assert_eq!(v.len().unwrap(), 5);
assert_eq!(v.get(0).unwrap(), Some(1u8));
for i in 1..5 {
assert_eq!(v.get(i).unwrap(), Some(99u8));
}
}
#[test]
fn resize_shrink_truncates() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::from_slice(&[1, 2, 3, 4, 5], &alloc).unwrap();
v.resize(2, 0).unwrap();
assert_eq!(v.len().unwrap(), 2);
assert_eq!(v.get(0).unwrap(), Some(1u8));
assert_eq!(v.get(1).unwrap(), Some(2u8));
assert_eq!(v.get(2).unwrap(), None);
}
#[test]
fn as_slice_covers_populated_bytes_only() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::from_slice(&[10u8, 20, 30], &alloc).unwrap();
let s = v.as_slice().unwrap();
assert_eq!(s.len(), 3);
let bytes = s.read().unwrap();
assert_eq!(bytes, [10u8, 20, 30]);
}
#[test]
fn iter_yields_all_bytes_in_order() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let src = [3u8, 1, 4, 1, 5, 9, 2, 6];
let v = BStackByteVec::from_slice(&src, &alloc).unwrap();
let collected: Vec<u8> = v.iter().unwrap().map(|r| r.unwrap()).collect();
assert_eq!(collected, src);
}
#[test]
fn iter_size_hint_tracks_remaining() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::from_slice(&[1u8, 2, 3, 4, 5], &alloc).unwrap();
let mut it = v.iter().unwrap();
assert_eq!(it.size_hint(), (5, Some(5)));
it.next().unwrap().unwrap();
assert_eq!(it.size_hint(), (4, Some(4)));
it.next().unwrap().unwrap();
it.next().unwrap().unwrap();
assert_eq!(it.size_hint(), (2, Some(2)));
}
#[test]
fn iter_stops_at_len_snapshot() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::from_slice(&[10u8, 20, 30], &alloc).unwrap();
let count = v.iter().unwrap().count();
assert_eq!(count, 3);
}
#[test]
fn iter_on_empty_vec_yields_nothing() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::new(&alloc).unwrap();
let count = v.iter().unwrap().count();
assert_eq!(count, 0);
}
#[test]
fn with_capacity_overflow_returns_error() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let err = BStackByteVec::with_capacity(u64::MAX, &alloc).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
fn reserve_overflow_through_grow_returns_error() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
let err = v.reserve(u64::MAX).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
}
#[test]
fn as_slice_readable_via_slice_reader() {
use std::io::Read;
let (alloc, path) = make_alloc();
let _g = Guard(path);
let v = BStackByteVec::from_slice(&[0x0Au8, 0x0B, 0x0C], &alloc).unwrap();
let s = v.as_slice().unwrap();
let mut reader = s.reader();
let mut buf = [0u8; 3];
reader.read_exact(&mut buf).unwrap();
assert_eq!(&buf, &[0x0Au8, 0x0B, 0x0C]);
}
#[test]
fn dealloc_reclaims_tail_from_linear_allocator() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let size_before = alloc.stack().len().unwrap();
let mut v = BStackByteVec::new(&alloc).unwrap();
v.push(1).unwrap();
v.push(2).unwrap();
let size_after_push = alloc.stack().len().unwrap();
assert!(size_after_push > size_before);
v.dealloc().unwrap();
assert_eq!(alloc.stack().len().unwrap(), size_before);
}
#[test]
fn two_vecs_on_same_allocator_do_not_interfere() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut a = BStackByteVec::with_capacity(4, &alloc).unwrap();
let mut b = BStackByteVec::with_capacity(4, &alloc).unwrap();
a.push(10).unwrap();
b.push(20).unwrap();
a.push(11).unwrap();
b.push(21).unwrap();
assert_eq!(a.len().unwrap(), 2);
assert_eq!(b.len().unwrap(), 2);
assert_eq!(a.get(0).unwrap(), Some(10u8));
assert_eq!(a.get(1).unwrap(), Some(11u8));
assert_eq!(b.get(0).unwrap(), Some(20u8));
assert_eq!(b.get(1).unwrap(), Some(21u8));
}
#[test]
fn as_slice_len_tracks_vec_len() {
let (alloc, path) = make_alloc();
let _g = Guard(path);
let mut v = BStackByteVec::new(&alloc).unwrap();
assert_eq!(v.as_slice().unwrap().len(), 0);
v.push(1).unwrap();
assert_eq!(v.as_slice().unwrap().len(), 1);
v.push(2).unwrap();
assert_eq!(v.as_slice().unwrap().len(), 2);
v.pop().unwrap();
assert_eq!(v.as_slice().unwrap().len(), 1);
}
}