wiredef

6 commits
Updated 2026-04-29 20:05:07
src
src/slice.rs
use crate::scalar::{ReadScalar, ScalarError, WriteScalar};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SliceError {
    Size(ScalarError),
    BufferTooSmall,
    Utf8,
}

pub trait WriteSlice: Sized {
    fn write_slice(&self, buf: &mut &mut [u8]) -> Result<(), SliceError> {
        self.write_slice_len(buf)?;
        self.write_slice_value(buf)
    }

    fn write_slice_len(&self, buf: &mut &mut [u8]) -> Result<(), SliceError>;
    fn write_slice_value(&self, buf: &mut &mut [u8]) -> Result<(), SliceError>;
}

pub trait ReadSlice<'a>: Sized {
    fn read_slice(buf: &mut &'a [u8]) -> Result<Self, SliceError> {
        let len = Self::read_slice_len(buf)?;

        Self::read_slice_value(buf, len)
    }

    fn read_slice_len(buf: &mut &'a [u8]) -> Result<usize, SliceError>;
    fn read_slice_value(buf: &mut &'a [u8], len: usize) -> Result<Self, SliceError>;
}

fn write_and_advance(buf: &mut &mut [u8], payload: &[u8]) -> Result<(), SliceError> {
    if payload.len() > buf.len() {
        return Err(SliceError::BufferTooSmall);
    }

    let (to_edit, remain) = core::mem::take(buf).split_at_mut(payload.len());

    to_edit.copy_from_slice(payload);
    *buf = remain;

    Ok(())
}

fn read_and_advance<'a>(buf: &mut &'a [u8], len: usize) -> Result<&'a [u8], SliceError> {
    if len > buf.len() {
        return Err(SliceError::BufferTooSmall);
    }

    let (ret, remain) = buf.split_at(len);
    *buf = remain;

    Ok(ret)
}

impl WriteSlice for &[u8] {
    fn write_slice_len(&self, buf: &mut &mut [u8]) -> Result<(), SliceError> {
        self.len().write_scalar(buf).map_err(SliceError::Size)
    }

    fn write_slice_value(&self, buf: &mut &mut [u8]) -> Result<(), SliceError> {
        write_and_advance(buf, self)
    }
}

impl<'a> ReadSlice<'a> for &'a [u8] {
    fn read_slice_len(buf: &mut &'a [u8]) -> Result<usize, SliceError> {
        usize::read_scalar(buf).map_err(SliceError::Size)
    }

    fn read_slice_value(buf: &mut &'a [u8], len: usize) -> Result<Self, SliceError> {
        read_and_advance(buf, len)
    }
}

impl WriteSlice for &str {
    fn write_slice_len(&self, buf: &mut &mut [u8]) -> Result<(), SliceError> {
        self.as_bytes().write_slice_len(buf)
    }

    fn write_slice_value(&self, buf: &mut &mut [u8]) -> Result<(), SliceError> {
        self.as_bytes().write_slice_value(buf)
    }
}

impl<'a> ReadSlice<'a> for &'a str {
    fn read_slice_len(buf: &mut &'a [u8]) -> Result<usize, SliceError> {
        <&[u8]>::read_slice_len(buf)
    }

    fn read_slice_value(buf: &mut &'a [u8], len: usize) -> Result<Self, SliceError> {
        core::str::from_utf8(<&[u8]>::read_slice_value(buf, len)?).map_err(|_| SliceError::Utf8)
    }
}

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

    macro_rules! test_slice_roundtrip {
        ($($name:ident: $ty:ty = $value:expr),+ $(,)?) => {
            $(
                #[test]
                fn $name() {
                    let mut buffer = [0u8; 32];
                    let mut buf = &mut buffer[..];

                    let value: $ty = $value;
                    value.write_slice(&mut buf).unwrap();

                    let mut read_buf = &buffer[..];
                    let result = <$ty>::read_slice(&mut read_buf).unwrap();
                    assert_eq!(result, value);
                }
            )+
        };
    }

    test_slice_roundtrip! {
        test_slice: &[u8] =  &[10, 20, 30, 40, 50],
        test_slice_empty: &[u8] =  &[],

        test_str: &str = "Hello, World!",
        test_str_empty: &str = "",
        test_str_unicode: &str = "Hëllö 世界 🦀",
    }

    #[test]
    fn test_str_invalid_utf8() {
        let mut buffer = [0u8; 20];
        buffer[0..8].copy_from_slice(&3usize.to_be_bytes());
        buffer[9] = 0xFF;
        buffer[10] = 0xFF;
        buffer[11] = 0xFF;

        let mut read_buf = &buffer[..];
        let result = <&str>::read_slice(&mut read_buf);
        assert!(matches!(result, Err(SliceError::Utf8)));
    }
}