use crate::error::{BufferError, Result};
use zeroize::Zeroize;
const MAX_CBUF_SIZE: usize = 100_000_000;
pub struct CircularBuffer {
data: Option<Box<[u8]>>,
size: usize,
used: usize,
read_pos: usize,
write_pos: usize,
is_pow2: bool,
}
#[inline(always)]
fn wrap(pos: usize, delta: usize, size: usize, is_pow2: bool) -> usize {
let new = pos + delta;
if is_pow2 {
new & (size - 1)
} else {
new % size
}
}
impl CircularBuffer {
pub fn new(size: usize) -> Self {
assert!(
size <= MAX_CBUF_SIZE,
"CircularBuffer size {size} exceeds maximum {MAX_CBUF_SIZE}"
);
Self {
data: None,
size,
used: 0,
read_pos: 0,
write_pos: 0,
is_pow2: size.is_power_of_two(),
}
}
pub fn new_pow2(size_log2: u32) -> Self {
let size = 1usize << size_log2;
assert!(
size <= MAX_CBUF_SIZE,
"CircularBuffer size {size} exceeds maximum {MAX_CBUF_SIZE}"
);
Self {
data: None,
size,
used: 0,
read_pos: 0,
write_pos: 0,
is_pow2: true,
}
}
#[inline(always)]
pub fn used(&self) -> usize {
self.used
}
#[inline(always)]
pub fn available(&self) -> usize {
self.size - self.used
}
#[inline(always)]
pub fn size(&self) -> usize {
self.size
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.used == 0
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.used == self.size
}
pub fn read_ptrs(&self) -> (&[u8], &[u8]) {
if self.used == 0 {
return (&[], &[]);
}
let buffer = match self.data.as_ref() {
Some(b) => b,
None => return (&[], &[]),
};
let len1 = self.used.min(self.size - self.read_pos);
let p1 = &buffer[self.read_pos..self.read_pos + len1];
if len1 < self.used {
let p2 = &buffer[..self.used - len1];
(p1, p2)
} else {
(p1, &[])
}
}
pub fn write_ptr(&mut self, len: usize) -> Result<&mut [u8]> {
if len > self.available() {
return Err(BufferError::InsufficientSpace);
}
if self.write_pos + len > self.size {
return Err(BufferError::InvalidState(
"write would cross ring boundary — use write_slices_mut or write()".into(),
));
}
self.ensure_allocated();
let buffer = self.data.as_mut().unwrap();
Ok(&mut buffer[self.write_pos..self.write_pos + len])
}
pub fn write_slices_mut(&mut self, len: usize) -> Result<(&mut [u8], &mut [u8])> {
if len == 0 {
return Ok((&mut [], &mut []));
}
if len > self.available() {
return Err(BufferError::InsufficientSpace);
}
self.ensure_allocated();
let buffer = self.data.as_mut().unwrap();
let write_pos = self.write_pos;
let space_to_end = self.size - write_pos;
if len <= space_to_end {
Ok((&mut buffer[write_pos..write_pos + len], &mut []))
} else {
let (head, tail) = buffer.split_at_mut(write_pos);
let len2 = len - space_to_end;
Ok((&mut tail[..space_to_end], &mut head[..len2]))
}
}
pub fn incr_write(&mut self, len: usize) -> Result<()> {
if len > self.available() {
return Err(BufferError::InsufficientSpace);
}
let (size, is_pow2) = (self.size, self.is_pow2);
self.write_pos = wrap(self.write_pos, len, size, is_pow2);
self.used += len;
Ok(())
}
pub fn incr_read(&mut self, len: usize) -> Result<()> {
if len > self.used {
return Err(BufferError::BufferOverflow);
}
let (size, is_pow2) = (self.size, self.is_pow2);
self.read_pos = wrap(self.read_pos, len, size, is_pow2);
self.used -= len;
Ok(())
}
pub fn write(&mut self, data: &[u8]) -> Result<usize> {
if data.is_empty() {
return Ok(0);
}
let len = data.len();
if self.available() < len {
return Err(BufferError::InsufficientSpace);
}
self.ensure_allocated();
let write_pos = self.write_pos;
let size = self.size;
let is_pow2 = self.is_pow2;
let space_to_end = size - write_pos;
{
let buffer = self.data.as_mut().unwrap();
if len <= space_to_end {
buffer[write_pos..write_pos + len].copy_from_slice(data);
} else {
buffer[write_pos..].copy_from_slice(&data[..space_to_end]);
buffer[..len - space_to_end].copy_from_slice(&data[space_to_end..]);
}
}
self.write_pos = wrap(write_pos, len, size, is_pow2);
self.used += len;
Ok(len)
}
pub fn read(&mut self, output: &mut [u8]) -> Result<usize> {
if self.used == 0 || self.data.is_none() {
return Ok(0);
}
let size = self.size;
let is_pow2 = self.is_pow2;
let to_read = output.len().min(self.used);
let read_pos = self.read_pos;
let space_to_end = size - read_pos;
{
let buffer = self.data.as_ref().unwrap();
if to_read <= space_to_end {
output[..to_read].copy_from_slice(&buffer[read_pos..read_pos + to_read]);
} else {
let (out1, out2) = output[..to_read].split_at_mut(space_to_end);
out1.copy_from_slice(&buffer[read_pos..]);
out2.copy_from_slice(&buffer[..to_read - space_to_end]);
}
}
self.read_pos = wrap(read_pos, to_read, size, is_pow2);
self.used -= to_read;
Ok(to_read)
}
pub fn peek(&self, output: &mut [u8]) -> Result<usize> {
if self.used == 0 || self.data.is_none() {
return Ok(0);
}
let size = self.size;
let is_pow2 = self.is_pow2;
let to_read = output.len().min(self.used);
let read_pos = self.read_pos;
let space_to_end = size - read_pos;
let buffer = self.data.as_ref().unwrap();
if to_read <= space_to_end {
output[..to_read].copy_from_slice(&buffer[read_pos..read_pos + to_read]);
} else {
let (out1, out2) = output[..to_read].split_at_mut(space_to_end);
out1.copy_from_slice(&buffer[read_pos..]);
out2.copy_from_slice(&buffer[..to_read - space_to_end]);
}
let _ = wrap(read_pos, to_read, size, is_pow2);
Ok(to_read)
}
#[inline]
pub fn clear(&mut self) {
self.used = 0;
self.read_pos = 0;
self.write_pos = 0;
}
pub fn free(&mut self) {
if let Some(data) = self.data.take() {
let mut vec = data.into_vec();
vec.zeroize();
}
self.clear();
}
pub fn burn_free(mut self) {
if let Some(data) = self.data.take() {
let mut vec = data.into_vec();
vec.zeroize();
}
drop(self);
}
#[inline]
fn ensure_allocated(&mut self) {
if self.data.is_none() {
self.data = Some(vec![0u8; self.size].into_boxed_slice());
}
}
}
impl Drop for CircularBuffer {
fn drop(&mut self) {
if let Some(data) = self.data.take() {
let mut vec = data.into_vec();
vec.zeroize();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circular_basic() {
let buf = CircularBuffer::new(256);
assert_eq!(buf.size(), 256);
assert_eq!(buf.used(), 0);
assert!(buf.is_empty());
}
#[test]
fn test_write_read() {
let mut buf = CircularBuffer::new(256);
buf.write(b"Hello").unwrap();
assert_eq!(buf.used(), 5);
let mut out = vec![0u8; 5];
buf.read(&mut out).unwrap();
assert_eq!(&out, b"Hello");
assert!(buf.is_empty());
}
#[test]
fn test_wrap_around_write_read() {
let mut buf = CircularBuffer::new(8);
buf.write(b"12345").unwrap();
let mut tmp = vec![0u8; 3];
buf.read(&mut tmp).unwrap();
buf.write(b"6789").unwrap(); assert_eq!(buf.used(), 6);
let mut out = vec![0u8; 6];
buf.read(&mut out).unwrap();
assert_eq!(&out, b"456789");
}
#[test]
fn test_wrap_around_large() {
let mut buf = CircularBuffer::new_pow2(4); for _ in 0..8 {
buf.write(b"ABCDE FGHI").unwrap(); let mut out = vec![0u8; 10];
buf.read(&mut out).unwrap();
assert_eq!(&out, b"ABCDE FGHI");
}
}
#[test]
fn test_write_ptr_no_wrap() {
let mut buf = CircularBuffer::new(16);
let slice = buf.write_ptr(4).unwrap();
slice.copy_from_slice(b"RUST");
buf.incr_write(4).unwrap();
assert_eq!(buf.used(), 4);
let mut out = [0u8; 4];
buf.read(&mut out).unwrap();
assert_eq!(&out, b"RUST");
}
#[test]
fn test_write_ptr_returns_err_on_wrap() {
let mut buf = CircularBuffer::new(8);
buf.write(b"123456").unwrap();
let mut tmp = [0u8; 6];
buf.read(&mut tmp).unwrap();
assert!(matches!(
buf.write_ptr(4),
Err(BufferError::InvalidState(_))
));
}
#[test]
fn test_write_slices_mut_wrap() {
let mut buf = CircularBuffer::new(8);
buf.write(b"123456").unwrap();
let mut tmp = [0u8; 6];
buf.read(&mut tmp).unwrap();
let (s1, s2) = buf.write_slices_mut(4).unwrap();
assert_eq!(s1.len(), 2);
assert_eq!(s2.len(), 2);
s1.copy_from_slice(b"AB");
s2.copy_from_slice(b"CD");
buf.incr_write(4).unwrap();
let mut out = [0u8; 4];
buf.read(&mut out).unwrap();
assert_eq!(&out, b"ABCD");
}
#[test]
fn test_peek() {
let mut buf = CircularBuffer::new(32);
buf.write(b"peekaboo").unwrap();
let mut out1 = vec![0u8; 8];
buf.peek(&mut out1).unwrap();
assert_eq!(&out1, b"peekaboo");
assert_eq!(buf.used(), 8);
let mut out2 = vec![0u8; 8];
buf.read(&mut out2).unwrap();
assert_eq!(&out2, b"peekaboo");
assert!(buf.is_empty());
}
#[test]
fn test_free() {
let mut buf = CircularBuffer::new(1024);
buf.write(b"sensitive data").unwrap();
buf.free();
assert!(buf.is_empty());
assert_eq!(buf.used(), 0);
assert!(buf.data.is_none());
}
#[test]
fn test_burn_free() {
let mut buf = CircularBuffer::new(1024);
buf.write(b"secret").unwrap();
buf.burn_free(); }
#[test]
fn test_lazy_allocation() {
let buf = CircularBuffer::new(1024);
assert!(buf.data.is_none()); }
#[test]
fn test_pow2_fast_wrap() {
let mut fast = CircularBuffer::new_pow2(3); let mut slow = CircularBuffer::new(8);
let data = b"ABCDEFGHIJKLMNOP";
for chunk in data.chunks(3) {
let len = chunk.len().min(fast.available());
fast.write(&chunk[..len]).unwrap();
slow.write(&chunk[..len]).unwrap();
let mut out_f = vec![0u8; fast.used()];
let mut out_s = vec![0u8; slow.used()];
fast.read(&mut out_f).unwrap();
slow.read(&mut out_s).unwrap();
assert_eq!(out_f, out_s);
}
}
}