bitsong 0.1.3

Fast `#[no_std]` macro-based serialization/deserialization.
Documentation
#![no_std]
#![doc = include_str!("../README.md")]

use core::mem::MaybeUninit;

pub mod option;
pub mod maybe_unwritten_max_bytes;
pub mod magic;
pub mod result;
#[cfg(test)]
mod test;
pub use bitsong_macros::*;

/// Something that has a serializable size. Required for [ToSong] and [FromSong].
pub trait SongSize {
    /// Number of bytes that are written when serialized.
    fn song_size(self: &Self) -> usize;
}

/// Automatically implemented when [SongSize::song_size] is the same
/// for all instances of the impl of the type.
pub trait ConstSongSize {
    /// Same as [SongSize::song_size]
    const SONG_SIZE: usize;
}

pub trait ToSong: SongSize {
    /// Serialize `self`` into `buf`. To see the number of bytes written, use `[SongSize::song_size]`
    fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError>;
}

pub trait FromSong: SongSize {
    /// Deserialize from `buf` and return the deserialized struct and bytes read
    fn from_song(buf: &[u8]) -> Result<Self, FromSongError> where Self: Sized;
}

/// Used for generated enum discriminant types.
pub trait SongDiscriminant {
    type Discriminant: Copy;

    fn song_discriminant(&self) -> Self::Discriminant;
}

// Use some tricks to automatically implement ConstSongSize.
pub(crate) trait ConstSongSizeImpl {
    const SIZE: usize;
}

impl ConstSongSizeImpl for () {
    const SIZE: usize = 0;
}

impl <T: ConstSongSizeImpl> ConstSongSizeImpl for (T,) {
    const SIZE: usize = T::SIZE;
}

impl <A: ConstSongSizeImpl, B: ConstSongSizeImpl> ConstSongSizeImpl for (A, B) {
    const SIZE: usize = A::SIZE + B::SIZE;
}

pub struct ConstSongSizeValue<const SIZE: usize>;

impl <const SIZE: usize> ConstSongSizeImpl for ConstSongSizeValue<SIZE> {
    const SIZE: usize = SIZE;
}

#[allow(private_bounds)]
pub struct MultiplyConstSongSizeImpl<A: ConstSongSizeImpl, B: ConstSongSizeImpl>(A, B);
impl <A: ConstSongSizeImpl, B: ConstSongSizeImpl> ConstSongSizeImpl for MultiplyConstSongSizeImpl<A, B> {
    const SIZE: usize = A::SIZE * B::SIZE;
}

pub struct ConstSongSizeImplFromConstSongSize<T>(T);
impl <T: ConstSongSize> ConstSongSizeImpl for ConstSongSizeImplFromConstSongSize<T> {
    const SIZE: usize = T::SONG_SIZE;
}

pub trait HasSongSize {
    type Size;
}

impl <T: HasSongSize> ConstSongSize for T
where T::Size: ConstSongSizeImpl {
    const SONG_SIZE: usize = T::Size::SIZE;
}
// Now inside the macro we can write an impl for HasSongSize and the above will do the rest.

// Fill out as needed.
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum ToSongError {
    BufferOverflow,
    NotImplemented,
    StringTooLong
}

#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FromSongError {
    BufferOverflow,
    NotImplemented,
    InvalidPacketId,
    Utf8Error,
    InvalidValue
}

impl SongSize for &str {
    fn song_size(self: &Self) -> usize {
        2 + self.len()
    }
}

impl ToSong for &str {
    fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError> {
        if buf[2..(2 + self.len())].len() != self.len() {
            return Err(ToSongError::BufferOverflow)
        }
        self.len().to_song(buf)?;
        buf[2..(2 + self.len())].copy_from_slice(self.as_bytes());
        Ok(())
    }
}

// This is not implemented correctly
/*
impl <const N: usize> SongSize for heapless::String<N> {
    fn song_size(self: &Self) -> usize {
        N
    }
}

impl <const N: usize> ToSong for heapless::String<N> {
    fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError> {
        if buf.len() < N {
            return Err(ToSongError::BufferOverflow)
        }

        buf.copy_from_slice(self.as_bytes());
        Ok(())
    }
}

impl <const N: usize> FromSong for heapless::String<N> {
    fn from_song(buf: &[u8]) -> Result<Self, FromSongError> where Self: Sized {
        if buf.len() < N {
            return Err(FromSongError::BufferOverflow)
        }

        let vec = heapless::Vec::from_slice(&buf[0..N]).unwrap();
        Ok(Self::from_utf8(vec).map_err(|_| FromSongError::Utf8Error)?)
    }
}*/

