diff options
Diffstat (limited to 'bzipper/src')
-rw-r--r-- | bzipper/src/deserialise/mod.rs | 245 | ||||
-rw-r--r-- | bzipper/src/deserialise/test.rs | 118 | ||||
-rw-r--r-- | bzipper/src/deserialise/tuple.rs | 298 | ||||
-rw-r--r-- | bzipper/src/dstream/mod.rs | 58 | ||||
-rw-r--r-- | bzipper/src/error/mod.rs | 126 | ||||
-rw-r--r-- | bzipper/src/fixed_iter/mod.rs | 46 | ||||
-rw-r--r-- | bzipper/src/fixed_string/mod.rs | 448 | ||||
-rw-r--r-- | bzipper/src/fixed_string/test.rs | 43 | ||||
-rw-r--r-- | bzipper/src/lib.rs | 202 | ||||
-rw-r--r-- | bzipper/src/serialise/mod.rs | 300 | ||||
-rw-r--r-- | bzipper/src/serialise/test.rs | 104 | ||||
-rw-r--r-- | bzipper/src/serialise/tuple.rs | 424 | ||||
-rw-r--r-- | bzipper/src/sstream/mod.rs | 58 |
13 files changed, 2470 insertions, 0 deletions
diff --git a/bzipper/src/deserialise/mod.rs b/bzipper/src/deserialise/mod.rs new file mode 100644 index 0000000..49ca74e --- /dev/null +++ b/bzipper/src/deserialise/mod.rs @@ -0,0 +1,245 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +#[cfg(test)] +mod test; + +use crate::{Dstream, Error, Result, Serialise}; + +use core::convert::Infallible; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::num::NonZero; + +mod tuple; + +/// Types capable of being deserialised. +/// +/// This trait requires [`Serialise`] also being implemented as it relies on the [`SERIALISED_SIZE`](crate::Serialise::SERIALISED_SIZE) constant. +pub trait Deserialise: Serialise + Sized { + /// Deserialises a slice into an object. + /// + /// This function must **never** take more bytes than specified by [`SERIALISED_SIZE`](crate::Serialise::SERIALISED_SIZE). + /// Doing so is considered a logic error. + /// Likewise, providing more than this amount is also disfavoured. + /// + /// # Errors + /// + /// If deserialisation failed, e.g. by an illegal byte being found, an error is returned. + /// + /// # Panics + /// + /// This method will usually panic if the provided slice has a length *less* than the value of `SERIALISED_SIZE`. + /// Official implementations of this trait (including those that are derived) always panic in debug mode if the provided slice has a length that is different at all. + fn deserialise(data: &[u8]) -> Result<Self>; +} + +macro_rules! impl_numeric { + ($ty:ty) => { + impl ::bzipper::Deserialise for $ty { + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + const SIZE: usize = ::core::mem::size_of::<$ty>(); + + let data = data + .get(0x0..SIZE) + .ok_or(::bzipper::Error::EndOfStream { req: SIZE, rem: data.len() })? + .try_into() + .unwrap(); + + Ok(Self::from_be_bytes(data)) + } + } + }; +} + +macro_rules! impl_non_zero { + ($ty:ty) => { + impl ::bzipper::Deserialise for NonZero<$ty> { + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + let value = <$ty as ::bzipper::Deserialise>::deserialise(data)?; + + NonZero::new(value) + .ok_or(Error::NullInteger) + } + } + }; +} + +impl<T, const N: usize> Deserialise for [T; N] +where + T: Deserialise { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + // Initialise the array incrementally. + + let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut pos = 0x0; + + for item in &mut buf { + let range = pos..pos + T::SERIALISED_SIZE; + + pos = range.end; + item.write(Deserialise::deserialise(&data[range])?); + } + + // This should be safe as `MaybeUninit<T>` is + // transparent to `T`, and we have initialised + // every element. The original buffer is NOT + // dropped automatically, so we can just forget + // about it from this point on. `transmute` cannot + // be used here, and `transmute_unchecked` is re- + // served for the greedy rustc devs. + let value: [T; N] = unsafe { buf.as_ptr().cast::<[T; N]>().read() }; + Ok(value) + } +} + +impl Deserialise for bool { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let value = u8::deserialise(data)?; + + match value { + 0x00 => Ok(false), + 0x01 => Ok(true), + _ => Err(Error::InvalidBoolean { value }) + } + } +} + +impl Deserialise for char { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let value = u32::deserialise(data)?; + + Self::from_u32(value) + .ok_or(Error::InvalidCodePoint { value }) + } +} + +impl Deserialise for Infallible { + #[allow(clippy::panic_in_result_fn)] + #[inline(always)] + fn deserialise(_data: &[u8]) -> Result<Self> { panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with") } +} + +impl Deserialise for isize { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let value = i32::deserialise(data)? + .try_into().expect("unable to convert from `i32` to `isize`"); + + Ok(value) + } +} + +impl<T: Deserialise> Deserialise for Option<T> { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let stream = Dstream::new(data); + + let sign = stream.take::<bool>()?; + + if sign { + Ok(Some(stream.take::<T>()?)) + } else { + Ok(None) + } + } +} + +impl<T> Deserialise for PhantomData<T> { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(Self) + } +} + +impl<T: Deserialise, E: Deserialise> Deserialise for core::result::Result<T, E> { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let stream = Dstream::new(data); + + let sign = stream.take::<bool>()?; + + let value = if sign { + Err(stream.take::<E>()?) + } else { + Ok(stream.take::<T>()?) + }; + + Ok(value) + } +} + +impl Deserialise for () { + fn deserialise(_data: &[u8]) -> Result<Self> { Ok(()) } +} + +impl Deserialise for usize { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + let value = u32::deserialise(data)? + .try_into().expect("unable to convert from `u32` to `usize`"); + + Ok(value) + } +} + +//impl_numeric!(f128); +//impl_numeric!(f16); +impl_numeric!(f32); +impl_numeric!(f64); +impl_numeric!(i128); +impl_numeric!(i16); +impl_numeric!(i32); +impl_numeric!(i64); +impl_numeric!(i8); +impl_numeric!(u128); +impl_numeric!(u16); +impl_numeric!(u32); +impl_numeric!(u64); +impl_numeric!(u8); + +impl_non_zero!(i128); +impl_non_zero!(i16); +impl_non_zero!(i32); +impl_non_zero!(i64); +impl_non_zero!(i8); +impl_non_zero!(isize); +impl_non_zero!(u128); +impl_non_zero!(u16); +impl_non_zero!(u32); +impl_non_zero!(u64); +impl_non_zero!(u8); +impl_non_zero!(usize); diff --git a/bzipper/src/deserialise/test.rs b/bzipper/src/deserialise/test.rs new file mode 100644 index 0000000..f593978 --- /dev/null +++ b/bzipper/src/deserialise/test.rs @@ -0,0 +1,118 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Deserialise, Serialise}; + +#[test] +fn test() { + #[derive(Debug, Deserialise, PartialEq, Serialise)] + struct ProcExit { + exit_code: i32, + timestmap: u64, + } + + #[derive(Debug, Deserialise, PartialEq, Serialise)] + struct NewByte(u8); + + #[derive(Debug, Deserialise, PartialEq, Serialise)] + struct Unit; + + #[derive(Debug, Deserialise, PartialEq, Serialise)] + enum UnitOrFields { + Unit(Unit), + Unnamed(i32), + Named { timestamp: u64 }, + } + + macro_rules! test { + ($ty:ty: $data:expr => $value:expr) => {{ + use ::bzipper::{Deserialise, Serialise}; + + let buf: [u8; <$ty as Serialise>::SERIALISED_SIZE] = $data; + + let left = <$ty as Deserialise>::deserialise(&buf).unwrap(); + let right = $value; + + assert_eq!(left, right); + }}; + } + + test!(i8: [0x00] => 0x00); + test!(i8: [0x7F] => 0x7F); + test!(i8: [0x80] => -0x80); + test!(i8: [0xFF] => -0x01); + + test!(i16: [0x00, 0x00] => 0x0000); + test!(i16: [0x7F, 0xFF] => 0x7FFF); + test!(i16: [0x80, 0x00] => -0x8000); + test!(i16: [0xFF, 0xFF] => -0x0001); + + test!(i32: [0x00, 0x00, 0x00, 0x00] => 0x00000000); + test!(i32: [0x7F, 0xFF, 0xFF, 0xFF] => 0x7FFFFFFF); + test!(i32: [0x80, 0x00, 0x00, 0x00] => -0x80000000); + test!(i32: [0xFF, 0xFF, 0xFF, 0xFF] => -0x00000001); + + test!(i64: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => 0x0000000000000000); + test!(i64: [0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] => 0x7FFFFFFFFFFFFFFF); + test!(i64: [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] => -0x8000000000000000); + test!(i64: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] => -0x0000000000000001); + + test!(u128: [ + 0xFF, 0x0F, 0xEF, 0x1F, 0xDF, 0x2F, 0xCF, 0x3F, + 0xBF, 0x4F, 0xAF, 0x5F, 0x9F, 0x6F, 0x8F, 0x7F, + ] => 0xFF_0F_EF_1F_DF_2F_CF_3F_BF_4F_AF_5F_9F_6F_8F_7F); + + test!([char; 0x5]: [ + 0x00, 0x00, 0x03, 0xBB, 0x00, 0x00, 0x03, 0x91, + 0x00, 0x00, 0x03, 0xBC, 0x00, 0x00, 0x03, 0x94, + 0x00, 0x00, 0x03, 0xB1 + ] => ['\u{03BB}', '\u{0391}', '\u{03BC}', '\u{0394}', '\u{03B1}']); + + test!(Option<()>: [0x00] => None); + test!(Option<()>: [0x01] => Some(())); + + test!(Result<(), i8>: [0x00, 0x00] => Ok(())); + test!(Result<(), i8>: [0x01, 0x7F] => Err(i8::MAX)); + + test!(ProcExit: [ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x5E, 0x0B, 0xE1, 0x00, + ] => ProcExit { exit_code: 0x1, timestmap: 1577836800 }); + + test!(NewByte: [0x80] => NewByte(0x80)); + + test!(Unit: [] => Unit); + + test!(UnitOrFields: [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ] => UnitOrFields::Unit(Unit)); + + test!(UnitOrFields: [ + 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + ] => UnitOrFields::Unnamed(-0x1)); + + test!(UnitOrFields: [ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x66, 0xC5, 0xC8, 0x4C, + ] => UnitOrFields::Named { timestamp: 1724237900 }); +} diff --git a/bzipper/src/deserialise/tuple.rs b/bzipper/src/deserialise/tuple.rs new file mode 100644 index 0000000..b1f7ac1 --- /dev/null +++ b/bzipper/src/deserialise/tuple.rs @@ -0,0 +1,298 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Deserialise, Result, Serialise}; + +impl<T0> Deserialise for (T0, ) +where + T0: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1> Deserialise for (T0, T1) +where + T0: Deserialise, + T1: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2> Deserialise for (T0, T1, T2) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3> Deserialise for (T0, T1, T2, T3) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4> Deserialise for (T0, T1, T2, T3, T4) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5> Deserialise for (T0, T1, T2, T3, T4, T5) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6> Deserialise for (T0, T1, T2, T3, T4, T5, T6) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, + T7: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, + T7: Deserialise, + T8: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, + T7: Deserialise, + T8: Deserialise, + T9: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, + T7: Deserialise, + T8: Deserialise, + T9: Deserialise, + T10: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T0: Deserialise, + T1: Deserialise, + T2: Deserialise, + T3: Deserialise, + T4: Deserialise, + T5: Deserialise, + T6: Deserialise, + T7: Deserialise, + T8: Deserialise, + T9: Deserialise, + T10: Deserialise, + T11: Deserialise, { + fn deserialise(data: &[u8]) -> Result<Self> { + debug_assert_eq!(data.len(), Self::SERIALISED_SIZE); + + Ok(( + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + Deserialise::deserialise(data)?, + )) + } +} diff --git a/bzipper/src/dstream/mod.rs b/bzipper/src/dstream/mod.rs new file mode 100644 index 0000000..e87edf8 --- /dev/null +++ b/bzipper/src/dstream/mod.rs @@ -0,0 +1,58 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Deserialise, Error, Result}; + +use core::cell::Cell; + +/// Byte stream for deserialisation. +/// +/// This type borrows a slice, keeping track internally of the used bytes. +pub struct Dstream<'a> { + data: &'a [u8], + pos: Cell<usize>, +} + +impl<'a> Dstream<'a> { + /// Constructs a new byte stream. + #[inline(always)] + #[must_use] + pub const fn new(data: &'a [u8]) -> Self { Self { data, pos: Cell::new(0x0) } } + + /// Deserialises an object from the stream. + /// + /// # Errors + /// + /// If the stream doesn't hold at least the amount of bytes specified by [`SERIALISED_SIZE`](crate::Serialise::SERIALISED_SIZE), an [`EndOfStream`](Error::EndOfStream) error is returned. + #[inline] + pub fn take<T: Deserialise>(&self) -> Result<T> { + let rem = self.data.len() - self.pos.get(); + let req = T::SERIALISED_SIZE; + + if rem < req { return Err(Error::EndOfStream { req, rem }) }; + + let start = self.pos.get(); + let stop = start + req; + + self.pos.set(stop); + T::deserialise(&self.data[start..stop]) + } +} diff --git a/bzipper/src/error/mod.rs b/bzipper/src/error/mod.rs new file mode 100644 index 0000000..090215a --- /dev/null +++ b/bzipper/src/error/mod.rs @@ -0,0 +1,126 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use core::fmt::{Display, Formatter}; +use core::str::Utf8Error; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Mapping of [`core::result::Result`]. +pub type Result<T> = core::result::Result<T, Error>; + +/// (De)serialisation failures. +/// +/// These variants are used when deserialisation fails. +/// Serialisations are assumed infallible. +#[derive(Debug)] +pub enum Error { + /// An array could not hold the requested amount of elements. + ArrayTooShort { req: usize, len: usize }, + + /// A string encountered an invalid UTF-8 sequence. + BadString { source: Utf8Error }, + + /// An implementor-defined error. + /// + /// This is mainly useful if none of the predefined errors are appropriate. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + CustomError { source: Box<dyn core::error::Error> }, + + /// Bytes were requested on an empty stream. + EndOfStream { req: usize, rem: usize }, + + /// A boolean encountered a value outside `0` and `1`. + InvalidBoolean { value: u8 }, + + /// An invalid code point was encountered. + /// + /// This includes surrogate points in the inclusive range `U+D800` to `U+DFFF`, as well as values larger than `U+10FFFF`. + InvalidCodePoint { value: u32 }, + + /// An invalid enumeration descriminant was provided. + InvalidDiscriminant { value: u32 }, + + /// An `isize` value couldn't fit into `16` bits. + IsizeOutOfRange { value: isize }, + + /// A non-zero integer encountered the value `0`. + NullInteger, + + /// A `usize` value couldn't fit into `16` bits. + UsizeOutOfRange { value: usize }, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + use Error::*; + + match *self { + ArrayTooShort { req, len } + => write!(f, "array of ({len}) element(s) cannot hold ({req})"), + + BadString { ref source } + => write!(f, "unable to parse utf8: \"{source}\""), + + #[cfg(feature = "alloc")] + CustomError { ref source } + => write!(f, "{source}"), + + EndOfStream { req, rem } + => write!(f, "({req}) byte(s) were requested but only ({rem}) byte(s) were left"), + + InvalidBoolean { value } + => write!(f, "expected boolean but got {value:#02X}"), + + InvalidCodePoint { value } + => write!(f, "code point U+{value:04X} is not valid"), + + InvalidDiscriminant { value } + => write!(f, "discriminant ({value}) is not valid for the given enumeration"), + + IsizeOutOfRange { value } + => write!(f, "signed size value ({value}) cannot be serialised: must be in the range ({}) to ({})", i16::MIN, i16::MAX), + + NullInteger + => write!(f, "expected non-zero integer but got (0)"), + + UsizeOutOfRange { value } + => write!(f, "unsigned size value ({value}) cannot be serialised: must be at most ({})", u16::MAX), + } + } +} + +impl core::error::Error for Error { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + use Error::*; + + match *self { + BadString { ref source } => Some(source), + + #[cfg(feature = "alloc")] + CustomError { ref source } => Some(source.as_ref()), + + _ => None, + } + } +} diff --git a/bzipper/src/fixed_iter/mod.rs b/bzipper/src/fixed_iter/mod.rs new file mode 100644 index 0000000..cc2110d --- /dev/null +++ b/bzipper/src/fixed_iter/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use core::mem::MaybeUninit; + +/// Iterator to a fixed vector. +/// +/// This type is used by the [`FixedString`](crate::FixedString) type for iterating over an owned string. +#[must_use] +pub struct FixedIter<T, const N: usize> { + pub(in crate) buf: [MaybeUninit<T>; N], + + pub(in crate) pos: usize, + pub(in crate) len: usize, +} + +impl<T, const N: usize> Iterator for FixedIter<T, N> { + type Item = T; + + fn next(&mut self) -> Option<Self::Item> { + if self.pos >= self.len { return None }; + + let item = unsafe { self.buf[self.pos].assume_init_read() }; + self.pos += 0x1; + + Some(item) + } +} diff --git a/bzipper/src/fixed_string/mod.rs b/bzipper/src/fixed_string/mod.rs new file mode 100644 index 0000000..5b40a87 --- /dev/null +++ b/bzipper/src/fixed_string/mod.rs @@ -0,0 +1,448 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +#[cfg(test)] +mod test; + +use crate::{Deserialise, Error, FixedIter, Serialise}; + +use core::cmp::Ordering; +use core::fmt::{Debug, Display, Formatter}; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut, Index, IndexMut}; +use core::slice::SliceIndex; +use core::str::FromStr; + +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; + +/// Owned string with maximum size. +/// +/// This is in contrast to [String] -- which has no size limit in practice -- and [str], which is unsized. +/// +/// # Examples +/// +/// All instances of this type have the same size if the value of `N` is also the same. +/// This size can be found through +/// +/// `size_of::<char>() * N + size_of::<usize>()`. +/// +/// Therefore, the following four strings have -- despite their different contents -- the same total size. +/// +/// ``` +/// use bzipper::FixedString; +/// use std::str::FromStr; +/// +/// let str0 = FixedString::<0xF>::new(); // Empty string. +/// let str1 = FixedString::<0xF>::from_str("Hello there!"); +/// let str2 = FixedString::<0xF>::from_str("أنا من أوروپا"); +/// let str3 = FixedString::<0xF>::from_str("COGITO ERGO SUM"); +/// +/// assert_eq!(size_of_val(&str0), size_of_val(&str1)); +/// assert_eq!(size_of_val(&str0), size_of_val(&str2)); +/// assert_eq!(size_of_val(&str0), size_of_val(&str3)); +/// assert_eq!(size_of_val(&str1), size_of_val(&str2)); +/// assert_eq!(size_of_val(&str1), size_of_val(&str3)); +/// assert_eq!(size_of_val(&str2), size_of_val(&str3)); +/// ``` +/// +/// These three strings can---by extend in theory---also interchange their contents between each other. +#[derive(Clone, Deserialise, Serialise)] +pub struct FixedString<const N: usize> { + buf: [char; N], + len: usize, +} + +impl<const N: usize> FixedString<N> { + /// Constructs a new, fixed-size string. + /// + /// Note that it is only the internal buffer that is size-constrained. + /// The string internally keeps track of the amount of used characters and acts accordingly. + /// One must therefore only see the value of `N` as a size *limit*. + /// + /// The constructed string will have a null length. + /// All characters inside the internal buffer are instanced as `U+0000 NULL`. + /// + /// For constructing a string with an already defined buffer, see [`from_chars`](Self::from_chars) and [`from_raw_parts`](Self::from_raw_parts). + #[inline(always)] + #[must_use] + pub const fn new() -> Self { Self { buf: ['\0'; N], len: 0x0 } } + + /// Consumes the buffer into a fixed string. + /// + /// The internal length is to `N`. + /// For a similar function but with an explicit size, see [`from_raw_parts`](Self::from_raw_parts). + #[inline(always)] + #[must_use] + pub const fn from_chars(buf: [char; N]) -> Self { Self { buf, len: N } } + + /// Constructs a fixed string from raw parts. + #[inline(always)] + #[must_use] + pub const fn from_raw_parts(buf: [char; N], len: usize) -> Self { Self { buf, len } } + + /// Deconstructs a fixed string into its raw parts. + #[inline(always)] + #[must_use] + pub const fn into_raw_parts(self) -> ([char; N], usize) { (self.buf, self.len) } + + /// Gets a pointer to the first character. + #[inline(always)] + #[must_use] + pub const fn as_ptr(&self) -> *const char { self.buf.as_ptr() } + + /// Gets a mutable pointer to the first character. + /// + /// This function can only be marked as `const` when `const_mut_refs` is implemented. + /// See tracking issue [`#57349`](https://github.com/rust-lang/rust/issues/57349/) for more information. + #[inline(always)] + #[must_use] + pub fn as_mut_ptr(&mut self) -> *mut char { self.buf.as_mut_ptr() } + + /// Borrows the string as a character slice. + /// + /// The range of the returned slice only includes characters that are "used." + /// For borrowing the entire internal buffer, see [`as_mut_slice`](Self::as_mut_slice). + #[inline(always)] + #[must_use] + pub const fn as_slice(&self) -> &[char] { + // We need to use `from_raw_parts` to mark this + // function `const`. + + unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } + } + + /// Mutably borrows the string as a character slice. + /// + /// The range of the returned slice includes the entire internal buffer. + /// For borrowing only the "used" characters, see [`as_slice`](Self::as_slice). + #[inline(always)] + #[must_use] + pub fn as_mut_slice(&mut self) -> &mut [char] { &mut self.buf[0x0..self.len] } + + /// Returns the length of the string. + /// + /// This does not necessarily equate to the value of `N`, as the internal buffer may be used but partially. + #[inline(always)] + #[must_use] + pub const fn len(&self) -> usize { self.len } + + /// Checks if the string is empty, i.e. `self.len() == 0x0`. + #[inline(always)] + #[must_use] + pub const fn is_empty(&self) -> bool { self.len() == 0x0 } + + /// Checks if the string is full, i.e. `self.len() == N`. + #[inline(always)] + #[must_use] + pub const fn is_full(&self) -> bool { self.len() == N } + + /// Sets the internal length. + /// + /// The length is compared with `N` to guarantee that bounds are honoured. + /// + /// # Panics + /// + /// This method panics if the value of `len` is greater than that of `N`. + #[inline(always)] + pub fn set_len(&mut self, len: usize) { + assert!(self.len <= N, "cannot set length longer than the fixed size"); + self.len = len; + } + + /// Borrows characters at the specified index. + /// + /// If no element can be retrieved using the given index, [`None`] is returned instead. + #[inline(always)] + #[must_use] + pub fn get<I: SliceIndex<[char]>>(&self, index: I) -> Option<&I::Output> { self.buf.get(index) } + + /// Borrows characters at the specified index *without* checking bounds. + /// + /// For performing a similar operation *with* bounds checks, see [`get`](Self::get). + /// + /// # Safety + /// + /// If the given index points out of the bounds of the string, behaviour is undefined. + #[inline(always)] + #[must_use] + pub unsafe fn get_unchecked<I: SliceIndex<[char]>>(&self, index: I) -> &I::Output { self.buf.get_unchecked(index) } + + /// Mutably borrows characters at the specified index. + /// + /// If no element can be retrieved using the given index, [`None`] is returned instead. + #[inline(always)] + #[must_use] + pub fn get_mut<I: SliceIndex<[char]>>(&mut self, index: I) -> Option<&mut I::Output> { self.buf.get_mut(index) } + + /// Mutably borrows characters at the specified index *without* checking bounds. + /// + /// For performing a similar operation *with* bounds checks, see [`get_mut`](Self::get_mut) + /// + /// # Safety + /// + /// If the given index points out of the bounds of the string, behaviour is undefined. + #[inline(always)] + #[must_use] + pub unsafe fn get_unchecked_mut<I: SliceIndex<[char]>>(&mut self, index: I) -> &I::Output { self.buf.get_unchecked_mut(index) } + + /// Pushes a character into the string. + /// + /// The internal length is updated accordingly. + /// + /// # Panics + /// + /// If the string cannot hold any more character (i.e. it is full), this method will panic. + #[inline(always)] + pub fn push(&mut self, c: char) { + assert!(!self.is_full(), "cannot push character to full string"); + + self.buf[self.len] = c; + self.len += 0x1; + } + + /// Pops a character from the string. + /// + /// The internal length is updated accordingly. + /// + /// If no characters are left (i.e. the string is empty), an instance of [`None`] is returned. + #[inline(always)] + pub fn pop(&mut self) -> Option<char> { + self.len + .checked_sub(0x1) + .map(|len| { + let c = self.buf[self.len]; + self.len = len; + + c + }) + } + + /// Returns an iterator to the contained characters. + /// + /// This iterator only covers "used" character. + /// See [`iter_mut`](Self::iter_mut) for borrowing the entire buffer. + #[inline(always)] + pub fn iter(&self) -> core::slice::Iter<'_, char> { self.as_slice().iter() } + + /// Returns a mutable iterator to the contained characters. + /// + /// This iterator covers the entire internal buffer. + /// See [`iter`](Self::iter) for borrowing only "used" characters. + #[inline(always)] + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, char> { self.as_mut_slice().iter_mut() } +} + +impl<const N: usize> AsMut<[char]> for FixedString<N> { + #[inline(always)] + fn as_mut(&mut self) -> &mut [char] { self.as_mut_slice() } +} + +impl<const N: usize> AsRef<[char]> for FixedString<N> { + #[inline(always)] + fn as_ref(&self) -> &[char] { self.as_slice() } +} + +impl<const N: usize> Debug for FixedString<N> { + #[inline] + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + write!(f, "\"")?; + for c in self { write!(f, "{}", c.escape_debug())? } + write!(f, "\"")?; + + Ok(()) + } +} + +impl<const N: usize> Default for FixedString<N> { + #[inline(always)] + fn default() -> Self { Self { buf: [Default::default(); N], len: 0x0 } } +} + +/// See [`as_slice`](Self::as_slice). +impl<const N: usize> Deref for FixedString<N> { + type Target = [char]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { self.as_slice() } +} + +/// See [`as_mut_slice`](Self::as_mut_slice). +impl<const N: usize> DerefMut for FixedString<N> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } +} + +impl<const N: usize> Display for FixedString<N> { + #[inline] + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { + for c in self { write!(f, "{c}")? } + + Ok(()) + } +} + +impl<const N: usize> Eq for FixedString<N> { } + +impl<const N: usize> From<[char; N]> for FixedString<N> { + #[inline(always)] + fn from(value: [char; N]) -> Self { Self::from_chars(value) } +} + +impl<const N: usize> FromStr for FixedString<N> { + type Err = Error; + + #[inline] + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut buf = [Default::default(); N]; + let len = s.chars().count(); + + for (i, c) in s.chars().enumerate() { + if i >= N { return Err(Error::ArrayTooShort { req: len, len: N }) } + + buf[i] = c; + } + + Ok(Self { buf, len }) + } +} + +impl<I: SliceIndex<[char]>, const N: usize> Index<I> for FixedString<N> { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { self.get(index).unwrap() } +} + +impl<I: SliceIndex<[char]>, const N: usize> IndexMut<I> for FixedString<N> { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { self.get_mut(index).unwrap() } +} + +impl<const N: usize> IntoIterator for FixedString<N> { + type Item = char; + + type IntoIter = FixedIter<char, N>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + FixedIter { + buf: unsafe { self.buf.as_ptr().cast::<[MaybeUninit<char>; N]>().read() }, + + pos: 0x0, + len: self.len, + } + } +} + +impl<'a, const N: usize> IntoIterator for &'a FixedString<N> { + type Item = &'a char; + + type IntoIter = core::slice::Iter<'a, char>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { self.iter() } +} + +impl<'a, const N: usize> IntoIterator for &'a mut FixedString<N> { + type Item = &'a mut char; + + type IntoIter = core::slice::IterMut<'a, char>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { self.iter_mut() } +} + +impl<const N: usize> Ord for FixedString<N> { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } +} + +impl<const N: usize, const M: usize> PartialEq<FixedString<M>> for FixedString<N> { + #[inline] + fn eq(&self, other: &FixedString<M>) -> bool { + if self.len() != other.len() { return false }; + + for i in 0x0..self.len() { + if self.buf[i] != other.buf[i] { return false }; + } + + true + } +} + +impl<const N: usize> PartialEq<&str> for FixedString<N> { + #[inline] + fn eq(&self, other: &&str) -> bool { + for (i, c) in other.chars().enumerate() { + if self.get(i) != Some(&c) { return false }; + } + + true + } +} + +impl<const N: usize, const M: usize> PartialOrd<FixedString<M>> for FixedString<N> { + fn partial_cmp(&self, other: &FixedString<M>) -> Option<Ordering> { + let len = self.len().max(other.len()); + + for i in 0x0..len { + let lc = self.get(i); + let rc = other.get(i); + + match (lc, rc) { + (None, None) => return Some(Ordering::Equal), + (Some(_), None) => return Some(Ordering::Greater), + (None, Some(_)) => return Some(Ordering::Less), + + (Some(lc), Some(rc)) => { + match lc.partial_cmp(rc) { + Some(Ordering::Equal) => {}, + ordering => return ordering + } + } + } + } + + Some(Ordering::Equal) + } +} + +impl<const N: usize> TryFrom<&str> for FixedString<N> { + type Error = <Self as FromStr>::Err; + + #[inline(always)] + fn try_from(value: &str) -> Result<Self, Self::Error> { Self::from_str(value) } +} + +#[cfg(feature = "alloc")] +impl<const N: usize> TryFrom<String> for FixedString<N> { + type Error = <Self as FromStr>::Err; + + #[inline(always)] + fn try_from(value: String) -> Result<Self, Self::Error> { Self::from_str(&value) } +} + +#[cfg(feature = "alloc")] +impl<const N: usize> From<FixedString<N>> for String { + #[inline(always)] + fn from(value: FixedString<N>) -> Self { value.to_string() } +} diff --git a/bzipper/src/fixed_string/test.rs b/bzipper/src/fixed_string/test.rs new file mode 100644 index 0000000..e2af6ce --- /dev/null +++ b/bzipper/src/fixed_string/test.rs @@ -0,0 +1,43 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::FixedString; + +use core::cmp::Ordering; + +#[test] +fn test_fixed_string() { + let str0 = FixedString::<0xC>::try_from("Hello there!").unwrap(); + let str1 = FixedString::<0xE>::try_from("MEIN_GRO\u{1E9E}_GOTT").unwrap(); + let str2 = FixedString::<0x5>::try_from("Hello").unwrap(); + + assert_eq!(str0.partial_cmp(&str0), Some(Ordering::Equal)); + assert_eq!(str0.partial_cmp(&str1), Some(Ordering::Less)); + assert_eq!(str0.partial_cmp(&str2), Some(Ordering::Greater)); + + assert_eq!(str1.partial_cmp(&str0), Some(Ordering::Greater)); + assert_eq!(str1.partial_cmp(&str1), Some(Ordering::Equal)); + assert_eq!(str1.partial_cmp(&str2), Some(Ordering::Greater)); + + assert_eq!(str2.partial_cmp(&str0), Some(Ordering::Less)); + assert_eq!(str2.partial_cmp(&str1), Some(Ordering::Less)); + assert_eq!(str2.partial_cmp(&str2), Some(Ordering::Equal)); +} diff --git a/bzipper/src/lib.rs b/bzipper/src/lib.rs new file mode 100644 index 0000000..3689176 --- /dev/null +++ b/bzipper/src/lib.rs @@ -0,0 +1,202 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg?ref_type=heads")] + +//! Binary (de)serialisation. +//! +//! Contrary to [Serde](https://crates.io/crates/serde/)/[Bincode](https://crates.io/crates/bincode/), the goal of bzipper is to serialise with a known size constraint. +//! Therefore, this crate may be more suited for networking or other cases where a fixed-sized buffer is needed. +//! +//! Keep in mind that this project is still work-in-progress. +//! +//! This crate is compatible with `no_std`. +//! +//! # Data model +//! +//! Most primitive types serialise losslessly, with the exception being [`usize`] and [`isize`]. +//! These serialise as [`u32`] and [`i32`], respectively, for portability reasons. +//! +//! Unsized types, such as [`str`] and [slices](slice), are not supported. +//! Instead, [arrays](array) should be used. +//! For strings, the [`FixedString`] type is also provided. +//! +//! # Usage +//! +//! This crate revolves around the [`Serialise`] and [`Deserialise`] traits, both of which are commonly used in conjunction with streams (more specifically, [s-streams](Sstream) and [d-streams](Dstream)). +//! +//! Many core types come implemented with bzipper, including primitives as well as some standard library types such as [`Option`] and [`Result`](core::result::Result). +//! +//! It is recommended in most cases to just derive these traits for custom types (enumerations and structures only). +//! Here, each field is chained in declaration order: +//! +//! ``` +//! use bzipper::{Deserialise, Serialise}; +//! +//! #[derive(Debug, Deserialise, PartialEq, Serialise)] +//! struct IoRegister { +//! addr: u32, +//! value: u16, +//! } +//! +//! let mut buf: [u8; IoRegister::SERIALISED_SIZE] = Default::default(); +//! IoRegister { addr: 0x04000000, value: 0x0402 }.serialise(&mut buf).unwrap(); +//! +//! assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02]); +//! +//! assert_eq!(IoRegister::deserialise(&buf).unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 }); +//! ``` +//! +//! ## Serialisation +//! +//! To serialise an object implementing `Serialise`, simply allocate a buffer for the serialisation. +//! The required size of any given serialisation is specified by the [`SERIALISED_SIZE`](Serialise::SERIALISED_SIZE) constant: +//! +//! ``` +//! use bzipper::Serialise; +//! +//! let mut buf: [u8; char::SERIALISED_SIZE] = Default::default(); +//! 'Ж'.serialise(&mut buf).unwrap(); +//! +//! assert_eq!(buf, [0x00, 0x00, 0x04, 0x16]); +//! ``` +//! +//! The only special requirement of the [`serialise`](Serialise::serialise) method is that the provided byte slice has an element count of exactly `SERIALISED_SIZE`. +//! +//! We can also use streams to *chain* multiple elements together. +//! +//! ``` +//! use bzipper::Serialise; +//! +//! let mut buf: [u8; char::SERIALISED_SIZE * 5] = Default::default(); +//! let mut stream = bzipper::Sstream::new(&mut buf); +//! +//! stream.append(&'ل'); +//! stream.append(&'ا'); +//! stream.append(&'م'); +//! stream.append(&'د'); +//! stream.append(&'ا'); +//! +//! assert_eq!(buf, [0x00, 0x00, 0x06, 0x44, 0x00, 0x00, 0x06, 0x27, 0x00, 0x00, 0x06, 0x45, 0x00, 0x00, 0x06, 0x2F, 0x00, 0x00, 0x06, 0x27]); +//! ``` +//! +//! When serialising primitives, the resulting byte stream is in big endian (a.k.a. network endian). +//! It is recommended for implementors to adhere to this convention as well. +//! +//! ## Deserialisation +//! +//! Deserialisation works with an almost identical syntax to serialisation. +//! +//! To deserialise a buffer, simply call the [`deserialise`](Deserialise::deserialise) method: +//! +//! ``` +//! use bzipper::Deserialise; +//! +//! let data = [0x45, 0x54]; +//! assert_eq!(<u16>::deserialise(&data).unwrap(), 0x4554); +//! ``` +//! +//! Just like with serialisations, the [`Dstream`] can be used to deserialise chained elements: +//! +//! ``` +//! use bzipper::Deserialise; +//! +//! let data = [0x45, 0x54]; +//! let stream = bzipper::Dstream::new(&data); +//! +//! assert_eq!(stream.take::<u8>().unwrap(), 0x45); +//! assert_eq!(stream.take::<u8>().unwrap(), 0x54); +//! ``` + +#![no_std] + +#![cfg_attr(doc, feature(doc_cfg))] + +extern crate self as bzipper; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +extern crate std; + +/// Implements [`Deserialise`] for the provided type. +/// +/// This macro assumes that `Serialise` was also derived, although this is not strictly required as it is unenforceable. +#[doc(inline)] +pub use bzipper_macros::Deserialise; + +/// Implements [`Serialise`] for the provided type. +/// +/// # Structs +/// +/// For structures, each element is chained in **order of declaration.** +/// For example, the following struct will serialise its field `foo` before `bar`: +/// +/// ``` +/// use bzipper::Serialise; +/// +/// #[derive(Serialise)] +/// pub struct FooBar { +/// pub foo: char, +/// pub bar: char, +/// } +/// ``` +/// +/// Should the order of declaration change, then most of---if not all---previous dervied serialisations become void. +/// +/// The value of [`SERIALISED_SIZE`](Serialise::SERIALISED_SIZE) is set to the combined value of all fields. +/// +/// If the structure is a unit structure (i.e. it has *no* fields), it is serialised equivalently to the [unit] type. +/// +/// # Enums +/// +/// Enumerations are serialised by first assigning each variant its own discriminant. +/// By default, each discriminant is assigned from the range 0 to infinite, to the extend allowed by the `u32` type (as which the discriminant is encoded). +/// In the future, however, custom representations and assigned discriminants will be honoured. +/// +/// Variants with fields are serialised exactly like structures. +/// That is, each field is chained in order of declaration. +/// +/// Each variant has its own serialised size, and the largest of these values is chosen as the serialised size of the enumeration type. +/// +/// # Unions +/// +/// Unions cannot derive `Serialise` due to the uncertainty of their contents. +/// The trait should therefore be implemented manually. +#[doc(inline)] +pub use bzipper_macros::Serialise; + +macro_rules! use_mod { + ($vis:vis $name:ident) => { + mod $name; + $vis use $name::*; + }; +} +pub(in crate) use use_mod; + +use_mod!(pub deserialise); +use_mod!(pub dstream); +use_mod!(pub error); +use_mod!(pub fixed_iter); +use_mod!(pub fixed_string); +use_mod!(pub serialise); +use_mod!(pub sstream); diff --git a/bzipper/src/serialise/mod.rs b/bzipper/src/serialise/mod.rs new file mode 100644 index 0000000..9c89e09 --- /dev/null +++ b/bzipper/src/serialise/mod.rs @@ -0,0 +1,300 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +#[cfg(test)] +mod test; + +use crate::{Error, Result, Sstream}; + +use core::{convert::Infallible, marker::PhantomData}; + +mod tuple; + +/// Denotes a type capable of being serialised. +/// +/// It is recommended to simply derive this trait for custom types. +/// It can, however, be manually implemented: +/// +/// ``` +/// use bzipper::{Result, Serialise, Sstream}; +/// +/// struct Foo { +/// bar: u16, +/// baz: f32, +/// } +/// +/// impl Serialise for Foo { +/// const SERIALISED_SIZE: usize = u16::SERIALISED_SIZE + f32::SERIALISED_SIZE; +/// +/// fn serialise(&self, buf: &mut [u8]) -> Result<()> { +/// debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); +/// +/// // Serialise fields using chaining. +/// +/// let mut stream = Sstream::new(buf); +/// +/// stream.append(&self.bar)?; +/// stream.append(&self.baz)?; +/// +/// Ok(()) +/// } +/// } +/// ``` +/// +/// Implementors of this trait should make sure that [`SERIALISED_SIZE`](Serialise::SERIALISED_SIZE) is properly defined. +/// This value indicates the definitive size of any serialisation of the `Self` type. +pub trait Serialise: Sized { + /// The amount of bytes that result from a serialisation. + /// + /// Implementors of this trait should make sure that no serialisation (or deserialisation) uses more than the amount specified by this constant. + /// When using these traits, always assume that exactly this amount has or will be used. + const SERIALISED_SIZE: usize; + + /// Serialises `self` into a slice. + /// + /// In most cases it is wiser to chain serialisations using [`Sstream`] instead of using this method directly. + /// + /// # Errors + /// + /// If serialisation failed, e.g. by an unencodable value being provided, an error is returned. + /// + /// # Panics + /// + /// This method will usually panic if the provided slice has a length *less* than the value of `SERIALISED_SIZE`. + /// Official implementations of this trait (including those that are derived) always panic in debug mode if the provided slice has a length that is different at all. + fn serialise(&self, buf: &mut [u8]) -> Result<()>; +} + +macro_rules! impl_numeric { + ($ty:ty) => { + impl ::bzipper::Serialise for $ty { + const SERIALISED_SIZE: usize = size_of::<$ty>(); + + #[inline] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + ::core::debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let data = self.to_be_bytes(); + buf.copy_from_slice(&data); + + Ok(()) + } + } + }; +} + +macro_rules! impl_non_zero { + ($ty:ty) => { + impl ::bzipper::Serialise for ::core::num::NonZero<$ty> { + const SERIALISED_SIZE: usize = ::core::mem::size_of::<$ty>(); + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + self.get().serialise(buf) + } + } + }; +} + +impl<T: Serialise, const N: usize> Serialise for [T; N] { + const SERIALISED_SIZE: usize = T::SERIALISED_SIZE * N; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + for v in self { stream.append(v)? } + + Ok(()) + } +} + +impl Serialise for bool { + const SERIALISED_SIZE: usize = u8::SERIALISED_SIZE; + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + u8::from(*self).serialise(buf) + } +} + +impl Serialise for char { + const SERIALISED_SIZE: usize = u32::SERIALISED_SIZE; + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + u32::from(*self).serialise(buf) + } + +} + +// Especially useful for `Result<T, Infallible>`. +// *If* that is needed, of course. +impl Serialise for Infallible { + const SERIALISED_SIZE: usize = 0x0; + + #[inline(always)] + fn serialise(&self, _buf: &mut [u8]) -> Result<()> { unreachable!() } + +} + +impl Serialise for isize { + const SERIALISED_SIZE: usize = i32::SERIALISED_SIZE; + + #[inline] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let value = i32::try_from(*self) + .map_err(|_| Error::IsizeOutOfRange { value: *self })?; + + value.serialise(buf) + } +} + +impl<T: Serialise> Serialise for Option<T> { + const SERIALISED_SIZE: usize = bool::SERIALISED_SIZE + T::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + // The first element is of type `bool` and is + // called the "sign." It signifies whether there is + // a following element or not. The remaining bytes + // are preserved if `self` is `None`. + + let mut stream = Sstream::new(buf); + + match *self { + None => { + stream.append(&false)?; + // No need to zero-fill. + }, + + Some(ref v) => { + stream.append(&true)?; + stream.append(v)?; + }, + }; + + Ok(()) + } +} + +impl<T> Serialise for PhantomData<T> { + const SERIALISED_SIZE: usize = size_of::<Self>(); + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + Ok(()) + } +} + +impl<T, E> Serialise for core::result::Result<T, E> +where + T: Serialise, + E: Serialise, { + const SERIALISED_SIZE: usize = bool::SERIALISED_SIZE + if size_of::<T>() > size_of::<E>() { size_of::<T>() } else { size_of::<E>() }; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + // Remember the descriminant. + match *self { + Ok(ref v) => { + stream.append(&false)?; + stream.append(v)?; + }, + + Err(ref e) => { + stream.append(&true)?; + stream.append(e)?; + }, + }; + + Ok(()) + } +} + +impl Serialise for () { + const SERIALISED_SIZE: usize = size_of::<Self>(); + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + Ok(()) + } +} + +impl Serialise for usize { + const SERIALISED_SIZE: Self = u32::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let value = u32::try_from(*self) + .map_err(|_| Error::UsizeOutOfRange { value: *self })?; + + value.serialise(buf) + } +} + +//impl_numeric!(f128); +//impl_numeric!(f16); +impl_numeric!(f32); +impl_numeric!(f64); +impl_numeric!(i128); +impl_numeric!(i16); +impl_numeric!(i32); +impl_numeric!(i64); +impl_numeric!(i8); +impl_numeric!(u128); +impl_numeric!(u16); +impl_numeric!(u32); +impl_numeric!(u64); +impl_numeric!(u8); + +impl_non_zero!(i128); +impl_non_zero!(i16); +impl_non_zero!(i32); +impl_non_zero!(i64); +impl_non_zero!(i8); +impl_non_zero!(isize); +impl_non_zero!(u128); +impl_non_zero!(u16); +impl_non_zero!(u32); +impl_non_zero!(u64); +impl_non_zero!(u8); +impl_non_zero!(usize); diff --git a/bzipper/src/serialise/test.rs b/bzipper/src/serialise/test.rs new file mode 100644 index 0000000..f2332a5 --- /dev/null +++ b/bzipper/src/serialise/test.rs @@ -0,0 +1,104 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +//test!(you can redistribut => []); +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{FixedString, Serialise}; + +#[test] +fn test_serialise() { + #[derive(Serialise)] + struct Foo(char); + + #[derive(Serialise)] + enum Bar { + Unit, + Pretty(bool), + Teacher { initials: [char; 0x3] }, + } + + assert_eq!(Foo::SERIALISED_SIZE, 0x4); + assert_eq!(Bar::SERIALISED_SIZE, 0x10); + + macro_rules! test { + ($ty:ty: $value:expr => $data:expr) => {{ + use ::bzipper::Serialise; + + let data: [u8; <$ty as Serialise>::SERIALISED_SIZE] = $data; + + let mut buf = [0x00; <$ty as Serialise>::SERIALISED_SIZE]; + <$ty as Serialise>::serialise(&mut $value, &mut buf).unwrap(); + + assert_eq!(buf, data); + }}; + } + + test!(u8: 0x00 => [0x00]); + test!(u8: 0xFF => [0xFF]); + test!(u8: 0x7F => [0x7F]); + + test!(u16: 0x0F_7E => [0x0F, 0x7E]); + + test!(u32: 0x00_2F_87_E7 => [0x00, 0x2F, 0x87, 0xE7]); + + test!(u64: 0xF3_37_CF_8B_DB_03_2B_39 => [0xF3, 0x37, 0xCF, 0x8B, 0xDB, 0x03, 0x2B, 0x39]); + + test!(u128: 0x45_A0_15_6A_36_77_17_8A_83_2E_3C_2C_84_10_58_1A => [ + 0x45, 0xA0, 0x15, 0x6A, 0x36, 0x77, 0x17, 0x8A, + 0x83, 0x2E, 0x3C, 0x2C, 0x84, 0x10, 0x58, 0x1A, + ]); + + test!(FixedString::<0x1>: FixedString::try_from("A").unwrap() => [0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x01]); + + test!(FixedString::<0x9>: FixedString::try_from("l\u{00F8}gma\u{00F0}ur").unwrap() => [ + 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0xF8, + 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x6D, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0xF0, + 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + ]); + + test!([char; 0x5]: ['\u{03B4}', '\u{0190}', '\u{03BB}', '\u{03A4}', '\u{03B1}'] => [ + 0x00, 0x00, 0x03, 0xB4, 0x00, 0x00, 0x01, 0x90, + 0x00, 0x00, 0x03, 0xBB, 0x00, 0x00, 0x03, 0xA4, + 0x00, 0x00, 0x03, 0xB1, + ]); + + test!(Result::<u16, char>: Ok(0x45_45) => [0x00, 0x45, 0x45, 0x00, 0x00]); + test!(Result::<u16, char>: Err(char::REPLACEMENT_CHARACTER) => [0x01, 0x00, 0x00, 0xFF, 0xFD]); + + test!(Option<()>: None => [0x00]); + test!(Option<()>: Some(()) => [0x01]); + + test!(Foo: Foo('\u{FDF2}') => [0x00, 0x00, 0xFD, 0xF2]); + + test!(Bar: Bar::Unit => [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + + test!(Bar: Bar::Pretty(true) => [ + 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + + test!(Bar: Bar::Teacher { initials: ['T', 'L', '\0'] } => [ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, + 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, + ]); +}
\ No newline at end of file diff --git a/bzipper/src/serialise/tuple.rs b/bzipper/src/serialise/tuple.rs new file mode 100644 index 0000000..feee2e2 --- /dev/null +++ b/bzipper/src/serialise/tuple.rs @@ -0,0 +1,424 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Result, Serialise, Sstream}; + +impl<T0> Serialise for (T0, ) +where + T0: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + + Ok(()) + } +} + +impl<T0, T1> Serialise for (T0, T1) +where + T0: Serialise, + T1: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + + Ok(()) + } +} + +impl<T0, T1, T2> Serialise for (T0, T1, T2) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3> Serialise for (T0, T1, T2, T3) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4> Serialise for (T0, T1, T2, T3, T4) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5> Serialise for (T0, T1, T2, T3, T4, T5) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6> Serialise for (T0, T1, T2, T3, T4, T5, T6) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, + T7: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE + + T7::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + stream.append(&self.7)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, + T7: Serialise, + T8: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE + + T7::SERIALISED_SIZE + + T8::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + stream.append(&self.7)?; + stream.append(&self.8)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, + T7: Serialise, + T8: Serialise, + T9: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE + + T7::SERIALISED_SIZE + + T8::SERIALISED_SIZE + + T9::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + stream.append(&self.7)?; + stream.append(&self.8)?; + stream.append(&self.9)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, + T7: Serialise, + T8: Serialise, + T9: Serialise, + T10: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE + + T7::SERIALISED_SIZE + + T8::SERIALISED_SIZE + + T9::SERIALISED_SIZE + + T10::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + stream.append(&self.7)?; + stream.append(&self.8)?; + stream.append(&self.9)?; + stream.append(&self.10)?; + + Ok(()) + } +} + +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T0: Serialise, + T1: Serialise, + T2: Serialise, + T3: Serialise, + T4: Serialise, + T5: Serialise, + T6: Serialise, + T7: Serialise, + T8: Serialise, + T9: Serialise, + T10: Serialise, + T11: Serialise, { + const SERIALISED_SIZE: usize = + T0::SERIALISED_SIZE + + T1::SERIALISED_SIZE + + T2::SERIALISED_SIZE + + T3::SERIALISED_SIZE + + T4::SERIALISED_SIZE + + T5::SERIALISED_SIZE + + T6::SERIALISED_SIZE + + T7::SERIALISED_SIZE + + T8::SERIALISED_SIZE + + T9::SERIALISED_SIZE + + T10::SERIALISED_SIZE + + T11::SERIALISED_SIZE; + + fn serialise(&self, buf: &mut [u8]) -> Result<()> { + debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = Sstream::new(buf); + + stream.append(&self.0)?; + stream.append(&self.1)?; + stream.append(&self.2)?; + stream.append(&self.3)?; + stream.append(&self.4)?; + stream.append(&self.5)?; + stream.append(&self.6)?; + stream.append(&self.7)?; + stream.append(&self.8)?; + stream.append(&self.9)?; + stream.append(&self.10)?; + stream.append(&self.11)?; + + Ok(()) + } +} diff --git a/bzipper/src/sstream/mod.rs b/bzipper/src/sstream/mod.rs new file mode 100644 index 0000000..257be95 --- /dev/null +++ b/bzipper/src/sstream/mod.rs @@ -0,0 +1,58 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Error, Result, Serialise}; + +use core::cell::Cell; + +/// Byte stream for deserialisation. +/// +/// This type borrows a slice, keeping track internally of the used bytes. +pub struct Sstream<'a> { + buf: &'a mut [u8], + pos: Cell<usize>, +} + +impl<'a> Sstream<'a> { + /// Constructs a new byte stream. + #[inline(always)] + #[must_use] + pub fn new(buf: &'a mut [u8]) -> Self { Self { buf, pos: Cell::new(0x0) } } + + /// Extends the stream by appending a new serialisation. + /// + /// # Errors + /// + /// If the stream cannot hold any arbitrary serialisation of `T`, an [`EndOfStream`](Error::EndOfStream) instance is returned. + #[inline] + pub fn append<T: Serialise>(&mut self, value: &T) -> Result<()> { + let rem = self.buf.len() - self.pos.get(); + let req = T::SERIALISED_SIZE; + + if rem < req { return Err(Error::EndOfStream { req, rem }) }; + + let start = self.pos.get(); + let stop = start + req; + + self.pos.set(stop); + value.serialise(&mut self.buf[start..stop]) + } +} |