summaryrefslogtreecommitdiff
path: root/bzipper
diff options
context:
space:
mode:
Diffstat (limited to 'bzipper')
-rw-r--r--bzipper/Cargo.toml26
-rw-r--r--bzipper/src/deserialise/mod.rs245
-rw-r--r--bzipper/src/deserialise/test.rs118
-rw-r--r--bzipper/src/deserialise/tuple.rs298
-rw-r--r--bzipper/src/dstream/mod.rs58
-rw-r--r--bzipper/src/error/mod.rs126
-rw-r--r--bzipper/src/fixed_iter/mod.rs46
-rw-r--r--bzipper/src/fixed_string/mod.rs448
-rw-r--r--bzipper/src/fixed_string/test.rs43
-rw-r--r--bzipper/src/lib.rs202
-rw-r--r--bzipper/src/serialise/mod.rs300
-rw-r--r--bzipper/src/serialise/test.rs104
-rw-r--r--bzipper/src/serialise/tuple.rs424
-rw-r--r--bzipper/src/sstream/mod.rs58
14 files changed, 2496 insertions, 0 deletions
diff --git a/bzipper/Cargo.toml b/bzipper/Cargo.toml
new file mode 100644
index 0000000..d8575f0
--- /dev/null
+++ b/bzipper/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "bzipper"
+edition = "2021"
+rust-version = "1.81"
+documentation = "https://docs.rs/bzipper/"
+
+version.workspace = true
+authors.workspace = true
+description.workspace = true
+readme.workspace = true
+homepage.workspace = true
+repository.workspace = true
+license.workspace = true
+
+[package.metadata.docs.rs]
+all-features = true
+
+[features]
+alloc = []
+std = []
+
+[dependencies]
+bzipper_macros = { path = "../bzipper_macros" }
+
+[lints]
+workspace = true
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])
+ }
+}