// TODO -- How are we going to handle reading this out?
/*impl <'a> FromSong for &'a str {
    fn from_song(buf: &[u8]) -> Result<Self, FromSongError> where Self: Sized {
        let len = u16::from_song(buf)?;
        if len as usize + 2 > buf.len() {
            return Err(FromSongError::BufferOverflow)
        }
        let str = core::str::from_utf8(&buf[2..(len as usize)]).map_err(|e| FromSongError::Utf8Error)?;
        
        Ok(str)
    }
}*/

impl HasSongSize for bool {
    type Size = ConstSongSizeValue<1>;
}

impl SongSize for bool {
    fn song_size(self: &Self) -> usize {
        1
    }
}

impl ToSong for bool {
    fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError> {
        if buf.len() < 1 {
            return Err(ToSongError::BufferOverflow)
        }

        buf[0] = if *self { 1 } else { 0 };
        Ok(())
    }
}

impl FromSong for bool {
    fn from_song(buf: &[u8]) -> Result<Self, FromSongError> where Self: Sized {
        if buf.len() < 1 {
            return Err(FromSongError::BufferOverflow)
        }

        match buf[0] {
            0 => Ok(false),
            1 => Ok(true),
            _ => Err(FromSongError::InvalidValue)
        }
    }
}

macro_rules! numeric_impl {
    ($($ty: ty),*) => {
        $(
            impl HasSongSize for $ty {
                type Size = ConstSongSizeValue<{ core::mem::size_of::<$ty>() }>;
            }

            impl SongSize for $ty {
                #[inline]
                fn song_size(&self) -> usize {
                    core::mem::size_of::<$ty>()
                }
            }

            impl ToSong for $ty {
                fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError> {
                    if buf.len() < core::mem::size_of::<$ty>() {
                        return Err(ToSongError::BufferOverflow);
                    }

                    buf[0..core::mem::size_of::<$ty>()].copy_from_slice(&self.to_le_bytes());
                    Ok(())
                }
            }

            impl FromSong for $ty {
                fn from_song(buf: &[u8]) -> Result<Self, FromSongError> {
                    if buf.len() < core::mem::size_of::<$ty>() {
                        return Err(FromSongError::BufferOverflow);
                    }
                    let mut arr = [0u8; core::mem::size_of::<$ty>()];
                    arr.copy_from_slice(&buf[0..core::mem::size_of::<$ty>()]);
                    Ok(<$ty>::from_le_bytes(arr))
                }
            }
        )*
    };
}

numeric_impl!(
    u8,
    u16,
    u32,
    u64,
    u128,
    usize,
    i8,
    i16,
    i32,
    i64,
    i128,
    isize,
    f32,
    f64
);

impl <T: HasSongSize, const N: usize> HasSongSize for [T; N]
where T::Size: ConstSongSizeImpl {
    type Size = MultiplyConstSongSizeImpl<T::Size, ConstSongSizeValue<N>>;
}

impl <T: SongSize, const N: usize> SongSize for [T; N] {
    fn song_size(self: &Self) -> usize {
        let mut i = 0;
        for item in self {
            i += item.song_size();
        }
        i
    }
}

impl <T: SongSize + ToSong, const N: usize> ToSong for [T; N] {
    fn to_song(&self, buf: &mut [u8]) -> Result<(), ToSongError> {
        let mut i = 0;
        for item in self {
            item.to_song(&mut buf[i..])?;
            i += item.song_size();
        }
        Ok(())
    }
}

impl <T: SongSize + FromSong, const N: usize> FromSong for [T; N] {
    fn from_song(buf: &[u8]) -> Result<Self, FromSongError> where Self: Sized {
        let mut i = 0;
        let mut arr = [const { MaybeUninit::uninit() }; N];

        for j in 0..N {
            let item = T::from_song(&buf[i..])?;
            i += item.song_size();
            arr[j] = MaybeUninit::new(item);
        }

        unsafe {
            // https://github.com/rust-lang/rust/issues/61956
            let ptr = &mut arr as *mut _ as *mut [T; N];
            let res = ptr.read();
            core::mem::forget(arr);
            Ok(res)
        }
    }
}