diff options
38 files changed, 3167 insertions, 2239 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index b6b156e..74c20ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,29 @@ This is the changelog of `bzipper`. See `"README.md"` for more information. +## 0.5.0 + +* Bump minor version +* Add macros crate +* Add derive macros +* Update package metadata +* Update readme +* Expand docs +* Require fixed size (de)serialisations +* Add more error variants +* Require `bzipper::Error` for (de)serialisation +* Reworks streams +* Remove `Buffer` +* Rework `FixedString` +* Serialise `usize` and `isize` as `u32` and `i32`, respectively +* Rework arrays (de)serialisation +* Fix `Result` serialisations +* Add new logo +* Add features `alloc` and `std` +* Specify rustc version +* Rename `FixedStringIter` to `FixedIter` +* Implement `Serialise` and `Deserialise` for single tuples and `PhantomData` + ## 0.4.7 * Extensively elaborate docs @@ -1,16 +1,17 @@ -[package] -name = "bzipper" -version = "0.4.7" -authors = ["Gabriel Bjørnager Jensen"] -edition = "2021" -description = "Binary (de)serialiser." -documentation = "https://docs.rs/bzipper" -readme = "README.md" -homepage = "https://achernar.dk/html/bzipper.html" -repository = "https://mandelbrot.dk/bzipper" -license = "LGPL-3.0-or-later" +[workspace] +members = ["bzipper", "bzipper_macros"] +resolver = "2" -[lints.clippy] +[workspace.package] +version = "0.5.0" +authors = ["Gabriel Bjørnager Jensen"] +description = "Binary (de)serialiser." +readme = "README.md" +homepage = "https://achernar.dk/index.php?p=bzipper" +repository = "https://mandelbrot.dk/bzipper/" +license = "LGPL-3.0-or-later" + +[workspace.lints.clippy] as_ptr_cast_mut = "forbid" as_underscore = "warn" assertions_on_result_states = "warn" @@ -2,92 +2,105 @@ [`bzipper`](https://crates.io/crates/bzipper) is a binary (de)serialiser for the Rust language. -Contrary to [Serde](https://crates.io/crates/serde/)/[Bincode](https://crates.io/crates/bincode/), the goal of this crate is to serialise data with a known size limit. +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 does not require any dependencies at the moment. -It is also compatible with `no_std`. +This crate is compatible with `no_std`. -See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for documentation. - -## Data Model +## Data model Most primitive types serialise losslessly, with the exception being `usize` and `isize`. -These serialise as `u16` and `i16`, respectively, for portability reasons. +These serialise as `u32` and `i32`, respectively, for portability reasons. Unsized types, such as `str` and slices, are not supported. -Instead, array should be used. +Instead, arrays should be used. For strings, the `FixedString` type is also provided. ## Usage -This crate revolves around the `Serialise` and `Deserialise` traits, both of which work around streams (more specifically, d-streams and s-streams). +This crate revolves around the `Serialise` and `Deserialise` traits, both of which are commonly used in conjunction with streams (more specifically, s-streams and d-streams). + +Many core types come implemented with bzipper, including primitives as well as some standard library types such as `Option` and `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: + +```rs +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(); -Many core types come implemented with `bzipper`, including primitives as well as some standard library types such as `Option` and `Result`. +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 so-called "s-stream" (short for *serialisation stream*) with the `Sstream` type: +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` constant: ```rs -let mut buf: [u8; 16] = Default::default(); +use bzipper::Serialise; -let mut stream = bzipper::Sstream::new(&mut buf); +let mut buf: [u8; char::SERIALISED_SIZE] = Default::default(); +'Ж'.serialise(&mut buf).unwrap(); + +assert_eq!(buf, [0x00, 0x00, 0x04, 0x16]); ``` -The resulting stream is immutable in the sense that it cannot grow its buffer, altough it does keep track of the buffer's state. +The only special requirement of the `serialise` method is that the provided byte slice has an element count of exactly `SERIALISED_SIZE`. -A byte sequence can be added to our new stream by passing the stream to a call to the `serialise` method: +We can also use streams to *chain* multiple elements together. ```rs use bzipper::Serialise; -let mut buf: [u8; 2] = Default::default(); +let mut buf: [u8; char::SERIALISED_SIZE * 5] = Default::default(); let mut stream = bzipper::Sstream::new(&mut buf); -0x4554_u16.serialise(&mut stream).unwrap(); -``` - -The ammount of bytes used by the serialiser (that is, the ammount of bytes written to the stream) is indicated by its return value (i.e. it has the type `Result<usize, Serialise::Error>`). +stream.append(&'ل'); +stream.append(&'ا'); +stream.append(&'م'); +stream.append(&'د'); +stream.append(&'ا'); -Whilst the *maximum* ammount of bytes is specified by the `SERIALISE_LIMIT` constant, this can in cases be lower (for example with `None` variants which are always encoded as a single, null byte). +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. -After serialisation, the s-stream records the new write-to position of the buffer. This allows for *chaining* of serialisations, which can prove useful when implementing the trait for custom types. - ### Deserialisation -As with serialisation, deserialisation uses streams (just with the `Dstream` type; short for *deserialisation stream*): +Deserialisation works with an almost identical syntax to serialisation. + +To deserialise a buffer, simply call the `deserialise` method: ```rs -let data = [0x45, 0x54]; +use bzipper::Deserialise; -let mut stream = bzipper::Dstream::new(&data); +let data = [0x45, 0x54]; +assert_eq!(<u16>::deserialise(&data).unwrap(), 0x4554); ``` -Using these streams is also just as simple as with s-streams: +Just like with serialisations, the `Dstream` can be used to deserialise chained elements: ```rs use bzipper::Deserialise; let data = [0x45, 0x54]; -let mut stream = bzipper::Dstream::new(&data); +let stream = bzipper::Dstream::new(&data); -assert_eq!(u16::deserialise(&mut stream).unwrap(), 0x4554); +assert_eq!(stream.take::<u8>().unwrap(), 0x45); +assert_eq!(stream.take::<u8>().unwrap(), 0x54); ``` - -When chaining serialisations, keep in mind that appropriate deserialisations should come in **reverse order** (streams function similarly to stacks in this sense). - -## Copyright & Licensing - -Copyright 2024 Gabriel Bjørnager Jensen. - -This program 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. - -This program 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 Lesser General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. diff --git a/bzipper.svg b/bzipper.svg index 974c50b..52050cc 100644 --- a/bzipper.svg +++ b/bzipper.svg @@ -1,9 +1,15 @@ <svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> <mask id="z"> - <polyline fill="none" points="20,28 20,20 76,20 36.970562748,76" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" /> - <polyline fill="none" points="76,68 76,76 20,76 59.029437252,20" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" /> + <rect fill="white" height="24" width="32" x="16" y="24" /> + <circle cx="48" cy="48" fill="black" r="16" /> + + <polygon fill="white" points="20,16 76,16 80,20 80,80 16,80 64,32 16,32 16,20" /> + <circle cx="20" cy="20" fill="white" r="4" /> + <circle cx="76" cy="20" fill="white" r="4" /> + + <circle cx="80" cy="80" fill="black" r="16" /> </mask> - <rect fill="#FFFFFF" height="100%" width="100%" x="0" y="0" /> - <rect fill="#B4202D" height="100%" mask="url(#z)" width="100%" x="0" y="0" /> + <rect fill="#526F03" height="100%" width="100%" x="0" y="0" /> + <rect fill="#FFFFFF" height="100%" mask="url(#z)" width="100%" x="0" y="0" /> </svg> 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/src/error/mod.rs b/bzipper/src/error/mod.rs index 30e4c1e..090215a 100644 --- a/src/error/mod.rs +++ b/bzipper/src/error/mod.rs @@ -19,14 +19,16 @@ // er General Public License along with bzipper. If // not, see <https://www.gnu.org/licenses/>. -use core::error::Error as StdError; 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>; -/// Denotes an error. +/// (De)serialisation failures. /// /// These variants are used when deserialisation fails. /// Serialisations are assumed infallible. @@ -38,10 +40,17 @@ pub enum Error { /// 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). + /// A boolean encountered a value outside `0` and `1`. InvalidBoolean { value: u8 }, /// An invalid code point was encountered. @@ -49,13 +58,16 @@ pub enum Error { /// 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 `isize` value couldn't fit into (16) bits. + /// 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). + /// A non-zero integer encountered the value `0`. NullInteger, - /// A `usize` value couldn't fit into (16) bits. + /// A `usize` value couldn't fit into `16` bits. UsizeOutOfRange { value: usize }, } @@ -64,48 +76,50 @@ impl Display for Error { use Error::*; match *self { - ArrayTooShort { req, len } => { - write!(f, "array of ({len}) element(s) cannot hold ({req})") - }, + ArrayTooShort { req, len } + => write!(f, "array of ({len}) element(s) cannot hold ({req})"), - BadString { ref source } =>{ - write!(f, "unable to parse utf8: \"{source}\"") - }, + BadString { ref source } + => write!(f, "unable to parse utf8: \"{source}\""), - EndOfStream { req, rem } => { - write!(f, "({req}) byte(s) were requested but only ({rem}) byte(s) were left") - }, + #[cfg(feature = "alloc")] + CustomError { ref source } + => write!(f, "{source}"), - InvalidBoolean { value } => { - write!(f, "expected boolean but got {value:#02X}") - }, + EndOfStream { req, rem } + => write!(f, "({req}) byte(s) were requested but only ({rem}) byte(s) were left"), - InvalidCodePoint { value } => { - write!(f, "code point U+{value:04X} is not valid") - }, + InvalidBoolean { value } + => write!(f, "expected boolean but got {value:#02X}"), - IsizeOutOfRange { value } => { - write!(f, "signed size value ({value}) cannot be serialised: must be in the range ({}) to ({})", i16::MIN, i16::MAX) - }, + InvalidCodePoint { value } + => write!(f, "code point U+{value:04X} is not valid"), - NullInteger => { - write!(f, "expected non-zero integer but got (0)") - }, + InvalidDiscriminant { value } + => write!(f, "discriminant ({value}) is not valid for the given enumeration"), - UsizeOutOfRange { value } => { - write!(f, "unsigned size value ({value}) cannot be serialised: must be at most ({})", u16::MAX) - }, + 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 StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +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/src/fixed_string/test.rs b/bzipper/src/fixed_string/test.rs index 1599efc..e2af6ce 100644 --- a/src/fixed_string/test.rs +++ b/bzipper/src/fixed_string/test.rs @@ -25,9 +25,9 @@ use core::cmp::Ordering; #[test] fn test_fixed_string() { - let str0 = FixedString::<0xC>::new("Hello there!").unwrap(); - let str1 = FixedString::<0xE>::new("MEIN_GRO\u{1E9E}_GOTT").unwrap(); - let str2 = FixedString::<0x5>::new("Hello").unwrap(); + 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)); 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]) + } +} diff --git a/bzipper_macros/Cargo.toml b/bzipper_macros/Cargo.toml new file mode 100644 index 0000000..562251e --- /dev/null +++ b/bzipper_macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bzipper_macros" +edition = "2021" +documentation = "https://docs.rs/bzipper_macros/" + +version.workspace = true +authors.workspace = true +description.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.86" +quote = "1.0.36" +syn = "2.0.72" + +[lints] +workspace = true diff --git a/bzipper_macros/src/closure/mod.rs b/bzipper_macros/src/closure/mod.rs new file mode 100644 index 0000000..86d19d4 --- /dev/null +++ b/bzipper_macros/src/closure/mod.rs @@ -0,0 +1,41 @@ +// 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 proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{Ident, Token}; + +/// A field capture list. +/// +/// This is used for capturing fields of structures or enumeration variants. +#[derive(Clone)] +pub struct Capture { + pub ref_token: Token![ref], + pub ident: Ident, +} + +impl ToTokens for Capture { + #[inline(always)] + fn to_tokens(&self, tokens: &mut TokenStream) { + self.ref_token.to_tokens(tokens); + self.ident.to_tokens(tokens); + } +} diff --git a/bzipper_macros/src/discriminant/mod.rs b/bzipper_macros/src/discriminant/mod.rs new file mode 100644 index 0000000..21a835a --- /dev/null +++ b/bzipper_macros/src/discriminant/mod.rs @@ -0,0 +1,98 @@ +// 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 proc_macro2::TokenStream; +use quote::ToTokens; + +/// An enumeration discriminant. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Discriminant(u32); + +impl Discriminant { + /// Constructs a new discriminant. + #[inline(always)] + #[must_use] + pub const fn new(value: u32) -> Self { Self(value) } + + /// Retrieves the raw discriminant value. + #[inline(always)] + #[must_use] + pub const fn get(self) -> u32 { self.0 } + + /// Unwraps the given value as a discriminant. + /// + /// # Panics + /// + /// If the given value cannot be represented as an `u32`, this function will panic. + #[inline(always)] + #[must_use] + pub fn unwrap_from<T: TryInto<Self>>(value: T) -> Self { + value + .try_into() + .unwrap_or_else(|_| panic!("enumeration discriminants must be representable in `u32`")) + } + + /// Unsafely unwraps the given value as a discriminant. + /// + /// This function assumes that this conversion is infallible for the given value. + /// If this is a false guarantee, the [`unwrap_from`](Self::unwrap_from) function should be used instead. + /// + /// # Safety + /// + /// Behaviour is undefined if the given value cannot be represented as an object of `u32`. + #[inline(always)] + #[must_use] + pub unsafe fn unwrap_from_unchecked<T: TryInto<Self>>(value: T) -> Self { + value + .try_into() + .unwrap_unchecked() + } +} + +impl ToTokens for Discriminant { + #[inline(always)] + fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } +} + +impl From<u32> for Discriminant { + #[inline(always)] + fn from(value: u32) -> Self { Self(value) } +} + +impl TryFrom<usize> for Discriminant { + type Error = <u32 as TryFrom<usize>>::Error; + + #[inline(always)] + fn try_from(value: usize) -> Result<Self, Self::Error> { value.try_into().map(Self) } +} + +impl From<Discriminant> for u32 { + #[inline(always)] + fn from(value: Discriminant) -> Self { value.0 } +} + +impl TryFrom<Discriminant> for usize { + type Error = <Self as TryFrom<u32>>::Error; + + #[inline(always)] + fn try_from(value: Discriminant) -> Result<Self, Self::Error> { value.0.try_into() } +} diff --git a/bzipper_macros/src/generic_name/mod.rs b/bzipper_macros/src/generic_name/mod.rs new file mode 100644 index 0000000..a0c51f5 --- /dev/null +++ b/bzipper_macros/src/generic_name/mod.rs @@ -0,0 +1,74 @@ +// 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 proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + GenericParam, + Generics, + Ident, + Lifetime, + Token, + punctuated::Punctuated, +}; + +/// A name of a genric. +#[derive(Clone)] +pub enum GenericName { + Const( Ident), + Lifetime(Lifetime), + Type( Ident), +} + +impl GenericName { + /// Extracts the names of the given generics. + #[must_use] + pub fn extract_from(generics: &Generics) -> Punctuated<Self, Token![,]> { + let mut names = Punctuated::new(); + + for generic in &generics.params { + let name = match *generic { + GenericParam::Const( ref param) => Self::Const( param.ident.clone()), + GenericParam::Lifetime(ref param) => Self::Lifetime(param.lifetime.clone()), + GenericParam::Type( ref param) => Self::Type( param.ident.clone()), + }; + + names.push(name); + } + + names + } +} + +impl ToTokens for GenericName { + #[inline(always)] + fn to_tokens(&self, tokens: &mut TokenStream) { + use GenericName::*; + + match *self { + | Const(ref ident) + | Type( ref ident) + => ident.to_tokens(tokens), + + Lifetime(ref lifetime) => lifetime.to_tokens(tokens), + } + } +} diff --git a/bzipper_macros/src/impls/deserialise_enum.rs b/bzipper_macros/src/impls/deserialise_enum.rs new file mode 100644 index 0000000..7bf0220 --- /dev/null +++ b/bzipper_macros/src/impls/deserialise_enum.rs @@ -0,0 +1,78 @@ +// 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::Discriminant; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DataEnum, Fields, Token}; +use syn::punctuated::Punctuated; + +#[must_use] +pub fn deserialise_enum(data: &DataEnum) -> TokenStream { + let mut match_arms = Punctuated::<TokenStream, Token![,]>::new(); + + for (index, variant) in data.variants.iter().enumerate() { + let variant_name = &variant.ident; + + let discriminant = Discriminant::unwrap_from(index); + + let block = if matches!(variant.fields, Fields::Unit) { + quote! { Self } + } else { + let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); + + for field in &variant.fields { + let field_ty = &field.ty; + + let command = field.ident + .as_ref() + .map_or_else( + || quote! { stream.take::<#field_ty>()? }, + |field_name| quote! { #field_name: stream.take::<#field_ty>()? } + ); + + chain_commands.push(command); + } + + match variant.fields { + Fields::Named( ..) => quote! { Self::#variant_name { #chain_commands } }, + Fields::Unnamed(..) => quote! { Self::#variant_name(#chain_commands) }, + Fields::Unit => unreachable!(), + } + }; + + match_arms.push(quote! { #discriminant => #block }); + } + + match_arms.push(quote! { value => return Err(::bzipper::Error::InvalidDiscriminant { value }) }); + + quote! { + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + let mut stream = ::bzipper::Dstream::new(data); + + let value = match (stream.take::<u32>()?) { #match_arms }; + Ok(value) + } + } +} diff --git a/bzipper_macros/src/impls/deserialise_struct.rs b/bzipper_macros/src/impls/deserialise_struct.rs new file mode 100644 index 0000000..414a313 --- /dev/null +++ b/bzipper_macros/src/impls/deserialise_struct.rs @@ -0,0 +1,78 @@ +// 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 proc_macro2::TokenStream; +use quote::quote; +use syn::{DataStruct, Fields, Token}; +use syn::punctuated::Punctuated; + +#[must_use] +pub fn deserialise_struct(data: &DataStruct) -> TokenStream { + if let Fields::Named(..) = data.fields { + let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); + + for field in &data.fields { + let name = field.ident.as_ref().unwrap(); + let ty = &field.ty; + + chain_commands.push(quote! { #name: stream.take::<#ty>()? }); + } + + quote! { + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + let stream = ::bzipper::Dstream::new(data); + + Ok(Self { #chain_commands }) + } + } + } else if let Fields::Unnamed(..) = data.fields { + let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); + + for field in &data.fields { + let ty = &field.ty; + + chain_commands.push(quote! { stream.take::<#ty>()? }); + } + + quote! { + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + let stream = ::bzipper::Dstream::new(data); + + Ok(Self(#chain_commands)) + } + } + } else { + // Fields::Unit + + quote! { + #[inline(always)] + fn deserialise(data: &[u8]) -> ::bzipper::Result<Self> { + ::core::debug_assert_eq!(data.len(), <Self as ::bzipper::Serialise>::SERIALISED_SIZE); + + Ok(Self) + } + } + } +} diff --git a/src/fixed_string_iter/mod.rs b/bzipper_macros/src/impls/mod.rs index 088f61f..d61cf90 100644 --- a/src/fixed_string_iter/mod.rs +++ b/bzipper_macros/src/impls/mod.rs @@ -19,25 +19,8 @@ // er General Public License along with bzipper. If // not, see <https://www.gnu.org/licenses/>. -/// Iterator to a fixed string. -pub struct FixedStringIter<const N: usize> { - pub(in crate) buf: [char; N], - pub(in crate) len: usize, - - pub(in crate) pos: Option<usize>, -} - -impl<const N: usize> Iterator for FixedStringIter<N> { - type Item = char; - - fn next(&mut self) -> Option<Self::Item> { - let pos = self.pos.as_mut()?; - - if *pos >= self.len { return None }; - - let item = self.buf[*pos]; - *pos += 0x1; - - Some(item) - } -} +use crate::use_mod; +use_mod!(pub deserialise_enum); +use_mod!(pub deserialise_struct); +use_mod!(pub serialise_enum); +use_mod!(pub serialise_struct); diff --git a/bzipper_macros/src/impls/serialise_enum.rs b/bzipper_macros/src/impls/serialise_enum.rs new file mode 100644 index 0000000..8f0693a --- /dev/null +++ b/bzipper_macros/src/impls/serialise_enum.rs @@ -0,0 +1,108 @@ +// 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::Capture; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataEnum, Fields, Ident, Token}; +use syn::punctuated::Punctuated; + +#[must_use] +pub fn serialise_enum(data: &DataEnum) -> TokenStream { + let mut sizes = Vec::new(); + + let mut match_arms = Punctuated::<TokenStream, Token![,]>::new(); + + for (index, variant) in data.variants.iter().enumerate() { + let mut serialised_size = Punctuated::<TokenStream, Token![+]>::new(); + + let name = &variant.ident; + + let discriminant = u32::try_from(index) + .expect("enumeration discriminants must be representable in `u32`"); + + // Discriminant size: + serialised_size.push(quote! { <u32 as ::bzipper::Serialise>::SERIALISED_SIZE }); + + let arm = if matches!(variant.fields, Fields::Unit) { + quote! { Self::#name => stream.append(&#discriminant)? } + } else { + let mut captures = Punctuated::<Capture, Token![,]>::new(); + + let mut chain_commands = Punctuated::<TokenStream, Token![;]>::new(); + chain_commands.push(quote! { stream.append(&#discriminant)? }); + + for (index, field) in variant.fields.iter().enumerate() { + let field_ty = &field.ty; + + let field_name = field.ident + .as_ref() + .map_or_else(|| Ident::new(&format!("v{index}"), Span::call_site()), Clone::clone); + + serialised_size.push(quote! { <#field_ty as ::bzipper::Serialise>::SERIALISED_SIZE }); + + captures.push(Capture { + ref_token: Token![ref](Span::call_site()), + ident: field_name.clone(), + }); + + chain_commands.push(quote! { stream.append(#field_name)? }); + } + + chain_commands.push_punct(Token![;](Span::call_site())); + + match variant.fields { + Fields::Named( ..) => quote! { Self::#name { #captures } => { #chain_commands } }, + Fields::Unnamed(..) => quote! { Self::#name(#captures) => { #chain_commands } }, + Fields::Unit => unreachable!(), + } + }; + + sizes.push(serialised_size); + match_arms.push(arm); + } + + let mut size_tests = Punctuated::<TokenStream, Token![else]>::new(); + + for size in &sizes { + let mut test = Punctuated::<TokenStream, Token![&&]>::new(); + + for other_size in &sizes { test.push(quote! { #size >= #other_size }) } + + size_tests.push(quote! { if #test { #size } }); + } + + size_tests.push(quote! { { core::unreachable!(); } }); + + quote! { + const SERIALISED_SIZE: usize = const { #size_tests }; + + fn serialise(&self, buf: &mut [u8]) -> ::bzipper::Result<()> { + ::core::debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = ::bzipper::Sstream::new(buf); + + match (*self) { #match_arms } + Ok(()) + } + } +} diff --git a/bzipper_macros/src/impls/serialise_struct.rs b/bzipper_macros/src/impls/serialise_struct.rs new file mode 100644 index 0000000..308a6bb --- /dev/null +++ b/bzipper_macros/src/impls/serialise_struct.rs @@ -0,0 +1,77 @@ +// 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 proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + DataStruct, + Fields, + Index, + Token, + punctuated::Punctuated +}; + +#[must_use] +pub fn serialise_struct(data: &DataStruct) -> TokenStream { + if matches!(data.fields, Fields::Unit) { + quote! { + const SERIALISED_SIZE: usize = 0x0; + + #[inline(always)] + fn serialise(&self, buf: &mut [u8]) -> ::bzipper::Result<()> { + ::core::debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + Ok(()) + } + } + } else { + let mut serialised_size = Punctuated::<TokenStream, Token![+]>::new(); + let mut chain_commands = Punctuated::<TokenStream, Token![;]>::new(); + + for (index, field) in data.fields.iter().enumerate() { + let ty = &field.ty; + + let name = field.ident + .as_ref() + .map_or_else(|| Index::from(index).to_token_stream(), ToTokens::to_token_stream); + + serialised_size.push(quote! { <#ty as ::bzipper::Serialise>::SERIALISED_SIZE }); + + chain_commands.push(quote! { stream.append(&self.#name)? }); + } + + chain_commands.push_punct(Token![;](Span::call_site())); + + quote! { + const SERIALISED_SIZE: usize = #serialised_size; + + fn serialise(&self, buf: &mut [u8]) -> ::bzipper::Result<()> { + ::core::debug_assert_eq!(buf.len(), Self::SERIALISED_SIZE); + + let mut stream = ::bzipper::Sstream::new(buf); + + #chain_commands + + Ok(()) + } + } + } +} diff --git a/bzipper_macros/src/lib.rs b/bzipper_macros/src/lib.rs new file mode 100644 index 0000000..f7979c8 --- /dev/null +++ b/bzipper_macros/src/lib.rs @@ -0,0 +1,102 @@ +// 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. +//! +//! This crate implements macros for the [`bzipper`](https://crates.io/crates/bzipper/) crate. + +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, parse_macro_input}; + +macro_rules! use_mod { + ($vis:vis $name:ident) => { + mod $name; + $vis use $name::*; + }; +} +pub(in crate) use use_mod; + +use_mod!(closure); +use_mod!(discriminant); +use_mod!(generic_name); + +mod impls; + +#[proc_macro_derive(Deserialise)] +pub fn derive_deserialise(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let impl_body = match input.data { + Data::Enum( ref data) => impls::deserialise_enum( data), + Data::Struct(ref data) => impls::deserialise_struct(data), + + Data::Union(..) => panic!("unions cannot derive `Deserialise`"), + }; + + let type_name = &input.ident; + + let generic_params = &input.generics.params; + let generic_where = &input.generics.where_clause; + + let generic_names = GenericName::extract_from(&input.generics); + + let output = quote! { + impl<#generic_params> ::bzipper::Deserialise for #type_name<#generic_names> + #generic_where { + #impl_body + } + }; + + output.into() +} + +#[proc_macro_derive(Serialise)] +pub fn derive_serialise(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let impl_body = match input.data { + Data::Enum( ref data) => impls::serialise_enum( data), + Data::Struct(ref data) => impls::serialise_struct(data), + + Data::Union(..) => panic!("unions cannot derive `Serialise`"), + }; + + let type_name = &input.ident; + + let generic_params = &input.generics.params; + let generic_where = &input.generics.where_clause; + + let generic_names = GenericName::extract_from(&input.generics); + + let output = quote! { + impl<#generic_params> ::bzipper::Serialise for #type_name<#generic_names> + #generic_where { + #impl_body + } + }; + + //if let Data::Enum(..) = input.data { panic!("{output}") }; + + output.into() +} diff --git a/doc-icon.svg b/doc-icon.svg index 5a6e1f8..bfea201 100644 --- a/doc-icon.svg +++ b/doc-icon.svg @@ -2,8 +2,13 @@ <mask id="z"> <rect fill="white" height="100%" rx="8" width="100%" x="0" y="0" /> - <polyline fill="none" points="20,28 20,20 76,20 36.970562748,76" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" /> - <polyline fill="none" points="76,68 76,76 20,76 59.029437252,20" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="8" /> + <rect fill="black" height="24" width="32" x="16" y="24" /> + <circle cx="48" cy="48" fill="white" r="16" /> + + <polygon fill="black" points="20,16 76,16 80,20 80,80 16,80 64,32 16,32 16,20" /> + <circle cx="20" cy="20" fill="black" r="4" /> + <circle cx="76" cy="20" fill="black" r="4" /> + <circle cx="80" cy="80" fill="white" r="16" /> </mask> <rect fill="#FFFFFF" height="100%" mask="url(#z)" width="100%" x="0" y="0" /> diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs deleted file mode 100644 index 3ce47bd..0000000 --- a/src/buffer/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -// 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 impl<T: Serialise>ied warranty of MERCHANTABILITY<T> 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, Dstream, Serialise, Sstream}; - -use alloc::vec; -use alloc::boxed::Box; -use core::fmt::{Debug, Formatter}; -use core::marker::PhantomData; - -/// Container type for (de)serialisations. -/// -/// The purpose of this type is to easily hold a buffer than can fit any serialisation of a given type (hence the generic). -/// -/// Do note that the internal buffer does not guarantee the state of any padding bytes that occur as a result of different serialisation sizes. -/// Deserialisations, however, are not affected by these. -#[derive(Clone, Eq, PartialEq)] -pub struct Buffer<T> { - data: Box<[u8]>, - len: usize, - - _phantom: PhantomData<T>, -} - -impl<T> Buffer<T> { - /// Sets the internal length of the buffer without checks. - /// - /// For a safe alternative, see [`set_len`](Self::set_len). - /// - /// # Safety - /// - /// The new length must **not** exceed [`T::SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT). - #[inline(always)] - pub unsafe fn set_len_unchecked(&mut self, len: usize) { - self.len = len; - } - - /// Returns a slice of the internal buffer. - /// - /// This only includes bytes written by the last serialisation, or as set by [`set_len`](Self::set_len). - #[inline(always)] - #[must_use] - pub fn as_slice(&self) -> &[u8] { &self.data[0x0..self.len] } - - /// Returns a mutable slice of the entire internal buffer. - /// - /// This is in contrast to [`as_slice`](Self::as_slice), which only yields the last serialisation. - /// - /// The length of bytes written to this slice should be set using [`set_len`](Self::set_len). - #[inline(always)] - #[must_use] - pub fn as_mut_slice(&mut self) -> &mut [u8] { self.data.as_mut() } -} - -impl<T: Serialise> Buffer<T> { - /// Constructs a new, empty buffer. - /// - /// The internal buffer is allocated on the heap instantly with the size [`T::SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT). - #[inline(always)] - #[must_use] - pub fn new() -> Self { Self { - data: vec![Default::default(); T::SERIALISE_LIMIT].into(), - len: 0x0, - - _phantom: PhantomData - } } - - /// Sets the length of the current serialisation. - /// - /// This is specifically meant for cases where the buffer is set externally, as is the case for networking: - /// - /// ``` - /// use bzipper::{Buffer, FixedString}; - /// use std::net::{SocketAddr, UdpSocket}; - /// use std::str::FromStr; - /// - /// let destination = SocketAddr::from_str("127.0.0.1:37279")?; - /// - /// let sender = UdpSocket::bind("0.0.0.0:0")?; - /// let reciever = UdpSocket::bind(destination)?; - /// - /// // Create a buffer for holding a fixed string. - /// let mut buffer = Buffer::<FixedString<0x10>>::new(); - /// - /// // Serialise and write the string: - /// buffer.write(&FixedString::new("Hello there!")?); - /// sender.send_to(buffer.as_ref(), destination); - /// - /// // Recieve and deserialise the string: - /// let (count, _source) = reciever.recv_from(buffer.as_mut_slice())?; - /// buffer.set_len(count); - /// - /// assert_eq!(buffer.read()?, "Hello there!"); - /// - /// # Ok::<(), Box<dyn std::error::Error>>(()) - /// ``` - /// - /// # Panics - /// - /// Panics if `len` is larger than [`T::SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT). - /// See [`set_len_unchecked`](Self::set_len_unchecked). - #[inline] - pub fn set_len(&mut self, len: usize) { - assert!(len <= T::SERIALISE_LIMIT); - self.len = len; - } -} - -impl<T: Serialise> Buffer<T> { - /// Serialises into the buffer. - /// - /// The result of [`serialise`](Serialise::serialise) is used as the length. - /// - /// # Panics - /// - /// Panics if the amount of written bytes exceeds [`SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT). - /// This *should*, in theory, not occur, as the internal buffer can only fit up to this limit, making all writes past this limit fail. - #[allow(clippy::panic_in_result_fn)] - pub fn write(&mut self, value: &T) -> Result<(), <T as Serialise>::Error> { - let mut stream = Sstream::new(&mut self.data); - - let count = value.serialise(&mut stream)?; - assert!(count <= T::SERIALISE_LIMIT); - - self.len = count; - Ok(()) - } -} - -impl<T: Deserialise> Buffer<T> { - /// Deserialises the contained buffer. - /// - /// Only bytes from the last serialisation, or as set by [`set_len`](Self::set_len), are used. - pub fn read(&self) -> Result<T, <T as Deserialise>::Error> { - let mut stream = Dstream::new(self.as_ref()); - T::deserialise(&mut stream) - } -} - -impl<T> AsRef<[u8]> for Buffer<T> { - #[inline(always)] - fn as_ref(&self) -> &[u8] { self.as_slice() } -} - -impl<T> Debug for Buffer<T> { - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { - self.data.fmt(f) - } -} - -impl<T: Serialise> Default for Buffer<T> { - #[inline(always)] - fn default() -> Self { Self::new() } -} diff --git a/src/deserialise/mod.rs b/src/deserialise/mod.rs deleted file mode 100644 index eaebe2b..0000000 --- a/src/deserialise/mod.rs +++ /dev/null @@ -1,512 +0,0 @@ -// 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, Dstream}; - -use alloc::boxed::Box; -use core::convert::Infallible; -use core::error::Error as StdError; -use core::mem::{MaybeUninit, size_of}; -use core::num::NonZero; -use core::ptr::read; - -/// Types capable of being deserialised. -pub trait Deserialise: Sized { - /// The error of deserialisation. - /// - /// Use [`Infallible`] if **all** deserialisations are infallible, as is the case of zero-length types. - type Error; - - /// Deserialises the byte stream to an object. - /// - /// This function should **not** take more bytes than specified by [`T::SERIALISE_LIMIT`](crate::Serialise::SERIALISE_LIMIT). - /// Doing so is considered a logic error. - /// - /// # Errors - /// - /// If deserialisation failed, e.g. by an invalid value being found, an error is returned. - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error>; -} - -macro_rules! impl_float { - ($type:ty) => { - impl Deserialise for $type { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let data = stream - .take(size_of::<Self>())? - .try_into() - .unwrap(); - - Ok(Self::from_be_bytes(data)) - } - } - }; -} - -macro_rules! impl_int { - ($type:ty) => { - impl Deserialise for $type { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let data = stream - .take(size_of::<Self>())? - .try_into() - .unwrap(); - - Ok(Self::from_be_bytes(data)) - } - } - }; -} - -macro_rules! impl_non_zero { - ($type:ty) => { - impl Deserialise for NonZero<$type> { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let value = <$type>::deserialise(stream)?; - - NonZero::new(value) - .ok_or(Error::NullInteger) - } - } - }; -} - -impl<T0, T1> Deserialise for (T0, T1) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2> Deserialise for (T0, T1, T2) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3> Deserialise for (T0, T1, T2, T3) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3, T4> Deserialise for (T0, T1, T2, T3, T4) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3, T4, T5> Deserialise for (T0, T1, T2, T3, T4, T5) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6> Deserialise for (T0, T1, T2, T3, T4, T5, T6) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6, T7> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, - T7: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Deserialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8) -where - T0: Deserialise<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, - T7: Deserialise<Error: StdError + 'static>, - T8: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -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<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, - T7: Deserialise<Error: StdError + 'static>, - T8: Deserialise<Error: StdError + 'static>, - T9: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -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<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, - T7: Deserialise<Error: StdError + 'static>, - T8: Deserialise<Error: StdError + 'static>, - T9: Deserialise<Error: StdError + 'static>, - T10: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -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<Error: StdError + 'static>, - T1: Deserialise<Error: StdError + 'static>, - T2: Deserialise<Error: StdError + 'static>, - T3: Deserialise<Error: StdError + 'static>, - T4: Deserialise<Error: StdError + 'static>, - T5: Deserialise<Error: StdError + 'static>, - T6: Deserialise<Error: StdError + 'static>, - T7: Deserialise<Error: StdError + 'static>, - T8: Deserialise<Error: StdError + 'static>, - T9: Deserialise<Error: StdError + 'static>, - T10: Deserialise<Error: StdError + 'static>, - T11: Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - Ok(( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - )) - } -} - -impl<T, const N: usize> Deserialise for [T; N] -where - T: Default + Deserialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let len = usize::deserialise(stream)?; - - if len != N { return Err(Box::new(Error::ArrayTooShort { req: len, len: N })) }; - - let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; - - // Deserialise t - for item in buf.iter_mut().take(len) { - item.write(Deserialise::deserialise(stream)?); - } - - for item in buf.iter_mut().skip(len) { - item.write(Default::default()); - } - - // This should be safe as `MaybeUninit<T>` is - // transparent to `T`. The original buffer is - // NOT dropped automatically, so we can just - // forget about it from this point on. - let buf = unsafe { read(core::ptr::from_ref(&buf).cast::<[T; N]>()) }; - Ok(buf) - } -} - -impl Deserialise for () { - type Error = Infallible; - - fn deserialise(_stream: &mut Dstream) -> Result<Self, Self::Error> { Ok(()) } -} - -impl Deserialise for bool { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let value = u8::deserialise(stream)?; - - match value { - 0x00 => Ok(false), - 0x01 => Ok(true), - _ => Err(Error::InvalidBoolean { value }) - } - } -} - -impl Deserialise for char { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let value = u32::deserialise(stream)?; - - Self::from_u32(value) - .ok_or(Error::InvalidCodePoint { value }) - } -} - -impl Deserialise for Infallible { - type Error = Self; - - fn deserialise(_stream: &mut Dstream) -> Result<Self, Self::Error> { unreachable!() } -} - -impl Deserialise for isize { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let value = i16::deserialise(stream)? - .into(); - - Ok(value) - } -} - -impl<T: Deserialise<Error: StdError + 'static>> Deserialise for Option<T> { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let sign = bool::deserialise(stream)?; - - if sign { - Ok(Some(T::deserialise(stream)?)) - } else { - Ok(None) - } - } -} - -impl<T: Deserialise, E: Deserialise> Deserialise for Result<T, E> -where - <T as Deserialise>::Error: StdError + 'static, - <E as Deserialise>::Error: StdError + 'static, { - type Error = Box<dyn StdError>; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let sign = bool::deserialise(stream)?; - - let value = if sign { - Err(E::deserialise(stream)?) - } else { - Ok(T::deserialise(stream)?) - }; - - Ok(value) - } -} - -impl Deserialise for usize { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let value = u16::deserialise(stream)? - .into(); - - Ok(value) - } -} - -impl_float!(f32); -impl_float!(f64); - -impl_int!(i128); -impl_int!(i16); -impl_int!(i32); -impl_int!(i64); -impl_int!(i8); -impl_int!(u128); -impl_int!(u16); -impl_int!(u32); -impl_int!(u64); -impl_int!(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/src/deserialise/test.rs b/src/deserialise/test.rs deleted file mode 100644 index e398b4d..0000000 --- a/src/deserialise/test.rs +++ /dev/null @@ -1,81 +0,0 @@ -// 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, Dstream, FixedString}; - -#[test] -fn test_deserialise() { - let data = [ - 0x00, 0xFF, 0xFF, 0x0F, 0xEF, 0x1F, 0xDF, 0x2F, - 0xCF, 0x3F, 0xBF, 0x4F, 0xAF, 0x5F, 0x9F, 0x6F, - 0x8F, 0x7F, 0x00, 0x09, 0x6D, 0xC3, 0xA1, 0x6E, - 0x61, 0xC3, 0xB0, 0x75, 0x72, 0x00, 0x05, 0x00, - 0x00, 0x03, 0xBB, 0x00, 0x00, 0x03, 0x91, 0x00, - 0x00, 0x03, 0xBC, 0x00, 0x00, 0x03, 0x94, 0x00, - 0x00, 0x03, 0xB1, 0x01, 0x00, 0x00, 0x01, 0x80, - ]; - - let mut stream = Dstream::new(&data); - - assert_eq!( - u8::deserialise(&mut stream).unwrap(), - 0x00, - ); - assert_eq!( - u8::deserialise(&mut stream).unwrap(), - 0xFF, - ); - - assert_eq!( - u128::deserialise(&mut stream).unwrap(), - 0xFF_0F_EF_1F_DF_2F_CF_3F_BF_4F_AF_5F_9F_6F_8F_7F, - ); - - assert_eq!( - FixedString::<0x10>::deserialise(&mut stream).unwrap(), - "m\u{00E1}na\u{00F0}ur", - ); - - assert_eq!( - <[char; 0x5]>::deserialise(&mut stream).unwrap(), - ['\u{03BB}', '\u{0391}', '\u{03BC}', '\u{0394}', '\u{03B1}'], - ); - - assert_eq!( - Option::<()>::deserialise(&mut stream).unwrap(), - Some(()), - ); - - assert_eq!( - Option::<()>::deserialise(&mut stream).unwrap(), - None, - ); - - assert_eq!( - Result::<(), i8>::deserialise(&mut stream).unwrap(), - Ok(()), - ); - - assert_eq!( - Result::<(), i8>::deserialise(&mut stream).unwrap(), - Err(i8::MIN), - ); -} diff --git a/src/dstream/mod.rs b/src/dstream/mod.rs deleted file mode 100644 index ca7f619..0000000 --- a/src/dstream/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -// 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}; - -use core::fmt::{Debug, Formatter}; - -/// Byte stream for deserialisation. -/// -/// This type borrows a byte slice (hence [`new`](Dstream::new)), keeping track internally of the used bytes. -#[derive(Clone)] -pub struct Dstream<'a> { - data: &'a [u8], - len: usize, -} - -impl<'a> Dstream<'a> { - /// Constructs a new byte stream. - #[inline(always)] - #[must_use] - pub fn new<T: AsRef<[u8]> + ?Sized>(buf: &'a T) -> Self { Self { - data: buf.as_ref(), - len: buf.as_ref().len(), - } } - - /// Takes bytes from the stream. - /// - /// # Errors - /// - /// If the internal buffer doesn't hold at least the requested amount of bytes, an [`EndOfStream`](Error::EndOfStream) error is returned. - pub fn take(&mut self, req: usize) -> Result<&[u8]> { - let rem = self.len; - - if rem < req { return Err(Error::EndOfStream { req, rem } ) } - - let start = self.data.len() - rem; - let stop = start + req; - - self.len -= req; - Ok(&self.data[start..stop]) - } - - /// Takes a single byte from the stream. - /// - /// # Errors - /// - /// If the internal buffer doesn't hold at least the requested amount of bytes, an [`EndOfStream`](Error::EndOfStream) error is returned. - pub fn take_byte(&mut self) -> Result<u8> { - const LEN: usize = 0x1; - - if self.len < LEN { return Err(Error::EndOfStream { req: LEN, rem: self.len } ) } - - self.len -= LEN; - - let index = self.data.len() - self.len; - Ok(self.data[index]) - } -} - -impl Debug for Dstream<'_> { - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { - self.data.fmt(f) - } -} - -impl<'a, T: AsRef<[u8]>> From<&'a T> for Dstream<'a> { - fn from(value: &'a T) -> Self { Self::new(value) } -} diff --git a/src/fixed_string/mod.rs b/src/fixed_string/mod.rs deleted file mode 100644 index b0342d3..0000000 --- a/src/fixed_string/mod.rs +++ /dev/null @@ -1,298 +0,0 @@ -// 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, - Dstream, - Error, - FixedStringIter, - Serialise, - Sstream, -}; - -use alloc::string::String; -use core::cmp::Ordering; -use core::fmt::{Debug, Display, Formatter}; -use core::ops::{Index, IndexMut}; -use core::str::FromStr; - -/// Owned string with maximum size. -/// -/// This is in contrast to [String], which has no size limit is practice, and [str], which is unsized. -#[derive(Clone)] -pub struct FixedString<const N: usize> { - buf: [char; N], - len: usize, -} - -impl<const N: usize> FixedString<N> { - /// Constructs a new fixed string. - /// - /// The contents of the provided string are copied into the internal buffer. - /// All residual characters are instanced as U+0000 `NULL`. - /// - /// # Errors - /// - /// If the given string `s` cannot fit into `N` characters, an [`ArrayTooShort`](Error::ArrayTooShort) error is returned. - pub fn new(s: &str) -> Result<Self, Error> { - let mut buf = ['\0'; 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 }) - } - - /// Returns the length of the string. - /// - /// This does not necessarily equal the value of `N`, as the internal buffer is not required to be used fully. - #[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 } - - /// Borrows the character at the specified index. - /// - /// If no element exists at that position, [`None`] is returned instead. - #[inline] - #[must_use] - pub const fn get(&self, index: usize) -> Option<&char> { - if index >= self.len { - None - } else { - Some(&self.buf[index]) - } - } - - /// Mutably borrows the character at the specified index. - /// - /// If no element exists at that position, [`None`] is returned instead. - #[inline] - #[must_use] - pub fn get_mut(&mut self, index: usize) -> Option<&mut char> { - if index >= self.len { - None - } else { - Some(&mut self.buf[index]) - } - } - - /// Returns an iterator to the contained characters. - #[inline(always)] - pub fn iter(&self) -> core::slice::Iter<'_, char> { self.buf[0x0..self.len].iter() } - - /// Returns a mutable iterator to the contained characters. - #[inline(always)] - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, char> { self.buf[0x0..self.len].iter_mut() } -} - -impl<const N: usize> Debug for FixedString<N> { - 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> Deserialise for FixedString<N> { - type Error = Error; - - fn deserialise(stream: &mut Dstream) -> Result<Self, Self::Error> { - let len = usize::deserialise(stream)?; - let data = stream.take(len)?; - - let s = core::str::from_utf8(data) - .map_err(|e| Error::BadString { source: e })?; - - let len = s.chars().count(); - - if len > N { - return Err(Error::ArrayTooShort { req: len, len: N }); - } - - let mut buf = ['\0'; N]; - for (i, c) in s.chars().enumerate() { - buf[i] = c; - } - - Ok(Self { buf, len }) - } -} - -impl<const N: usize> Default for FixedString<N> { - #[inline(always)] - fn default() -> Self { Self { - buf: ['\0'; N], - len: 0x0, - } } -} - -impl<const N: usize> Display for FixedString<N> { - 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> { - fn from(value: [char; N]) -> Self { Self { - buf: value, - len: N, - } } -} - -impl<const N: usize> FromStr for FixedString<N> { - type Err = Error; - - fn from_str(s: &str) -> Result<Self, Error> { Self::new(s) } -} - -impl<const N: usize> Index<usize> for FixedString<N> { - type Output = char; - - fn index(&self, index: usize) -> &Self::Output { self.get(index).unwrap() } -} - -impl<const N: usize> IndexMut<usize> for FixedString<N> { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { self.get_mut(index).unwrap() } -} - -impl<const N: usize> IntoIterator for FixedString<N> { - type Item = char; - - type IntoIter = FixedStringIter<N>; - - fn into_iter(self) -> Self::IntoIter { - FixedStringIter { - buf: self.buf, - len: self.len, - - pos: Some(0x0), - } - } -} - -impl<'a, const N: usize> IntoIterator for &'a FixedString<N> { - type Item = &'a char; - - type IntoIter = core::slice::Iter<'a, char>; - - 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>; - - fn into_iter(self) -> Self::IntoIter { self.iter_mut() } -} - -impl<const N: usize> Ord for FixedString<N> { - fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } -} - -impl<const N: usize, const M: usize> PartialEq<FixedString<M>> for FixedString<N> { - 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> { - fn eq(&self, other: &&str) -> bool { - for (i, c) in other.chars().enumerate() { - if self.buf.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)) => { - let ordering = lc.cmp(rc); - - if ordering != Ordering::Equal { return Some(ordering) }; - } - } - } - - Some(Ordering::Equal) - } -} - -impl<const N: usize> Serialise for FixedString<N> { - type Error = Error; - - const SERIALISE_LIMIT: usize = N * 0x4; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - let s: String = self.iter().collect(); - - count += s.len().serialise(stream)?; - count += stream.add(&s.into_bytes())?; - - Ok(count) - } -} - -impl<const N: usize> TryFrom<&str> for FixedString<N> { - type Error = Error; - - #[inline(always)] - fn try_from(value: &str) -> Result<Self, Self::Error> { Self::new(value) } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 4355f75..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,123 +0,0 @@ -// 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 does not require any dependencies at the moment. -//! It is also compatible with `no_std`. -//! -//! # Data model -//! -//! Most primitive types serialise losslessly, with the exception being [`usize`] and [`isize`]. -//! These serialise as [`u16`] and [`i16`], 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 work around streams (more specifically, [d-streams](Dstream) and [s-streams](Sstream)). -//! -//! Many core types come implemented with `bzipper`, including primitives as well as some standard library types such as [`Option`] and [`Result`](core::result::Result). -//! -//! ## Serialisation -//! -//! To serialise an object implementing `Serialise`, simply allocate a so-called "s-stream" (short for *serialisation stream*) with the [`Sstream`] type: -//! -//! ``` -//! let mut buf: [u8; 16] = Default::default(); -//! -//! let mut stream = bzipper::Sstream::new(&mut buf); -//! ``` -//! -//! The resulting stream is immutable in the sense that it cannot grow its buffer, altough it does keep track of the buffer's state. -//! -//! A byte sequence can be added to our new stream by passing the stream to a call to the [`serialise`](Serialise::serialise) method: -//! -//! ``` -//! use bzipper::Serialise; -//! -//! let mut buf: [u8; 2] = Default::default(); -//! let mut stream = bzipper::Sstream::new(&mut buf); -//! -//! 0x4554_u16.serialise(&mut stream).unwrap(); -//! ``` -//! -//! The ammount of bytes used by the serialiser (that is, the ammount of bytes written to the stream) is indicated by its return value (i.e. it has the type `Result<usize, Serialise::Error>`). -//! -//! Whilst the *maximum* ammount of bytes is specified by the [`SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT) constant, this can in cases be lower (for example with [`None`] variants which are always encoded as a single, null byte). -//! -//! 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. -//! -//! After serialisation, the s-stream records the new write-to position of the buffer. This allows for *chaining* of serialisations, which can prove useful when implementing the trait for custom types. -//! -//! ## Deserialisation -//! -//! As with serialisation, deserialisation uses streams (just with the [`Dstream`] type; short for *deserialisation stream*): -//! -//! ``` -//! let data = [0x45, 0x54]; -//! -//! let mut stream = bzipper::Dstream::new(&data); -//! ``` -//! -//! Using these streams is also just as simple as with s-streams: -//! -//! ``` -//! use bzipper::Deserialise; -//! -//! let data = [0x45, 0x54]; -//! let mut stream = bzipper::Dstream::new(&data); -//! -//! assert_eq!(u16::deserialise(&mut stream).unwrap(), 0x4554); -//! ``` -//! -//! When chaining serialisations, keep in mind that appropriate deserialisations should come in **reverse order** (streams function similarly to stacks in this sense). - -#![no_std] - -extern crate alloc; - -macro_rules! use_mod { - ($vis:vis $name:ident) => { - mod $name; - $vis use $name::*; - }; -} -pub(in crate) use use_mod; - -use_mod!(pub buffer); -use_mod!(pub deserialise); -use_mod!(pub dstream); -use_mod!(pub error); -use_mod!(pub fixed_string); -use_mod!(pub fixed_string_iter); -use_mod!(pub serialise); -use_mod!(pub sstream); diff --git a/src/serialise/mod.rs b/src/serialise/mod.rs deleted file mode 100644 index 16c8313..0000000 --- a/src/serialise/mod.rs +++ /dev/null @@ -1,676 +0,0 @@ -// 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, Sstream}; - -use alloc::boxed::Box; -use core::convert::Infallible; -use core::error::Error as StdError; -use core::mem::size_of; -use core::num::NonZero; - -/// Types capable of being serialised. -pub trait Serialise: Sized { - /// The error of serialisation. - /// - /// Use [`Infallible`] if **all** deserialisations are infallible, as is the case of zero-length types (such as [the unit type](unit)). - type Error; - - /// The maximum amount of bytes that can result from serialisation. - /// - /// Until derive macros are implemented, this value should be set to the sum of the members' own size limits (if chaining is used, that is): - /// - /// ``` - /// use bzipper::{Serialise, Sstream}; - /// - /// struct Foo { - /// bar: u16, - /// baz: f32, - /// } - /// - /// impl Serialise for Foo { - /// type Error = bzipper::Error; - /// - /// const SERIALISE_LIMIT: usize = u16::SERIALISE_LIMIT + f32::SERIALISE_LIMIT; - /// - /// fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - /// let mut count = 0x0; - /// - /// // Serialise fields using chaining. - /// count += self.bar.serialise(stream)?; - /// count += self.baz.serialise(stream)?; - /// - /// Ok(count) - /// } - /// } - /// ``` - /// - /// In the future, dervice macros will make manual chaining redundant. - const SERIALISE_LIMIT: usize; - - /// Serialises `self` into a byte stream. - /// - /// The number of bytes written is returned. - /// This should **not** exceed [`SERIALISE_LIMIT`](Serialise::SERIALISE_LIMIT), and doing so is considered a logic error. - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error>; -} - -macro_rules! impl_float { - ($type:ty) => { - impl Serialise for $type { - type Error = Error; - - const SERIALISE_LIMIT: usize = size_of::<$type>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let data = self.to_be_bytes(); - stream.add(&data)?; - - Ok(data.len()) - } - } - }; -} - -macro_rules! impl_int { - ($type:ty) => { - impl Serialise for $type { - type Error = Error; - - const SERIALISE_LIMIT: usize = size_of::<$type>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let data = self.to_be_bytes(); - stream.add(&data)?; - - Ok(data.len()) - } - } - }; -} - -macro_rules! impl_non_zero { - ($type:ty) => { - impl Serialise for NonZero<$type> { - type Error = <$type as Serialise>::Error; - - const SERIALISE_LIMIT: usize = size_of::<$type>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - self.get().serialise(stream) - } - } - }; -} - -impl<T0, T1> Serialise for (T0, T1) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2> Serialise for (T0, T1, T2) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3> Serialise for (T0, T1, T2, T3) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3, T4> Serialise for (T0, T1, T2, T3, T4) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3, T4, T5> Serialise for (T0, T1, T2, T3, T4, T5) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6> Serialise for (T0, T1, T2, T3, T4, T5, T6) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6, T7> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, - T7: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT - + T7::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - count += self.7.serialise(stream)?; - - Ok(count) - } -} - -impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Serialise for (T0, T1, T2, T3, T4, T5, T6, T7, T8) -where - T0: Serialise<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, - T7: Serialise<Error: StdError + 'static>, - T8: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT - + T7::SERIALISE_LIMIT - + T8::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - count += self.7.serialise(stream)?; - count += self.8.serialise(stream)?; - - Ok(count) - } -} - -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<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, - T7: Serialise<Error: StdError + 'static>, - T8: Serialise<Error: StdError + 'static>, - T9: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT - + T7::SERIALISE_LIMIT - + T8::SERIALISE_LIMIT - + T9::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - count += self.7.serialise(stream)?; - count += self.8.serialise(stream)?; - count += self.9.serialise(stream)?; - - Ok(count) - } -} - -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<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, - T7: Serialise<Error: StdError + 'static>, - T8: Serialise<Error: StdError + 'static>, - T9: Serialise<Error: StdError + 'static>, - T10: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT - + T7::SERIALISE_LIMIT - + T8::SERIALISE_LIMIT - + T9::SERIALISE_LIMIT - + T10::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - count += self.7.serialise(stream)?; - count += self.8.serialise(stream)?; - count += self.9.serialise(stream)?; - count += self.10.serialise(stream)?; - - Ok(count) - } -} - -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<Error: StdError + 'static>, - T1: Serialise<Error: StdError + 'static>, - T2: Serialise<Error: StdError + 'static>, - T3: Serialise<Error: StdError + 'static>, - T4: Serialise<Error: StdError + 'static>, - T5: Serialise<Error: StdError + 'static>, - T6: Serialise<Error: StdError + 'static>, - T7: Serialise<Error: StdError + 'static>, - T8: Serialise<Error: StdError + 'static>, - T9: Serialise<Error: StdError + 'static>, - T10: Serialise<Error: StdError + 'static>, - T11: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = - T0::SERIALISE_LIMIT - + T1::SERIALISE_LIMIT - + T2::SERIALISE_LIMIT - + T3::SERIALISE_LIMIT - + T4::SERIALISE_LIMIT - + T5::SERIALISE_LIMIT - + T6::SERIALISE_LIMIT - + T7::SERIALISE_LIMIT - + T8::SERIALISE_LIMIT - + T9::SERIALISE_LIMIT - + T10::SERIALISE_LIMIT - + T11::SERIALISE_LIMIT; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - count += self.0.serialise(stream)?; - count += self.1.serialise(stream)?; - count += self.2.serialise(stream)?; - count += self.3.serialise(stream)?; - count += self.4.serialise(stream)?; - count += self.5.serialise(stream)?; - count += self.6.serialise(stream)?; - count += self.7.serialise(stream)?; - count += self.8.serialise(stream)?; - count += self.9.serialise(stream)?; - count += self.10.serialise(stream)?; - count += self.11.serialise(stream)?; - - Ok(count) - } -} - -impl<T: Serialise<Error: StdError + 'static>, const N: usize> Serialise for [T; N] { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = T::SERIALISE_LIMIT * N; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - self.len().serialise(stream)?; - for v in self { count += v.serialise(stream)? } - - Ok(count) - } -} - -impl Serialise for () { - type Error = Infallible; - - const SERIALISE_LIMIT: usize = size_of::<Self>(); - - #[inline(always)] - fn serialise(&self, mut _stream: &mut Sstream) -> Result<usize, Self::Error> { - Ok(Self::SERIALISE_LIMIT) - } -} - -impl Serialise for bool { - type Error = Error; - - const SERIALISE_LIMIT: usize = size_of::<Self>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - u8::from(*self).serialise(stream) - } -} - -impl Serialise for char { - type Error = Error; - - const SERIALISE_LIMIT: usize = size_of::<Self>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - u32::from(*self).serialise(stream) - } -} - -// Especially useful for `Result<T, Infallible>`. -impl Serialise for Infallible { - type Error = Self; - - const SERIALISE_LIMIT: usize = size_of::<Self>(); - - fn serialise(&self, mut _stream: &mut Sstream) -> Result<usize, Self::Error> { unreachable!() } -} - -impl Serialise for isize { - type Error = Error; - - const SERIALISE_LIMIT: usize = size_of::<i16>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let value = i16::try_from(*self) - .map_err(|_| Error::IsizeOutOfRange { value: *self })?; - - value.serialise(stream) - } -} - -impl<T: Serialise<Error: StdError + 'static>> Serialise for Option<T> { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = T::SERIALISE_LIMIT + 0x1; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let mut count = 0x0; - - match *self { - None => { - count += false.serialise(stream)?; - // No need to zero-fill. - }, - - Some(ref v) => { - count += true.serialise(stream)?; - count += v.serialise(stream)?; - }, - }; - - Ok(count) - } -} - -impl<T, E> Serialise for core::result::Result<T, E> -where - T: Serialise<Error: StdError + 'static>, - E: Serialise<Error: StdError + 'static>, { - type Error = Box<dyn StdError>; - - const SERIALISE_LIMIT: usize = const { - if size_of::<T>() > size_of::<T>() { - size_of::<T>() - } else { - size_of::<E>() - } - }; - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - // Remember the descriminant. - let mut count = 0x0; - - match *self { - Ok(ref v) => { - count += false.serialise(stream)?; - count += v.serialise(stream)?; - }, - - Err(ref e) => { - count += true.serialise(stream)?; - count += e.serialise(stream)?; - }, - }; - - Ok(count) - } -} - -impl Serialise for usize { - type Error = Error; - - const SERIALISE_LIMIT: Self = size_of::<u16>(); - - fn serialise(&self, stream: &mut Sstream) -> Result<usize, Self::Error> { - let value = u16::try_from(*self) - .map_err(|_| Error::UsizeOutOfRange { value: *self })?; - - value.serialise(stream) - } -} - -impl_float!(f32); -impl_float!(f64); - -impl_int!(i128); -impl_int!(i16); -impl_int!(i32); -impl_int!(i64); -impl_int!(i8); -impl_int!(u128); -impl_int!(u16); -impl_int!(u32); -impl_int!(u64); -impl_int!(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/src/serialise/test.rs b/src/serialise/test.rs deleted file mode 100644 index 43f2b9f..0000000 --- a/src/serialise/test.rs +++ /dev/null @@ -1,72 +0,0 @@ -// 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, Serialise, Sstream}; - -use alloc::boxed::Box; -use alloc::vec; - -#[test] -fn test_serialise() { - let mut buf = vec![0x00; 0x50]; - let mut stream = Sstream::new(&mut buf); - - 0x00_u8.serialise(&mut stream).unwrap(); - 0xFF_u8.serialise(&mut stream).unwrap(); - 0x7F_u8.serialise(&mut stream).unwrap(); - - 0x0F_7E_u16.serialise(&mut stream).unwrap(); - - 0x00_2F_87_E7_u32.serialise(&mut stream).unwrap(); - - 0xF3_37_CF_8B_DB_03_2B_39_u64.serialise(&mut stream).unwrap(); - - 0x45_A0_15_6A_36_77_17_8A_83_2E_3C_2C_84_10_58_1A_u128.serialise(&mut stream).unwrap(); - - FixedString::<0x1>::new("A").unwrap().serialise(&mut stream).unwrap(); - FixedString::<0x8>::new("l\u{00F8}gma\u{00F0}ur").unwrap().serialise(&mut stream).unwrap(); - - ['\u{03B4}', '\u{0190}', '\u{03BB}', '\u{03A4}', '\u{03B1}'].serialise(&mut stream).unwrap(); - - Ok::<u16, char>(0x45_45).serialise(&mut stream).unwrap(); - Err::<u16, char>(char::REPLACEMENT_CHARACTER).serialise(&mut stream).unwrap(); - - None::<()>.serialise(&mut stream).unwrap(); - Some::<()>(()).serialise(&mut stream).unwrap(); - - let data: Box<[u8]> = buf.into(); - - assert_eq!( - data.as_ref(), - [ - 0x00, 0xFF, 0x7F, 0x0F, 0x7E, 0x00, 0x2F, 0x87, - 0xE7, 0xF3, 0x37, 0xCF, 0x8B, 0xDB, 0x03, 0x2B, - 0x39, 0x45, 0xA0, 0x15, 0x6A, 0x36, 0x77, 0x17, - 0x8A, 0x83, 0x2E, 0x3C, 0x2C, 0x84, 0x10, 0x58, - 0x1A, 0x00, 0x01, 0x41, 0x00, 0x0A, 0x6C, 0xC3, - 0xB8, 0x67, 0x6D, 0x61, 0xC3, 0xB0, 0x75, 0x72, - 0x00, 0x05, 0x00, 0x00, 0x03, 0xB4, 0x00, 0x00, - 0x01, 0x90, 0x00, 0x00, 0x03, 0xBB, 0x00, 0x00, - 0x03, 0xA4, 0x00, 0x00, 0x03, 0xB1, 0x00, 0x45, - 0x45, 0x01, 0x00, 0x00, 0xFF, 0xFD, 0x00, 0x01, - ], - ); -}
\ No newline at end of file diff --git a/src/sstream/mod.rs b/src/sstream/mod.rs deleted file mode 100644 index 83536d0..0000000 --- a/src/sstream/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -// 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}; - -use core::fmt::{Debug, Formatter}; - -/// Byte stream for serialisation. -/// -/// This type borrows a byte slice (hence [`new`](Sstream::new)), keeping track internally of the used bytes. -#[derive(Eq, PartialEq)] -pub struct Sstream<'a> { - data: &'a mut [u8], - len: usize -} - -impl<'a> Sstream<'a> { - /// Constructs a new byte stream. - /// - /// If the borrowed slice already contains data, this may overwritten by subsequent serialisations. - #[inline(always)] - #[must_use] - pub fn new(data: &'a mut [u8]) -> Self { Self { data, len: 0x0 } } - - /// Extends the byte stream. - /// - /// # Errors - /// - /// If the stream cannot hold the requested bytes, an [`EndOfStream`](Error::EndOfStream) instance is returned. - pub fn add(&mut self, extra: &[u8]) -> Result<usize> { - let rem = self.data.len() - self.len; - let req = extra.len(); - - if rem.checked_sub(req).is_none() { - return Err(Error::EndOfStream { req, rem }); - } - - let start = self.len; - let stop = self.len + req; - - self.len += req; - self.data[start..stop].copy_from_slice(extra); - - Ok(req) - } - - /// Extends the byte stream by a single byte. - /// - /// # Errors - /// - /// If the stream cannot hold the byte, an [`EndOfStream`](Error::EndOfStream) instance is returned. - pub fn add_byte(&mut self, extra: u8) -> Result<usize> { - self.add(&[extra]) - } - - /// Yields the length of the stream. - /// - /// That is, the amount of bytes written so far. - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { self.len } - - /// Tests if the stream is empty. - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { self.len == 0x0 } - - /// Returns a slice to the stream contents. - /// - /// This includes all previously written bytes. - #[inline(always)] - #[must_use] - pub fn as_slice(&self) -> &[u8] { &self.data[0x0..self.len] } -} - -impl AsRef<[u8]> for Sstream<'_> { - #[inline(always)] - fn as_ref(&self) -> &[u8] { self.as_slice() } -} - -impl Debug for Sstream<'_> { - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { - self.data.fmt(f) - } -} |