tbytes 0.1.0-alpha1

A tiny library for reading and writing typed data into buffers
Documentation
//! # Bytes reader.
//!
//! [`TBytesReader`] is designed to read primitive types and fixed arrays of primitive types from
//! a provided [`TBytesReaderBackend`].
#![warn(missing_docs)]
#![deny(rustdoc::broken_intra_doc_links)]

use core::cell::Cell;

use super::errors::TBytesError;

/// Reads primitive types and arrays of primitive types from [`TBytesReaderBackend`].
#[derive(Clone, Debug)]
pub struct TBytesReader<B: TBytesReaderBackend> {
    backend: B,
}

impl<B: TBytesReaderBackend> TBytesReader<B> {
    /// Default constructor.
    pub fn new(backend: B) -> Self {
        Self { backend }
    }
}

/// Type-aware read interface for [`TBytesReader`].
pub trait TBytesReaderFor<T> {
    /// Reads value of type `T`.
    fn read(&self) -> Result<T, TBytesError>;
    /// Reads array of values of type `T`.
    ///
    /// # Arguments
    ///
    /// Array length is specified as a generic parameter `N`.
    fn read_array<const N: usize>(&self) -> Result<[T; N], TBytesError>;
}

/// Implements [`TBytesReaderFor`] for a specific type.
macro_rules! t_bytes_reader_for {
    ($type_: ident) => {
        impl<B: TBytesReaderBackend> TBytesReaderFor<$type_> for TBytesReader<B> {
            fn read(&self) -> Result<$type_, TBytesError> {
                const SIZE: usize = core::mem::size_of::<$type_>();
                let mut bytes = [0u8; SIZE];
                bytes.copy_from_slice(self.backend.read(SIZE)?);

                Ok($type_::from_le_bytes(bytes))
            }

            fn read_array<const N: usize>(&self) -> Result<[$type_; N], TBytesError> {
                const SIZE: usize = core::mem::size_of::<$type_>();
                let mut result: [$type_; N] = [0 as $type_; N];

                for i in 0..N {
                    let mut bytes = [0u8; SIZE];
                    bytes.copy_from_slice(self.backend.read(SIZE)?);
                    result[i] = $type_::from_le_bytes(bytes);
                }

                Ok(result)
            }
        }
    };
}

// Implement bytes reader for primitive types
t_bytes_reader_for!(u8);
t_bytes_reader_for!(u16);
t_bytes_reader_for!(u32);
t_bytes_reader_for!(u64);
t_bytes_reader_for!(u128);
t_bytes_reader_for!(i8);
t_bytes_reader_for!(i16);
t_bytes_reader_for!(i32);
t_bytes_reader_for!(i64);
t_bytes_reader_for!(i128);
t_bytes_reader_for!(f32);
t_bytes_reader_for!(f64);

/// Backend for [`TBytesReader`].
pub trait TBytesReaderBackend: Sized {
    /// Reads a sequence of bytes from buffer.
    fn read(&self, num_bytes: usize) -> Result<&[u8], TBytesError>;
}

/// Backend for [`TBytesReader`] which operates on read-only slices of bytes.
///
/// > This is not a thread-safe implementation!
#[derive(Clone, Debug)]
pub struct TBytesReaderSliceBackend<'a> {
    buffer: &'a [u8],
    pos: Cell<usize>,
}

impl<'a> TBytesReaderSliceBackend<'a> {
    /// Default constructor.
    pub fn new(buffer: &'a [u8]) -> Self {
        Self {
            buffer,
            pos: Default::default(),
        }
    }

    /// Current cursor position.
    pub fn pos(&self) -> usize {
        self.pos.get()
    }
}

impl<'a> TBytesReaderBackend for TBytesReaderSliceBackend<'a> {
    /// Reads `num_bytes` from slice.
    fn read(&self, num_bytes: usize) -> Result<&[u8], TBytesError> {
        let pos = self.pos.get();

        if self.buffer.len() < pos + num_bytes {
            return Err(TBytesError::OutOfBounds);
        }

        let bytes = &self.buffer[pos..pos + num_bytes];
        self.pos.replace(pos + num_bytes);

        Ok(bytes)
    }
}

impl<'a> From<&'a [u8]> for TBytesReader<TBytesReaderSliceBackend<'a>> {
    /// Constructs [`TBytesReader`] from provided slice.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use tbytes::{TBytesReader, TBytesReaderFor};
    ///
    /// let buffer = [255, 1];
    /// let reader = TBytesReader::from(buffer.as_slice());
    ///
    /// let val: u16 = reader.read().unwrap();
    /// assert_eq!(val, 511);
    /// ```
    fn from(value: &'a [u8]) -> Self {
        TBytesReader::new(TBytesReaderSliceBackend::new(value))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn slice_backend() {
        let buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9u8];
        let backend = TBytesReaderSliceBackend::new(&buffer);

        assert_eq!(backend.read(2).unwrap(), &[0, 1u8]);
        assert_eq!(backend.read(4).unwrap(), &[2, 3, 4, 5u8]);
        assert_eq!(backend.read(4).unwrap(), &[6, 7, 8, 9u8]);

        let eof = backend.read(1);
        assert!(eof.is_err());
        assert!(matches!(eof, Err(TBytesError::OutOfBounds)));
    }

    #[test]
    fn slice_backend_out_of_bouts_is_idempotent() {
        let buffer = [42; 10];
        let backend = TBytesReaderSliceBackend::new(&buffer);

        assert_eq!(backend.read(10).unwrap(), &[42; 10]);

        let eof = backend.read(1);
        assert!(matches!(eof, Err(TBytesError::OutOfBounds)));
        let eof = backend.read(1);
        assert!(matches!(eof, Err(TBytesError::OutOfBounds)));

        assert_eq!(backend.pos(), 10);
    }

    #[test]
    fn from_slice() {
        let buffer = [128, 255, 1, 1u8, 1, 255];
        let reader = TBytesReader::from(buffer.as_slice());

        let val: u8 = reader.read().unwrap();
        assert_eq!(val, 128);

        let val: i8 = reader.read().unwrap();
        assert_eq!(val, -1);

        let val: u16 = reader.read().unwrap();
        assert_eq!(val, 257);

        let val: [u8; 2] = reader.read_array().unwrap();
        assert_eq!(val, [1, 255]);
    }

    #[test]
    fn bytes_reader_for() {
        let buffer = [128, 255, 1, 1u8, 1, 255];
        let backend = TBytesReaderSliceBackend::new(&buffer);
        let reader = TBytesReader::new(backend);

        let val: u8 = reader.read().unwrap();
        assert_eq!(val, 128);

        let val: i8 = reader.read().unwrap();
        assert_eq!(val, -1);

        let val: u16 = reader.read().unwrap();
        assert_eq!(val, 257);

        let val: [u8; 2] = reader.read_array().unwrap();
        assert_eq!(val, [1, 255]);
    }

    #[test]
    fn arrays() {
        let buffer = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9u8];
        let backend = TBytesReaderSliceBackend::new(&buffer);
        let reader = &TBytesReader::new(backend);

        let result: Result<[u8; 8], TBytesError> = reader.read_array();
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), [0, 1, 2, 3, 4, 5, 6, 7u8]);

        let result: Result<[u8; 4], TBytesError> = reader.read_array();
        assert!(result.is_err());
    }
}