use std::mem::size_of;
use std::ops::Index;
use crate::byte_store::ByteStore;
#[derive(Debug)]
pub struct Buffers<T: ByteStore> {
byte_store: T,
count: usize,
data_end: usize,
}
#[derive(Debug)]
pub struct BuffersSlice<'a, T: ByteStore> {
buffers: &'a Buffers<T>,
start: usize, end: usize, }
impl<'a, T: ByteStore> Clone for BuffersSlice<'a, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, T: ByteStore> Copy for BuffersSlice<'a, T> {}
impl<'a, T: ByteStore> BuffersSlice<'a, T> {
pub fn slice(&self, start: usize, end: usize) -> Self {
assert!(start <= end, "start must be <= end");
assert!(end <= self.len(), "end out of bounds");
BuffersSlice {
buffers: self.buffers,
start: self.start + start,
end: self.start + end,
}
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn iter(&self) -> BuffersSliceIter<'a, T> {
BuffersSliceIter {
slice: *self,
pos: 0,
}
}
pub fn get(&self, index: usize) -> Option<&'a [u8]> {
if index >= self.len() {
return None;
}
self.buffers.get(self.start + index)
}
}
pub struct BuffersSliceIter<'a, T: ByteStore> {
slice: BuffersSlice<'a, T>,
pos: usize,
}
impl<'a, T: ByteStore> Iterator for BuffersSliceIter<'a, T> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.pos < self.slice.len() {
let item = self.slice.get(self.pos);
self.pos += 1;
item
} else {
None
}
}
}
impl<'a, B: ByteStore> IntoIterator for BuffersSlice<'a, B> {
type Item = &'a [u8];
type IntoIter = BuffersSliceIter<'a, B>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<T: ByteStore> Buffers<T> {
fn bs(&self) -> &[u8] {
self.byte_store.as_ref()
}
fn bs_mut(&mut self) -> &mut [u8] {
self.byte_store.as_mut()
}
pub fn new(byte_store: T) -> Self {
let data_end = byte_store.as_ref().len();
let mut s = Self {
byte_store,
count: 0,
data_end,
};
s.write_header_and_initial_offset();
s
}
fn get_count(byte_store: &T) -> usize {
let count_bytes_range = 0..size_of::<usize>();
let count_bytes: [u8; size_of::<usize>()] =
byte_store.as_ref()[count_bytes_range].try_into().unwrap();
usize::from_le_bytes(count_bytes)
}
fn set_count(&mut self) {
let count_bytes_range = 0..size_of::<usize>();
let count = self.count;
self.bs_mut()[count_bytes_range].copy_from_slice(&count.to_le_bytes());
}
pub fn load(byte_store: T) -> Self {
let count = Self::get_count(&byte_store);
let mut s = Self {
byte_store,
count,
data_end: 0, };
let last_offset = s.offsets().last().copied().unwrap_or(0);
s.data_end = s.bs().len() - last_offset;
s
}
fn write_header_and_initial_offset(&mut self) {
let needed = self.offsets_end();
if needed > self.bs().len() {
let grow_by = needed - self.bs().len();
self.byte_store.grow(grow_by);
self.data_end = self.bs().len();
}
self.set_count();
let initial_offset_start = Self::header_size();
let initial_offset_end = initial_offset_start + size_of::<usize>();
let initial_offset_slice = &mut self.bs_mut()[initial_offset_start..initial_offset_end];
initial_offset_slice.copy_from_slice(&0usize.to_le_bytes());
}
const fn header_size() -> usize {
size_of::<usize>()
}
pub fn store(&self) -> &T {
&self.byte_store
}
pub fn offsets(&self) -> &[usize] {
let offsets_start = Self::header_size();
let offsets_end = self.offsets_end();
let offset_bytes = &self.bs()[offsets_start..offsets_end];
bytemuck::cast_slice(offset_bytes)
}
fn offsets_mut(&mut self) -> &mut [usize] {
let offsets_start = Self::header_size();
let offsets_end = self.offsets_end();
let offset_bytes = &mut self.bs_mut()[offsets_start..offsets_end];
bytemuck::cast_slice_mut(offset_bytes)
}
pub fn slice(&self, start: usize, end: usize) -> BuffersSlice<'_, T> {
assert!(start <= end, "start must be <= end");
assert!(end <= self.len(), "end out of bounds");
BuffersSlice {
buffers: self,
start,
end,
}
}
pub fn iter(&self) -> BuffersSliceIter<'_, T> {
self.slice(0, self.len()).iter()
}
pub fn append(&mut self, bytes: impl AsRef<[u8]>) -> usize {
let bytes = bytes.as_ref();
let offset_size = size_of::<usize>();
let actual_len = bytes.len();
let len_prefix_size = size_of::<u64>();
let data_with_prefix_len = len_prefix_size + actual_len;
let padded_len = (data_with_prefix_len + 7) & !7; let total_data_size = padded_len;
let needed_space = offset_size + total_data_size;
if self.free_space() < needed_space {
let old_len = self.bs().len();
let data_len = old_len - self.data_end;
let mut new_len = if old_len == 0 { 256 } else { old_len * 2 };
let required_len = self.offsets_end() + data_len + needed_space;
while new_len < required_len {
new_len *= 2;
}
let growth = new_len - old_len;
self.byte_store.grow(growth);
let new_actual_len = self.bs().len();
let new_data_end = new_actual_len - data_len;
if data_len > 0 {
let data_src_range = self.data_end..old_len;
self.bs_mut().copy_within(data_src_range, new_data_end);
}
self.data_end = new_data_end;
}
self.data_end -= total_data_size;
let data_write_start = self.data_end;
let len_bytes = (actual_len as u64).to_le_bytes();
self.bs_mut()[data_write_start..data_write_start + len_prefix_size]
.copy_from_slice(&len_bytes);
let data_start = data_write_start + len_prefix_size;
self.bs_mut()[data_start..data_start + actual_len].copy_from_slice(bytes);
let padding_start = data_start + actual_len;
let padding_end = data_write_start + total_data_size;
if padding_end > padding_start {
self.bs_mut()[padding_start..padding_end].fill(0);
}
let cumulative_len = self.bs().len() - self.data_end;
let index = self.count;
self.count += 1;
self.set_count();
let count = self.count;
self.offsets_mut()[count] = cumulative_len;
index
}
fn offsets_end(&self) -> usize {
Self::header_size() + (self.count + 1) * size_of::<usize>()
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn get(&self, index: usize) -> Option<&[u8]> {
if index >= self.count {
return None;
}
let offsets = self.offsets();
let start_cumulative = offsets[index];
let end_cumulative = offsets[index + 1];
if end_cumulative < start_cumulative {
panic!(
"Invalid offsets: end_cumulative ({end_cumulative}) < start_cumulative ({start_cumulative}) for index {index}"
);
}
let total_len = self.bs().len();
let entry_start = total_len - end_cumulative;
let entry_end = total_len - start_cumulative;
let entry_data = &self.bs()[entry_start..entry_end];
if entry_data.len() < size_of::<u64>() {
panic!("Entry data too small to contain length prefix for index {index}");
}
let len_prefix_size = size_of::<u64>();
let len_bytes: [u8; 8] = entry_data[0..len_prefix_size].try_into().unwrap();
let actual_len = u64::from_le_bytes(len_bytes) as usize;
let data_start = len_prefix_size;
let data_end = data_start + actual_len;
if data_end > entry_data.len() {
panic!(
"Actual length {actual_len} exceeds entry size {} for index {index}",
entry_data.len() - len_prefix_size
);
}
Some(&entry_data[data_start..data_end])
}
pub fn get_aligned_raw(&self, index: usize) -> Option<&[u8]> {
if index >= self.count {
return None;
}
let offsets = self.offsets();
let start_cumulative = offsets[index];
let end_cumulative = offsets[index + 1];
if end_cumulative < start_cumulative {
panic!(
"Invalid offsets: end_cumulative ({end_cumulative}) < start_cumulative ({start_cumulative}) for index {index}"
);
}
let total_len = self.bs().len();
let entry_start = total_len - end_cumulative;
let entry_end = total_len - start_cumulative;
Some(&self.bs()[entry_start..entry_end])
}
pub fn get_aligned_data(&self, index: usize) -> Option<&[u8]> {
if index >= self.count {
return None;
}
let offsets = self.offsets();
let start_cumulative = offsets[index];
let end_cumulative = offsets[index + 1];
if end_cumulative < start_cumulative {
panic!(
"Invalid offsets: end_cumulative ({end_cumulative}) < start_cumulative ({start_cumulative}) for index {index}"
);
}
let total_len = self.bs().len();
let entry_start = total_len - end_cumulative;
let entry_end = total_len - start_cumulative;
let entry_data = &self.bs()[entry_start..entry_end];
let len_prefix_size = size_of::<u64>();
if entry_data.len() < len_prefix_size {
panic!("Entry data too small to contain length prefix for index {index}");
}
Some(&entry_data[len_prefix_size..])
}
pub fn free_space(&self) -> usize {
if self.data_end < self.offsets_end() {
0
} else {
self.data_end - self.offsets_end()
}
}
pub fn clear(&mut self) {
self.count = 0;
self.data_end = self.bs().len();
self.write_header_and_initial_offset();
}
}
impl<B: ByteStore> Index<usize> for Buffers<B> {
type Output = [u8];
fn index(&self, index: usize) -> &Self::Output {
self.get(index).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::byte_store::{MMapFile, VecStore};
use proptest::prelude::*;
use tempfile::NamedTempFile;
macro_rules! test_buffers {
($test_name:ident, $store_type:expr) => {
#[test]
fn $test_name() {
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_basic_operations(&mut buffers);
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_auto_growing(&mut buffers);
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_clear(&mut buffers);
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_empty_data(&mut buffers);
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_buffers_slice_and_iter(&mut buffers);
let store = $store_type;
let mut buffers = Buffers::new(store);
check_test_buffers_iter_equivalence(&mut buffers);
}
};
}
test_buffers!(test_basic_operations_vec_backend, VecStore::new());
test_buffers!(
test_basic_operations_mmap_backend,
MMapFile::new(NamedTempFile::new().unwrap().path(), 1024).unwrap()
);
fn check_test_basic_operations<T: ByteStore>(buffers: &mut Buffers<T>) {
assert!(buffers.is_empty());
assert_eq!(buffers.len(), 0);
let idx1 = buffers.append(b"hello");
assert_eq!(idx1, 0);
assert_eq!(buffers.len(), 1);
assert!(!buffers.is_empty());
let idx2 = buffers.append(b"world");
assert_eq!(idx2, 1);
assert_eq!(buffers.len(), 2);
assert_eq!(buffers.get(0), Some(b"hello".as_ref()));
assert_eq!(buffers.get(1), Some(b"world".as_ref()));
assert_eq!(buffers.get(2), None);
assert_eq!(&buffers[0], b"hello");
assert_eq!(&buffers[1], b"world");
}
fn check_test_auto_growing<T: ByteStore>(buffers: &mut Buffers<T>) {
let initial_free_space = buffers.free_space();
for i in 0..10 {
let data = format!("data{i}");
buffers.append(data.as_bytes());
}
assert_eq!(buffers.len(), 10);
assert!(
buffers.store().as_ref().len() > initial_free_space,
"Buffer should have grown"
);
let _ = buffers.free_space();
for i in 0..10 {
let expected_data = format!("data{i}");
assert_eq!(buffers.get(i), Some(expected_data.as_bytes()));
}
}
fn check_test_clear<T: ByteStore>(buffers: &mut Buffers<T>) {
buffers.append(b"some_data");
buffers.append(b"more_data");
assert_eq!(buffers.len(), 2);
buffers.clear();
assert!(buffers.is_empty());
assert_eq!(buffers.len(), 0);
assert_eq!(buffers.get(0), None);
buffers.append(b"after_clear");
assert_eq!(buffers.len(), 1);
assert_eq!(buffers.get(0), Some(b"after_clear".as_ref()));
}
fn check_test_empty_data<T: ByteStore>(buffers: &mut Buffers<T>) {
buffers.append(b"");
buffers.append(b"non-empty");
buffers.append(b"");
assert_eq!(buffers.len(), 3);
assert_eq!(buffers.get(0), Some(b"".as_ref()));
assert_eq!(buffers.get(1), Some(b"non-empty".as_ref()));
assert_eq!(buffers.get(2), Some(b"".as_ref()));
}
proptest! {
#[test]
fn prop_test_store_retrieval(data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..128), 0..256)) {
let mut buffers = Buffers::new(VecStore::new());
for data in &data_list {
buffers.append(data);
}
prop_assert_eq!(buffers.len(), data_list.len());
for (i, data) in data_list.iter().enumerate() {
prop_assert_eq!(buffers.get(i), Some(data.as_slice()));
}
}
}
fn check_test_buffers_slice_and_iter<T: ByteStore>(buffers: &mut Buffers<T>) {
buffers.append(b"a");
buffers.append(b"b");
buffers.append(b"c");
buffers.append(b"d");
buffers.append(b"e");
let slice = buffers.slice(1, 4); assert_eq!(slice.len(), 3);
assert_eq!(slice.get(0), Some(b"b".as_ref()));
assert_eq!(slice.get(1), Some(b"c".as_ref()));
assert_eq!(slice.get(2), Some(b"d".as_ref()));
assert_eq!(slice.get(3), None);
let collected: Vec<&[u8]> = slice.iter().collect();
assert_eq!(collected, vec![b"b", b"c", b"d"]);
}
fn check_test_buffers_iter_equivalence<T: ByteStore>(buffers: &mut Buffers<T>) {
buffers.append(b"a");
buffers.append(b"b");
buffers.append(b"c");
let from_buffers: Vec<&[u8]> = buffers.iter().collect();
let from_slice: Vec<&[u8]> = buffers.slice(0, buffers.len()).iter().collect();
assert_eq!(from_buffers, from_slice);
assert_eq!(
from_buffers,
vec![b"a".as_ref(), b"b".as_ref(), b"c".as_ref()]
);
}
proptest! {
#[test]
fn prop_slice_iter_equivalence(
data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..128), 0..256),
start_pct in 0.0f64..1.0,
end_pct in 0.0f64..1.0
) {
let mut buffers = Buffers::new(VecStore::with_capacity(data_list.len() * 100));
for data in &data_list {
buffers.append(data);
}
let len = buffers.len();
if len > 0 {
let start_idx = (start_pct * len as f64).floor() as usize;
let end_idx = (end_pct * len as f64).floor() as usize;
if start_idx <= end_idx {
let slice = buffers.slice(start_idx, end_idx);
let from_slice_iter: Vec<&[u8]> = slice.iter().collect();
let expected: Vec<&[u8]> = data_list[start_idx..end_idx].iter().map(|v| v.as_slice()).collect();
prop_assert_eq!(from_slice_iter, expected);
}
}
}
}
proptest! {
#[test]
fn prop_nested_slice_equivalence(
data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..32), 10..40)
) {
let mut buffers = Buffers::new(VecStore::with_capacity(data_list.len() * 40));
for data in &data_list {
buffers.append(data);
}
let slice1 = buffers.slice(2, 8); let slice2 = slice1.slice(1, 5);
let collected: Vec<_> = slice2.iter().collect();
let expected: Vec<_> = data_list[3..7].iter().map(|v| v.as_slice()).collect();
prop_assert_eq!(collected, expected);
}
}
proptest! {
#[test]
fn prop_test_store_bounds(
data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..128), 0..256)
) {
let mut buffers = Buffers::new(VecStore::new());
for data in &data_list {
buffers.append(data);
}
prop_assert!(buffers.get(data_list.len()).is_none());
if !data_list.is_empty() {
prop_assert!(buffers.get(data_list.len() - 1).is_some());
}
}
}
proptest! {
#[test]
fn prop_test_no_out_of_bounds_access(
data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..128), 0..256)
) {
let mut buffers = Buffers::new(VecStore::with_capacity(1024 * 1024));
for data in &data_list {
buffers.append(data);
}
for i in 0..buffers.len() {
let _ = buffers.get(i);
}
let _ = buffers.get(buffers.len());
}
}
proptest! {
#[test]
fn prop_test_free_space_calculation(
data_list in prop::collection::vec(prop::collection::vec(0u8..255, 0..128), 0..256)
) {
let mut buffers = Buffers::new(VecStore::with_capacity(1024));
for data in &data_list {
buffers.append(data);
}
assert_eq!(buffers.free_space(), buffers.data_end - buffers.offsets_end());
}
}
proptest! {
#[test]
fn proptest_free_space_calculation_empty(
data_list: Vec<Vec<u8>>
) {
let mut buffers = Buffers::new(VecStore::with_capacity(0));
assert!(buffers.free_space() == 0);
for data in &data_list {
buffers.append(data);
}
assert_eq!(buffers.free_space(), buffers.data_end - buffers.offsets_end());
}
}
#[test]
fn test_exact_interface_requirements() {
let mut buffers = Buffers::new(VecStore::with_capacity(128));
buffers.append([1, 2]); buffers.append([3, 4, 5]);
let _data_bytes = buffers.store().as_ref();
let offsets = buffers.offsets();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 16); assert_eq!(offsets[2], 32);
assert_eq!(buffers.get(0).unwrap(), &[1, 2]);
assert_eq!(buffers.get(1).unwrap(), &[3, 4, 5]);
}
#[test]
fn test_offset_system_understanding() {
let mut buffers = Buffers::new(VecStore::with_capacity(64));
buffers.append([1]); buffers.append([2, 2]); buffers.append([3, 3, 3]);
let offsets = buffers.offsets();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 16); assert_eq!(offsets[2], 32); assert_eq!(offsets[3], 48);
assert_eq!(buffers.get(0).unwrap(), [1]);
assert_eq!(buffers.get(1).unwrap(), [2, 2]);
assert_eq!(buffers.get(2).unwrap(), [3, 3, 3]);
}
#[test]
fn test_empty_buffer() {
let mut buffers = Buffers::new(VecStore::with_capacity(64));
check_test_empty_data(&mut buffers);
}
#[test]
fn test_load() {
let mut store = VecStore::with_capacity(128);
let mut buffers = Buffers::new(store);
buffers.append(b"hello");
buffers.append(b"world");
store = buffers.byte_store;
let buffers2 = Buffers::load(store);
assert_eq!(buffers2.len(), 2);
assert_eq!(buffers2.get(0).unwrap(), b"hello");
assert_eq!(buffers2.get(1).unwrap(), b"world");
}
#[test]
fn test_offset_system_with_growth() {
let mut buffers = Buffers::new(VecStore::with_capacity(32));
buffers.append([1; 10]); buffers.append([2; 10]);
assert_eq!(buffers.len(), 2);
assert!(buffers.store().as_ref().len() > 32);
let offsets = buffers.offsets();
assert_eq!(offsets[0], 0);
assert_eq!(offsets[1], 24); assert_eq!(offsets[2], 48);
assert_eq!(buffers.get(0).unwrap(), &[1; 10]);
assert_eq!(buffers.get(1).unwrap(), &[2; 10]);
}
#[test]
fn test_detailed_offset_analysis() {
let mut buffers = Buffers::new(VecStore::with_capacity(64));
buffers.append([1, 2]); buffers.append([3, 4, 5]); buffers.append([6]);
assert_eq!(buffers.get(0), Some(&[1, 2][..]));
assert_eq!(buffers.get(1), Some(&[3, 4, 5][..]));
assert_eq!(buffers.get(2), Some(&[6][..]));
}
}