#![no_std]
use core::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
};
use spin::Mutex;
#[derive(Debug, PartialEq)]
pub enum OuroBufferError {
BufferOverflow,
}
impl fmt::Display for OuroBufferError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BufferOverflow => write!(
f,
"Buffer overflow: attempted to write beyond buffer capacity"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for OuroBufferError {}
struct BufferState<const N: usize> {
buffer: [u8; N],
write_pos: usize,
read_pos: usize,
count: usize,
}
pub struct OuroBuffer<const N: usize> {
state: Mutex<BufferState<N>>,
atomic_count: AtomicUsize,
}
impl<const N: usize> OuroBuffer<N> {
pub const fn new() -> Self {
Self {
state: Mutex::new(BufferState {
buffer: [0; N],
write_pos: 0,
read_pos: 0,
count: 0,
}),
atomic_count: AtomicUsize::new(0),
}
}
pub fn push(&self, data: &[u8]) -> Result<(), OuroBufferError> {
let data_len = data.len();
let available = N - self.atomic_count.load(Ordering::Relaxed);
if data_len > available {
return Err(OuroBufferError::BufferOverflow);
}
let mut state = self.state.lock();
let actual_available = N - state.count;
if data_len > actual_available {
return Err(OuroBufferError::BufferOverflow);
}
let write_pos = state.write_pos;
let first_chunk = core::cmp::min(data_len, N - write_pos);
let (first, second) = data.split_at(first_chunk);
state.buffer[write_pos..write_pos + first_chunk].copy_from_slice(first);
state.buffer[..second.len()].copy_from_slice(second);
state.write_pos = (write_pos + data_len) % N;
state.count += data_len;
self.atomic_count.store(state.count, Ordering::Release);
Ok(())
}
pub fn pop(&self, output: &mut [u8]) -> usize {
let atomic_count = self.atomic_count.load(Ordering::Relaxed);
if atomic_count == 0 {
return 0;
}
let mut state = self.state.lock();
let to_read = core::cmp::min(output.len(), state.count);
if to_read == 0 {
return 0;
}
let read_pos = state.read_pos;
let first_chunk = core::cmp::min(to_read, N - read_pos);
let (first, second) = output[..to_read].split_at_mut(first_chunk);
first.copy_from_slice(&state.buffer[read_pos..read_pos + first_chunk]);
second.copy_from_slice(&state.buffer[..second.len()]);
state.read_pos = (read_pos + to_read) % N;
state.count -= to_read;
self.atomic_count.store(state.count, Ordering::Release);
to_read
}
pub fn len(&self) -> usize {
self.atomic_count.load(Ordering::Acquire)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn available_space(&self) -> usize {
N - self.len()
}
pub fn clear(&self) {
let mut state = self.state.lock();
state.write_pos = 0;
state.read_pos = 0;
state.count = 0;
state.buffer.iter_mut().for_each(|x| *x = 0);
self.atomic_count.store(0, Ordering::Release);
}
}
impl<const N: usize> Clone for OuroBuffer<N> {
fn clone(&self) -> Self {
let state = self.state.lock();
Self {
state: Mutex::new(BufferState {
buffer: state.buffer,
write_pos: state.write_pos,
read_pos: state.read_pos,
count: state.count,
}),
atomic_count: AtomicUsize::new(state.count),
}
}
}
#[cfg(feature = "heapless")]
impl<const N: usize> OuroBuffer<N> {
pub fn push_heapless<const V: usize>(
&self,
data: &heapless::Vec<u8, V>,
) -> Result<(), OuroBufferError> {
self.push(data.as_slice())
}
pub fn pop_heapless<const V: usize>(&self, count: usize) -> heapless::Vec<u8, V> {
let mut vec = heapless::Vec::new();
let mut temp = [0u8; N];
let to_read = count.min(V).min(N);
let read = self.pop(&mut temp[..to_read]);
let _ = vec.extend_from_slice(&temp[..read]);
vec
}
}
impl<const N: usize> fmt::Debug for OuroBuffer<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OuroBuffer<{}>[{}B used, {}B free]",
N,
self.len(),
self.available_space()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_operations() {
let buf = OuroBuffer::<8>::new();
assert_eq!(buf.len(), 0);
assert!(buf.push(&[1, 2, 3]).is_ok());
assert_eq!(buf.len(), 3);
let mut output = [0u8; 3];
assert_eq!(buf.pop(&mut output), 3);
assert_eq!(output, [1, 2, 3]);
assert!(buf.is_empty());
}
#[cfg(feature = "heapless")]
#[test]
fn heapless_integration() {
use heapless::Vec;
let buf = OuroBuffer::<16>::new();
let mut vec = Vec::<u8, 4>::new();
vec.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
buf.push_heapless(&vec).unwrap();
let result = buf.pop_heapless::<8>(4);
assert_eq!(result.as_slice(), &[0xDE, 0xAD, 0xBE, 0xEF]);
}
}