oca 0.2.0

An experiment with no_std
Documentation
pub trait Buffer {
    fn len(&self) -> usize;

    fn is_empty(&self) -> bool {
        self.len() == 0
    }

    fn as_slice(&self) -> &[u8];

    fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), BufferFull>;

    fn drain(&mut self, amount: usize);
}

#[derive(Debug, thiserror::Error)]
#[error("buffer is too small to hold the pending data")]
pub struct BufferFull;

#[derive(Debug)]
pub struct FixedCapacityBuffer<const N: usize> {
    data: [u8; N],
    len: usize,
}

impl<const N: usize> FixedCapacityBuffer<N> {
    pub fn new() -> Self {
        Self {
            data: [0; N],
            len: 0,
        }
    }
}

impl<const N: usize> Default for FixedCapacityBuffer<N> {
    fn default() -> Self {
        Self::new()
    }
}

impl<const N: usize> Buffer for FixedCapacityBuffer<N> {
    fn len(&self) -> usize {
        self.len
    }

    fn as_slice(&self) -> &[u8] {
        &self.data[..self.len]
    }

    fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), BufferFull> {
        let end = self
            .len
            .checked_add(bytes.len())
            .filter(|&end| end <= N)
            .ok_or(BufferFull)?;

        self.data[self.len..end].copy_from_slice(bytes);
        self.len = end;

        Ok(())
    }

    fn drain(&mut self, amount: usize) {
        self.data.copy_within(amount..self.len, 0);
        self.len -= amount;
    }
}

#[cfg(feature = "alloc")]
pub use alloc::DynamicBuffer;

#[cfg(feature = "alloc")]
mod alloc {
    use {super::*, ::alloc::vec::Vec};

    #[derive(Debug, Default)]
    pub struct DynamicBuffer {
        data: Vec<u8>,
    }

    impl DynamicBuffer {
        pub fn new() -> Self {
            Self::default()
        }

        pub fn with_capacity(capacity: usize) -> Self {
            Self {
                data: Vec::with_capacity(capacity),
            }
        }
    }

    impl Buffer for DynamicBuffer {
        fn len(&self) -> usize {
            self.data.len()
        }

        fn as_slice(&self) -> &[u8] {
            &self.data
        }

        fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), BufferFull> {
            self.data.extend_from_slice(bytes);
            Ok(())
        }

        fn drain(&mut self, amount: usize) {
            self.data.drain(..amount);
        }
    }
}

pub struct Bounded<B, const N: usize> {
    buffer: B,
}

impl<B, const N: usize> Bounded<B, N>
where
    B: Buffer,
{
    pub fn new(buffer: B) -> Self {
        Self { buffer }
    }
}

impl<B, const N: usize> Buffer for Bounded<B, N>
where
    B: Buffer,
{
    fn len(&self) -> usize {
        self.buffer.len()
    }

    fn as_slice(&self) -> &[u8] {
        self.buffer.as_slice()
    }

    fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), BufferFull> {
        if self.buffer.len() + bytes.len() > N {
            return Err(BufferFull);
        }
        self.buffer.extend_from_slice(bytes)
    }

    fn drain(&mut self, amount: usize) {
        self.buffer.drain(amount);
    }
}

#[cfg(test)]
mod tests {
    use {super::*, core::assert_matches};

    #[test]
    fn fixed_capacity_buffer_rejects_overflow() {
        let mut buffer = FixedCapacityBuffer::<4>::new();
        buffer.extend_from_slice(&[1, 2]).unwrap();
        assert_matches!(buffer.extend_from_slice(&[3, 4, 5]), Err(BufferFull));
        assert_eq!(buffer.as_slice(), &[1, 2]);
    }

    #[test]
    fn fixed_capacity_buffer_drains_from_the_front() {
        let mut buffer = FixedCapacityBuffer::<8>::new();
        buffer.extend_from_slice(&[1, 2, 3, 4]).unwrap();
        buffer.drain(2);
        assert_eq!(buffer.as_slice(), &[3, 4]);

        buffer.extend_from_slice(&[5, 6]).unwrap();
        assert_eq!(buffer.as_slice(), &[3, 4, 5, 6]);
    }

    #[cfg(feature = "alloc")]
    #[test]
    fn dynamic_buffer_grows_to_fit() {
        let mut buffer = DynamicBuffer::new();
        buffer.extend_from_slice(&[0; 1024]).unwrap();
        assert_eq!(buffer.len(), 1024);
    }
}