diff options
61 files changed, 6828 insertions, 2983 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a32a290..63d84ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,98 @@ # Changelog This is the changelog of bzipper. -See `"README.md"` for more information. +See `README.md` for more information. + +## 0.8.0 + +* Rename `FixedString` to `SizedStr` +* Implement `PartialEq<String>` and `PartialOrd<String>` for `SizedStr` +* Add constructors `from_utf8` and `from_utf8_unchecked` to `SizedStr` +* Remove `pop`, `push_str`, and `push` from `SizedStr` +* Implement `FromIterator<char>` for `SizedStr` +* Rename `Serialise` to `Encode` +* Rename `Deserialise` to `Decode` +* Remove `Sized` requirement for `Encode` +* Add benchmarks +* Update package metadata +* Rename `Sstream` to `OStream` +* Rename `Dstream` to `IStream` +* Update readme +* Refactor code +* Update lints +* Implement `Encode` and `Decode` for `IpAddr`, `Ipv4Addr`, `Ipv6Addr`, `Mutex`, `Box`, `RwLock`, `Rc`, `Arc`, `Wrapping`, `Saturating`, `AtomicBool`, `AtomicU8`, `AtomicU16`, `AtomicU32`, `AtomicU64`, `AtomicI8`, `AtomicI16`, `AtomicI32`, `AtomicI64`, `AtomicUsize`, `AtomicIsize`, `SocketAddrV4`, `SocketAddrV6`, `SocketAddr`, `Range`, `RangeFrom`, `RangeFull`, `RangeInclusive`, `RangeTo`, `RangeToInclusive`, `Bound`, `RefCell`, `String`, and `Vec` +* Update docs +* Add `SizedSlice` type +* Add `SizedIter` type +* Rename `Buffer` type to `Buf` +* Remove `Add` and `AddAssign` implementations from `SizedStr` +* Add *Features* section to readme +* Honour explicit enumeration discriminants +* Encode enumeration discriminants as `isize` +* Add `SizedEncode` trait +* Outsource `MAX_SERIALISED_SIZE` to `SizedEncode` as `MAX_ENCODED_SIZE` +* Implement `Iterator`, `ExactSizeIterator`, `FusedIterator`, and `DoubleEndedIterator` for `SizedIter` +* Implement `AsRef<[T]>` and `AsMut<[T]>` for `SizedIter<T, ..>` +* Implement `Clone` for `SizedIter` +* Add `as_slice` and `as_mut_slice` methods to `SizedIter` +* Add `from_raw_parts` constructor and `into_raw_parts` destructor to `SizedSlice` +* Add `set_len` method to `SizedSlice` +* Add `len`, `is_empty`, `is_full`, and `capacity` methods to `SizedSlice` +* Add `as_slice` and `as_mut_slice` methods to `SizedSlice` +* Add `as_ptr` and `as_mut_ptr` methods to `SizedSlice` +* Implement `AsMut<[T]>` and `AsRef<[T]>` for `SizedSlice<T, ..>` +* Implement `Borrow<[T]>` and `BorrowMut<[T]>` for `SizedSlice<T, ..>` +* Implement `Deref<[T]>` and `DerefMut<[T]>` for `SizedSlice<T, ..>` +* Implement `Debug` for `SizedSlice` +* Implement `Default` for `SizedSlice` +* Implement `Clone` for `SizedSlice` +* Implement `Encode`, `Decode`, and `SizedEncode` for `SizedSlice` +* Implement `Eq` and `PartialEq` for `SizedSlice` +* Implement `Ord` and `PartialOrd` for `SizedSlice` +* Implement `From<[T; N]>` for `SizedSlice<T, N>` +* Implement `Hash` for `SizedSlice` +* Implement `Index` and `IndexMut` for `SizedSlice` +* Implement `IntoIterator` for `SizedSlice` (including references hereto) +* Implement `TryFrom<&[T]>` for `SizedSlice<T, ..>` +* Implement `From<SizedSlice<T, ..>>` for `Vec<[T]>` +* Implement `From<SizedSlice<T, ..>>` for `Box<[T]>` +* Add `into_boxed_slice` and `into_vec` destructors to `SizedSlice` +* Add `into_boxed_str` and `into_string` destructors to `SizedStr` +* Bump Rust version to `1.83` for `bzipper` +* Mark `SizedStr::as_mut_ptr` as const +* Implement `FromIterator<T>` for `SizedSlice<T, ..>` +* Make `SizedStr::new` take a `&str` object +* Add `is_empty` and `is_full` methods to `Buf` +* Disallow non-empty single-line functions +* Add `SAFETY` comments +* Implement `PartialEq<&mut [u8]>` and `PartialEq<[u8]>` for `Buf` +* Implement `Index` and `IndexMut` for `Buf` +* Add `from_raw_parts` constructor and `into_raw_parts` destructor to `Buf` +* Add *Documentation* and *Contribution* sections to readme +* Add *Copyright & Licence* section to readme +* Add Clippy configuration file +* Add more unit tests +* Add debug assertions +* Remove `as_ptr` and `as_slice` methods from `IStream` and `OStream` +* Remove `len`, `is_empty`, and `is_full` methods from `IStream` and `OStream` +* Unimplement all manually-implemented traits from `IStream` and `OStream` +* Mark `new` and `write` in `OStream` as const +* Mark the `read` method in `IStream` as const +* Add `close` destructor to `OStream` and `IStream` +* Implement `Encode` for `[T]` and `str` +* Encode `usize` and `isize` as `u16` and `i16` again +* Split `Error` type into `EncodeError`, `DecodeError`, `Utf8Error`, `Utf16Error`, `SizeError`, and `StringError` +* Remove `Result` type +* Add `error` module +* Make `IStream::read` and `OSream::write` panic on error +* Update logo +* Add more examples to docs +* Unmark all functions in `Buf` as const +* Implement `From<SizedStr>` for `Box<str>` +* Always implement `Freeze`, `RefUnwindSafe`, `Send`, `Sync`, `Unpin`, and `UnwindSafe` for `Buf` +* Add *Examples* section to readme +* Implement `SizedEncode` for all previous `Encode` types +* Bump dependency versions ## 0.7.0 @@ -1,5 +1,5 @@ [workspace] -members = ["bzipper", "bzipper_macros"] +members = ["bzipper", "bzipper_benchmarks", "bzipper_macros"] resolver = "2" [workspace.package] @@ -9,6 +9,8 @@ readme = "README.md" homepage = "https://achernar.dk/index.php?p=bzipper" repository = "https://mandelbrot.dk/bzipper/" license = "LGPL-3.0-or-later" +keywords = ["api", "binary-encoding", "client", "encoding", "io", "network", "no-std", "protocol", "server"] +categories = ["encoding", "network-programming", "parsing"] [workspace.lints.clippy] as_ptr_cast_mut = "forbid" @@ -80,9 +82,7 @@ match_bool = "warn" match_on_vec_items = "warn" match_same_arms = "warn" mismatching_type_param_order = "warn" -missing_const_for_fn = "warn" mixed_read_write_in_expression = "deny" -must_use_candidate = "deny" mut_mut = "deny" mutex_atomic = "deny" mutex_integer = "deny" @@ -106,7 +106,6 @@ pattern_type_mismatch = "deny" ptr_as_ptr = "forbid" ptr_cast_constness = "forbid" pub_underscore_fields = "deny" -pub_with_shorthand = "deny" read_zero_byte_vec = "deny" redundant_clone = "deny" redundant_closure_for_method_calls = "warn" @@ -121,6 +120,7 @@ return_self_not_must_use = "deny" same_functions_in_if_condition = "deny" same_name_method = "deny" self_named_module_files = "deny" +separated_literal_suffix = "warn" single_char_pattern = "warn" str_split_at_newline = "warn" string_lit_as_bytes = "deny" @@ -138,7 +138,6 @@ unnecessary_self_imports = "deny" unnecessary_wraps = "warn" unneeded_field_pattern = "warn" unnested_or_patterns = "warn" -unseparated_literal_suffix = "warn" unused_async = "warn" unused_peekable = "warn" unused_rounding = "warn" @@ -149,6 +148,7 @@ useless_let_if_seq = "warn" verbose_bit_mask = "warn" verbose_file_reads = "warn" wildcard_dependencies = "deny" +zero_prefixed_literal = "allow" zero_sized_map_values = "deny" [profile.release] @@ -1,84 +1,119 @@ -# bzipper +# bZipper -[bzipper](https://crates.io/crates/bzipper/) is a binary (de)serialiser for the Rust language. +bZipper is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams -In contrast to [Serde](https://crates.io/crates/serde/)/[Bincode](https://crates.io/crates/bincode/), the primary 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. +What separates this crate from others such as [Bincode](https://crates.io/crates/bincode/) or [Postcard](https://crates.io/crates/postcard/) is that this crate is extensively optimised for *just* binary encodings (whilst the mentioned crates specifically use Serde and build on a more abstract data model). +The original goal of this project was specifically to guarantee size constraints for encodings on a per-type basis at compile-time. +Therefore, this crate may be more suited for networking or other cases where many allocations are unwanted. Keep in mind that this project is still work-in-progress. +Until the interfaces are stabilised, different facilities may be replaced, removed, or altered in a breaking way. This crate is compatible with `no_std`. +## Performance + +As bZipper is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. + +The `bzipper_benchmarks` binary compares multiple scenarios using bZipper and other, similar crates. +According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that bZipper outperform all of the tested crates -- as demonstrated in the following table: + +| Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | +| :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | +| `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | +| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | +| `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | +| `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | +| `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | +| **Total time** → | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | +| **Total deviation (p.c.)** → | +115 | +9 | ±0 | +446 | +10 | + +[Bincode]: https://crates.io/crates/bincode/ +[Borsh]: https://crates.io/crates/borsh/ +[Ciborium]: https://crates.io/crates/ciborium/ +[Postcard]: https://crates.io/crates/postcard/ + +All quantities are measured in seconds unless otherwise noted. +Please feel free to conduct your own tests of bZipper. + ## Data model -Most primitive types serialise losslessly, with the exception being `usize` and `isize`. -These serialise as `u32` and `i32`, respectively, for portability reasons. +Most primitives encode losslessly, with the main exceptions being `usize` and `isize`. +These are instead first cast as `u16` and `i16`, respectively, due to portability concerns (with respect to embedded systems). -Unsized types, such as `str` and slices, are not supported. -Instead, arrays should be used. -For strings, the `FixedString` type is also provided. +See specific types' implementations for notes on their data models. + +**Note that the data model is currently not stabilised,** and may not necessarily be in the near future (before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +It may therefore be undesired to store encodings long-term. ## Usage -This crate revolves around the `Serialise` and `Deserialise` traits, both of which use *streams* – or more specifically – s-streams and d-streams. +This crate revolves around the `Encode` and `Decode` traits which both handle conversions to and from byte streams. -Many core types come implemented with bzipper, including primitives as well as some standard library types such as `Option` and `Result`. +Many standard types come implemented with bZipper, including most primitives as well as some standard library types such as `Option` and `Result`. +Some [features](#features-flags) enable an extended set of implementations. -It is recommended in most cases to just derive these two traits for custom types (although this is only supported with enumerations and structures). +It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures – not untagged unions). Here, each field is *chained* according to declaration order: ```rust -use bzipper::{Buffer, Deserialise, Serialise}; +use bzipper::{Buf, Decode, Encode, SizedEncode}; -#[derive(Debug, Deserialise, PartialEq, Serialise)] +#[derive(Debug, Decode, PartialEq, SizedEncode)] struct IoRegister { addr: u32, value: u16, } -let mut buf = Buffer::new(); +let mut buf = Buf::new(); buf.write(IoRegister { addr: 0x04000000, value: 0x0402 }).unwrap(); assert_eq!(buf.len(), 0x6); -assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02]); +assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02].as_slice()); assert_eq!(buf.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 }); ``` -### Serialisation +### Buffer types + +The `Encode` and `Decode` traits both rely on streams for carrying the manipulated byte streams. -To serialise an object implementing `Serialise`, simply allocate a buffer for the serialisation and wrap it in an s-stream (*serialisation stream*) with the `Sstream` type. +These streams are separated into two type: *O-streams* (output streams) and *i-streams* (input streams). +Often, but not always, the `Buf` type is preferred over directly calling the `encode` and `decode` methods. + +### Encoding + +To encode an object directly using the `Encode` trait, simply allocate a buffer for the encoding and wrap it in an `OStream` object: ```rust -use bzipper::{Serialise, Sstream}; +use bzipper::{Encode, OStream, SizedEncode}; -let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE]; -let mut stream = Sstream::new(&mut buf); +let mut buf = [0x00; char::MAX_ENCODED_SIZE]; +let mut stream = OStream::new(&mut buf); -'Ж'.serialise(&mut stream).unwrap(); +'Ж'.encode(&mut stream).unwrap(); -assert_eq!(stream, [0x00, 0x00, 0x04, 0x16]); +assert_eq!(buf, [0x00, 0x00, 0x04, 0x16].as_slice()); ``` -The maximum size of any given serialisation is specified by the `MAX_SERIALISED_SIZE` constant. - -We can also use streams to chain multiple elements together: +Streams can also be used to chain multiple objects together: ```rust -use bzipper::{Serialise, Sstream}; +use bzipper::{Encode, OStream, SizedEncode}; -let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE * 0x5]; -let mut stream = Sstream::new(&mut buf); +let mut buf = [0x0; char::MAX_ENCODED_SIZE * 0x5]; +let mut stream = OStream::new(&mut buf); // Note: For serialising multiple characters, the -// `FixedString` type is usually preferred. +// `String` and `SizedStr` types are usually +// preferred. -'ل'.serialise(&mut stream).unwrap(); -'ا'.serialise(&mut stream).unwrap(); -'م'.serialise(&mut stream).unwrap(); -'د'.serialise(&mut stream).unwrap(); -'ا'.serialise(&mut stream).unwrap(); +'ل'.encode(&mut stream).unwrap(); +'ا'.encode(&mut stream).unwrap(); +'م'.encode(&mut stream).unwrap(); +'د'.encode(&mut stream).unwrap(); +'ا'.encode(&mut stream).unwrap(); assert_eq!(buf, [ 0x00, 0x00, 0x06, 0x44, 0x00, 0x00, 0x06, 0x27, @@ -87,38 +122,172 @@ assert_eq!(buf, [ ]); ``` -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. +If the encoded type additionally implements `SizedEncode`, then the maximum size of any encoding is guaranteed with the `MAX_ENCODED_SIZE` constant. -### Deserialisation +Numerical primitives are encoded in big endian (a.k.a. [network order](https://en.wikipedia.org/wiki/Endianness#Networking)) for... reasons. +It is recommended for implementors to follow this convention as well. -Deserialisation works with a similar syntax to serialisation. +### Decoding -D-streams (*deserialisation streams*) use the `Dstream` type and are constructed in a manner similar to s-streams. -To deserialise a buffer, simply call the `deserialise` method with the strema: +Decoding works with a similar syntax to encoding. +To decode a byte array, simply call the `decode` method with an `IStream` object: ```rust -use bzipper::{Deserialise, Dstream}; +use bzipper::{Decode, IStream}; let data = [0x45, 0x54]; -let stream = Dstream::new(&data); -assert_eq!(u16::deserialise(&stream).unwrap(), 0x4554); +let mut stream = IStream::new(&data); + +assert_eq!(u16::decode(&mut stream).unwrap(), 0x4554); + +// Data can theoretically be reinterpretred: + +stream = IStream::new(&data); + +assert_eq!(u8::decode(&mut stream).unwrap(), 0x45); +assert_eq!(u8::decode(&mut stream).unwrap(), 0x54); + +// Including as tuples: + +stream = IStream::new(&data); + +assert_eq!(<(u8, u8)>::decode(&mut stream).unwrap(), (0x45, 0x54)); ``` -And just like s-streams, d-streams can also be used to handle chaining: +## Examples + +A UDP server/client for geographic data: ```rust -use bzipper::{Deserialise, Dstream}; +use bzipper::{Buf, Decode, SizedEncode}; +use std::io; +use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; +use std::thread::spawn; + +// City, region, etc.: +#[derive(Clone, Copy, Debug, Decode, Eq, PartialEq, SizedEncode)] +enum Area { + AlQuds, + Byzantion, + Cusco, + Tenochtitlan, + // ... +} -let data = [0x45, 0x54]; -let stream = Dstream::new(&data); +// Client-to-server message: +#[derive(Debug, Decode, PartialEq, SizedEncode)] +enum Request { + AtmosphericHumidity { area: Area }, + AtmosphericPressure { area: Area }, + AtmosphericTemperature { area: Area }, + // ... +} + +// Server-to-client message: +#[derive(Debug, Decode, PartialEq, SizedEncode)] +enum Response { + AtmosphericHumidity(f64), + AtmosphericPressure(f64), // Pascal + AtmosphericTemperature(f64), // Kelvin + // ... +} + +struct Party { + pub socket: UdpSocket, + + pub request_buf: Buf::<Request>, + pub response_buf: Buf::<Response>, +} + +impl Party { + pub fn new<A: ToSocketAddrs>(addr: A) -> io::Result<Self> { + let socket = UdpSocket::bind(addr)?; + + let this = Self { + socket, + + request_buf: Buf::new(), + response_buf: Buf::new(), + }; -assert_eq!(u8::deserialise(&stream).unwrap(), 0x45); -assert_eq!(u8::deserialise(&stream).unwrap(), 0x54); + Ok(this) + } +} + +let mut server = Party::new("127.0.0.1:27015").unwrap(); + +let mut client = Party::new("0.0.0.0:0").unwrap(); + +spawn(move || { + let Party { socket, mut request_buf, mut response_buf } = server; + + // Recieve initial request from client. + + let (len, addr) = socket.recv_from(&mut request_buf).unwrap(); + request_buf.set_len(len); + + let request = request_buf.read().unwrap(); + assert_eq!(request, Request::AtmosphericTemperature { area: Area::AlQuds }); + + // Handle request and respond back to client. + + let response = Response::AtmosphericTemperature(44.4); // For demonstration's sake. + + response_buf.write(response).unwrap(); + socket.send_to(&response_buf, addr).unwrap(); +}); + +spawn(move || { + let Party { socket, mut request_buf, mut response_buf } = client; + + // Send initial request to server. + + socket.connect("127.0.0.1:27015").unwrap(); + + let request = Request::AtmosphericTemperature { area: Area::AlQuds }; + + request_buf.write(request); + socket.send(&request_buf).unwrap(); -// The data can also be deserialised as a tuple (up -// to twelve elements). + // Recieve final response from server. -let stream = Dstream::new(&data); -assert_eq!(<(u8, u8)>::deserialise(&stream).unwrap(), (0x45, 0x54)); + socket.recv(&mut response_buf).unwrap(); + + let response = response_buf.read().unwrap(); + assert_eq!(response, Response::AtmosphericTemperature(44.4)); +}); ``` + +## Feature flags + +bZipper defines the following features: + +* `alloc` (default): Enables the `Buf` type and implementations for e.g. `Box` and `Arc` +* `std` (default): Enables implementations for types such as `Mutex` and `RwLock` + +## Documentation + +bZipper has its documentation written in-source for use by `rustdoc`. +See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for an on-line, rendered instance. + +Currently, these docs make use of some unstable features for the sake of readability. +The nightly toolchain is therefore required when rendering them. + +## Contribution + +bZipper does not accept source code contributions at the moment. +This is a personal choice by the maintainer and may be undone in the future. + +Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/bzipper/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/bzipper/issues/) if you feel the need to express any concerns over the project. + +## Copyright & Licence + +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 52050cc..2d15f64 100644 --- a/bzipper.svg +++ b/bzipper.svg @@ -1,15 +1,8 @@ <svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> <mask id="z"> - <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" /> + <polygon fill="white" points="24,24 84,24 72,36 60,36 54,42 78,42 66,54 60,54 54,60 84,60 72,72 12,72 24,60 36,60 42,54 17,54 30,42 36,42 42,36 12,36" /> </mask> - <rect fill="#526F03" height="100%" width="100%" x="0" y="0" /> + <rect fill="#02764a" height="100%" width="100%" x="0" y="0" /> <!-- oklch(50% 0.115300 158.520) --> <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 index 28fae76..e2b502c 100644 --- a/bzipper/Cargo.toml +++ b/bzipper/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "bzipper" -version = "0.7.0" +version = "0.8.0" edition = "2021" -rust-version = "1.81" +rust-version = "1.83" documentation = "https://docs.rs/bzipper/" authors.workspace = true @@ -11,6 +11,8 @@ readme.workspace = true homepage.workspace = true repository.workspace = true license.workspace = true +keywords.workspace = true +categories.workspace = true [package.metadata.docs.rs] all-features = true @@ -22,7 +24,7 @@ alloc = [] std = [] [dependencies] -bzipper_macros = { path = "../bzipper_macros", version = "0.7.0"} +bzipper_macros = { path = "../bzipper_macros", version = "0.8.0" } [lints] workspace = true diff --git a/bzipper/src/buf/mod.rs b/bzipper/src/buf/mod.rs new file mode 100644 index 0000000..d567760 --- /dev/null +++ b/bzipper/src/buf/mod.rs @@ -0,0 +1,415 @@ +// 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::{ + Decode, + Encode, + IStream, + OStream, + SizedEncode, +}; +use crate::error::{DecodeError, EncodeError}; + +use alloc::boxed::Box; +use alloc::vec; +use core::borrow::{Borrow, BorrowMut}; +use core::fmt::{self, Debug, Formatter}; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut, Index, IndexMut}; +use core::ptr::{self, copy_nonoverlapping}; +use core::slice::{self, SliceIndex}; + +/// Typed encode buffer. +/// +/// This structure is intended as a lightweight byte buffer suitable for encoding a single, predefined type. +/// +/// The methods [`write`](Self::write) and [`read`](Self::read) can be used to handle the buffer's contents. +/// Other methods also exist for accessing the contents directly. +/// +/// # Examples +/// +/// Create a buffer for holding a `Request` enumeration: +/// +/// ``` +/// use bzipper::{Buf, SizedEncode, SizedStr}; +/// +/// #[derive(SizedEncode)] +/// enum Request { +/// Join { username: SizedStr<0x40> }, +/// +/// Quit { username: SizedStr<0x40> }, +/// +/// SendMessage { message: SizedStr<0x80> }, +/// } +/// +/// let mut buf = Buf::new(); +/// +/// buf.write(Request::Join { username: "epsiloneridani".parse().unwrap() }).unwrap(); +/// assert_eq!(buf.as_slice(), b"\0\0\0\x0Eepsiloneridani"); +/// +/// // Do something with the buffer... +/// ``` +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +pub struct Buf<T> { + buf: Box<[u8]>, + len: usize, + + _ty: PhantomData<fn() -> T>, +} + +impl<T> Buf<T> { + /// Allocates a new buffer suitable for encoding. + /// + /// The given capacity should be large enough to hold any expected encoding of `T`. + /// + /// If `T` implements [`SizedEncode`], it is usually preferred to instead use the [`new`](Self::new) constructor as it reserves enough space for *any* arbitrary encoding (according to [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE)): + #[inline] + #[must_use] + pub fn with_capacity(cap: usize) -> Self { + let buf = vec![0x00; cap].into(); + + Self { + buf, + len: 0x0, + + _ty: PhantomData, + } + } + + /// Constructs a new buffer from raw parts. + /// + /// # Safety + /// + /// The provided pointer `ptr` must be a valid reference to a mutable array of exactly `capacity` elements. + /// This array must additionally be allocated with the global allocator using the default layout (i.e. with a specified alignement of `1`), and `len` must also be within the bounds of this array. + #[inline] + #[must_use] + pub unsafe fn from_raw_parts(ptr: *mut u8, cap: usize, len: usize) -> Self { + let buf = { + let buf = ptr::slice_from_raw_parts_mut(ptr, cap); + + Box::from_raw(buf) + }; + + Self { + buf, + len, + + _ty: PhantomData, + } + } + + /// Gets a pointer to the first byte of the buffer. + /// + /// Note that the all reads to bytes up to the amount specified by [`capacity`](Self::capacity) are valid (i.e. the bytes are always initialised). + #[inline(always)] + #[must_use] + pub fn as_ptr(&self) -> *const u8 { + self.buf.as_ptr() + } + + /// Gets a mutable pointer to the first byte of the buffer. + /// + /// Note that the all reads to bytes up to the amount specified by [`capacity`](Self::capacity) are valid (i.e. the bytes are always initialised). + #[inline(always)] + #[must_use] + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.buf.as_mut_ptr() + } + + /// Gets a slice of the buffer. + /// + /// The returned slice will only include the used part of the buffer (as specified by [`len`](Self::len)). + /// This is in contrast to [`as_mut_slice`](Self::as_mut_slice), which references the entire buffer. + #[inline(always)] + #[must_use] + pub fn as_slice(&self) -> &[u8] { + // SAFETY: References always contain valid values. + unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) } + } + + /// Gets a mutable slice of the buffer. + /// + /// Contrary to [`as_slice`](Self::as_slice), this method returns a slice of the **entire** buffer (as specified by [`capacity`](Self::capacity)). + /// + /// Users should call [`set_len`](Self::set_len) if writing has modified the portion of used bytes. + #[inline(always)] + #[must_use] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + // SAFETY: Our pointer is a valid reference. + unsafe { slice::from_raw_parts_mut(self.as_mut_ptr(), self.capacity()) } + } + + /// Copies data from another slice. + /// + /// The length of `self` is updated to reflect the new data. + /// + /// If `self` cannot contain the entirety of `data` then this method will panic. + #[inline] + pub fn copy_from_slice(&mut self, data: &[u8]) { + let len = data.len(); + + assert!(len <= self.capacity(), "buffer cannot contain source slice"); + + unsafe { + let src = data.as_ptr(); + let dst = self.as_mut_ptr(); + + // SAFETY: The pointers are guaranteed to be valid + // and the length has been tested. `dst` also guaran- + // tees exclusivity due to be a mutable reference. + copy_nonoverlapping(src, dst, len); + + // SAFETY: We have asserted bounds. + self.set_len_unchecked(len); + } + } + + /// Sets the length of the buffer. + /// + /// The provided size is checked before being written (i.e. `len` may not be greater than [`len`](Self::len)). + /// For the same operation *without* these checks, see [`set_len_unchecked`](Self::set_len_unchecked). + /// + /// # Panics + /// + /// The provided size must not be greater than the buffer's capacity. + /// If this is the case, however, this method will panic. + #[inline(always)] + pub fn set_len(&mut self, len: usize) { + assert!(len <= self.capacity(), "cannot extend buffer beyond capacity"); + + // SAFETY: The length has been tested. + unsafe { self.set_len_unchecked(len) } + } + + /// Sets the length of the buffer without checks. + /// + /// The provided size is **not** tested before being written. + /// For the same operation *with* checks, see [`set_len`](Self::set_len). + /// + /// # Safety + /// + /// The value of `len` may never be greater than the capacity of the buffer. + /// Exceeding this will yield undefined behaviour. + #[inline(always)] + pub unsafe fn set_len_unchecked(&mut self, len: usize) { + debug_assert!(len <= self.capacity(), "cannot extend buffer beyond capacity"); + + // SAFETY: The length has been guaranteed by the + // caller. + self.len = len; + } + + /// Retrieves the capacity of the buffer. + /// + /// If the buffer was constructed using [`new`](Self::new), this value is exactly equal to that of [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE). + /// In other cases, however, this may either be greater or less than this value. + #[inline(always)] + #[must_use] + pub fn capacity(&self) -> usize { + self.buf.len() + } + + /// Retrieves the length of the buffer. + /// + /// This value specifically denotes the length of the previous encoding (if any). + /// + /// For retrieving the capacity of the buffer, see [`capacity`](Self::capacity). + #[inline(always)] + #[must_use] + pub fn len(&self) -> usize { + self.len + } + + /// Tests if the buffer is empty. + /// + /// This is strictly equivalent to testing if [`len`](Self::len) is null. + #[inline(always)] + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0x0 + } + + /// Tests if the buffer is full. + /// + /// This is strictly equivalent to testing if [`len`](Self::len) is equal to [`capacity`](Self::capacity). + #[inline(always)] + #[must_use] + pub fn is_full(&self) -> bool { + self.len() == self.capacity() + } +} + +impl<T: Encode> Buf<T> { + /// Encodes an object into the buffer. + /// + /// The object is encoded as by being passed to <code><T as [Encode]>::[encode](Encode::encode)</code>. + /// + /// # Errors + /// + /// Any error that occurs during encoding is passed on and returned from this method. + #[inline] + pub fn write<U: Borrow<T>>(&mut self, value: U) -> Result<(), EncodeError> { + let mut stream = OStream::new(&mut self.buf); + + value.borrow().encode(&mut stream)?; + + let len = stream.close(); + self.set_len(len); + + Ok(()) + } +} + +impl<T: Decode> Buf<T> { + /// Decodes an object from the buffer. + /// + /// This is done as by passing the contained bytes to <code><T as [Decode]>::[decode](Decode::decode)</code>. + /// + /// Note that only the bytes specified by [`len`](Self::len) are passed in this call. + /// See [`as_slice`](Self::as_slice) for more information. + /// + /// # Errors + /// + /// Any error that occurs during decoding is passed on and returned from this method. + #[inline] + pub fn read(&self) -> Result<T, DecodeError> { + // We should only pass the used part of the buffer + // to `deserialise`. + + let mut stream = IStream::new(&self.buf); + + let value = Decode::decode(&mut stream)?; + Ok(value) + } +} + +impl<T: SizedEncode> Buf<T> { + /// Allocates a new buffer suitable for encoding. + /// + /// The capacity of the buffer is set so that any encoding of `T` may be stored (as specified by [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE)). + /// See also the [`with_capacity`](Self::with_capacity) constructor. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::with_capacity(T::MAX_ENCODED_SIZE) + } +} + +/// See also [`as_mut_slice`](Self::as_mut_slice). +impl<T> AsMut<[u8]> for Buf<T> { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8] { + self.as_mut_slice() + } +} + +/// See also [`as_slice`](Self::as_slice). +impl<T> AsRef<[u8]> for Buf<T> { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +/// See also [`as_slice`](Self::as_slice). +impl<T> Borrow<[u8]> for Buf<T> { + #[inline(always)] + fn borrow(&self) -> &[u8] { + self.as_slice() + } +} + +/// See also [`as_mut_slice`](Self::as_mut_slice). +impl<T> BorrowMut<[u8]> for Buf<T> { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut [u8] { + self.as_mut_slice() + } +} + +impl<T> Debug for Buf<T> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{:?}", self.as_slice()) } +} + +impl<T: SizedEncode> Default for Buf<T> { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl<T> Deref for Buf<T> { + type Target = [u8]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl<T> DerefMut for Buf<T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl<T, I: SliceIndex<[u8]>> Index<I> for Buf<T> { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<T, I: SliceIndex<[u8]>> IndexMut<I> for Buf<T> { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +impl<T> PartialEq<[u8]> for Buf<T> { + #[inline(always)] + fn eq(&self, other: &[u8]) -> bool { + self.as_slice() == other + } +} + +impl<T> PartialEq<&[u8]> for Buf<T> { + #[inline(always)] + fn eq(&self, other: &&[u8]) -> bool { + self.as_slice() == *other + } +} + +impl<T> PartialEq<&mut [u8]> for Buf<T> { + #[inline(always)] + fn eq(&self, other: &&mut [u8]) -> bool { + self.as_slice() == *other + } +} diff --git a/bzipper/src/buf/test.rs b/bzipper/src/buf/test.rs new file mode 100644 index 0000000..eec22df --- /dev/null +++ b/bzipper/src/buf/test.rs @@ -0,0 +1,47 @@ +// 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 bzipper::Buf; +use bzipper::error::DecodeError; + +#[test] +fn test_buf_write_read() { + let mut buf = Buf::<char>::new(); + + macro_rules! test_read { + ($pattern:pat$(,)?) => {{ + match buf.read() { + $pattern => { } + + value => panic!("value `{value:?}` does not match pattern `{}`", stringify!($pattern)), + } + }}; + } + + buf.write('\u{1F44D}').unwrap(); + assert_eq!(buf, [0x00, 0x01, 0xF4, 0x4D].as_slice()); + + buf.copy_from_slice(&[0x00, 0x00, 0xD8, 0x00]); + test_read!(Err(DecodeError::InvalidCodePoint(0xD800))); + + buf.copy_from_slice(&[0x00, 0x00, 0xFF, 0x3A]); + test_read!(Ok('\u{FF3A}')); +} diff --git a/bzipper/src/buffer/mod.rs b/bzipper/src/buffer/mod.rs deleted file mode 100644 index c0da902..0000000 --- a/bzipper/src/buffer/mod.rs +++ /dev/null @@ -1,249 +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, Result, Serialise, Sstream}; - -use alloc::vec; -use alloc::boxed::Box; -use core::borrow::Borrow; -use core::fmt::{Debug, Formatter}; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; - -/// Typed (de)serialisation buffer. -/// -/// This structure is intended as a lightweight wrapper around byte buffers for specific (de)serialisations of specific types. -/// -/// The methods [`write`](Self::write) and [`read`](Self::read) can be used to handle the internal buffer. -/// Other methods exist for accessing the internal buffer directly. -/// -/// # Examples -/// -/// Create a buffer for holding a `Request` enumeration: -/// -/// ```rust -/// use bzipper::{Buffer, FixedString, Serialise}; -/// -/// #[derive(Serialise)] -/// enum Request { -/// Join { username: FixedString<0x40> }, -/// -/// Quit { username: FixedString<0x40> }, -/// -/// SendMessage { message: FixedString<0x80> }, -/// } -/// -/// use Request::*; -/// -/// let join_request = Join { username: FixedString::try_from("epsiloneridani").unwrap() }; -/// -/// let mut buf = Buffer::new(); -/// buf.write(join_request); -/// -/// // Do something with the buffer... -/// ``` -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -#[derive(Clone, Eq, PartialEq)] -pub struct Buffer<T> { - buf: Box<[u8]>, - len: usize, - - _phanton: PhantomData<T> -} - -impl<T> Buffer<T> { - /// Allocates a new buffer suitable for serialisation. - /// - /// The given capacity should be large enough to hold any expected serialisation of `T`. - /// Therefore, if `T` implements [`Serialise`], it is recommended to use [`new`](Self::new) instead, which is equivalent to passing [`MAX_SERIALISED_SIZE`](Serialise::MAX_SERIALISED_SIZE) to this function: - #[inline] - #[must_use] - pub fn with_capacity(len: usize) -> Self { - Self { - buf: vec![0x00; len].into(), - len: 0x0, - - _phanton: PhantomData, - } - } - - /// Sets the length of the used buffer. - /// - /// The provided size is checked before being written. - /// For the same operation *without* checks, see [`set_len_unchecked`](Self::set_len_unchecked). - /// - /// # Panics - /// - /// The provided size must not be greater than the buffer's capacity. - /// If this is the case, however, this method will panic. - #[inline(always)] - pub fn set_len(&mut self, len: usize) { - assert!(len <= self.capacity(), "cannot extend buffer beyond capacity"); - - self.len = len; - } - - /// Sets the length of the used buffer without checks. - /// - /// The validity of the provided size is **not** checked before being written. - /// For the same operation *with* checks, see [`set_len`](Self::set_len). - /// - /// # Safety - /// - /// If the value of `len` is greater than the buffer's capacity, behaviour is undefined. - #[inline(always)] - pub unsafe fn set_len_unchecked(&mut self, len: usize) { self.len = len } - - /// Retrieves a pointer to the first byte of the internal buffer. - #[inline(always)] - #[must_use] - pub const fn as_ptr(&self) -> *const u8 { self.buf.as_ptr() } - - /// Retrieves a mutable pointer to the first byte of the internal buffer. - #[inline(always)] - #[must_use] - pub fn as_mut_ptr(&mut self) -> *mut u8 { self.buf.as_mut_ptr() } - - /// Gets a slice of the internal buffer. - /// - /// The returned slice will only include the used part of the buffer (as specified by [`len`](Self::len)). - #[inline(always)] - #[must_use] - pub const fn as_slice(&self) -> &[u8] { unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } } - - /// Gets a mutable slice of the internal buffer. - /// - /// In contrast to [`as_slice`](Self::as_slice), this method returns a slice of the **entire** internal buffer. - /// - /// If the returned reference is written through, the new buffer length -- if different -- should be set using [`set_len`](Self::set_len). - #[inline(always)] - #[must_use] - pub fn as_mut_slice(&mut self) -> &mut [u8] { &mut self.buf } - - /// Gets the length of the buffer. - #[allow(clippy::len_without_is_empty)] - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { self.len } - - /// Gets the capacity of the buffer. - /// - /// If the buffer was constructed using [`new`](Self::new), this value is exactly the same as [`MAX_SERIALISED_SIZE`](Serialise::MAX_SERIALISED_SIZE). - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { self.buf.len() } -} - -impl<T: Serialise> Buffer<T> { - /// Allocates a new buffer suitable for serialisation. - /// - /// The capacity of the internal buffer is set so that any serialisation of `T` may be stored. - /// - /// This is equivalent to calling [`with_capacity`](Self::with_capacity) with [`MAX_SERIALISED_SIZE`](Serialise::MAX_SERIALISED_SIZE). - #[inline(always)] - #[must_use] - pub fn new() -> Self { Self::with_capacity(T::MAX_SERIALISED_SIZE) } - - /// Serialises into the contained buffer. - /// - /// # Errors - /// - /// Any error that occurs during serialisation is passed on and returned from this method. - /// - /// # Panics - /// - /// If the amount of bytes read by [`serialise`](Serialise::serialise) is greater than that specified by [`MAX_SERIALISED_SIZE`](Serialise::MAX_SERIALISED_SIZE), this method panics. - /// - /// In reality, however, this error can only be detected if the buffer's capacity is set to a value greater than `MAX_SERIALISED_SIZE` to begin with (e.g. using [`with_capacity`](Self::with_capacity)). - #[inline(always)] - pub fn write<U: Borrow<T>>(&mut self, value: U) -> Result<()> { - let mut stream = Sstream::new(&mut self.buf); - value.borrow().serialise(&mut stream)?; - - assert!(stream.len() <= T::MAX_SERIALISED_SIZE); - self.len = stream.len(); - - Ok(()) - } -} - -impl<T: Deserialise> Buffer<T> { - /// Deserialises from the contained buffer. - /// - /// # Errors - /// - /// Any error that occurs during deserialisation is passed on and returned from this method. - #[inline(always)] - pub fn read(&self) -> Result<T> { - // We should only pass the used part of the buffer - // to `deserialise`. - - let stream = Dstream::new(&self.buf[0x0..self.len()]); - let value = Deserialise::deserialise(&stream)?; - - Ok(value) - } -} - -impl<T> AsMut<[u8]> for Buffer<T> { - #[inline(always)] - fn as_mut(&mut self) -> &mut [u8] { self.as_mut_slice() } -} - -impl<T> AsRef<[u8]> for Buffer<T> { - #[inline(always)] - fn as_ref(&self) -> &[u8] { self.as_slice() } -} - -impl<T> Debug for Buffer<T> { - #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { write!(f, "{:?}", self.as_slice()) } -} - -impl<T: Serialise> Default for Buffer<T> { - #[inline(always)] - fn default() -> Self { Self::new() } -} - -impl<T> Deref for Buffer<T> { - type Target = [u8]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { self.as_slice() } -} - -impl<T> DerefMut for Buffer<T> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } -} - -impl<T> PartialEq<&[u8]> for Buffer<T> { - #[inline(always)] - fn eq(&self, other: &&[u8]) -> bool { self.as_slice() == *other } -} - -impl<T, const N: usize> PartialEq<[u8; N]> for Buffer<T> { - #[inline(always)] - fn eq(&self, other: &[u8; N]) -> bool { self.as_slice() == other.as_slice() } -} diff --git a/bzipper/src/buffer/test.rs b/bzipper/src/buffer/test.rs deleted file mode 100644 index e92ae4b..0000000 --- a/bzipper/src/buffer/test.rs +++ /dev/null @@ -1,36 +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::{Buffer, Error}; - -#[test] -fn test_buffer() { - let mut buf = Buffer::<char>::new(); - - buf.write('\u{1F44D}').unwrap(); - assert_eq!(buf, [0x00, 0x01, 0xF4, 0x4D].as_slice()); - - buf.as_mut_slice().copy_from_slice(&[0x00, 0x00, 0xD8, 0x00]); - assert!(matches!(buf.read(), Err(Error::InvalidCodePoint(0xD800)))); - - buf.as_mut_slice().copy_from_slice(&[0x00, 0x00, 0xFF, 0x3A]); - assert_eq!(buf.read().unwrap(), '\u{FF3A}'); -} diff --git a/bzipper/src/decode/mod.rs b/bzipper/src/decode/mod.rs new file mode 100644 index 0000000..6a0c35d --- /dev/null +++ b/bzipper/src/decode/mod.rs @@ -0,0 +1,577 @@ +// 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::{IStream, SizedEncode}; +use crate::error::{DecodeError, Utf8Error}; + +use core::convert::Infallible; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::net::{ + IpAddr, + Ipv4Addr, + Ipv6Addr, + SocketAddr, + SocketAddrV4, + SocketAddrV6, +}; +use core::num::{NonZero, Saturating, Wrapping}; +use core::ops::{ + Bound, + Range, + RangeFrom, + RangeFull, + RangeInclusive, + RangeTo, + RangeToInclusive, +}; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(feature = "alloc")] +use alloc::rc::Rc; + +#[cfg(feature = "alloc")] +use alloc::sync::Arc; + +#[cfg(feature = "std")] +use std::sync::{Mutex, RwLock}; + +mod tuple; + +// Should we require `Encode` for `Decode`? + +/// Denotes a type capable of being decoded. +pub trait Decode: Sized { + /// Decodes an object from the provided stream. + /// + /// # Errors + /// + /// If decoding fails due to e.g. an invalid byte sequence in the stream, then an error should be returned. + fn decode(stream: &mut IStream) -> Result<Self, DecodeError>; +} + +macro_rules! impl_numeric { + ($ty:ty$(,)?) => { + impl ::bzipper::Decode for $ty { + #[inline] + fn decode(stream: &mut IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> { + let data = stream + .read(Self::MAX_ENCODED_SIZE) + .try_into() + .expect(concat!("mismatch between `", stringify!($ty), "::MAX_ENCODED_SIZE` and buffer needed by `", stringify!($ty), "::from_be_bytes`")); + + let this = Self::from_be_bytes(data); + Ok(this) + } + } + }; +} + +macro_rules! impl_non_zero { + ($ty:ty$(,)?) => { + impl ::bzipper::Decode for NonZero<$ty> { + #[inline] + fn decode(stream: &mut IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> { + let value = <$ty as ::bzipper::Decode>::decode(stream)?; + + let this = NonZero::new(value) + .ok_or(::bzipper::error::DecodeError::NullInteger)?; + + Ok(this) + } + } + }; +} + +macro_rules! impl_atomic { + { + width: $width:literal, + ty: $ty:ty$(,)? + } => { + #[cfg(target_has_atomic = $width)] + #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] + impl ::bzipper::Decode for $ty { + #[inline(always)] + fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> { + Ok(Self::new(::bzipper::Decode::decode(stream)?)) + } + } + }; +} + +impl<T: Decode, const N: usize> Decode for [T; N] { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + // Initialise the array incrementally. + + let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for item in &mut buf { + let value = Decode::decode(stream)?; + item.write(value); + } + + // 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 this = unsafe { buf.as_ptr().cast::<[T; N]>().read() }; + Ok(this) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Decode> Decode for Arc<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self::new(Decode::decode(stream)?)) + } +} + +impl Decode for bool { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = u8::decode(stream)?; + + match value { + 0x0 => Ok(false), + 0x1 => Ok(true), + _ => Err(DecodeError::InvalidBoolean(value)) + } + } +} + +impl<T: Decode> Decode for Bound<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let discriminant = u8::decode(stream)?; + + let this = match discriminant { + 0x0 => { + let bound = Decode::decode(stream)?; + Self::Included(bound) + } + + 0x1 => { + let bound = Decode::decode(stream)?; + Self::Excluded(bound) + } + + 0x2 => Self::Unbounded, + + _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())), + }; + + Ok(this) + } +} + +impl Decode for char { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = u32::decode(stream)?; + + let this = value + .try_into() + .map_err(|_| DecodeError::InvalidCodePoint(value))?; + + Ok(this) + } +} + +impl Decode for Infallible { + #[expect(clippy::panic_in_result_fn)] + #[inline(always)] + fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> { + panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with") + } +} + +impl Decode for IpAddr { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + use IpAddr::*; + + let discriminant = u8::decode(stream)?; + + let this = match discriminant { + 0x4 => V4(Decode::decode(stream)?), + 0x6 => V6(Decode::decode(stream)?), + + _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())) + }; + + Ok(this) + } +} + +impl Decode for Ipv4Addr { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = Decode::decode(stream)?; + + Ok(Self::from_bits(value)) + } +} + +impl Decode for Ipv6Addr { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = Decode::decode(stream)?; + + Ok(Self::from_bits(value)) + } +} + +impl Decode for isize { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = i16::decode(stream)?; + Ok(value as Self) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<T: Decode> Decode for Mutex<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self::new(Decode::decode(stream)?)) + } +} + +impl<T: Decode> Decode for Option<T> { + #[expect(clippy::if_then_some_else_none)] + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let sign = bool::decode(stream)?; + + let this = if sign { + Some(Decode::decode(stream)?) + } else { + None + }; + + Ok(this) + } +} + +impl<T> Decode for PhantomData<T> { + #[inline(always)] + fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self) + } +} + +impl<T: Decode> Decode for Range<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let start = Decode::decode(stream)?; + let end = Decode::decode(stream)?; + + Ok(start..end) + } +} + +impl<T: Decode> Decode for RangeFrom<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let start = Decode::decode(stream)?; + + Ok(start..) + } +} + +impl Decode for RangeFull { + #[inline(always)] + fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(..) + } +} + +impl<T: Decode> Decode for RangeInclusive<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let start = Decode::decode(stream)?; + let end = Decode::decode(stream)?; + + Ok(start..=end) + } +} + +impl<T: Decode> Decode for RangeTo<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let end = Decode::decode(stream)?; + + Ok(..end) + } +} + +impl<T: Decode> Decode for RangeToInclusive<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let end = Decode::decode(stream)?; + + Ok(..=end) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Decode> Decode for Rc<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self::new(Decode::decode(stream)?)) + } +} + +impl<T: Decode, E: Decode> Decode for core::result::Result<T, E> { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let sign = bool::decode(stream)?; + + let this = if sign { + Err(E::decode(stream)?) + } else { + Ok(Decode::decode(stream)?) + }; + + Ok(this) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<T: Decode> Decode for RwLock<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self::new(Decode::decode(stream)?)) + } +} + +impl<T: Decode> Decode for Saturating<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self(Decode::decode(stream)?)) + } +} + +impl Decode for SocketAddr { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + use SocketAddr::*; + + let discriminant = u8::decode(stream)?; + + let this = match discriminant { + 0x4 => V4(Decode::decode(stream)?), + 0x6 => V6(Decode::decode(stream)?), + + _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())) + }; + + Ok(this) + } +} + +impl Decode for SocketAddrV4 { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let ip = Decode::decode(stream)?; + let port = Decode::decode(stream)?; + + let this = Self::new(ip, port); + Ok(this) + } +} + +impl Decode for SocketAddrV6 { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let ip = Decode::decode(stream)?; + let port = Decode::decode(stream)?; + let flow_info = Decode::decode(stream)?; + let scope_id = Decode::decode(stream)?; + + let this = Self::new(ip, port, flow_info, scope_id); + Ok(this) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Decode for String { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let data = <Vec::<u8>>::decode(stream)?; + + Self::from_utf8(data) + .map_err(|e| { + let data = e.as_bytes(); + let i = e.utf8_error().valid_up_to(); + + DecodeError::BadString(Utf8Error { value: data[i], index: i }) + }) + } +} + +impl Decode for () { + #[inline(always)] + fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(()) + } +} + +impl Decode for usize { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = u16::decode(stream)?; + Ok(value as Self) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Decode> Decode for Vec<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let len = Decode::decode(stream)?; + + let mut v = Self::with_capacity(len); + + let buf = v.as_mut_ptr(); + for i in 0x0..len { + let value = Decode::decode(stream)?; + + // SAFETY: Each index is within bounds (i.e. capac- + // ity). + unsafe { buf.add(i).write(value) }; + } + + // SAFETY: We have initialised the buffer. + unsafe { v.set_len(len); } + + Ok(v) + } +} + +impl<T: Decode> Decode for Wrapping<T> { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + Ok(Self(Decode::decode(stream)?)) + } +} + +//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); + +impl_atomic! { + width: "8", + ty: std::sync::atomic::AtomicBool, +} + +impl_atomic! { + width: "16", + ty: std::sync::atomic::AtomicI16, +} + +impl_atomic! { + width: "32", + ty: std::sync::atomic::AtomicI32, +} + +impl_atomic! { + width: "64", + ty: std::sync::atomic::AtomicI64, +} + +impl_atomic! { + width: "8", + ty: std::sync::atomic::AtomicI8, +} + +impl_atomic! { + width: "ptr", + ty: std::sync::atomic::AtomicIsize, +} + +impl_atomic! { + width: "16", + ty: std::sync::atomic::AtomicU16, +} + +impl_atomic! { + width: "32", + ty: std::sync::atomic::AtomicU32, +} + +impl_atomic! { + width: "64", + ty: std::sync::atomic::AtomicU64, +} + +impl_atomic! { + width: "8", + ty: std::sync::atomic::AtomicU8, +} + +impl_atomic! { + width: "ptr", + ty: std::sync::atomic::AtomicUsize, +} diff --git a/bzipper/src/deserialise/test.rs b/bzipper/src/decode/test.rs index 8624448..a7aa089 100644 --- a/bzipper/src/deserialise/test.rs +++ b/bzipper/src/decode/test.rs @@ -1,43 +1,43 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// er General Public License along with bZipper. If // not, see <https://www.gnu.org/licenses/>. +use alloc::vec::Vec; +use bzipper::{Decode, IStream, SizedEncode}; use core::char; -use crate::{Deserialise, Dstream, Serialise}; - #[test] -fn test() { - #[derive(Debug, Deserialise, PartialEq, Serialise)] +fn test_decode() { + #[derive(Debug, Decode, PartialEq, SizedEncode)] struct ProcExit { exit_code: i32, timestmap: u64, } - #[derive(Debug, Deserialise, PartialEq, Serialise)] + #[derive(Debug, Decode, PartialEq, SizedEncode)] struct NewByte(u8); - #[derive(Debug, Deserialise, PartialEq, Serialise)] + #[derive(Debug, Decode, PartialEq, SizedEncode)] struct Unit; - #[derive(Debug, Deserialise, PartialEq, Serialise)] + #[derive(Debug, Decode, PartialEq, SizedEncode)] enum UnitOrFields { Unit, Unnamed(i32), @@ -46,12 +46,9 @@ fn test() { macro_rules! test { ($ty:ty: $data:expr => $value:expr) => {{ - use ::bzipper::{Deserialise, Serialise}; - - let mut buf: [u8; <$ty as Serialise>::MAX_SERIALISED_SIZE] = $data; - let stream = Dstream::new(&mut buf); + let mut stream = IStream::new(&$data); - let left = <$ty as Deserialise>::deserialise(&stream).unwrap(); + let left = <$ty as Decode>::decode(&mut stream).unwrap(); let right = $value; assert_eq!(left, right); @@ -108,16 +105,18 @@ fn test() { test!(UnitOrFields: [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, ] => UnitOrFields::Unit); test!(UnitOrFields: [ - 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, - 0x00, 0x00, 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, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x66, 0xC5, + 0xC8, 0x4C, ] => UnitOrFields::Named { timestamp: 1724237900 }); + + test!(Vec<u16>: [0x00, 0x02, 0xFF, 0xEE, 0xDD, 0xCC] => [0xFF_EE, 0xDD_CC].as_slice()); } diff --git a/bzipper/src/decode/tuple.rs b/bzipper/src/decode/tuple.rs new file mode 100644 index 0000000..eb66db7 --- /dev/null +++ b/bzipper/src/decode/tuple.rs @@ -0,0 +1,324 @@ +// 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::{IStream, Decode}; +use crate::error::DecodeError; + +/// Implemented for tuples with up to twelve members. +#[cfg_attr(doc, doc(fake_variadic))] +impl<T> Decode for (T, ) +where + T: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1> Decode for (T0, T1) +where + T0: Decode, + T1: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2> Decode for (T0, T1, T2) +where + T0: Decode, + T1: Decode, + T2: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3> Decode for (T0, T1, T2, T3) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4> Decode for (T0, T1, T2, T3, T4) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5> Decode for (T0, T1, T2, T3, T4, T5) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6> Decode for (T0, T1, T2, T3, T4, T5, T6) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7> Decode for (T0, T1, T2, T3, T4, T5, T6, T7) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, + T7: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Decode for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, + T7: Decode, + T8: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> Decode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, + T7: Decode, + T8: Decode, + T9: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Decode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, + T7: Decode, + T8: Decode, + T9: Decode, + T10: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Decode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T0: Decode, + T1: Decode, + T2: Decode, + T3: Decode, + T4: Decode, + T5: Decode, + T6: Decode, + T7: Decode, + T8: Decode, + T9: Decode, + T10: Decode, + T11: Decode, { + #[inline(always)] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let value = ( + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + Decode::decode(stream)?, + ); + + Ok(value) + } +} diff --git a/bzipper/src/deserialise/mod.rs b/bzipper/src/deserialise/mod.rs deleted file mode 100644 index e51a552..0000000 --- a/bzipper/src/deserialise/mod.rs +++ /dev/null @@ -1,235 +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::{Dstream, Error, Result, Serialise}; - -use core::convert::Infallible; -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::num::NonZero; - -mod tuple; - -/// Denotes a type capable of deserialisation. -pub trait Deserialise: Sized { - /// Deserialises an object from the given d-stream. - /// - /// This method must **never** read more bytes than specified by [`MAX_SERIALISED_SIZE`](crate::Serialise::MAX_SERIALISED_SIZE) (if [`Serialise`] is defined, that is). - /// Doing so is considered a logic error. - /// - /// # 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 `MAX_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(stream: &Dstream) -> Result<Self>; -} - -macro_rules! impl_numeric { - ($ty:ty) => { - impl ::bzipper::Deserialise for $ty { - #[inline] - fn deserialise(stream: &Dstream) -> ::bzipper::Result<Self> { - let data = stream - .read(Self::MAX_SERIALISED_SIZE) - .unwrap() - //.ok_or(::bzipper::Error::EndOfStream { req: Self::MAX_SERIALISED_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> { - #[inline] - fn deserialise(stream: &Dstream) -> ::bzipper::Result<Self> { - let value = <$ty as ::bzipper::Deserialise>::deserialise(stream)?; - - let value = NonZero::new(value) - .ok_or(Error::NullInteger)?; - - Ok(value) - } - } - }; -} - -impl<T: Deserialise, const N: usize> Deserialise for [T; N] { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - // Initialise the array incrementally. - - let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; - - for item in &mut buf { - let value = T::deserialise(stream)?; - item.write(value); - } - - // 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 { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = u8::deserialise(stream)?; - - match value { - 0x00 => Ok(false), - 0x01 => Ok(true), - _ => Err(Error::InvalidBoolean(value)) - } - } -} - -impl Deserialise for char { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = u32::deserialise(stream)?; - - let value = value - .try_into() - .map_err(|_| Error::InvalidCodePoint(value))?; - - Ok(value) - } -} - -impl Deserialise for Infallible { - #[allow(clippy::panic_in_result_fn)] - #[inline(always)] - fn deserialise(_stream: &Dstream) -> Result<Self> { panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with") } -} - -impl Deserialise for isize { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = i32::deserialise(stream)?; - - let value = value - .try_into() - .expect("unable to convert from `i32` to `isize`"); - - Ok(value) - } -} - -impl<T: Deserialise> Deserialise for Option<T> { - #[allow(clippy::if_then_some_else_none)] - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let sign = bool::deserialise(stream)?; - - let value = if sign { - Some(T::deserialise(stream)?) - } else { - None - }; - - Ok(value) - } -} - -impl<T> Deserialise for PhantomData<T> { - #[inline(always)] - fn deserialise(_stream: &Dstream) -> Result<Self> { Ok(Self) } -} - -impl<T: Deserialise, E: Deserialise> Deserialise for core::result::Result<T, E> { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let sign = bool::deserialise(stream)?; - - let value = if sign { - Err(E::deserialise(stream)?) - } else { - Ok(T::deserialise(stream)?) - }; - - Ok(value) - } -} - -impl Deserialise for () { - #[inline(always)] - fn deserialise(_stream: &Dstream) -> Result<Self> { Ok(()) } -} - -impl Deserialise for usize { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = u32::deserialise(stream)?; - - let value = value - .try_into() - .expect("must be able 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/tuple.rs b/bzipper/src/deserialise/tuple.rs deleted file mode 100644 index fedbad6..0000000 --- a/bzipper/src/deserialise/tuple.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/>. - -use crate::{Deserialise, Dstream, Result}; - -impl<T0> Deserialise for (T0, ) -where - T0: Deserialise, { - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -impl<T0, T1> Deserialise for (T0, T1) -where - T0: Deserialise, - T1: Deserialise, { - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -impl<T0, T1, T2> Deserialise for (T0, T1, T2) -where - T0: Deserialise, - T1: Deserialise, - T2: Deserialise, { - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -impl<T0, T1, T2, T3> Deserialise for (T0, T1, T2, T3) -where - T0: Deserialise, - T1: Deserialise, - T2: Deserialise, - T3: Deserialise, { - fn deserialise(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - Deserialise::deserialise(stream)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - 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)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - 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)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - 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)?, - ); - - Ok(value) - } -} - -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(stream: &Dstream) -> Result<Self> { - let value = ( - 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)?, - ); - - Ok(value) - } -} diff --git a/bzipper/src/dstream/mod.rs b/bzipper/src/dstream/mod.rs deleted file mode 100644 index 3cdae50..0000000 --- a/bzipper/src/dstream/mod.rs +++ /dev/null @@ -1,121 +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::cell::Cell; -use core::fmt::{Debug, Formatter}; - -/// Byte stream suitable for deserialisation. -/// -/// This type borrows a buffer, keeping track internally of the used bytes. -pub struct Dstream<'a> { - pub(in crate) data: &'a [u8], - pub(in crate) 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) } } - - /// Takes (borrows) raw bytes from the stream. - #[inline] - pub fn read(&self, count: usize) -> Result<&[u8]> { - let rem = self.data.len() - self.pos.get(); - let req = count; - - if rem < req { return Err(Error::EndOfStream { req, rem }) } - - let start = self.pos.get(); - let stop = start + req; - - self.pos.set(stop); - - let data = &self.data[start..stop]; - Ok(data) - } - - /// Gets a pointer to the first byte in the stream. - #[inline(always)] - #[must_use] - pub const fn as_ptr(&self) -> *const u8 { self.data.as_ptr() } - - /// Gets a slice of the stream. - #[inline(always)] - #[must_use] - pub const fn as_slice(&self) -> &[u8] { - let ptr = self.as_ptr(); - let len = self.len(); - - unsafe { core::slice::from_raw_parts(ptr, len) } - } - - /// Gets the length of the stream. - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { unsafe { self.pos.as_ptr().read() } } - - /// Tests if the stream is empty. - /// - /// If no deserialisations have been made at the time of calling, this method returns `false`. - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { self.len() == 0x0 } - - /// Tests if the stream is full. - /// - /// Note that zero-sized types such as [`()`](unit) can still be deserialised from this stream. - #[inline(always)] - #[must_use] - pub const fn is_full(&self) -> bool { self.len() == self.data.len() } -} - -impl Debug for Dstream<'_> { - #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { Debug::fmt(self.as_slice(), f) } -} - -impl<'a> From<&'a [u8]> for Dstream<'a> { - #[inline(always)] - fn from(value: &'a [u8]) -> Self { Self::new(value) } -} - -impl<'a> From<&'a mut [u8]> for Dstream<'a> { - #[inline(always)] - fn from(value: &'a mut [u8]) -> Self { Self::new(value) } -} - -impl PartialEq for Dstream<'_> { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() } -} - -impl PartialEq<&[u8]> for Dstream<'_> { - #[inline(always)] - fn eq(&self, other: &&[u8]) -> bool { self.as_slice() == *other } -} - -impl<const N: usize> PartialEq<[u8; N]> for Dstream<'_> { - #[inline(always)] - fn eq(&self, other: &[u8; N]) -> bool { self.as_slice() == other.as_slice() } -} diff --git a/bzipper/src/encode/mod.rs b/bzipper/src/encode/mod.rs new file mode 100644 index 0000000..faa910f --- /dev/null +++ b/bzipper/src/encode/mod.rs @@ -0,0 +1,659 @@ +// 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::OStream; +use crate::error::EncodeError; + +use core::cell::RefCell; +use core::convert::Infallible; +use core::hint::unreachable_unchecked; +use core::marker::PhantomData; +use core::net::{ + IpAddr, + Ipv4Addr, + Ipv6Addr, + SocketAddr, + SocketAddrV4, + SocketAddrV6, +}; +use core::num::{Saturating, Wrapping}; +use core::ops::{ + Bound, + Range, + RangeFrom, + RangeFull, + RangeInclusive, + RangeTo, + RangeToInclusive, +}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(feature = "alloc")] +use alloc::rc::Rc; + +#[cfg(feature = "alloc")] +use alloc::sync::Arc; + +#[cfg(feature = "std")] +use std::sync::{Mutex, RwLock}; + +mod tuple; + +/// Denotes a type capable of being encoded. +/// +/// It is recommended to simply derive this trait for custom types. +/// It can, however, also be manually implemented. +/// +/// If all possible encodings have a known maximum size, then the [`SizedEncode`](crate::SizedEncode) trait should additionally be implemented. +/// +/// # Examples +/// +/// A manual implementation of `Encode`: +/// +/// ``` +/// // Manual implementation of custom type. This im- +/// // plementation is equivalent to what would have +/// // been derived. +/// +/// use bzipper::{Encode, OStream}; +/// use bzipper::error::EncodeError; +/// +/// struct Foo { +/// bar: u16, +/// baz: f32, +/// } +/// +/// impl Encode for Foo { +/// fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { +/// // Encode fields using chaining. +/// +/// self.bar.encode(stream)?; +/// self.baz.encode(stream)?; +/// +/// Ok(()) +/// } +/// } +/// ``` +pub trait Encode { + /// Encodes `self` into the provided stream. + /// + /// # Errors + /// + /// If encoding fails, such as if `self` is unencodable, an error is returned. + /// + /// # Panics + /// + /// If `stream` cannot contain the entirety of the resulting encoding, then this method should panic. + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError>; +} + +macro_rules! impl_numeric { + ($ty:ty$(,)?) => { + impl ::bzipper::Encode for $ty { + #[inline] + fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + stream.write(&self.to_be_bytes()); + + Ok(()) + } + } + }; +} + +macro_rules! impl_non_zero { + ($ty:ty$(,)?) => { + impl ::bzipper::Encode for ::core::num::NonZero<$ty> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + self.get().encode(stream) + } + } + }; +} + +macro_rules! impl_atomic { + { + width: $width:literal, + ty: $ty:ty, + atomic_ty: $atomic_ty:ty$(,)? + } => { + /// This implementation uses the same format as the atomic's primitive counterpart. + /// The atomic object itself is read with the [`Relaxed`](core::sync::atomic::Ordering) ordering scheme. + #[cfg(target_has_atomic = $width)] + #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] + impl ::bzipper::Encode for $atomic_ty { + #[inline(always)] + fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + self.load(::std::sync::atomic::Ordering::Relaxed).encode(stream) + } + } + }; +} + +impl<T: Encode, const N: usize> Encode for [T; N] { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + for v in self { + v.encode(stream)?; + } + + Ok(()) + } +} + +impl<T: Encode> Encode for [T] { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.len().encode(stream)?; + + for v in self { + v.encode(stream)?; + } + + Ok(()) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Encode> Encode for Arc<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + T::encode(self, stream) + } +} + +impl Encode for bool { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + u8::from(*self).encode(stream) + } +} + +impl<T: Encode> Encode for Bound<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + use Bound::*; + + match *self { + Included(ref bound) => { + 0x0u8.encode(stream)?; + bound.encode(stream)?; + } + + Excluded(ref bound) => { + 0x1u8.encode(stream)?; + bound.encode(stream)?; + } + + Unbounded => { + 0x2u8.encode(stream)?; + } + } + + Ok(()) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Encode> Encode for Box<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + T::encode(self, stream) + } +} + +impl Encode for char { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + u32::from(*self).encode(stream) + } +} + +// Especially useful for `Result<T, Infallible>`. +// **If** that is even needed, of course. +impl Encode for Infallible { + #[inline(always)] + fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + // SAFETY: `Infallible` can **never** be construct- + // ed. + unsafe { unreachable_unchecked() } + } +} + +/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6). +/// This is then followed by the respective address' own encoding (either [`Ipv4Addr`] or [`Ipv6Addr`]). +impl Encode for IpAddr { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + use IpAddr::*; + + // The discriminant here is the IP version. + + match *self { + V4(ref addr) => { + 0x4u8.encode(stream)?; + addr.encode(stream)?; + } + + V6(ref addr) => { + 0x6u8.encode(stream)?; + addr.encode(stream)?; + } + } + + Ok(()) + } +} + +/// This implementation encodes the address's bits in big-endian. +impl Encode for Ipv4Addr { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + let value = self.to_bits(); + value.encode(stream) + } +} + +/// This implementation encodes the address's bits in big-endian. +impl Encode for Ipv6Addr { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + let value = self.to_bits(); + value.encode(stream) + } +} + +/// This implementation casts `self` to `i16` before encoding. +/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned. +impl Encode for isize { + #[inline] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + let value = i16::try_from(*self) + .map_err(|_| EncodeError::IsizeOutOfRange(*self))?; + + value.encode(stream) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<T: Encode> Encode for Mutex<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self + .lock() + .or_else(|e| Ok(e.into_inner()))? + .encode(stream) + } +} + +/// This implementation encodes a sign denoting the optional's variant. +/// The sign is `false` for `None` instances and `true` for `Some` instances. +/// The contained value is encoded proceeding the sign. +impl<T: Encode> Encode for Option<T> { + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + // The first element is of type `bool` and is + // called the "sign." It signifies whether there is + // a following element or not. + + match *self { + None => false.encode(stream)?, + + Some(ref v) => { + true.encode(stream)?; + v.encode(stream)?; + } + }; + + Ok(()) + } +} + +impl<T> Encode for PhantomData<T> { + #[inline(always)] + fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + Ok(()) + } +} + +impl<T: Encode> Encode for Range<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.start.encode(stream)?; + self.end.encode(stream)?; + + Ok(()) + } +} + +impl<T: Encode> Encode for RangeFrom<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.start.encode(stream) + } +} + +impl Encode for RangeFull { + #[inline(always)] + fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + Ok(()) + } +} + +impl<T: Encode> Encode for RangeInclusive<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.start().encode(stream)?; + self.end().encode(stream)?; + + Ok(()) + } +} + +impl<T: Encode> Encode for RangeTo<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.end.encode(stream) + } +} + +impl<T: Encode> Encode for RangeToInclusive<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.end.encode(stream)?; + + Ok(()) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Encode> Encode for Rc<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + T::encode(self, stream) + } +} + +impl<T: Encode> Encode for RefCell<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + let value = self + .try_borrow() + .map_err(EncodeError::BadBorrow)?; + + T::encode(&value, stream) + } +} + +/// This implementation encodes a sign denoting the optional's variant. +/// The sign is `false` for denoting `Ok` and `true` for denoting `Err`. +/// The contained value is encoded proceeding the sign. +impl<T: Encode, E: Encode> Encode for core::result::Result<T, E> { + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + // The sign here is `false` for `Ok` objects and + // `true` for `Err` objects. + + match *self { + Ok(ref v) => { + false.encode(stream)?; + v.encode(stream)?; + } + + Err(ref e) => { + true.encode(stream)?; + e.encode(stream)?; + } + }; + + Ok(()) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<T: Encode> Encode for RwLock<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self + .read() + .or_else(|e| Ok(e.into_inner()))? + .encode(stream) + } +} + +impl<T: Encode> Encode for Saturating<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream) + } +} + +/// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6). +/// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]). +impl Encode for SocketAddr { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + use SocketAddr::*; + + // The discriminant here is the IP version. + + match *self { + V4(ref addr) => { + 0x4u8.encode(stream)?; + addr.encode(stream)?; + } + + V6(ref addr) => { + 0x6u8.encode(stream)?; + addr.encode(stream)?; + } + } + + Ok(()) + } +} + +/// This implementation encodes the address's bits followed by the port number, all of which in big-endian. +impl Encode for SocketAddrV4 { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.ip().encode(stream)?; + self.port().encode(stream)?; + + Ok(()) + } +} + +/// This implementation encodes the address's bits followed by the port number, all of which in big-endian. +impl Encode for SocketAddrV6 { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.ip().encode(stream)?; + self.port().encode(stream)?; + self.flowinfo().encode(stream)?; + self.scope_id().encode(stream)?; + + Ok(()) + } +} + +impl Encode for str { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + // Optimised encode. Don't just rely on `[char]`. + + self.len().encode(stream)?; + stream.write(self.as_bytes()); + + Ok(()) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Encode for String { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.as_str().encode(stream) + } +} + +impl Encode for () { + #[inline(always)] + fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + Ok(()) + } +} + +/// This implementation casts `self` to `u16` before encoding. +/// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned. +impl Encode for usize { + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + let value = u16::try_from(*self) + .map_err(|_| EncodeError::UsizeOutOfRange(*self))?; + + value.encode(stream) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: Encode> Encode for Vec<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.as_slice().encode(stream) + } +} + +impl<T: Encode> Encode for Wrapping<T> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream) + } +} + +//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); + +impl_atomic! { + width: "8", + ty: bool, + atomic_ty: std::sync::atomic::AtomicBool, +} + +impl_atomic! { + width: "16", + ty: i16, + atomic_ty: std::sync::atomic::AtomicI16, +} + +impl_atomic! { + width: "32", + ty: i32, + atomic_ty: std::sync::atomic::AtomicI32, +} + +impl_atomic! { + width: "64", + ty: i64, + atomic_ty: std::sync::atomic::AtomicI64, +} + +impl_atomic! { + width: "8", + ty: i8, + atomic_ty: std::sync::atomic::AtomicI8, +} + +impl_atomic! { + width: "ptr", + ty: isize, + atomic_ty: std::sync::atomic::AtomicIsize, +} + +impl_atomic! { + width: "16", + ty: u16, + atomic_ty: std::sync::atomic::AtomicU16, +} + +impl_atomic! { + width: "32", + ty: u32, + atomic_ty: std::sync::atomic::AtomicU32, +} + +impl_atomic! { + width: "64", + ty: u64, + atomic_ty: std::sync::atomic::AtomicU64, +} + +impl_atomic! { + width: "8", + ty: u8, + atomic_ty: std::sync::atomic::AtomicU8, +} + +impl_atomic! { + width: "ptr", + ty: usize, + atomic_ty: std::sync::atomic::AtomicUsize, +} diff --git a/bzipper/src/serialise/test.rs b/bzipper/src/encode/test.rs index 2dee489..2404859 100644 --- a/bzipper/src/serialise/test.rs +++ b/bzipper/src/encode/test.rs @@ -18,33 +18,36 @@ // er General Public License along with bzipper. If // not, see <https://www.gnu.org/licenses/>. -use crate::{FixedString, Serialise, Sstream}; +use alloc::vec; +use alloc::vec::Vec; +use bzipper::{Encode, OStream, SizedEncode, SizedStr}; -#[test] -fn test_serialise() { - #[derive(Serialise)] - struct Foo(char); - - #[derive(Serialise)] - enum Bar { - Unit, - Pretty(bool), - Teacher { initials: [char; 0x3] }, - } +#[derive(SizedEncode)] +struct Foo(char); + +#[derive(SizedEncode)] +#[repr(u8)] // Not honoured. +enum Bar { + Unit = 0x45, - assert_eq!(Foo::MAX_SERIALISED_SIZE, 0x4); - assert_eq!(Bar::MAX_SERIALISED_SIZE, 0x10); + Pretty(bool) = 127, + Teacher { initials: [char; 0x3] }, +} + +#[test] +fn test_encode() { macro_rules! test { ($ty:ty: $value:expr => $data:expr) => {{ - use ::bzipper::Serialise; + let data = $data; - let mut buf = [0x00; <$ty as Serialise>::MAX_SERIALISED_SIZE]; + let mut buf = vec![0x00; data.len()]; - let mut stream = Sstream::new(&mut buf); - <$ty as Serialise>::serialise(&mut $value, &mut stream).unwrap(); + let mut stream = OStream::new(&mut buf); + <$ty as Encode>::encode(&$value, &mut stream).unwrap(); - assert_eq!(stream, $data); + let len = stream.close(); + assert_eq!(&buf[..len], data.as_slice()); }}; } @@ -63,11 +66,11 @@ fn test_serialise() { 0x83, 0x2E, 0x3C, 0x2C, 0x84, 0x10, 0x58, 0x1A, ]); - test!(FixedString::<0x1>: FixedString::try_from("A").unwrap() => [0x00, 0x00, 0x00, 0x01, 0x41]); + test!(SizedStr::<0x1>: SizedStr::try_from("A").unwrap() => [0x00, 0x01, 0x41]); - test!(FixedString::<0x24>: FixedString::try_from("l\u{00F8}gma\u{00F0}ur").unwrap() => [ - 0x00, 0x00, 0x00, 0x0A, 0x6C, 0xC3, 0xB8, 0x67, - 0x6D, 0x61, 0xC3, 0xB0, 0x75, 0x72, + test!(SizedStr::<0x24>: SizedStr::try_from("l\u{00F8}gma\u{00F0}ur").unwrap() => [ + 0x00, 0x0A, 0x6C, 0xC3, 0xB8, 0x67, 0x6D, 0x61, + 0xC3, 0xB0, 0x75, 0x72, ]); test!([char; 0x5]: ['\u{03B4}', '\u{0190}', '\u{03BB}', '\u{03A4}', '\u{03B1}'] => [ @@ -84,12 +87,14 @@ fn test_serialise() { test!(Foo: Foo('\u{FDF2}') => [0x00, 0x00, 0xFD, 0xF2]); - test!(Bar: Bar::Unit => [0x00, 0x00, 0x00, 0x00]); + test!(Bar: Bar::Unit => [0x00, 0x45]); - test!(Bar: Bar::Pretty(true) => [0x00, 0x00, 0x00, 0x01, 0x01]); + test!(Bar: Bar::Pretty(true) => [0x00, 0x7F, 0x01]); test!(Bar: Bar::Teacher { initials: ['T', 'L', '\0'] } => [ - 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, - 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, + 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, ]); -}
\ No newline at end of file + + test!(Vec<u8>: Vec::from([0xAA, 0xBB, 0xCC]) => [0x00, 0x03, 0xAA, 0xBB, 0xCC]); +} diff --git a/bzipper/src/encode/tuple.rs b/bzipper/src/encode/tuple.rs new file mode 100644 index 0000000..66cdf24 --- /dev/null +++ b/bzipper/src/encode/tuple.rs @@ -0,0 +1,302 @@ +// 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::{Encode, OStream}; +use crate::error::EncodeError; + +/// Implemented for tuples with up to twelve members. +#[cfg_attr(doc, doc(fake_variadic))] +impl<T> Encode for (T, ) +where + T: Encode, { + + #[doc(hidden)] + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1> Encode for (T0, T1) +where + T0: Encode, + T1: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2> Encode for (T0, T1, T2) +where + T0: Encode, + T1: Encode, + T2: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3> Encode for (T0, T1, T2, T3) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4> Encode for (T0, T1, T2, T3, T4) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5> Encode for (T0, T1, T2, T3, T4, T5) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6> Encode for (T0, T1, T2, T3, T4, T5, T6) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7> Encode for (T0, T1, T2, T3, T4, T5, T6, T7) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + self.7.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> Encode for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + self.7.encode(stream)?; + self.8.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> Encode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + self.7.encode(stream)?; + self.8.encode(stream)?; + self.9.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Encode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, + T10: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + self.7.encode(stream)?; + self.8.encode(stream)?; + self.9.encode(stream)?; + self.10.encode(stream)?; + + Ok(()) + } +} + +#[doc(hidden)] +impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> Encode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T0: Encode, + T1: Encode, + T2: Encode, + T3: Encode, + T4: Encode, + T5: Encode, + T6: Encode, + T7: Encode, + T8: Encode, + T9: Encode, + T10: Encode, + T11: Encode, { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.0.encode(stream)?; + self.1.encode(stream)?; + self.2.encode(stream)?; + self.3.encode(stream)?; + self.4.encode(stream)?; + self.5.encode(stream)?; + self.6.encode(stream)?; + self.7.encode(stream)?; + self.8.encode(stream)?; + self.9.encode(stream)?; + self.10.encode(stream)?; + self.11.encode(stream)?; + + Ok(()) + } +} diff --git a/bzipper/src/error/decode_error/mod.rs b/bzipper/src/error/decode_error/mod.rs new file mode 100644 index 0000000..bef820d --- /dev/null +++ b/bzipper/src/error/decode_error/mod.rs @@ -0,0 +1,113 @@ +// 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::{SizeError, Utf8Error}; + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Decode error variants. +/// +/// These errors may be returned from implementation of [`Decode`](crate::Decode). +#[derive(Debug)] +#[non_exhaustive] +pub enum DecodeError { + /// Bytes were requested on an empty stream. + /// + /// This variant is different from [`SmallBuffer`](Self::SmallBuffer) in that this is exclusively for use by the stream types, whilst `SmallBuffer` is for any other array-like type. + BadString(Utf8Error), + + /// An unspecified error. + /// + /// This is mainly useful by third-party implementors if none of the other predefined variants are appropriate. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + CustomError(Box<dyn core::error::Error>), + + /// A boolean encountered a value outside `0` and `1`. + InvalidBoolean(u8), + + /// An invalid code point was encountered. + /// + /// This includes surrogate points in the inclusive range `U+D800` to `U+DFFF`, as well as all values larger than `U+10FFFF`. + InvalidCodePoint(u32), + + /// An invalid enumeration descriminant was provided. + InvalidDiscriminant(isize), + + /// A non-zero integer had the value `0`. + NullInteger, + + /// An array could not hold the requested amount of elements. + SmallBuffer(SizeError), +} + +impl Display for DecodeError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use DecodeError::*; + + match *self { + BadString(ref source) + => write!(f, "bad string: {source}"), + + #[cfg(feature = "alloc")] + CustomError(ref source) + => write!(f, "{source}"), + + InvalidBoolean(value) + => write!(f, "expected boolean but got {value:#02X}"), + + InvalidCodePoint(value) + => write!(f, "code point U+{value:04X} is not defined"), + + InvalidDiscriminant(value) + => write!(f, "discriminant ({value}) is not valid for the given enumeration"), + + NullInteger + => write!(f, "expected non-zero integer but got (0)"), + + SmallBuffer(ref source) + => write!(f, "buffer too small: {source}"), + } + } +} + +impl Error for DecodeError { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use DecodeError::*; + + match *self { + BadString(ref source) => Some(source), + + #[cfg(feature = "alloc")] + CustomError(ref source) => Some(source.as_ref()), + + SmallBuffer(ref source) => Some(source), + + _ => None, + } + } +} diff --git a/bzipper/src/error/encode_error/mod.rs b/bzipper/src/error/encode_error/mod.rs new file mode 100644 index 0000000..4a9d0d2 --- /dev/null +++ b/bzipper/src/error/encode_error/mod.rs @@ -0,0 +1,89 @@ +// 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::cell::BorrowError; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +/// Encode error variants. +/// +/// These errors may be returned from implementation of [`Encode`](crate::Encode). +#[derive(Debug)] +#[non_exhaustive] +pub enum EncodeError { + /// A [`RefCell`](core::cell::RefCell) object could not be borrowed. + BadBorrow(BorrowError), + + /// An unspecified error. + /// + /// This is mainly useful by third-party implementors if none of the other predefined variants are appropriate. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + CustomError(Box<dyn core::error::Error>), + + /// An `isize` value could not be cast as `i16`. + IsizeOutOfRange(isize), + + /// A `usize` value could not be cast as `u16`. + UsizeOutOfRange(usize), +} + +impl Display for EncodeError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use EncodeError::*; + + match *self { + BadBorrow(ref source) + => write!(f, "could not borrow reference cell: {source}"), + + #[cfg(feature = "alloc")] + CustomError(ref source) + => write!(f, "{source}"), + + IsizeOutOfRange(value) + => write!(f, "signed size value ({value}) cannot be serialised: must be in the range ({}) to ({})", i16::MIN, i16::MAX), + + UsizeOutOfRange(value) + => write!(f, "unsigned size value ({value}) cannot be serialised: must be at most ({})", u16::MAX), + } + } +} + +impl Error for EncodeError { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use EncodeError::*; + + match *self { + // In practice useless. + BadBorrow(ref source) => Some(source), + + #[cfg(feature = "alloc")] + CustomError(ref source) => Some(source.as_ref()), + + _ => None, + } + } +} diff --git a/bzipper/src/error/mod.rs b/bzipper/src/error/mod.rs index 4e0fe11..a46ccf1 100644 --- a/bzipper/src/error/mod.rs +++ b/bzipper/src/error/mod.rs @@ -1,132 +1,34 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// er General Public License along with bZipper. If // not, see <https://www.gnu.org/licenses/>. -use core::fmt::{Display, Formatter}; -use core::str::Utf8Error; +//! Error variants. +//! +//! This module defines the error types used by bZipper. +//! All of these types define the [`Error`](core::error::Error) trait. -#[cfg(feature = "alloc")] -use alloc::boxed::Box; +use crate::use_mod; -/// Mapping of [`core::result::Result`]. -pub type Result<T> = core::result::Result<T, Error>; - -/// bzipper errors. -/// -/// These variants are used when deserialisation fails. -/// Serialisations are assumed infallible. -#[derive(Debug)] -pub enum Error { - /// An array could not hold the requested amount of elements. - ArrayTooShort { - /// The required amount of bytes. - req: usize, - - /// The remaining amount of bytes. - len: usize, - }, - - /// A string encountered an invalid UTF-8 sequence. - BadString { source: Utf8Error }, - - /// An unspecified (de)serialisation error. - /// - /// This is mainly useful if none of the predefined errors are appropriate. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - CustomError(Box<dyn core::error::Error>), - - /// Bytes were requested on an empty stream. - EndOfStream { req: usize, rem: usize }, - - /// A boolean encountered a value outside `0` and `1`. - InvalidBoolean(u8), - - /// An invalid code point was encountered. - /// - /// This includes surrogate points in the inclusive range `U+D800` to `U+DFFF`, as well as values larger than `U+10FFFF`. - InvalidCodePoint(u32), - - /// An invalid enumeration descriminant was provided. - InvalidDiscriminant(u32), - - /// An `isize` value couldn't fit into `32` bits. - IsizeOutOfRange(isize), - - /// A non-zero integer encountered the value `0`. - NullInteger, - - /// A `usize` value couldn't fit into `32` bits. - UsizeOutOfRange(usize), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { - use Error::*; - - match *self { - ArrayTooShort { req, len } - => write!(f, "array of ({len}) element(s) cannot hold ({req})"), - - BadString { ref source } - => write!(f, "unable to parse utf8: \"{source}\""), - - #[cfg(feature = "alloc")] - CustomError(ref source) - => write!(f, "{source}"), - - EndOfStream { req, rem } - => write!(f, "({req}) byte(s) were requested but only ({rem}) byte(s) were left"), - - InvalidBoolean(value) - => write!(f, "expected boolean but got {value:#02X}"), - - InvalidCodePoint(value) - => write!(f, "code point U+{value:04X} is not valid"), - - InvalidDiscriminant(value) - => write!(f, "discriminant ({value}) is not valid for the given enumeration"), - - IsizeOutOfRange(value) - => write!(f, "signed size value ({value}) cannot be serialised: must be in the range ({}) to ({})", i16::MIN, i16::MAX), - - NullInteger - => write!(f, "expected non-zero integer but got (0)"), - - UsizeOutOfRange(value) - => write!(f, "unsigned size value ({value}) cannot be serialised: must be at most ({})", u16::MAX), - } - } -} - -impl core::error::Error for Error { - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { - use Error::*; - - match *self { - BadString { ref source } => Some(source), - - #[cfg(feature = "alloc")] - CustomError(ref source) => Some(source.as_ref()), - - _ => None, - } - } -} +use_mod!(pub decode_error); +use_mod!(pub encode_error); +use_mod!(pub size_error); +use_mod!(pub string_error); +use_mod!(pub utf16_error); +use_mod!(pub utf8_error); diff --git a/bzipper/src/error/size_error/mod.rs b/bzipper/src/error/size_error/mod.rs new file mode 100644 index 0000000..8fb39f3 --- /dev/null +++ b/bzipper/src/error/size_error/mod.rs @@ -0,0 +1,42 @@ +// 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::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A fixed-size buffer was too small. +#[derive(Debug)] +pub struct SizeError { + /// The required amount of bytes. + pub req: usize, + + /// The total capacity of the buffer. + pub len: usize, +} + +impl Display for SizeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "collection of size ({}) cannot hold ({}) elements", self.len, self.req) + } +} + +impl Error for SizeError { } diff --git a/bzipper/src/error/string_error/mod.rs b/bzipper/src/error/string_error/mod.rs new file mode 100644 index 0000000..c39796f --- /dev/null +++ b/bzipper/src/error/string_error/mod.rs @@ -0,0 +1,75 @@ +// 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::{SizeError, Utf16Error, Utf8Error}; + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// String error variants. +#[derive(Debug)] +#[non_exhaustive] +pub enum StringError { + /// An invalid UTF-16 sequence was encountered. + BadUtf16(Utf16Error), + + /// An invalid UTF-8 sequence was encountered. + BadUtf8(Utf8Error), + + /// A fixed-size buffer was too small. + SmallBuffer(SizeError), +} + +impl Display for StringError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use StringError::*; + + match *self { + BadUtf16(ref source) + => write!(f, "bad utf-16: {source}"), + + BadUtf8(ref source) + => write!(f, "bad utf-8: {source}"), + + SmallBuffer(ref source) + => write!(f, "buffer too small: {source}"), + } + } +} + +impl Error for StringError { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use StringError::*; + + match *self { + BadUtf16(ref source) + => Some(source), + + BadUtf8(ref source) + => Some(source), + + SmallBuffer(ref source) + => Some(source), + } + } +} diff --git a/bzipper/src/error/utf16_error/mod.rs b/bzipper/src/error/utf16_error/mod.rs new file mode 100644 index 0000000..0279662 --- /dev/null +++ b/bzipper/src/error/utf16_error/mod.rs @@ -0,0 +1,42 @@ +// 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::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// An invalid UTF-16 sequence was encountered. +#[derive(Debug)] +pub struct Utf16Error { + /// The invalid UTF-16 hextet. + pub value: u16, + + /// The index of the invalid hextet. + pub index: usize, +} + +impl Display for Utf16Error { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "found invalid utf-16 hextet {:#04X} at offset ({})", self.value, self.index) + } +} + +impl Error for Utf16Error { } diff --git a/bzipper/src/error/utf8_error/mod.rs b/bzipper/src/error/utf8_error/mod.rs new file mode 100644 index 0000000..f3dc3e1 --- /dev/null +++ b/bzipper/src/error/utf8_error/mod.rs @@ -0,0 +1,43 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bZipper. +// +// bZipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bZipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bZipper. If +// not, see <https://www.gnu.org/licenses/>. + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// An invalid UTF-8 sequence was encountered. +#[derive(Debug)] +#[non_exhaustive] +pub struct Utf8Error { + /// The invalid UTF-8 octet. + pub value: u8, + + /// The index of the invalid octet. + pub index: usize, +} + +impl Display for Utf8Error { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "found invalid utf-8 octet {:#02X} at offset ({})", self.value, self.index) + } +} + +impl Error for Utf8Error { } diff --git a/bzipper/src/fixed_string/mod.rs b/bzipper/src/fixed_string/mod.rs deleted file mode 100644 index 377937a..0000000 --- a/bzipper/src/fixed_string/mod.rs +++ /dev/null @@ -1,480 +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, - Serialise, - Sstream, -}; - -use core::borrow::{Borrow, BorrowMut}; -use core::cmp::Ordering; -use core::fmt::{Debug, Display, Formatter}; -use core::hash::{Hash, Hasher}; -use core::ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut}; -use core::slice::SliceIndex; -use core::str::{Chars, CharIndices, FromStr}; - -#[cfg(feature = "alloc")] -use alloc::string::String; - -#[cfg(feature = "std")] -use std::ffi::OsStr; - -#[cfg(feature = "std")] -use std::net::ToSocketAddrs; - -#[cfg(feature = "std")] -use std::path::Path; - -/// Heap-allocated string with maximum size. -/// -/// This is in contrast to [String] -- which has no size limit in practice -- and [str], which is unsized. -/// -/// The string itself is encoded in UTF-8 for interoperability wtih Rust's standard string facilities, as well as for memory concerns. -/// -/// Keep in mind that the size limit specified by `N` denotes *bytes* and not *characters* -- i.e. a value of `8` may translate to between two and eight characters, depending on their codepoints. -/// -/// # Examples -/// -/// All instances of this type have the same size if the value of `N` is also the same. -/// Therefore, the following four strings have -- despite their different contents -- the same total size. -/// -/// ```rust -/// use bzipper::FixedString; -/// use std::str::FromStr; -/// -/// let str0 = FixedString::<0x40>::new(); // Empty string. -/// let str1 = FixedString::<0x40>::from_str("Hello there!").unwrap(); -/// let str2 = FixedString::<0x40>::from_str("أنا من أوروپا").unwrap(); -/// let str3 = FixedString::<0x40>::from_str("COGITO ERGO SUM").unwrap(); -/// -/// 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)] -pub struct FixedString<const N: usize> { - buf: [u8; 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_raw_parts`](Self::from_raw_parts) and [`from_str`](Self::from_str). - #[inline(always)] - #[must_use] - pub const fn new() -> Self { Self { buf: [0x00; N], len: 0x0 } } - - /// Constructs a new, fixed-size string from raw parts. - /// - /// The provided parts are not tested in any way. - /// - /// # Safety - /// - /// The value of `len` may not exceed that of `N`. - /// Additionally, the octets in `buf` (from index zero up to the value of `len`) must be valid UTF-8 codepoints. - /// - /// If any of these requirements are violated, behaviour is undefined. - #[inline(always)] - #[must_use] - pub const unsafe fn from_raw_parts(buf: [u8; N], len: usize) -> Self { Self { buf, len } } - - /// Destructs the provided string into its raw parts. - /// - /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). - /// - /// The returned byte array is guaranteed to be fully initialised. - /// However, only octets up to an index of [`len`](Self::len) are also guaranteed to be valid UTF-8 codepoints. - #[inline(always)] - #[must_use] - pub const fn into_raw_parts(self) -> ([u8; N], usize) { (self.buf, self.len) } - - /// Gets a pointer to the first octet. - #[inline(always)] - #[must_use] - pub const fn as_ptr(&self) -> *const u8 { self.buf.as_ptr() } - - // This function can only be marked as `const` when - // `const_mut_refs` is implemented. See tracking - // issue #57349 for more information. - /// Gets a mutable pointer to the first octet. - /// - #[inline(always)] - #[must_use] - pub fn as_mut_ptr(&mut self) -> *mut u8 { self.buf.as_mut_ptr() } - - /// Borrows the string as a byte slice. - /// - /// The range of the returned slice only includes characters that are "used." - #[inline(always)] - #[must_use] - pub const fn as_bytes(&self) -> &[u8] { - // We need to use `from_raw_parts` to mark this - // function `const`. - - unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } - } - - /// Borrows the string as a string slice. - /// - /// The range of the returned slice only includes characters that are "used." - #[inline(always)] - #[must_use] - pub const fn as_str(&self) -> &str { unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } } - - /// Mutably borrows the string as a string slice. - /// - /// The range of the returned slice only includes characters that are "used." - #[inline(always)] - #[must_use] - pub fn as_mut_str(&mut self) -> &mut str { - let range = 0x0..self.len(); - - unsafe { core::str::from_utf8_unchecked_mut(&mut self.buf[range]) } - } - - /// 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. no characters are contained. - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { self.len() == 0x0 } - - /// Checks if the string is full, i.e. it cannot hold any more characters. - #[inline(always)] - #[must_use] - pub const fn is_full(&self) -> bool { self.len() == N } - - /// Returns the total capacity of the string. - /// - /// This is defined as being exactly the value of `N`. - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { N } - - /// Gets a substring of the string. - #[inline(always)] - #[must_use] - pub fn get<I: SliceIndex<str>>(&self, index: I) -> Option<&I::Output> { self.as_str().get(index) } - - /// Gets a mutable substring of the string. - #[inline(always)] - #[must_use] - pub fn get_mut<I: SliceIndex<str>>(&mut self, index: I) -> Option<&mut I::Output> { self.as_mut_str().get_mut(index) } - - /// Pushes a character into the string. - /// - /// The internal length is updated accordingly. - /// - /// # Panics - /// - /// If the string cannot hold the provided character *after* encoding, this method will panic. - #[inline(always)] - pub fn push(&mut self, c: char) { - let mut buf = [0x00; 0x4]; - let s = c.encode_utf8(&mut buf); - - self.push_str(s); - } - - /// Pushes a string slice into the string. - /// - /// The internal length is updated accordingly. - /// - /// # Panics - /// - /// If the string cannot hold the provided slice, this method will panic. - #[inline(always)] - pub fn push_str(&mut self, s: &str) { - let rem = self.buf.len() - self.len; - let req = s.len(); - - assert!(rem >= req, "cannot push string beyond fixed length"); - - let start = self.len; - let stop = start + req; - - let buf = &mut self.buf[start..stop]; - buf.copy_from_slice(s.as_bytes()); - } - - /// 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. - /// - /// **Note that this method is currently unimplemented.** - #[deprecated = "temporarily unimplemented"] - #[inline(always)] - pub fn pop(&mut self) -> Option<char> { todo!() } - - /// Returns an iterator of the string's characters. - #[inline(always)] - pub fn chars(&self) -> Chars { self.as_str().chars() } - - /// Returns an iterator of the string's characters along with their positions. - #[inline(always)] - pub fn char_indices(&self) -> CharIndices { self.as_str().char_indices() } -} - -impl<const N: usize> Add<&str> for FixedString<N> { - type Output = Self; - - fn add(mut self, rhs: &str) -> Self::Output { - self.push_str(rhs); - self - } -} - -impl<const N: usize> AddAssign<&str> for FixedString<N> { - fn add_assign(&mut self, rhs: &str) { self.push_str(rhs) } -} - -impl<const N: usize> AsMut<str> for FixedString<N> { - #[inline(always)] - fn as_mut(&mut self) -> &mut str { self.as_mut_str() } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl<const N: usize> AsRef<OsStr> for FixedString<N> { - #[inline(always)] - fn as_ref(&self) -> &OsStr { self.as_str().as_ref() } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl<const N: usize> AsRef<Path> for FixedString<N> { - #[inline(always)] - fn as_ref(&self) -> &Path { self.as_str().as_ref() } -} - -impl<const N: usize> AsRef<str> for FixedString<N> { - #[inline(always)] - fn as_ref(&self) -> &str { self.as_str() } -} - -impl<const N: usize> AsRef<[u8]> for FixedString<N> { - #[inline(always)] - fn as_ref(&self) -> &[u8] { self.as_bytes() } -} - -impl<const N: usize> Borrow<str> for FixedString<N> { - #[inline(always)] - fn borrow(&self) -> &str { self.as_str() } -} - -impl<const N: usize> BorrowMut<str> for FixedString<N> { - #[inline(always)] - fn borrow_mut(&mut self) -> &mut str { self.as_mut_str() } -} - -impl<const N: usize> Debug for FixedString<N> { - #[inline] - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { Debug::fmt(self.as_str(), f) } -} - -impl<const N: usize> Default for FixedString<N> { - #[inline(always)] - fn default() -> Self { Self { buf: [Default::default(); N], len: 0x0 } } -} - -impl<const N: usize> Deref for FixedString<N> { - type Target = str; - - #[inline(always)] - fn deref(&self) -> &Self::Target { self.as_str() } -} - -impl<const N: usize> DerefMut for FixedString<N> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_str() } -} - -impl<const N: usize> Deserialise for FixedString<N> { - #[inline] - fn deserialise(stream: &Dstream) -> Result<Self, Error> { - let len = Deserialise::deserialise(stream)?; - if len > N { return Err(Error::ArrayTooShort { req: len, len: N }) }; - - let bytes = stream.read(len)?; - - let s = core::str::from_utf8(bytes) - .map_err(|e| Error::BadString { source: e })?; - - Self::from_str(s) - } -} - -impl<const N: usize> Display for FixedString<N> { - #[inline] - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { Display::fmt(self.as_str(), f) } -} - -impl<const N: usize> Eq for FixedString<N> { } - -impl<const N: usize> FromStr for FixedString<N> { - type Err = Error; - - #[inline] - fn from_str(s: &str) -> Result<Self, Self::Err> { - let len = s.len(); - if len > N { return Err(Error::ArrayTooShort { req: len, len: N }) }; - - let mut buf = [0x00; N]; - unsafe { core::ptr::copy_nonoverlapping(s.as_ptr(), buf.as_mut_ptr(), len) }; - - // The remaining bytes are already initialised to - // null. - - Ok(Self { buf, len }) - } -} - -impl<const N: usize> Hash for FixedString<N> { - #[inline(always)] - fn hash<H: Hasher>(&self, state: &mut H) { self.as_str().hash(state) } -} - -impl<I: SliceIndex<str>, 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<str>, 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> Ord for FixedString<N> { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { self.as_str().cmp(other.as_str()) } -} - -impl<const N: usize, const M: usize> PartialEq<FixedString<M>> for FixedString<N> { - #[inline(always)] - fn eq(&self, other: &FixedString<M>) -> bool { self.as_str() == other.as_str() } -} - -impl<const N: usize> PartialEq<&str> for FixedString<N> { - #[inline(always)] - fn eq(&self, other: &&str) -> bool { self.as_str() == *other } -} - -impl<const N: usize, const M: usize> PartialOrd<FixedString<M>> for FixedString<N> { - #[inline(always)] - fn partial_cmp(&self, other: &FixedString<M>) -> Option<Ordering> { self.as_str().partial_cmp(other.as_str()) } -} - -impl<const N: usize> PartialOrd<&str> for FixedString<N> { - #[inline(always)] - fn partial_cmp(&self, other: &&str) -> Option<Ordering> { self.as_str().partial_cmp(*other) } -} - -impl<const N: usize> Serialise for FixedString<N> { - const MAX_SERIALISED_SIZE: usize = N + usize::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<(), Error> { - self.len().serialise(stream)?; - stream.write(self.as_bytes())?; - - Ok(()) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(doc, doc(cfg(feature = "std")))] -impl<const N: usize> ToSocketAddrs for FixedString<N> { - type Iter = <str as ToSocketAddrs>::Iter; - - #[inline(always)] - fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> { self.as_str().to_socket_addrs() } -} - -impl<const N: usize> TryFrom<char> for FixedString<N> { - type Error = <Self as FromStr>::Err; - - #[inline(always)] - fn try_from(value: char) -> Result<Self, Self::Error> { - let mut buf = [0x00; 0x4]; - let s = value.encode_utf8(&mut buf); - - s.parse() - } -} - -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")] -#[cfg_attr(doc, doc(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) } -} - -/// Converts the fixed-size string into a dynamic string. -/// -/// The capacity of the resulting [`String`] object is equal to the value of `N`. -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl<const N: usize> From<FixedString<N>> for String { - #[inline(always)] - fn from(value: FixedString<N>) -> Self { - let mut s = Self::with_capacity(N); - s.push_str(value.as_str()); - - s - } -} diff --git a/bzipper/src/fixed_string/test.rs b/bzipper/src/fixed_string/test.rs deleted file mode 100644 index 09f4b39..0000000 --- a/bzipper/src/fixed_string/test.rs +++ /dev/null @@ -1,47 +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; - -use core::cmp::Ordering; - -#[test] -fn test_fixed_string() { - let str0 = FixedString::<0x0C>::try_from("Hello there!").unwrap(); - let str1 = FixedString::<0x12>::try_from("MEIN_GRO\u{1E9E}_GOTT").unwrap(); - let str2 = FixedString::<0x05>::try_from("Hello").unwrap(); - - assert_eq!(str0.partial_cmp(&str0), Some(Ordering::Equal)); - assert_eq!(str0.partial_cmp(&str1), Some(Ordering::Less)); - assert_eq!(str0.partial_cmp(&str2), Some(Ordering::Greater)); - - assert_eq!(str1.partial_cmp(&str0), Some(Ordering::Greater)); - assert_eq!(str1.partial_cmp(&str1), Some(Ordering::Equal)); - assert_eq!(str1.partial_cmp(&str2), Some(Ordering::Greater)); - - assert_eq!(str2.partial_cmp(&str0), Some(Ordering::Less)); - assert_eq!(str2.partial_cmp(&str1), Some(Ordering::Less)); - assert_eq!(str2.partial_cmp(&str2), Some(Ordering::Equal)); - - assert_eq!(str0, "Hello there!"); - assert_eq!(str1, "MEIN_GRO\u{1E9E}_GOTT"); - assert_eq!(str2, "Hello"); -} diff --git a/bzipper/src/i_stream/mod.rs b/bzipper/src/i_stream/mod.rs new file mode 100644 index 0000000..30cb16f --- /dev/null +++ b/bzipper/src/i_stream/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 core::slice; + +/// Byte stream suitable for decoding. +pub struct IStream<'a> { + buf: &'a [u8], + pos: usize, +} + +impl<'a> IStream<'a> { + /// Constructs a new i-stream. + #[inline(always)] + #[must_use] + pub fn new(buf: &'a [u8]) -> Self { + Self { buf, pos: 0x0 } + } + + /// Reads bytes from the stream. + /// + /// # Panics + /// + /// If the requested amount of bytes could not exactly be read, then this method will panic. + #[inline] + pub fn read(&mut self, count: usize) -> &[u8] { + let remaining = self.buf.len() - self.pos; + + assert!( + remaining >= count, + "cannot read ({count}) bytes at ({}) from stream with capacity of ({})", + self.pos, + self.buf.len(), + ); + + let data = unsafe { + let ptr = self.buf.as_ptr().add(self.pos); + + slice::from_raw_parts(ptr, count) + }; + + self.pos += count; + + data + } + + /// Closes the stream. + /// + /// The total ammount of bytes read is returned. + #[inline(always)] + pub const fn close(self) -> usize { + let Self { pos, .. } = self; + + pos + } +} diff --git a/bzipper/src/lib.rs b/bzipper/src/lib.rs index c505f50..525464c 100644 --- a/bzipper/src/lib.rs +++ b/bzipper/src/lib.rs @@ -1,105 +1,140 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// 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")] +#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")] -//! Binary (de)serialisation. +//! bZipper is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams //! -//! In contrast to [Serde](https://crates.io/crates/serde/)/[Bincode](https://crates.io/crates/bincode/), the primary 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. +//! What separates this crate from others such as [Bincode](https://crates.io/crates/bincode/) or [Postcard](https://crates.io/crates/postcard/) is that this crate is extensively optimised for *just* binary encodings (whilst the mentioned crates specifically use Serde and build on a more abstract data model). +//! The original goal of this project was specifically to guarantee size constraints for encodings on a per-type basis at compile-time. +//! Therefore, this crate may be more suited for networking or other cases where many allocations are unwanted. //! //! Keep in mind that this project is still work-in-progress. +//! Until the interfaces are stabilised, different facilities may be replaced, removed, or altered in a breaking way. //! //! This crate is compatible with `no_std`. //! +//! # Performance +//! +//! As bZipper is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. +//! +//! The `bzipper_benchmarks` binary compares multiple scenarios using bZipper and other, similar crates. +//! According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that bZipper outperform all of the tested crates -- as demonstrated in the following table: +//! +//! | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | +//! | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | +//! | `encode_u8` | 1.262 | 1.271 | 1.153 | 2.854 | 1.270 | +//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.447 | 0.000 | +//! | `encode_struct_unnamed` | 1.270 | 1.102 | 0.998 | 1.948 | 1.182 | +//! | `encode_struct_named` | 4.205 | 1.186 | 1.136 | 10.395 | 1.168 | +//! | `encode_enum_unit` | 0.328 | 0.008 | 0.000 | 2.293 | 0.004 | +//! | **Total time** → | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 | +//! | **Total deviation (p.c.)** → | +115 | +9 | ±0 | +446 | +10 | +//! +//! [Bincode]: https://crates.io/crates/bincode/ +//! [Borsh]: https://crates.io/crates/borsh/ +//! [Ciborium]: https://crates.io/crates/ciborium/ +//! [Postcard]: https://crates.io/crates/postcard/ +//! +//! All quantities are measured in seconds unless otherwise noted. +//! Please feel free to conduct your own tests of bZipper. +//! //! # Data model //! -//! Most primitive types serialise losslessly, with the exception being [`usize`] and [`isize`]. -//! These serialise as [`u32`] and [`i32`], respectively, for portability reasons. +//! Most primitives encode losslessly, with the main exceptions being [`usize`] and [`isize`]. +//! These are instead first cast as [`u16`] and [`i16`], respectively, due to portability concerns (with respect to embedded systems). //! -//! 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. +//! See specific types' implementations for notes on their data models. +//! +//! **Note that the data model is currently not stabilised,** and may not necessarily be in the near future (before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +//! It may therefore be undesired to store encodings long-term. //! //! # Usage //! -//! This crate revolves around the [`Serialise`] and [`Deserialise`] traits, both of which use *streams* -- or more specifically -- [s-streams](Sstream) and [d-streams](Dstream). +//! This crate revolves around the [`Encode`] and [`Decode`] traits which both handle conversions to and from byte streams. //! -//! Many core types come implemented with bzipper, including primitives as well as some standard library types such as [`Option`] and [`Result`](core::result::Result). +//! Many standard types come implemented with bZipper, including most primitives as well as some standard library types such as [`Option`] and [`Result`]. +//! Some [features](#feature-flags) enable an extended set of implementations. //! -//! It is recommended in most cases to just derive these two traits for custom types (although this is only supported with enumerations and structures). +//! It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures -- not untagged unions). //! Here, each field is *chained* according to declaration order: //! //! ``` -//! use bzipper::{Buffer, Deserialise, Serialise}; +//! use bzipper::{Buf, Decode, Encode, SizedEncode}; //! -//! #[derive(Debug, Deserialise, PartialEq, Serialise)] +//! #[derive(Debug, Decode, PartialEq, SizedEncode)] //! struct IoRegister { //! addr: u32, //! value: u16, //! } //! -//! let mut buf = Buffer::new(); +//! let mut buf = Buf::new(); //! //! buf.write(IoRegister { addr: 0x04000000, value: 0x0402 }).unwrap(); //! //! assert_eq!(buf.len(), 0x6); -//! assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02]); +//! assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02].as_slice()); //! //! assert_eq!(buf.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 }); //! ``` //! -//! ## Serialisation +//! ## Buffer types //! -//! To serialise an object implementing `Serialise`, simply allocate a buffer for the serialisation and wrap it in an s-stream (*serialisation stream*) with the [`Sstream`] type. +//! The [`Encode`] and [`Decode`] traits both rely on streams for carrying the manipulated byte streams. //! -//! ``` -//! use bzipper::{Serialise, Sstream}; +//! These streams are separated into two type: [*O-streams*](OStream) (output streams) and [*i-streams*](IStream) (input streams). +//! Often, but not always, the [`Buf`] type is preferred over directly calling the [`encode`](Encode::encode) and [`decode`](Decode::decode) methods. //! -//! let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE]; -//! let mut stream = Sstream::new(&mut buf); +//! ## Encoding //! -//! 'Ж'.serialise(&mut stream).unwrap(); +//! To encode an object directly using the [`Encode`] trait, simply allocate a buffer for the encoding and wrap it in an [`OStream`] object: //! -//! assert_eq!(stream, [0x00, 0x00, 0x04, 0x16]); //! ``` +//! use bzipper::{Encode, OStream, SizedEncode}; +//! +//! let mut buf = [0x00; char::MAX_ENCODED_SIZE]; +//! let mut stream = OStream::new(&mut buf); //! -//! The maximum size of any given serialisation is specified by the [`MAX_SERIALISED_SIZE`](Serialise::MAX_SERIALISED_SIZE) constant. +//! 'Ж'.encode(&mut stream).unwrap(); +//! +//! assert_eq!(buf, [0x00, 0x00, 0x04, 0x16].as_slice()); +//! ``` //! -//! We can also use streams to chain multiple elements together: +//! Streams can also be used to chain multiple objects together: //! //! ``` -//! use bzipper::{Serialise, Sstream}; +//! use bzipper::{Encode, OStream, SizedEncode}; //! -//! let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE * 0x5]; -//! let mut stream = Sstream::new(&mut buf); +//! let mut buf = [0x0; char::MAX_ENCODED_SIZE * 0x5]; +//! let mut stream = OStream::new(&mut buf); //! //! // Note: For serialising multiple characters, the -//! // `FixedString` type is usually preferred. +//! // `String` and `SizedStr` types are usually +//! // preferred. //! -//! 'ل'.serialise(&mut stream).unwrap(); -//! 'ا'.serialise(&mut stream).unwrap(); -//! 'م'.serialise(&mut stream).unwrap(); -//! 'د'.serialise(&mut stream).unwrap(); -//! 'ا'.serialise(&mut stream).unwrap(); +//! 'ل'.encode(&mut stream).unwrap(); +//! 'ا'.encode(&mut stream).unwrap(); +//! 'م'.encode(&mut stream).unwrap(); +//! 'د'.encode(&mut stream).unwrap(); +//! 'ا'.encode(&mut stream).unwrap(); //! //! assert_eq!(buf, [ //! 0x00, 0x00, 0x06, 0x44, 0x00, 0x00, 0x06, 0x27, @@ -108,113 +143,297 @@ //! ]); //! ``` //! -//! 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. +//! If the encoded type additionally implements [`SizedEncode`], then the maximum size of any encoding is guaranteed with the [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE) constant. //! -//! ## Deserialisation +//! Numerical primitives are encoded in big endian (a.k.a. [network order](https://en.wikipedia.org/wiki/Endianness#Networking)) for... reasons. +//! It is recommended for implementors to follow this convention as well. //! -//! Deserialisation works with a similar syntax to serialisation. +//! ## Decoding //! -//! D-streams (*deserialisation streams*) use the [`Dstream`] type and are constructed in a manner similar to s-streams. -//! To deserialise a buffer, simply call the [`deserialise`](Deserialise::deserialise) method with the strema: +//! Decoding works with a similar syntax to encoding. +//! To decode a byte array, simply call the [`decode`](Decode::decode) method with an [`IStream`] object: //! //! ``` -//! use bzipper::{Deserialise, Dstream}; +//! use bzipper::{Decode, IStream}; //! //! let data = [0x45, 0x54]; -//! let stream = Dstream::new(&data); -//! assert_eq!(u16::deserialise(&stream).unwrap(), 0x4554); +//! let mut stream = IStream::new(&data); +//! +//! assert_eq!(u16::decode(&mut stream).unwrap(), 0x4554); +//! +//! // Data can theoretically be reinterpretred: +//! +//! stream = IStream::new(&data); +//! +//! assert_eq!(u8::decode(&mut stream).unwrap(), 0x45); +//! assert_eq!(u8::decode(&mut stream).unwrap(), 0x54); +//! +//! // Including as tuples: +//! +//! stream = IStream::new(&data); +//! +//! assert_eq!(<(u8, u8)>::decode(&mut stream).unwrap(), (0x45, 0x54)); //! ``` //! -//! And just like s-streams, d-streams can also be used to handle chaining: +//! # Examples +//! +//! A UDP server/client for geographic data: //! //! ``` -//! use bzipper::{Deserialise, Dstream}; +//! use bzipper::{Buf, Decode, SizedEncode}; +//! use std::io; +//! use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; +//! use std::thread::spawn; +//! +//! // City, region, etc.: +//! #[derive(Clone, Copy, Debug, Decode, Eq, PartialEq, SizedEncode)] +//! enum Area { +//! AlQuds, +//! Byzantion, +//! Cusco, +//! Tenochtitlan, +//! // ... +//! } //! -//! let data = [0x45, 0x54]; -//! let stream = Dstream::new(&data); +//! // Client-to-server message: +//! #[derive(Debug, Decode, PartialEq, SizedEncode)] +//! enum Request { +//! AtmosphericHumidity { area: Area }, +//! AtmosphericPressure { area: Area }, +//! AtmosphericTemperature { area: Area }, +//! // ... +//! } +//! +//! // Server-to-client message: +//! #[derive(Debug, Decode, PartialEq, SizedEncode)] +//! enum Response { +//! AtmosphericHumidity(f64), +//! AtmosphericPressure(f64), // Pascal +//! AtmosphericTemperature(f64), // Kelvin +//! // ... +//! } +//! +//! struct Party { +//! pub socket: UdpSocket, +//! +//! pub request_buf: Buf::<Request>, +//! pub response_buf: Buf::<Response>, +//! } +//! +//! impl Party { +//! pub fn new<A: ToSocketAddrs>(addr: A) -> io::Result<Self> { +//! let socket = UdpSocket::bind(addr)?; +//! +//! let this = Self { +//! socket, +//! +//! request_buf: Buf::new(), +//! response_buf: Buf::new(), +//! }; +//! +//! Ok(this) +//! } +//! } +//! +//! let mut server = Party::new("127.0.0.1:27015").unwrap(); +//! +//! let mut client = Party::new("0.0.0.0:0").unwrap(); +//! +//! spawn(move || { +//! let Party { socket, mut request_buf, mut response_buf } = server; +//! +//! // Recieve initial request from client. +//! +//! let (len, addr) = socket.recv_from(&mut request_buf).unwrap(); +//! request_buf.set_len(len); +//! +//! let request = request_buf.read().unwrap(); +//! assert_eq!(request, Request::AtmosphericTemperature { area: Area::AlQuds }); +//! +//! // Handle request and respond back to client. +//! +//! let response = Response::AtmosphericTemperature(44.4); // For demonstration's sake. +//! +//! response_buf.write(response).unwrap(); +//! socket.send_to(&response_buf, addr).unwrap(); +//! }); +//! +//! spawn(move || { +//! let Party { socket, mut request_buf, mut response_buf } = client; +//! +//! // Send initial request to server. +//! +//! socket.connect("127.0.0.1:27015").unwrap(); //! -//! assert_eq!(u8::deserialise(&stream).unwrap(), 0x45); -//! assert_eq!(u8::deserialise(&stream).unwrap(), 0x54); +//! let request = Request::AtmosphericTemperature { area: Area::AlQuds }; //! -//! // The data can also be deserialised as a tuple (up -//! // to twelve elements). +//! request_buf.write(request); +//! socket.send(&request_buf).unwrap(); //! -//! let stream = Dstream::new(&data); -//! assert_eq!(<(u8, u8)>::deserialise(&stream).unwrap(), (0x45, 0x54)); +//! // Recieve final response from server. +//! +//! socket.recv(&mut response_buf).unwrap(); +//! +//! let response = response_buf.read().unwrap(); +//! assert_eq!(response, Response::AtmosphericTemperature(44.4)); +//! }); //! ``` +//! +//! # Feature flags +//! +//! bZipper defines the following features: +//! +//! * `alloc` (default): Enables the [`Buf`] type and implementations for e.g. [`Box`](alloc::boxed::Box) and [`Arc`](alloc::sync::Arc) +//! * `std` (default): Enables implementations for types such as [`Mutex`](std::sync::Mutex) and [`RwLock`](std::sync::RwLock) +//! +//! # Documentation +//! +//! bZipper has its documentation written in-source for use by `rustdoc`. +//! See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for an on-line, rendered instance. +//! +//! Currently, these docs make use of some unstable features for the sake of readability. +//! The nightly toolchain is therefore required when rendering them. +//! +//! # Contribution +//! +//! bZipper does not accept source code contributions at the moment. +//! This is a personal choice by the maintainer and may be undone in the future. +//! +//! Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/bzipper/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/bzipper/issues/) if you feel the need to express any concerns over the project. +//! +//! # Copyright & Licence +//! +//! 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/>. #![no_std] -#![cfg_attr(doc, feature(doc_cfg))] +#![cfg_attr(doc, allow(internal_features))] +#![cfg_attr(doc, feature(doc_cfg, rustdoc_internals))] +// For use in macros: extern crate self as bzipper; #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "alloc")] +#[cfg(feature = "std")] extern crate std; -/// Implements [`Deserialise`] for the provided type. +/// Implements [`Decode`] for the provided type. +/// +/// This macro assumes the same format used by the equivalent [`Encode`](derive@Encode) macro. #[doc(inline)] -pub use bzipper_macros::Deserialise; +pub use bzipper_macros::Decode; -/// Implements [`Serialise`] for the provided type. +/// Implements [`Encode`] for the provided type. +/// +/// Note that if all fields additionally implement [`SizedEncode`](trait@SizedEncode), then the [`SizedEncode`](derive@SizedEncode) derive macro is usually prefered instead. /// /// # Structs /// /// For structures, each element is chained in **order of declaration.** -/// For example, the following struct will serialise its field `foo` before `bar`: +/// For example, the following struct will encode its field `foo` followed by `bar`: /// -/// ```rust -/// use bzipper::Serialise; +/// ``` +/// use bzipper::Encode; /// -/// #[derive(Serialise)] -/// pub struct FooBar { +/// #[derive(Encode)] +/// struct FooBar { /// pub foo: char, /// pub bar: char, /// } /// ``` /// -/// Should the structure's declaration change, then all previous derived serialisations be considered void. +/// This should be kept in mind when changing the structure's declaration as doing so may invalidate previous encodings. /// -/// The value of [`MAX_SERIALISED_SIZE`](Serialise::MAX_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. +/// If the structure is a unit structure (i.e. it has *no* fields) then it is encoded 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. +/// Enumerations encode like structures except that each variant additionally encodes a unique discriminant. /// -/// Variants with fields are serialised exactly like structures. -/// That is, each field is chained in order of declaration. +/// By default, each discriminant is assigned from the range 0 to infinite, to the extend allowed by the [`isize`] type and its encoding (as which **all** discriminants are encoded). +/// A custom discriminant may be set instead by assigning the variant an integer constant. +/// Unspecified discriminants then increment the previous variant's discriminant: /// -/// Each variant has its own value of `MAX_SERIALISED_SIZE`, and the largest of these values is chosen as the value of the enumeration's own `MAX_SERIALISED_SIZE`. +/// ``` +/// use bzipper::{Buf, SizedEncode}; +/// +/// #[derive(SizedEncode)] +/// enum Num { +/// Two = 0x2, +/// +/// Three, +/// +/// Zero = 0x0, +/// +/// One, +/// } +/// +/// let mut buf = Buf::new(); +/// +/// buf.write(Num::Zero).unwrap(); +/// assert_eq!(buf, [0x00, 0x00].as_slice()); +/// +/// buf.write(Num::One).unwrap(); +/// assert_eq!(buf, [0x00, 0x01].as_slice()); +/// +/// buf.write(Num::Two).unwrap(); +/// assert_eq!(buf, [0x00, 0x02].as_slice()); +/// +/// buf.write(Num::Three).unwrap(); +/// assert_eq!(buf, [0x00, 0x03].as_slice()); +/// ``` +/// +/// Variants with fields are encoded exactly like structures. +/// That is, each field is chained in order of declaration. /// /// # Unions /// -/// Unions cannot derive `Serialise` due to the uncertainty of their contents. +/// Unions cannot derive `Encode` due to the uncertainty of their contents. /// The trait should therefore be implemented manually for such types. #[doc(inline)] -pub use bzipper_macros::Serialise; +pub use bzipper_macros::Encode; + +/// Implements [`Encode`](trait@Encode) and [`SizedEncode`] for the given type. +/// +/// See also the [`Encode`](derive@Encode) derive macro for how the resulting encoder is implemented. +/// +/// For simple structures, the value of [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE) is set to the combined value of all fields' own definition. +/// +/// For enumerations, each variant has its own `MAX_ENCODED_SIZE` value calculated as if it was an equivalent structure (additionally containing the discriminant). +/// The largest of these values is then chosen as the enumeration type's actual `MAX_ENCODED_SIZE` value. +/// +/// As untagged unions cannot derive `Encode`, `SizedEncode` also cannot be derived for them. +#[doc(inline)] +pub use bzipper_macros::SizedEncode; macro_rules! use_mod { - ($vis:vis $name:ident) => { + ($vis:vis $name:ident$(,)?) => { mod $name; $vis use $name::*; }; } -pub(in crate) use use_mod; +pub(crate) use use_mod; -use_mod!(pub deserialise); -use_mod!(pub dstream); -use_mod!(pub error); -use_mod!(pub fixed_string); -use_mod!(pub serialise); -use_mod!(pub sstream); +use_mod!(pub decode); +use_mod!(pub encode); +use_mod!(pub i_stream); +use_mod!(pub o_stream); +use_mod!(pub sized_encode); +use_mod!(pub sized_iter); +use_mod!(pub sized_str); +use_mod!(pub sized_slice); #[cfg(feature = "alloc")] -use_mod!(pub buffer); +use_mod!(pub buf); + +pub mod error;
\ No newline at end of file diff --git a/bzipper/src/o_stream/mod.rs b/bzipper/src/o_stream/mod.rs new file mode 100644 index 0000000..c38c079 --- /dev/null +++ b/bzipper/src/o_stream/mod.rs @@ -0,0 +1,75 @@ +// 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::ptr::copy_nonoverlapping; + +/// Byte stream suitable for encoding. +pub struct OStream<'a> { + buf: &'a mut [u8], + pos: usize, +} + +impl<'a> OStream<'a> { + /// Constructs a new o-stream. + #[inline(always)] + #[must_use] + pub const fn new(buf: &'a mut [u8]) -> Self { + Self { buf, pos: 0x0 } + } + + /// Writes bytes to the stream. + /// + /// # Panics + /// + /// If the requested amount of bytes could not exactly be written, then this method will panic. + #[inline] + pub fn write(&mut self, data: &[u8]) { + let remaining = self.buf.len() - self.pos; + let count = data.len(); + + assert!( + remaining >= count, + "cannot write ({count}) bytes at ({}) to stream with capacity of ({})", + self.pos, + self.buf.len(), + ); + + unsafe { + let src = data.as_ptr(); + let dst = self.buf.as_mut_ptr().add(self.pos); + + copy_nonoverlapping(src, dst, count); + } + + self.pos += count; + } + + /// Closes the stream. + /// + /// The total ammount of bytes written is returned. + #[expect(clippy::must_use_candidate)] + #[inline(always)] + pub const fn close(self) -> usize { + let Self { pos, .. } = self; + + pos + } +} diff --git a/bzipper/src/serialise/mod.rs b/bzipper/src/serialise/mod.rs deleted file mode 100644 index b22d68e..0000000 --- a/bzipper/src/serialise/mod.rs +++ /dev/null @@ -1,258 +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, Result, Sstream}; - -use core::{convert::Infallible, hint::unreachable_unchecked, marker::PhantomData}; - -mod tuple; - -/// Denotes a type capable of serialisation. -/// -/// It is recommended to simply derive this trait for custom types. -/// It can, however, also be manually implemented: -/// -/// ```rust -/// // Manual implementation of custom type. This im- -/// // plementation is equivalent to what would have -/// // been derived. -/// -/// use bzipper::{Result, Serialise, Sstream}; -/// -/// struct Foo { -/// bar: u16, -/// baz: f32, -/// } -/// -/// impl Serialise for Foo { -/// const MAX_SERIALISED_SIZE: usize = u16::MAX_SERIALISED_SIZE + f32::MAX_SERIALISED_SIZE; -/// -/// fn serialise(&self, stream: &mut Sstream) -> Result<()> { -/// // Serialise fields using chaining. -/// -/// self.bar.serialise(stream)?; -/// self.baz.serialise(stream)?; -/// -/// Ok(()) -/// } -/// } -/// ``` -/// -/// Implementors of this trait should make sure that [`MAX_SERIALISED_SIZE`](Self::MAX_SERIALISED_SIZE) is properly defined. -/// This value indicates the definitively largest size of any serialisation of `Self`. -pub trait Serialise: Sized { - /// The maximum amount of bytes that can 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. - const MAX_SERIALISED_SIZE: usize; - - /// Serialises `self` into the given s-stream. - /// - /// This method must **never** write more bytes than specified by [`MAX_SERIALISED_SIZE`](Self::MAX_SERIALISED_SIZE). - /// Doing so is considered a logic error. - /// - /// # Errors - /// - /// If serialisation fails, e.g. by an unencodable value being provided, an error is returned. - fn serialise(&self, stream: &mut Sstream) -> Result<()>; -} - -macro_rules! impl_numeric { - ($ty:ty) => { - impl ::bzipper::Serialise for $ty { - const MAX_SERIALISED_SIZE: usize = size_of::<$ty>(); - - #[inline] - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - stream.write(&self.to_be_bytes())?; - - Ok(()) - } - } - }; -} - -macro_rules! impl_non_zero { - ($ty:ty) => { - impl ::bzipper::Serialise for ::core::num::NonZero<$ty> { - const MAX_SERIALISED_SIZE: usize = ::core::mem::size_of::<$ty>(); - - #[inline(always)] - fn serialise(&self, stream: &mut Sstream) -> Result<()> { self.get().serialise(stream) } - } - }; -} - -impl<T: Serialise, const N: usize> Serialise for [T; N] { - const MAX_SERIALISED_SIZE: usize = T::MAX_SERIALISED_SIZE * N; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - for v in self { v.serialise(stream)? } - - Ok(()) - } -} - -impl Serialise for bool { - const MAX_SERIALISED_SIZE: usize = u8::MAX_SERIALISED_SIZE; - - #[inline(always)] - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - u8::from(*self).serialise(stream) - } -} - -impl Serialise for char { - const MAX_SERIALISED_SIZE: usize = u32::MAX_SERIALISED_SIZE; - - #[inline(always)] - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - u32::from(*self).serialise(stream) - } - -} - -// Especially useful for `Result<T, Infallible>`. -// *If* that is even needed, of course. -impl Serialise for Infallible { - const MAX_SERIALISED_SIZE: usize = 0x0; - - #[inline(always)] - fn serialise(&self, _stream: &mut Sstream) -> Result<()> { unsafe { unreachable_unchecked() } } - -} - -impl Serialise for isize { - const MAX_SERIALISED_SIZE: usize = i32::MAX_SERIALISED_SIZE; - - #[inline] - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - let value = i32::try_from(*self) - .map_err(|_| Error::IsizeOutOfRange(*self))?; - - value.serialise(stream) - } -} - -impl<T: Serialise> Serialise for Option<T> { - const MAX_SERIALISED_SIZE: usize = bool::MAX_SERIALISED_SIZE + T::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - // The first element is of type `bool` and is - // called the "sign." It signifies whether there is - // a following element or not. - - match *self { - None => { - false.serialise(stream)?; - // No need to zero-fill. - }, - - Some(ref v) => { - true.serialise(stream)?; - v.serialise(stream)?; - }, - }; - - Ok(()) - } -} - -impl<T> Serialise for PhantomData<T> { - const MAX_SERIALISED_SIZE: usize = size_of::<Self>(); - - #[inline(always)] - fn serialise(&self, _stream: &mut Sstream) -> Result<()> { Ok(()) } -} - -impl<T, E> Serialise for core::result::Result<T, E> -where - T: Serialise, - E: Serialise, { - const MAX_SERIALISED_SIZE: usize = bool::MAX_SERIALISED_SIZE + if size_of::<T>() > size_of::<E>() { size_of::<T>() } else { size_of::<E>() }; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - // Remember the descriminant. - - match *self { - Ok(ref v) => { - false.serialise(stream)?; - v.serialise(stream)?; - }, - - Err(ref e) => { - true.serialise(stream)?; - e.serialise(stream)?; - }, - }; - - Ok(()) - } -} - -impl Serialise for () { - const MAX_SERIALISED_SIZE: usize = 0x0; - - #[inline(always)] - fn serialise(&self, _stream: &mut Sstream) -> Result<()> { Ok(()) } -} - -impl Serialise for usize { - const MAX_SERIALISED_SIZE: Self = u32::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - let value = u32::try_from(*self) - .map_err(|_| Error::UsizeOutOfRange(*self))?; - - value.serialise(stream) - } -} - -//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/tuple.rs b/bzipper/src/serialise/tuple.rs deleted file mode 100644 index f2332b8..0000000 --- a/bzipper/src/serialise/tuple.rs +++ /dev/null @@ -1,376 +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::{Result, Serialise, Sstream}; - -impl<T0> Serialise for (T0, ) -where - T0: Serialise, { - const MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - - Ok(()) - } -} - -impl<T0, T1> Serialise for (T0, T1) -where - T0: Serialise, - T1: Serialise, { - const MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - - Ok(()) - } -} - -impl<T0, T1, T2> Serialise for (T0, T1, T2) -where - T0: Serialise, - T1: Serialise, - T2: Serialise, { - const MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - - Ok(()) - } -} - -impl<T0, T1, T2, T3> Serialise for (T0, T1, T2, T3) -where - T0: Serialise, - T1: Serialise, - T2: Serialise, - T3: Serialise, { - const MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE - + T7::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - self.7.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE - + T7::MAX_SERIALISED_SIZE - + T8::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - self.7.serialise(stream)?; - self.8.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE - + T7::MAX_SERIALISED_SIZE - + T8::MAX_SERIALISED_SIZE - + T9::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - self.7.serialise(stream)?; - self.8.serialise(stream)?; - self.9.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE - + T7::MAX_SERIALISED_SIZE - + T8::MAX_SERIALISED_SIZE - + T9::MAX_SERIALISED_SIZE - + T10::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - self.7.serialise(stream)?; - self.8.serialise(stream)?; - self.9.serialise(stream)?; - self.10.serialise(stream)?; - - 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 MAX_SERIALISED_SIZE: usize = - T0::MAX_SERIALISED_SIZE - + T1::MAX_SERIALISED_SIZE - + T2::MAX_SERIALISED_SIZE - + T3::MAX_SERIALISED_SIZE - + T4::MAX_SERIALISED_SIZE - + T5::MAX_SERIALISED_SIZE - + T6::MAX_SERIALISED_SIZE - + T7::MAX_SERIALISED_SIZE - + T8::MAX_SERIALISED_SIZE - + T9::MAX_SERIALISED_SIZE - + T10::MAX_SERIALISED_SIZE - + T11::MAX_SERIALISED_SIZE; - - fn serialise(&self, stream: &mut Sstream) -> Result<()> { - self.0.serialise(stream)?; - self.1.serialise(stream)?; - self.2.serialise(stream)?; - self.3.serialise(stream)?; - self.4.serialise(stream)?; - self.5.serialise(stream)?; - self.6.serialise(stream)?; - self.7.serialise(stream)?; - self.8.serialise(stream)?; - self.9.serialise(stream)?; - self.10.serialise(stream)?; - self.11.serialise(stream)?; - - Ok(()) - } -} diff --git a/bzipper/src/sized_encode/mod.rs b/bzipper/src/sized_encode/mod.rs new file mode 100644 index 0000000..3a52397 --- /dev/null +++ b/bzipper/src/sized_encode/mod.rs @@ -0,0 +1,342 @@ +// 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::Encode; + +use core::cell::RefCell; +use core::convert::Infallible; +use core::marker::PhantomData; +use core::net::{ + IpAddr, + Ipv4Addr, + Ipv6Addr, + SocketAddr, + SocketAddrV4, + SocketAddrV6, +}; +use core::num::{Saturating, Wrapping}; +use core::ops::{ + Bound, + Range, + RangeFrom, + RangeFull, + RangeInclusive, + RangeTo, + RangeToInclusive, +}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "std")] +use std::rc::Rc; + +#[cfg(feature = "std")] +use std::sync::{Arc, Mutex, RwLock}; + +mod tuple; + +/// Denotes a size-constrained, encodable type. +/// +/// When using [`Encode`], the size of the resulting encoding cannot always be known beforehand. +/// This trait defines an upper bound for these sizes. +/// +/// # Safety +/// +/// Users of the `Encode` and [`Decode`](crate::Decode) traits may assume that the [`MAX_ENCODED_SIZE`](Self::MAX_ENCODED_SIZE) constant is properly defined and that no encoding will be larger than this value. +/// Implementors must therefore guarantee that **no** call to [`encode`](Encode::encode) or [`decode`](bzipper::Decode::decode) consumes more bytes than specified by this constant. +pub unsafe trait SizedEncode: Encode + Sized { + /// The maximum guaranteed amount of bytes that can result from an encoding. + /// + /// Implementors of this trait should make sure that no encoding (or decoding) uses more than the amount specified by this constant. + const MAX_ENCODED_SIZE: usize; +} + +macro_rules! impl_numeric { + ($ty:ty$(,)?) => { + unsafe impl ::bzipper::SizedEncode for $ty { + const MAX_ENCODED_SIZE: usize = size_of::<$ty>(); + } + }; +} + +macro_rules! impl_non_zero { + ($ty:ty$(,)?) => { + unsafe impl ::bzipper::SizedEncode for ::core::num::NonZero<$ty> { + const MAX_ENCODED_SIZE: usize = <$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE; + } + }; +} + +macro_rules! impl_atomic { + { + width: $width:literal, + ty: $ty:ty, + atomic_ty: $atomic_ty:ty$(,)? + } => { + #[cfg(target_has_atomic = $width)] + #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] + unsafe impl ::bzipper::SizedEncode for $atomic_ty { + const MAX_ENCODED_SIZE: usize = <$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE; + } + }; +} + +unsafe impl<T: SizedEncode, const N: usize> SizedEncode for [T; N] { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +unsafe impl<T: SizedEncode> SizedEncode for Arc<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for bool { + const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for Bound<T> { + const MAX_ENCODED_SIZE: usize = 0x0; +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +unsafe impl<T: SizedEncode> SizedEncode for Box<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for char { + const MAX_ENCODED_SIZE: usize = u32::MAX_ENCODED_SIZE; + +} + +unsafe impl SizedEncode for Infallible { + const MAX_ENCODED_SIZE: usize = 0x0; +} + +unsafe impl SizedEncode for IpAddr { + const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE + Ipv6Addr::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for Ipv4Addr { + const MAX_ENCODED_SIZE: usize = u32::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for Ipv6Addr { + const MAX_ENCODED_SIZE: usize = u128::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for isize { + const MAX_ENCODED_SIZE: usize = i16::MAX_ENCODED_SIZE; +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +unsafe impl<T: SizedEncode> SizedEncode for Mutex<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for Option<T> { + const MAX_ENCODED_SIZE: usize = + bool::MAX_ENCODED_SIZE + + T::MAX_ENCODED_SIZE; +} + +unsafe impl<T> SizedEncode for PhantomData<T> { + const MAX_ENCODED_SIZE: usize = 0x0; +} + +unsafe impl<T: SizedEncode> SizedEncode for Range<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2; +} + +unsafe impl<T: SizedEncode> SizedEncode for RangeFrom<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for RangeFull { + const MAX_ENCODED_SIZE: usize = 0x0; +} + +unsafe impl<T: SizedEncode> SizedEncode for RangeInclusive<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2; +} + +unsafe impl<T: SizedEncode> SizedEncode for RangeTo<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for RangeToInclusive<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +unsafe impl<T: SizedEncode> SizedEncode for Rc<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for RefCell<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode, E: SizedEncode> SizedEncode for core::result::Result<T, E> { + const MAX_ENCODED_SIZE: usize = + bool::MAX_ENCODED_SIZE + + if size_of::<T>() > size_of::<E>() { size_of::<T>() } else { size_of::<E>() }; +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +unsafe impl<T: SizedEncode> SizedEncode for RwLock<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for Saturating<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for SocketAddr { + const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE + SocketAddrV6::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for SocketAddrV4 { + const MAX_ENCODED_SIZE: usize = Ipv4Addr::MAX_ENCODED_SIZE + u16::MAX_ENCODED_SIZE; +} + +/// This implementation encodes the address's bits followed by the port number, all of which in big-endian. +unsafe impl SizedEncode for SocketAddrV6 { + const MAX_ENCODED_SIZE: usize = + Ipv6Addr::MAX_ENCODED_SIZE + + u16::MAX_ENCODED_SIZE + + u32::MAX_ENCODED_SIZE + + u32::MAX_ENCODED_SIZE; +} + +unsafe impl SizedEncode for () { + const MAX_ENCODED_SIZE: usize = 0x0; +} + +unsafe impl SizedEncode for usize { + const MAX_ENCODED_SIZE: Self = u16::MAX_ENCODED_SIZE; +} + +unsafe impl<T: SizedEncode> SizedEncode for Wrapping<T> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; +} + +//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); + +impl_atomic! { + width: "8", + ty: bool, + atomic_ty: std::sync::atomic::AtomicBool, +} + +impl_atomic! { + width: "16", + ty: i16, + atomic_ty: std::sync::atomic::AtomicI16, +} + +impl_atomic! { + width: "32", + ty: i32, + atomic_ty: std::sync::atomic::AtomicI32, +} + +impl_atomic! { + width: "64", + ty: i64, + atomic_ty: std::sync::atomic::AtomicI64, +} + +impl_atomic! { + width: "8", + ty: i8, + atomic_ty: std::sync::atomic::AtomicI8, +} + +impl_atomic! { + width: "ptr", + ty: isize, + atomic_ty: std::sync::atomic::AtomicIsize, +} + +impl_atomic! { + width: "16", + ty: u16, + atomic_ty: std::sync::atomic::AtomicU16, +} + +impl_atomic! { + width: "32", + ty: u32, + atomic_ty: std::sync::atomic::AtomicU32, +} + +impl_atomic! { + width: "64", + ty: u64, + atomic_ty: std::sync::atomic::AtomicU64, +} + +impl_atomic! { + width: "8", + ty: u8, + atomic_ty: std::sync::atomic::AtomicU8, +} + +impl_atomic! { + width: "ptr", + ty: usize, + atomic_ty: std::sync::atomic::AtomicUsize, +} diff --git a/bzipper/src/sized_encode/test.rs b/bzipper/src/sized_encode/test.rs new file mode 100644 index 0000000..3e88812 --- /dev/null +++ b/bzipper/src/sized_encode/test.rs @@ -0,0 +1,97 @@ +// 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 bzipper::{SizedStr, SizedEncode}; +use std::convert::Infallible; +use std::marker::PhantomData; +use std::net::{ + IpAddr, + Ipv4Addr, + Ipv6Addr, + SocketAddr, + SocketAddrV4, + SocketAddrV6, +}; +use std::num::NonZero; + +#[derive(SizedEncode)] +struct Foo(char); + +#[derive(SizedEncode)] +#[expect(dead_code)] +#[repr(u8)] // Not honoured. +enum Bar { + Unit = 0x45, + + Pretty(bool) = 127, + + Teacher { initials: [char; 0x3] }, +} + +#[test] +fn test_sized_encode() { + macro_rules! assert_encoded_size { + ($ty:ty, $value:expr$(,)?) => {{ + assert_eq!(<$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE, $value); + }}; + } + + assert_encoded_size!(bool, 0x1); + assert_encoded_size!(char, 0x4); + assert_encoded_size!(f32, 0x4); + assert_encoded_size!(f64, 0x8); + assert_encoded_size!(i128, 0x10); + assert_encoded_size!(i16, 0x2); + assert_encoded_size!(i32, 0x4); + assert_encoded_size!(i64, 0x8); + assert_encoded_size!(i8, 0x1); + assert_encoded_size!(isize, 0x2); + assert_encoded_size!(SizedStr::<0x45>, 0x47); + assert_encoded_size!(Infallible, 0x0); + assert_encoded_size!(IpAddr, 0x11); + assert_encoded_size!(Ipv4Addr, 0x4); + assert_encoded_size!(Ipv6Addr, 0x10); + assert_encoded_size!(Option<NonZero<i128>>, 0x11); + assert_encoded_size!(Option<NonZero<i16>>, 0x3); + assert_encoded_size!(Option<NonZero<i32>>, 0x5); + assert_encoded_size!(Option<NonZero<i64>>, 0x9); + assert_encoded_size!(Option<NonZero<i8>>, 0x2); + assert_encoded_size!(Option<NonZero<isize>>, 0x3); + assert_encoded_size!(Option<NonZero<u128>>, 0x11); + assert_encoded_size!(Option<NonZero<u16>>, 0x3); + assert_encoded_size!(Option<NonZero<u32>>, 0x5); + assert_encoded_size!(Option<NonZero<u64>>, 0x9); + assert_encoded_size!(Option<NonZero<u8>>, 0x2); + assert_encoded_size!(Option<NonZero<usize>>, 0x3); + assert_encoded_size!(PhantomData<[u128; 0x10]>, 0x0); + assert_encoded_size!(SocketAddr, 0x1B); + assert_encoded_size!(SocketAddrV4, 0x6); + assert_encoded_size!(SocketAddrV6, 0x1A); + assert_encoded_size!(u128, 0x10); + assert_encoded_size!(u16, 0x2); + assert_encoded_size!(u32, 0x4); + assert_encoded_size!(u64, 0x8); + assert_encoded_size!(u8, 0x1); + assert_encoded_size!(usize, 0x2); + assert_encoded_size!((), 0x0); + + assert_encoded_size!(Foo, 0x4); + assert_encoded_size!(Bar, 0xE); +} diff --git a/bzipper/src/sized_encode/tuple.rs b/bzipper/src/sized_encode/tuple.rs new file mode 100644 index 0000000..5a88fb7 --- /dev/null +++ b/bzipper/src/sized_encode/tuple.rs @@ -0,0 +1,253 @@ +// 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::SizedEncode; + +/// Implemented for tuples with up to twelve members. +#[cfg_attr(doc, doc(fake_variadic))] +unsafe impl<T> SizedEncode for (T, ) +where + T: SizedEncode, { + + #[doc(hidden)] + const MAX_ENCODED_SIZE: usize = + T::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1> SizedEncode for (T0, T1) +where + T0: SizedEncode, + T1: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2> SizedEncode for (T0, T1, T2) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3> SizedEncode for (T0, T1, T2, T3) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4> SizedEncode for (T0, T1, T2, T3, T4) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5> SizedEncode for (T0, T1, T2, T3, T4, T5) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6> SizedEncode for (T0, T1, T2, T3, T4, T5, T6) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6, T7> SizedEncode for (T0, T1, T2, T3, T4, T5, T6, T7) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, + T7: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE + + T7::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6, T7, T8> SizedEncode for (T0, T1, T2, T3, T4, T5, T6, T7, T8) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, + T7: SizedEncode, + T8: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE + + T7::MAX_ENCODED_SIZE + + T8::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> SizedEncode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, + T7: SizedEncode, + T8: SizedEncode, + T9: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE + + T7::MAX_ENCODED_SIZE + + T8::MAX_ENCODED_SIZE + + T9::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> SizedEncode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, + T7: SizedEncode, + T8: SizedEncode, + T9: SizedEncode, + T10: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE + + T7::MAX_ENCODED_SIZE + + T8::MAX_ENCODED_SIZE + + T9::MAX_ENCODED_SIZE + + T10::MAX_ENCODED_SIZE; +} + +#[doc(hidden)] +unsafe impl<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> SizedEncode for (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) +where + T0: SizedEncode, + T1: SizedEncode, + T2: SizedEncode, + T3: SizedEncode, + T4: SizedEncode, + T5: SizedEncode, + T6: SizedEncode, + T7: SizedEncode, + T8: SizedEncode, + T9: SizedEncode, + T10: SizedEncode, + T11: SizedEncode, { + const MAX_ENCODED_SIZE: usize = + T0::MAX_ENCODED_SIZE + + T1::MAX_ENCODED_SIZE + + T2::MAX_ENCODED_SIZE + + T3::MAX_ENCODED_SIZE + + T4::MAX_ENCODED_SIZE + + T5::MAX_ENCODED_SIZE + + T6::MAX_ENCODED_SIZE + + T7::MAX_ENCODED_SIZE + + T8::MAX_ENCODED_SIZE + + T9::MAX_ENCODED_SIZE + + T10::MAX_ENCODED_SIZE + + T11::MAX_ENCODED_SIZE; +} diff --git a/bzipper/src/sized_iter/mod.rs b/bzipper/src/sized_iter/mod.rs new file mode 100644 index 0000000..7612907 --- /dev/null +++ b/bzipper/src/sized_iter/mod.rs @@ -0,0 +1,161 @@ +// 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::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator}; +use core::mem::MaybeUninit; +use core::slice; + +/// Iterator to a sized slice. +#[must_use] +pub struct SizedIter<T, const N: usize> { + buf: [MaybeUninit<T>; N], + + pos: usize, + len: usize, +} + +impl<T, const N: usize> SizedIter<T, N> { + /// Constructs a new, fixed-size iterator. + #[inline(always)] + pub(crate) const unsafe fn new(buf: [MaybeUninit<T>; N], len: usize) -> Self { + debug_assert!(len <= N, "cannot construct iterator longer than its capacity"); + + Self { buf, pos: 0x0, len } + } + + /// Gets a slice of the remaining elements. + #[inline(always)] + pub const fn as_slice(&self) -> &[T] { + unsafe { + let ptr = self.buf + .as_ptr() + .add(self.pos) + .cast(); + + slice::from_raw_parts(ptr, self.len) + } + } + + /// Gets a mutable slice of the remaining elements. + #[inline(always)] + pub const fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { + let ptr = self.buf + .as_mut_ptr() + .add(self.pos) + .cast(); + + slice::from_raw_parts_mut(ptr, self.len) + } + } +} + +impl<T, const N: usize> AsMut<[T]> for SizedIter<T, N> { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl<T, const N: usize> AsRef<[T]> for SizedIter<T, N> { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl<T: Clone, const N: usize> Clone for SizedIter<T, N> { + #[inline] + fn clone(&self) -> Self { + unsafe { + let mut buf: [MaybeUninit<T>; N] = MaybeUninit::uninit().assume_init(); + let Self { pos, len, .. } = *self; + + let start = pos; + let stop = start.unchecked_add(len); + + for i in start..stop { + let value = &*self.buf + .as_ptr() + .add(i) + .cast::<T>(); + + buf + .get_unchecked_mut(i) + .write(value.clone()); + } + + Self { buf, pos, len } + } + } +} + +impl<T, const N: usize> DoubleEndedIterator for SizedIter<T, N> { + #[inline] + fn next_back(&mut self) -> Option<Self::Item> { + if self.len == 0x0 { return None }; + + unsafe { + let index = self.pos.unchecked_add(self.len); + + let item = self.buf + .get_unchecked(index) + .assume_init_read(); + + self.len = self.len.unchecked_sub(0x1); + + Some(item) + } + } +} + +impl<T, const N: usize> ExactSizeIterator for SizedIter<T, N> { } + +impl<T, const N: usize> FusedIterator for SizedIter<T, N> { } + +impl<T, const N: usize> Iterator for SizedIter<T, N> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + if self.len == 0x0 { return None }; + + unsafe { + let index = self.pos; + + let item = self.buf + .get_unchecked(index) + .assume_init_read(); + + self.pos = self.pos.unchecked_add(0x1); + self.len = self.len.unchecked_sub(0x1); + + Some(item) + } + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option<usize>) { + let rem = unsafe { self.len.unchecked_sub(self.pos) }; + + (rem, Some(rem)) + } +} diff --git a/bzipper/src/sized_slice/mod.rs b/bzipper/src/sized_slice/mod.rs new file mode 100644 index 0000000..2f880c3 --- /dev/null +++ b/bzipper/src/sized_slice/mod.rs @@ -0,0 +1,552 @@ +// 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::{ + Decode, + Encode, + IStream, + OStream, + SizedEncode, + SizedIter, +}; +use crate::error::{DecodeError, EncodeError, SizeError}; + +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::Ordering; +use core::fmt::{self, Debug, Formatter}; +use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut, Index, IndexMut}; +use core::ptr::copy_nonoverlapping; +use core::slice; +use core::slice::{Iter, IterMut, SliceIndex}; + +#[cfg(feature = "alloc")] +use alloc::alloc::{alloc, Layout}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Stack-allocated vector with maximum length. +/// +/// This type is intended as an [sized-encodable](SizedEncode) alternative to [`Vec`] for cases where [arrays](array) may not be wanted. +/// +/// Note that this type is immutable in the sense that it does **not** define methods like `push` and `pop`, unlike `Vec`. +/// +/// See [`SizedStr`](crate::SizedStr) for an equivalent alternative to [`String`](alloc::string::String). +/// +/// # Examples +/// +/// All instances of this type with the same `T` and `N` also have the exact same layout: +/// +/// ``` +/// use bzipper::SizedSlice; +/// +/// let vec0 = SizedSlice::<u8, 0x4>::try_from([0x3].as_slice()).unwrap(); +/// let vec1 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2].as_slice()).unwrap(); +/// let vec2 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2, 0x4].as_slice()).unwrap(); +/// let vec3 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap(); +/// +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec1)); +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec2)); +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec3)); +/// assert_eq!(size_of_val(&vec1), size_of_val(&vec2)); +/// assert_eq!(size_of_val(&vec1), size_of_val(&vec3)); +/// assert_eq!(size_of_val(&vec2), size_of_val(&vec3)); +/// ``` +pub struct SizedSlice<T, const N: usize> { + buf: [MaybeUninit<T>; N], + len: usize, +} + +impl<T, const N: usize> SizedSlice<T, N> { + /// Constructs a fixed-size vector from raw parts. + /// + /// The provided parts are not tested in any way. + /// + /// # Safety + /// + /// The value of `len` may not exceed that of `N`. + /// Additionally, all elements of `buf` in the range specified by `len` must be initialised. + /// + /// If any of these requirements are violated, behaviour is undefined. + #[inline(always)] + #[must_use] + pub const unsafe fn from_raw_parts(buf: [MaybeUninit<T>; N], len: usize) -> Self { + debug_assert!(len <= N, "cannot construct vector longer than its capacity"); + + Self { buf, len } + } + + /// Sets the length of the vector. + /// + /// The provided length is not tested in any way. + /// + /// # Safety + /// + /// The new length `len` may not be larger than `N`. + /// + /// It is only valid to enlarge vectors if `T` supports being in a purely uninitialised state. + /// Such is permitted with e.g. [`MaybeUninit`]. + #[inline(always)] + pub const unsafe fn set_len(&mut self, len: usize) { + debug_assert!(len <= N, "cannot set length past bounds"); + + self.len = len + } + + /// Gets a pointer to the first element. + /// + /// The pointed-to element may not necessarily be initialised. + /// See [`len`](Self::len) for more information. + #[inline(always)] + #[must_use] + pub const fn as_ptr(&self) -> *const T { + self.buf.as_ptr().cast() + } + + /// Gets a mutable pointer to the first element. + /// + /// The pointed-to element may not necessarily be initialised. + /// See [`len`](Self::len) for more information. + #[inline(always)] + #[must_use] + pub const fn as_mut_ptr(&mut self) -> *mut T { + self.buf.as_mut_ptr().cast() + } + + /// Borrows the vector as a slice. + /// + /// The range of the returned slice only includes the elements specified by [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn as_slice(&self) -> &[T] { + let ptr = self.as_ptr(); + let len = self.len(); + + unsafe { slice::from_raw_parts(ptr, len) } + } + + /// Borrows the vector as a mutable slice. + /// + /// The range of the returned slice only includes the elements specified by [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn as_mut_slice(&mut self) -> &mut [T] { + let ptr = self.as_mut_ptr(); + let len = self.len(); + + unsafe { slice::from_raw_parts_mut(ptr, len) } + } + + /// Returns the total capacity of the vector. + /// + /// By definition, this is always exactly equal to the value of `N`. + #[expect(clippy::unused_self)] + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the length of the vector. + /// + /// This value may necessarily be smaller than `N`. + #[inline(always)] + #[must_use] + pub const fn len(&self) -> usize { + self.len + } + + /// Checks if the vector is empty, i.e. no elements are recorded. + /// + /// Note that the internal buffer may still contain objects that have been "shadowed" by setting a smaller length with [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len() == 0x0 + } + + /// Checks if the vector is full, i.e. it cannot hold any more elements. + #[inline(always)] + #[must_use] + pub const fn is_full(&self) -> bool { + self.len() == self.capacity() + } + + /// Destructs the vector into its raw parts. + /// + /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). + #[inline(always)] + #[must_use] + pub const fn into_raw_parts(self) -> ([MaybeUninit<T>; N], usize) { + let Self { buf, len } = self; + (buf, len) + } + + /// Converts the vector into a boxed slice. + /// + /// The vector is reallocated using the global allocator. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[must_use] + pub fn into_boxed_slice(self) -> Box<[T]> { + let (buf, len) = self.into_raw_parts(); + + unsafe { + let layout = Layout::array::<T>(len).unwrap(); + let ptr = alloc(layout).cast::<T>(); + + assert!(!ptr.is_null(), "allocation failed"); + + copy_nonoverlapping(buf.as_ptr().cast(), ptr, len); + + let slice = core::ptr::slice_from_raw_parts_mut(ptr, len); + Box::from_raw(slice) + + // `self.buf` is dropped without destructors being + // run. + } + } + + /// Converts the vector into a dynamic vector. + /// + /// The vector is reallocated using the global allocator. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[inline(always)] + #[must_use] + pub fn into_vec(self) -> Vec<T> { + self.into_boxed_slice().into_vec() + } +} + +impl<T: Clone, const N: usize> SizedSlice<T, N> { + /// Constructs an empty, fixed-size vector. + #[inline] + pub fn new(data: &[T]) -> Result<Self, SizeError> { + let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + let len = data.len(); + if len > N { return Err(SizeError { req: len, len: N }) }; + + for (item, value) in buf.iter_mut().zip(data.iter()) { + item.write(value.clone()); + } + + Ok(Self { buf, len }) + } +} + +impl<T, const N: usize> AsMut<[T]> for SizedSlice<T, N> { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl<T, const N: usize> AsRef<[T]> for SizedSlice<T, N> { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl<T, const N: usize> Borrow<[T]> for SizedSlice<T, N> { + #[inline(always)] + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl<T, const N: usize> BorrowMut<[T]> for SizedSlice<T, N> { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl<T: Clone, const N: usize> Clone for SizedSlice<T, N> { + #[inline] + fn clone(&self) -> Self { + unsafe { + let mut buf: [MaybeUninit<T>; N] = MaybeUninit::uninit().assume_init(); + + for i in 0x0..self.len() { + let value = self.get_unchecked(i).clone(); + buf.get_unchecked_mut(i).write(value); + } + + Self { buf, len: self.len } + } + } +} + +impl<T: Debug, const N: usize> Debug for SizedSlice<T, N> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_slice(), f) + } +} + +impl<T, const N: usize> Default for SizedSlice<T, N> { + #[inline(always)] + fn default() -> Self { + Self { buf: unsafe { MaybeUninit::uninit().assume_init() }, len: 0x0 } + } +} + +impl<T, const N: usize> Deref for SizedSlice<T, N> { + type Target = [T]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl<T, const N: usize> DerefMut for SizedSlice<T, N> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl<T: Decode, const N: usize> Decode for SizedSlice<T, N> { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let len = Decode::decode(stream)?; + if len > N { return Err(DecodeError::SmallBuffer(SizeError { req: len, len: N })) }; + + let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for item in &mut buf { + let value = Decode::decode(stream)?; + + item.write(value); + } + + Ok(Self { buf, len }) + } +} + +impl<T: Encode, const N: usize> Encode for SizedSlice<T, N> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + self.as_slice().encode(stream) + } +} + +impl<T: Eq, const N: usize> Eq for SizedSlice<T, N> { } + +impl<T, const N: usize> From<[T; N]> for SizedSlice<T, N> { + #[inline(always)] + fn from(value: [T; N]) -> Self { + unsafe { + let buf = value.as_ptr().cast::<[MaybeUninit<T>; N]>().read(); + + Self { buf, len: N } + } + } +} + +impl<T, const N: usize> FromIterator<T> for SizedSlice<T, N> { + #[inline] + fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { + let mut iter = iter.into_iter(); + + let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut len = 0x0; + + for item in &mut buf { + let Some(value) = iter.next() else { break }; + item.write(value); + + len += 0x1; + } + + Self { buf, len } + } +} + +impl<T: Hash, const N: usize> Hash for SizedSlice<T, N> { + #[inline(always)] + fn hash<H: Hasher>(&self, state: &mut H) { + for v in self { + v.hash(state); + } + } +} + +impl<T, I: SliceIndex<[T]>, const N: usize> Index<I> for SizedSlice<T, N> { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<T, I: SliceIndex<[T]>, const N: usize> IndexMut<I> for SizedSlice<T, N> { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +impl<T, const N: usize> IntoIterator for SizedSlice<T, N> { + type Item = T; + + type IntoIter = SizedIter<T, N>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + let Self { buf, len } = self; + + unsafe { SizedIter::new(buf, len) } + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a SizedSlice<T, N> { + type Item = &'a T; + + type IntoIter = Iter<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut SizedSlice<T, N> { + type Item = &'a mut T; + + type IntoIter = IterMut<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl<T: Ord, const N: usize> Ord for SizedSlice<T, N> { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<SizedSlice<U, M>> for SizedSlice<T, N> { + #[inline(always)] + fn eq(&self, other: &SizedSlice<U, M>) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<[U; M]> for SizedSlice<T, N> { + #[inline(always)] + fn eq(&self, other: &[U; M]) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<&[U]> for SizedSlice<T, N> { + #[inline(always)] + fn eq(&self, other: &&[U]) -> bool { + self.as_slice() == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<Vec<U>> for SizedSlice<T, N> { + #[inline(always)] + fn eq(&self, other: &Vec<U>) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<SizedSlice<T, M>> for SizedSlice<T, N> { + #[inline(always)] + fn partial_cmp(&self, other: &SizedSlice<T, M>) -> Option<Ordering> { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<[T; M]> for SizedSlice<T, N> { + #[inline(always)] + fn partial_cmp(&self, other: &[T; M]) -> Option<Ordering> { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl<T: PartialOrd, const N: usize> PartialOrd<&[T]> for SizedSlice<T, N> { + #[inline(always)] + fn partial_cmp(&self, other: &&[T]) -> Option<Ordering> { + self.as_slice().partial_cmp(*other) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T: PartialOrd, const N: usize> PartialOrd<Vec<T>> for SizedSlice<T, N> { + #[inline(always)] + fn partial_cmp(&self, other: &Vec<T>) -> Option<Ordering> { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +unsafe impl<T: SizedEncode, const N: usize> SizedEncode for SizedSlice<T, N> { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; +} + +impl<T: Clone, const N: usize> TryFrom<&[T]> for SizedSlice<T, N> { + type Error = SizeError; + + #[inline(always)] + fn try_from(value: &[T]) -> Result<Self, Self::Error> { + Self::new(value) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T, const N: usize> From<SizedSlice<T, N>> for Box<[T]> { + #[inline(always)] + fn from(value: SizedSlice<T, N>) -> Self { + value.into_boxed_slice() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<T, const N: usize> From<SizedSlice<T, N>> for Vec<T> { + #[inline(always)] + fn from(value: SizedSlice<T, N>) -> Self { + value.into_vec() + } +} diff --git a/bzipper/src/sized_slice/test.rs b/bzipper/src/sized_slice/test.rs new file mode 100644 index 0000000..8fb5065 --- /dev/null +++ b/bzipper/src/sized_slice/test.rs @@ -0,0 +1,47 @@ +// 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 alloc::vec::Vec; +use bzipper::SizedSlice; + +#[test] +fn test_fixed_vec_from_iter() { + let f = |x: u32| -> u32 { + let x = f64::from(x); + + let y = x.sin().powi(0x2) * 1000.0; + + y as u32 + }; + + let mut vec = Vec::new(); + + for x in 0x0..0x8 { + vec.push(f(x)); + } + + let vec: SizedSlice<_, 0x10> = vec.into_iter().collect(); + + assert_eq!( + vec, + [0, 708, 826, 19, 572, 919, 78, 431], + ); +} diff --git a/bzipper/src/sized_str/mod.rs b/bzipper/src/sized_str/mod.rs new file mode 100644 index 0000000..c6db3f2 --- /dev/null +++ b/bzipper/src/sized_str/mod.rs @@ -0,0 +1,629 @@ +// 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::{ + Decode, + Encode, + IStream, + OStream, + SizedEncode, + SizedSlice, +}; +use crate::error::{ + DecodeError, + EncodeError, + SizeError, + StringError, + Utf8Error, +}; + +use core::borrow::{Borrow, BorrowMut}; +use core::cmp::Ordering; +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::mem::{ManuallyDrop, MaybeUninit}; +use core::ops::{Deref, DerefMut, Index, IndexMut}; +use core::ptr::{addr_of, copy_nonoverlapping}; +use core::slice; +use core::slice::SliceIndex; +use core::str; +use core::str::{Chars, CharIndices, FromStr}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +#[cfg(feature = "std")] +use std::ffi::OsStr; + +#[cfg(feature = "std")] +use std::net::ToSocketAddrs; + +#[cfg(feature = "std")] +use std::path::Path; + +/// Stack-allocated string with maximum length. +/// +/// This is in contrast to [`String`] -- which has no size limit in practice -- and [`prim@str`], which is unsized. +/// +/// The string itself is encoded in UTF-8 for interoperability wtih Rust's standard string facilities, and partly due to memory concerns. +/// +/// Keep in mind that the size limit specified by `N` denotes *bytes* (octets) and **not** *characters* -- i.e. a value of `8` may translate to between two and eight characters due to variable-length encoding. +/// +/// See [`SizedSlice`] for an equivalent alternative to [`Vec`](alloc::vec::Vec). +/// +/// # Examples +/// +/// All instances of this type have the same size if the value of `N` is also the same. +/// Therefore, the following four strings have -- despite their different contents -- the same total size. +/// +/// ``` +/// use bzipper::SizedStr; +/// use std::str::FromStr; +/// +/// let str0 = SizedStr::<0x40>::default(); // Empty string. +/// let str1 = SizedStr::<0x40>::from_str("Hello there!").unwrap(); +/// let str2 = SizedStr::<0x40>::from_str("أنا من أوروپا").unwrap(); +/// let str3 = SizedStr::<0x40>::from_str("COGITO ERGO SUM").unwrap(); +/// +/// 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, Default)] +pub struct SizedStr<const N: usize>(SizedSlice<u8, N>); + +impl<const N: usize> SizedStr<N> { + /// Constructs an empty, fixed-size string. + /// + /// Note that string is not required to completely fill out its size-constraint. + /// + /// The constructed string will have a null length. + /// + /// For constructing a string with an already defined buffer, see [`from_raw_parts`](Self::from_raw_parts) and [`from_str`](Self::from_str). + /// + /// # Errors + /// + /// If the internal buffer cannot contain the entirety of `s`, then an error is returned. + #[inline(always)] + pub const fn new(s: &str) -> Result<Self, StringError> { + if s.len() > N { return Err(StringError::SmallBuffer(SizeError { req: s.len(), len: N })) }; + + let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; + Ok(this) + } + + /// Constructs a fixed-size string from UTF-8 octets. + /// + /// The passed slice is checked for its validity. + /// For a similar function *without* these checks, see [`from_utf8_unchecked`](Self::from_utf8_unchecked). + /// + /// # Errors + /// + /// Each byte value must be a valid UTF-8 code point. + #[inline] + pub const fn from_utf8(data: &[u8]) -> Result<Self, StringError> { + if data.len() > N { return Err(StringError::SmallBuffer(SizeError { req: data.len(), len: N })) }; + + let s = match str::from_utf8(data) { + Ok(s) => s, + + Err(e) => { + let i = e.valid_up_to(); + + return Err(StringError::BadUtf8(Utf8Error { value: data[i], index: i })); + } + }; + + // SAFETY: `s` is guaranteed to only contain valid + // octets. + let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; + Ok(this) + } + + /// Unsafely constructs a new, fixed-size string from UTF-8 octets. + /// + /// # Safety + /// + /// Each byte value must be a valid UTF-8 code point. + /// The behaviour of a programme that passes invalid values to this function is undefined. + #[inline] + #[must_use] + pub const unsafe fn from_utf8_unchecked(s: &[u8]) -> Self { + // Should we assert the length? + debug_assert!(s.len() <= N, "cannot construct string from utf-8 sequence that is longer"); + + let mut buf = [0x00; N]; + copy_nonoverlapping(s.as_ptr(), buf.as_mut_ptr(), s.len()); + + // SAFETY: `s` is guaranteed by the caller to only + // contain valid octets. It has also been tested to + // not exceed bounds. + Self::from_raw_parts(buf, s.len()) + } + + /// Constructs a fixed-size string from raw parts. + /// + /// The provided parts are not tested in any way. + /// + /// # Safety + /// + /// The value of `len` may not exceed that of `N`. + /// Additionally, the octets in `buf` (from index zero up to the value of `len`) must be valid UTF-8 codepoints. + /// + /// If any of these requirements are violated, behaviour is undefined. + #[inline(always)] + #[must_use] + pub const unsafe fn from_raw_parts(buf: [u8; N], len: usize) -> Self { + debug_assert!(len <= N, "cannot construct string that is longer than its capacity"); + + let init_buf = ManuallyDrop::new(buf); + let buf = unsafe { addr_of!(init_buf).cast::<[MaybeUninit<u8>; N]>().read() }; + + Self(SizedSlice::from_raw_parts(buf, len)) + } + + /// Gets a pointer to the first octet. + #[inline(always)] + #[must_use] + pub const fn as_ptr(&self) -> *const u8 { + self.0.as_ptr() + } + + // This function can only be marked as `const` when + // `const_mut_refs` is implemented. See tracking + // issue #57349 for more information. + /// Gets a mutable pointer to the first octet. + /// + #[inline(always)] + #[must_use] + pub const fn as_mut_ptr(&mut self) -> *mut u8 { + self.0.as_mut_ptr() + } + + /// Borrows the string as a byte slice. + /// + /// The range of the returned slice only includes characters that are "used." + #[inline(always)] + #[must_use] + pub const fn as_bytes(&self) -> &[u8] { + // We need to use `from_raw_parts` to mark this + // function `const`. + + let ptr = self.as_ptr(); + let len = self.len(); + + unsafe { slice::from_raw_parts(ptr, len) } + } + + /// Borrows the string as a string slice. + /// + /// The range of the returned slice only includes characters that are "used." + #[inline(always)] + #[must_use] + pub const fn as_str(&self) -> &str { + unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } + } + + /// Mutably borrows the string as a string slice. + /// + /// The range of the returned slice only includes characters that are "used." + #[inline(always)] + #[must_use] + pub fn as_mut_str(&mut self) -> &mut str { + unsafe { + let ptr = self.as_mut_ptr(); + let len = self.len(); + + let bytes = slice::from_raw_parts_mut(ptr, len); + core::str::from_utf8_unchecked_mut(bytes) + } + } + + /// Returns the total capacity of the string. + /// + /// This is defined as being exactly the value of `N`. + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + self.0.capacity() + } + + /// 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.0.len() + } + + /// Checks if the string is empty, i.e. no characters are contained. + #[inline(always)] + #[must_use] + pub const fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Checks if the string is full, i.e. it cannot hold any more characters. + #[inline(always)] + #[must_use] + pub const fn is_full(&self) -> bool { + self.0.is_full() + } + + /// Returns an iterator of the string's characters. + #[inline(always)] + pub fn chars(&self) -> Chars { + self.as_str().chars() + } + + /// Returns an iterator of the string's characters along with their positions. + #[inline(always)] + pub fn char_indices(&self) -> CharIndices { + self.as_str().char_indices() + } + + /// Destructs the provided string into its raw parts. + /// + /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). + /// + /// The returned byte array is guaranteed to be fully initialised. + /// However, only octets up to an index of [`len`](Self::len) are also guaranteed to be valid UTF-8 codepoints. + #[inline(always)] + #[must_use] + pub const fn into_raw_parts(self) -> ([u8; N], usize) { + let Self(vec) = self; + let (buf, len) = vec.into_raw_parts(); + + let init_buf = ManuallyDrop::new(buf); + let buf = unsafe { addr_of!(init_buf).cast::<[u8; N]>().read() }; + + (buf, len) + } + + /// Converts the fixed-size string into a boxed string slice. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[inline(always)] + #[must_use] + pub fn into_boxed_str(self) -> Box<str> { + let Self(vec) = self; + + unsafe { alloc::str::from_boxed_utf8_unchecked(vec.into_boxed_slice()) } + } + + /// Converts the fixed-size string into a dynamic string. + /// + /// The capacity of the resulting [`String`] object is equal to the value of `N`. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[inline(always)] + #[must_use] + pub fn into_string(self) -> String { + self.into_boxed_str().into_string() + } +} + +impl<const N: usize> AsMut<str> for SizedStr<N> { + #[inline(always)] + fn as_mut(&mut self) -> &mut str { + self.as_mut_str() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<const N: usize> AsRef<OsStr> for SizedStr<N> { + #[inline(always)] + fn as_ref(&self) -> &OsStr { + self.as_str().as_ref() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<const N: usize> AsRef<Path> for SizedStr<N> { + #[inline(always)] + fn as_ref(&self) -> &Path { + self.as_str().as_ref() + } +} + +impl<const N: usize> AsRef<str> for SizedStr<N> { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl<const N: usize> AsRef<[u8]> for SizedStr<N> { + #[inline(always)] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl<const N: usize> Borrow<str> for SizedStr<N> { + #[inline(always)] + fn borrow(&self) -> &str { + self.as_str() + } +} + +impl<const N: usize> BorrowMut<str> for SizedStr<N> { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut str { + self.as_mut_str() + } +} + +impl<const N: usize> Debug for SizedStr<N> { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_str(), f) + } +} + +impl<const N: usize> Deref for SizedStr<N> { + type Target = str; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_str() + } +} + +impl<const N: usize> DerefMut for SizedStr<N> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_str() + } +} + +impl<const N: usize> Decode for SizedStr<N> { + #[inline] + fn decode(stream: &mut IStream) -> Result<Self, DecodeError> { + let len = Decode::decode(stream)?; + + let data = stream.read(len); + + Self::from_utf8(data) + .map_err(|e| match e { + StringError::BadUtf8(e) => DecodeError::BadString(e), + + StringError::SmallBuffer(e) => DecodeError::SmallBuffer(e), + + _ => unreachable!(), + }) + } +} + +impl<const N: usize> Display for SizedStr<N> { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self.as_str(), f) + } +} + +impl<const N: usize> Encode for SizedStr<N> { + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + // Optimised encode. Don't just rely on `SizedSlice`. + + self.as_str().encode(stream) + } +} + +impl<const N: usize> Eq for SizedStr<N> { } + +impl<const N: usize> FromIterator<char> for SizedStr<N> { + #[inline] + fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self { + let mut buf = [0x00; N]; + let mut len = 0x0; + + for c in iter { + let rem = N - len; + let req = c.len_utf8(); + + if rem < req { break } + + let start = len; + let stop = start + req; + + c.encode_utf8(&mut buf[start..stop]); + + len += req; + } + + // SAFETY: All octets are initialised and come from + // `char::encode_utf8`. + unsafe { Self::from_raw_parts(buf, len) } + } +} + +impl<const N: usize> FromStr for SizedStr<N> { + type Err = StringError; + + #[inline] + fn from_str(s: &str) -> Result<Self, Self::Err> { + if s.len() > N { return Err(StringError::SmallBuffer(SizeError { req: s.len(), len: N })) }; + + let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; + Ok(this) + } +} + +impl<const N: usize> Hash for SizedStr<N> { + #[inline(always)] + fn hash<H: Hasher>(&self, state: &mut H) { + self.as_str().hash(state) + } +} + +impl<I: SliceIndex<str>, const N: usize> Index<I> for SizedStr<N> { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl<I: SliceIndex<str>, const N: usize> IndexMut<I> for SizedStr<N> { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +impl<const N: usize> Ord for SizedStr<N> { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl<const N: usize, const M: usize> PartialEq<SizedStr<M>> for SizedStr<N> { + #[inline(always)] + fn eq(&self, other: &SizedStr<M>) -> bool { + self.as_str() == other.as_str() + } +} + +impl<const N: usize> PartialEq<&str> for SizedStr<N> { + #[inline(always)] + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<const N: usize> PartialEq<String> for SizedStr<N> { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} + +impl<const N: usize, const M: usize> PartialOrd<SizedStr<M>> for SizedStr<N> { + #[inline(always)] + fn partial_cmp(&self, other: &SizedStr<M>) -> Option<Ordering> { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl<const N: usize> PartialOrd<&str> for SizedStr<N> { + #[inline(always)] + fn partial_cmp(&self, other: &&str) -> Option<Ordering> { + self.as_str().partial_cmp(*other) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<const N: usize> PartialOrd<String> for SizedStr<N> { + #[inline(always)] + fn partial_cmp(&self, other: &String) -> Option<Ordering> { + self.as_str().partial_cmp(other.as_str()) + } +} + +unsafe impl<const N: usize> SizedEncode for SizedStr<N> { + const MAX_ENCODED_SIZE: usize = + usize::MAX_ENCODED_SIZE + + SizedSlice::<u8, N>::MAX_ENCODED_SIZE; +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl<const N: usize> ToSocketAddrs for SizedStr<N> { + type Iter = <str as ToSocketAddrs>::Iter; + + #[inline(always)] + fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> { + self.as_str().to_socket_addrs() + } +} + +impl<const N: usize> TryFrom<char> for SizedStr<N> { + type Error = <Self as FromStr>::Err; + + #[inline(always)] + fn try_from(value: char) -> Result<Self, Self::Error> { + let mut buf = [0x00; 0x4]; + let s = value.encode_utf8(&mut buf); + + s.parse() + } +} + +impl<const N: usize> TryFrom<&str> for SizedStr<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")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<const N: usize> TryFrom<String> for SizedStr<N> { + type Error = <Self as FromStr>::Err; + + #[inline(always)] + fn try_from(value: String) -> Result<Self, Self::Error> { + Self::from_str(&value) + } +} + +/// See [`into_boxed_str`](SizedStr::into_boxed_str). +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<const N: usize> From<SizedStr<N>> for Box<str> { + #[inline(always)] + fn from(value: SizedStr<N>) -> Self { + value.into_boxed_str() + } +} + +/// See [`into_string`](SizedStr::into_string). +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl<const N: usize> From<SizedStr<N>> for String { + #[inline(always)] + fn from(value: SizedStr<N>) -> Self { + value.into_string() + } +} diff --git a/bzipper/src/sized_str/test.rs b/bzipper/src/sized_str/test.rs new file mode 100644 index 0000000..bfe0e7b --- /dev/null +++ b/bzipper/src/sized_str/test.rs @@ -0,0 +1,95 @@ +// 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 bzipper::SizedStr; +use bzipper::error::{StringError, Utf8Error}; +use core::cmp::Ordering; + +#[test] +fn test_fixed_str_from_iter() { + let s: SizedStr::<0x4> = "hello world".chars().collect(); + assert_eq!(s, "hell") +} + +#[test] +fn test_fixed_str_size() { + let string0 = SizedStr::<0x0C>::try_from("Hello there!").unwrap(); + let string1 = SizedStr::<0x12>::try_from("MEIN_GRO\u{1E9E}_GOTT").unwrap(); + let string2 = SizedStr::<0x05>::try_from("Hello").unwrap(); + + assert_eq!(string0.partial_cmp(&string0), Some(Ordering::Equal)); + assert_eq!(string0.partial_cmp(&string1), Some(Ordering::Less)); + assert_eq!(string0.partial_cmp(&string2), Some(Ordering::Greater)); + + assert_eq!(string1.partial_cmp(&string0), Some(Ordering::Greater)); + assert_eq!(string1.partial_cmp(&string1), Some(Ordering::Equal)); + assert_eq!(string1.partial_cmp(&string2), Some(Ordering::Greater)); + + assert_eq!(string2.partial_cmp(&string0), Some(Ordering::Less)); + assert_eq!(string2.partial_cmp(&string1), Some(Ordering::Less)); + assert_eq!(string2.partial_cmp(&string2), Some(Ordering::Equal)); + + assert_eq!(string0, "Hello there!"); + assert_eq!(string1, "MEIN_GRO\u{1E9E}_GOTT"); + assert_eq!(string2, "Hello"); +} + +#[test] +fn test_fixed_str_from_utf8() { + macro_rules! test_utf8 { + { + len: $len:literal, + utf8: $utf8:expr, + result: $result:pat$(,)? + } => {{ + let utf8: &[u8] = $utf8.as_ref(); + + assert!(matches!( + SizedStr::<$len>::from_utf8(utf8), + $result, + )); + }}; + } + + test_utf8!( + len: 0x3, + utf8: b"A\xF7c", + result: Err(StringError::BadUtf8(Utf8Error { value: 0xF7, index: 0x1 })), + ); + + test_utf8!( + len: 0x4, + utf8: "A\u{00F7}c", + result: Ok(..), + ); + + test_utf8!( + len: 0x4, + utf8: b"20\x20\xAC", + result: Err(StringError::BadUtf8(Utf8Error { value: 0xAC, index: 0x3 })), + ); + + test_utf8!( + len: 0x5, + utf8: "20\u{20AC}", + result: Ok(..), + ); +} diff --git a/bzipper/src/sstream/mod.rs b/bzipper/src/sstream/mod.rs deleted file mode 100644 index 470a27f..0000000 --- a/bzipper/src/sstream/mod.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/>. - -use crate::{Dstream, Error, Result}; - -use core::cell::Cell; -use core::fmt::{Debug, Formatter}; - -/// Byte stream suitable for serialisation. -/// -/// This type mutably borrows a buffer, keeping track internally of the used bytes. -pub struct Sstream<'a> { - pub(in crate) buf: &'a mut [u8], - pub(in crate) 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) } } - - /// Appends raw bytes to the stream. - #[inline] - pub fn write(&mut self, bytes: &[u8]) -> Result<()> { - let rem = self.buf.len() - self.pos.get(); - let req = bytes.len(); - - if rem < req { return Err(Error::EndOfStream { req, rem }) } - - let start = self.pos.get(); - let stop = start + req; - - self.pos.set(stop); - - let buf = &mut self.buf[start..stop]; - buf.copy_from_slice(bytes); - - Ok(()) - } - - /// Gets a pointer to the first byte in the stream. - #[inline(always)] - #[must_use] - pub const fn as_ptr(&self) -> *const u8 { self.buf.as_ptr() } - - /// Gets an immutable slice of the stream. - #[inline(always)] - #[must_use] - pub const fn as_slice(&self) -> &[u8] { - let ptr = self.as_ptr(); - let len = self.len(); - - unsafe { core::slice::from_raw_parts(ptr, len) } - } - - /// Gets the length of the stream. - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { unsafe { self.pos.as_ptr().read() } } - - /// Tests if the stream is empty. - /// - /// If no serialisations have been made so far, this method returns `false`. - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { self.len() == 0x0 } - - /// Tests if the stream is full. - /// - /// Note that zero-sized types such as [`()`](unit) can still be serialised into this stream. - #[inline(always)] - #[must_use] - pub const fn is_full(&self) -> bool { self.len() == self.buf.len() } -} - -impl Debug for Sstream<'_> { - #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { Debug::fmt(self.as_slice(), f) } -} - -impl<'a> From<&'a mut [u8]> for Sstream<'a> { - #[inline(always)] - fn from(value: &'a mut [u8]) -> Self { Self::new(value) } -} - -impl PartialEq for Sstream<'_> { - #[inline(always)] - fn eq(&self, other: &Self) -> bool { self.as_slice() == other.as_slice() } -} - -impl PartialEq<&[u8]> for Sstream<'_> { - #[inline(always)] - fn eq(&self, other: &&[u8]) -> bool { self.as_slice() == *other } -} - -impl<const N: usize> PartialEq<[u8; N]> for Sstream<'_> { - #[inline(always)] - fn eq(&self, other: &[u8; N]) -> bool { self.as_slice() == other.as_slice() } -} - -impl<'a> From<Sstream<'a>> for Dstream<'a> { - #[inline(always)] - fn from(value: Sstream<'a>) -> Self { Self { data: value.buf, pos: value.pos } } -} diff --git a/bzipper_benchmarks/Cargo.toml b/bzipper_benchmarks/Cargo.toml new file mode 100644 index 0000000..f866db0 --- /dev/null +++ b/bzipper_benchmarks/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "bzipper_benchmarks" +version = "0.8.0" +edition = "2021" +description = "bZipper benchmarks." + +authors.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +bzipper = { path = "../bzipper", version = "0.8.0" } + +bincode = "1.3.3" +ciborium = "0.2.2" +rand = "0.8.5" + +borsh = { version = "1.5.1", features = ["derive"] } +postcard = { version = "1.0.10", features = ["use-std"] } +serde = { version = "1.0.214", features = ["derive"] } + +[lints] +workspace = true diff --git a/bzipper_benchmarks/src/main.rs b/bzipper_benchmarks/src/main.rs new file mode 100644 index 0000000..7a8d7a4 --- /dev/null +++ b/bzipper_benchmarks/src/main.rs @@ -0,0 +1,522 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. + +use rand::random; + +// Bincode uses so much memory that it crashes if +// we set `VALUE_COUNT` too high. +const VALUE_COUNT: usize = 0x0FFFFFFF; + +use std::time::Instant; + +macro_rules! benchmark { + { + $($name:ident: { + bincode: $bincode_op:block$(,)? + + borsh: $borsh_op:block$(,)? + + bzipper: $bzipper_op:block$(,)? + + ciborium: $ciborium_op:block$(,)? + + postcard: $postcard_op:block$(,)? + }$(,)?)+ + } => {{ + use ::std::{concat, eprint, eprintln, stringify}; + + macro_rules! time { + { $op: block } => {{ + let start = Instant::now(); + $op + let stop = Instant::now(); + + let duration = stop - start; + (duration.as_nanos() as f64) / 1_000_000_000.0 + }}; + } + + fn format_score(duration: f64, ref_duration: f64) -> String { + let vps = (VALUE_COUNT as f64 / duration).round(); + + let difference = (duration / ref_duration - 1.0) * 100.0; + + let colour: u8 = if difference >= 0.0 { + 0x20 + } else if difference < 0.0 { + 0x1F + } else { + 0x00 + }; + + format!("{duration:.3}s ({vps:.0} vps) => \u{001B}[{colour}m{difference:+.2}%\u{001B}[000m") + } + + let mut total_bincode_duration = 0.0; + let mut total_borsh_duration = 0.0; + let mut total_bzipper_duration = 0.0; + let mut total_ciborium_duration = 0.0; + let mut total_postcard_duration = 0.0; + + $({ + eprintln!(); + eprintln!(concat!("\u{001B}[001mrunning benchmark `", stringify!($name), "`...\u{001B}[022m")); + eprint!("\u{001B}[093m"); + + let bincode_duration = time! { $bincode_op }; + let borsh_duration = time! { $borsh_op }; + let bzipper_duration = time! { $bzipper_op }; + let ciborium_duration = time! { $ciborium_op }; + let postcard_duration = time! { $postcard_op }; + + eprint!("\u{001B}[000m"); + eprintln!("bincode: {}", format_score(bincode_duration, bzipper_duration)); + eprintln!("borsh: {}", format_score(borsh_duration, bzipper_duration)); + eprintln!("bzipper: {}", format_score(bzipper_duration, bzipper_duration)); + eprintln!("ciborium: {}", format_score(ciborium_duration, bzipper_duration)); + eprintln!("postcard: {}", format_score(postcard_duration, bzipper_duration)); + + total_bincode_duration += bincode_duration; + total_borsh_duration += borsh_duration; + total_bzipper_duration += bzipper_duration; + total_ciborium_duration += ciborium_duration; + total_postcard_duration += postcard_duration; + })* + + eprintln!(); + eprintln!("\u{001B}[001mtotal score:\u{001B}[022m"); + eprintln!("bincode: {}", format_score(total_bincode_duration, total_bzipper_duration)); + eprintln!("borsh: {}", format_score(total_borsh_duration, total_bzipper_duration)); + eprintln!("bzipper: {}", format_score(total_bzipper_duration, total_bzipper_duration)); + eprintln!("ciborium: {}", format_score(total_ciborium_duration, total_bzipper_duration)); + eprintln!("postcard: {}", format_score(total_postcard_duration, total_bzipper_duration)); + }}; +} + +#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(borsh::BorshSerialize)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(transparent)] +struct Unit; + +#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(borsh::BorshSerialize)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(transparent)] +struct Unnamed(u32); + +impl Unnamed { + #[inline(always)] + #[must_use] + pub const fn from_char(value: char) -> Self { + Self(value as u32) + } +} + +#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(borsh::BorshSerialize)] +#[derive(serde::Deserialize, serde::Serialize)] +#[repr(transparent)] +struct Named { buf: [u8; 0x8] } + +#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(borsh::BorshSerialize)] +#[derive(serde::Deserialize, serde::Serialize)] +enum Enum { + Unit(Unit), + Unnamed(Unnamed), + Named(Named), +} + +impl Named { + #[inline(always)] + #[must_use] + pub const fn from_u64(value: u64) -> Self { + let buf = [ + (value & 0x00_00_00_00_00_00_00_FF) as u8, + ((value & 0x00_00_00_00_00_00_FF_00) >> 0x2) as u8, + ((value & 0x00_00_00_00_00_FF_00_00) >> 0x4) as u8, + ((value & 0x00_00_00_00_FF_00_00_00) >> 0x6) as u8, + ((value & 0x00_00_00_FF_00_00_00_00) >> 0x8) as u8, + ((value & 0x00_00_FF_00_00_00_00_00) >> 0xA) as u8, + ((value & 0x00_FF_00_00_00_00_00_00) >> 0xC) as u8, + ((value & 0xFF_00_00_00_00_00_00_00) >> 0xE) as u8, + ]; + + Self { buf } + } +} + +fn main() { + println!("######################"); + println!("# BZIPPER BENCHMARKS #"); + println!("######################"); + println!(); + println!("Each benchmark has a version written for the following crates:"); + println!(); + println!("- Bincode: <https://crates.io/crates/bincode/>"); + println!("- Borsh: <https://crates.io/crates/borsh/>"); + println!("- Ciborium: <https://crates.io/crates/ciborium/>"); + println!("- bzipper: <https://crates.io/crates/bzipper/>"); + println!("- Postcard: <https://crates.io/crates/postcard/>"); + println!(); + println!("The total time the benchmark took (including memory allocations and dealloca-"); + println!("tions) is printed when the benchmark has completed. The `vps` counter signifies"); + println!("the amount of values per second that the benchmark has handled (during the en-"); + println!("tirety of the benchmark)."); + println!(); + println!("When every benchmark has concluded, the total run time and vps is listed for"); + println!("each crate. A percantage additionally compares the run time between the listed"); + println!("crate and bzipper (which should always be `0%` for bzipper itself). DO NOTE THAT"); + println!("THESE FINAL RESULTS INDICATE A NON-WEIGHTED AVERAGE ACROSS BENCHMARKS. It can"); + println!("therefore be skewed relative to real-world performance by the similarity of some"); + println!("benchmarks."); + println!(); + + eprintln!("value_count: {VALUE_COUNT}"); + + benchmark! { + encode_u8: { + bincode: { + // Requires `std`. + + use bincode::serialize_into; + use std::io::Cursor; + + let buf_size = size_of::<u8>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &random::<u8>()).unwrap(); + } + } + + borsh: { + use std::io::Cursor; + + let buf_size = size_of::<u8>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &random::<u8>()).unwrap(); + } + } + + bzipper: { + use bzipper::{Encode, OStream, SizedEncode}; + + let buf_size = u8::MAX_ENCODED_SIZE; // value + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + random::<u8>().encode(&mut stream).unwrap(); + } + } + + ciborium: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // header + + size_of::<u8>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + ciborium::into_writer(&random::<u8>(), &mut buf).unwrap(); + } + } + + postcard: { + use std::io::Cursor; + + let buf_size = size_of::<u8>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&random::<u8>(), &mut buf).unwrap(); + } + } + } + + encode_struct_unit: { + bincode: { + use bincode::serialize_into; + use std::io::Cursor; + + let buf_size = size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &Unit).unwrap(); + } + } + + borsh: { + use std::io::Cursor; + + let buf_size = size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &Unit).unwrap(); + } + } + + bzipper: { + use bzipper::{Encode, OStream, SizedEncode}; + + let buf_size = Unit::MAX_ENCODED_SIZE; // value + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + Unit.encode(&mut stream).unwrap(); + } + } + + ciborium: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // header + + size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + ciborium::into_writer(&Unit, &mut buf).unwrap(); + } + } + + postcard: { + use std::io::Cursor; + + let buf_size = size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&Unit, &mut buf).unwrap(); + } + } + } + + encode_struct_unnamed: { + bincode: { + use bincode::serialize_into; + use std::io::Cursor; + + let buf_size = size_of::<Unnamed>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &Unnamed::from_char(random())).unwrap(); + } + } + + borsh: { + use std::io::Cursor; + + let buf_size = size_of::<Unnamed>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &Unnamed::from_char(random())).unwrap(); + } + } + + bzipper: { + use bzipper::{Encode, OStream, SizedEncode}; + + let buf_size = Unnamed::MAX_ENCODED_SIZE; // value + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + Unnamed::from_char(random()).encode(&mut stream).unwrap(); + } + } + + ciborium: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // header + + size_of::<Unnamed>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + ciborium::into_writer(&Unnamed::from_char(random()), &mut buf).unwrap(); + } + } + + postcard: { + use std::io::Cursor; + + let buf_size = size_of::<Unnamed>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&Unnamed::from_char(random()), &mut buf).unwrap(); + } + } + } + + encode_struct_named: { + bincode: { + use bincode::serialize_into; + use std::io::Cursor; + + let buf_size = size_of::<Named>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &Named::from_u64(random())).unwrap(); + } + } + + borsh: { + use std::io::Cursor; + + let buf_size = size_of::<Named>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &Named::from_u64(random())).unwrap(); + } + } + + bzipper: { + use bzipper::{Encode, OStream, SizedEncode}; + + let buf_size = Named::MAX_ENCODED_SIZE; // value + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + Named::from_u64(random()).encode(&mut stream).unwrap(); + } + } + + ciborium: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // header + + size_of::<u64>() // tag + + size_of::<u8>() // header + + size_of::<Named>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + ciborium::into_writer(&Named::from_u64(random()), &mut buf).unwrap(); + } + } + + postcard: { + use std::io::Cursor; + + let buf_size = size_of::<Named>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&Named::from_u64(random()), &mut buf).unwrap(); + } + } + } + + encode_enum_unit: { + bincode: { + use bincode::serialize_into; + use std::io::Cursor; + + let buf_size = + size_of::<u32>() // discriminant + + size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &Enum::Unit(Unit)).unwrap(); + } + } + + borsh: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // discriminant + + size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &Enum::Unit(Unit)).unwrap(); + } + } + + bzipper: { + use bzipper::{Encode, OStream, SizedEncode}; + + let buf_size = + isize::MAX_ENCODED_SIZE // discriminant + + Unit::MAX_ENCODED_SIZE; // value + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + Enum::Unit(Unit).encode(&mut stream).unwrap(); + } + } + + ciborium: { + use std::io::Cursor; + + let buf_size = + size_of::<u8>() // header + + size_of::<u64>() // tag (discriminant) + + size_of::<u8>() // header + + size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + ciborium::into_writer(&Enum::Unit(Unit), &mut buf).unwrap(); + } + } + + postcard: { + use std::io::Cursor; + + let buf_size = + size_of::<u32>() // discriminant + + size_of::<Unit>(); // value + + let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&Enum::Unit(Unit), &mut buf).unwrap(); + } + } + } + }; +}
\ No newline at end of file diff --git a/bzipper_macros/Cargo.toml b/bzipper_macros/Cargo.toml index 8167029..9b00254 100644 --- a/bzipper_macros/Cargo.toml +++ b/bzipper_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper_macros" -version = "0.7.0" +version = "0.8.0" edition = "2021" documentation = "https://docs.rs/bzipper_macros/" @@ -10,14 +10,16 @@ readme.workspace = true homepage.workspace = true repository.workspace = true license.workspace = true +keywords.workspace = true +categories.workspace = true [lib] proc-macro = true [dependencies] -proc-macro2 = "1.0.86" +proc-macro2 = "1.0.89" quote = "1.0.37" -syn = "2.0.75" +syn = "2.0.85" [lints] workspace = true diff --git a/bzipper_macros/src/discriminant/mod.rs b/bzipper_macros/src/discriminant/mod.rs index 21a835a..a5d6a95 100644 --- a/bzipper_macros/src/discriminant/mod.rs +++ b/bzipper_macros/src/discriminant/mod.rs @@ -1,98 +1,60 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// er General Public License along with bZipper. If // not, see <https://www.gnu.org/licenses/>. use proc_macro2::TokenStream; use quote::ToTokens; +use syn::{Expr, Lit}; /// An enumeration discriminant. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[repr(transparent)] -pub struct Discriminant(u32); +pub struct Discriminant(pub isize); 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. + /// Parses the expression as a discriminant value. /// /// # Panics /// - /// If the given value cannot be represented as an `u32`, this function will panic. - #[inline(always)] + /// This constructor will panic if the provided expression is not a valid `isize` literal. + #[inline] #[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) } -} + pub fn parse(expr: &Expr) -> Self { + let Expr::Lit(ref expr) = *expr else { + panic!("expected literal expression for discriminant value"); + }; -impl From<u32> for Discriminant { - #[inline(always)] - fn from(value: u32) -> Self { Self(value) } -} + let Lit::Int(ref expr) = expr.lit else { + panic!("expected integer literal for discriminant value"); + }; -impl TryFrom<usize> for Discriminant { - type Error = <u32 as TryFrom<usize>>::Error; + let value = expr.base10_parse::<isize>() + .expect("expected `isize` literal for discriminant value"); - #[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 } + Self(value) + } } -impl TryFrom<Discriminant> for usize { - type Error = <Self as TryFrom<u32>>::Error; - +impl ToTokens for Discriminant { #[inline(always)] - fn try_from(value: Discriminant) -> Result<Self, Self::Error> { value.0.try_into() } + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } } diff --git a/bzipper_macros/src/discriminant_iter/mod.rs b/bzipper_macros/src/discriminant_iter/mod.rs new file mode 100644 index 0000000..ea34f6d --- /dev/null +++ b/bzipper_macros/src/discriminant_iter/mod.rs @@ -0,0 +1,71 @@ +// 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 std::borrow::Borrow; +use syn::Variant; + +pub struct DiscriminantIter<I: IntoIterator<Item: Borrow<Variant>>> { + variants: I::IntoIter, + prev: Option<Discriminant>, +} + +impl<I: IntoIterator<Item: Borrow<Variant>>> DiscriminantIter<I> { + #[inline(always)] + #[must_use] + pub fn new(variants: I) -> Self { + Self { + variants: variants.into_iter(), + prev: None, + } + } +} + +impl<I: IntoIterator<Item: Borrow<Variant>>> Iterator for DiscriminantIter<I> { + type Item = (Discriminant, I::Item); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + let variant = self.variants.next()?; + + let discriminant = if let Some((_, ref discriminant)) = variant.borrow().discriminant { + Discriminant::parse(discriminant) + } else if let Some(discriminant) = self.prev { + let value = discriminant.0 + .checked_add(0x1) + .unwrap_or_else(|| panic!("overflow following discriminant `{discriminant:?}`")); + + Discriminant(value) + } else { + Default::default() + }; + + self.prev = Some(discriminant); + + Some((discriminant, variant)) + } + + #[inline(always)] + fn size_hint(&self) -> (usize, Option<usize>) { + self.variants.size_hint() + } +} diff --git a/bzipper_macros/src/generic_name/mod.rs b/bzipper_macros/src/generic_name/mod.rs index a0c51f5..6fc0bc9 100644 --- a/bzipper_macros/src/generic_name/mod.rs +++ b/bzipper_macros/src/generic_name/mod.rs @@ -1,26 +1,28 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// er General Public License along with bZipper. If // not, see <https://www.gnu.org/licenses/>. use proc_macro2::TokenStream; use quote::ToTokens; +use std::fmt; +use std::fmt::{Debug, Formatter}; use syn::{ GenericParam, Generics, @@ -33,9 +35,14 @@ use syn::{ /// A name of a genric. #[derive(Clone)] pub enum GenericName { - Const( Ident), + /// Denotes a generic constant. + Const(Ident), + + /// Denotes a generic lifetime. Lifetime(Lifetime), - Type( Ident), + + /// Denotes a generic type. + Ty(Ident), } impl GenericName { @@ -48,14 +55,28 @@ impl GenericName { 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()), + GenericParam::Type( ref param) => Self::Ty( param.ident.clone()), }; names.push(name); } names - } + } +} + +impl Debug for GenericName { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let ident = match *self { + | Self::Const(ref ident) + | Self::Lifetime(Lifetime { ref ident, .. }) + | Self::Ty(ref ident) + => ident, + }; + + Debug::fmt(ident, f) + } } impl ToTokens for GenericName { @@ -65,7 +86,7 @@ impl ToTokens for GenericName { match *self { | Const(ref ident) - | Type( ref ident) + | Ty( ref ident) => ident.to_tokens(tokens), Lifetime(ref lifetime) => lifetime.to_tokens(tokens), diff --git a/bzipper_macros/src/impls/decode_enum.rs b/bzipper_macros/src/impls/decode_enum.rs new file mode 100644 index 0000000..c773de2 --- /dev/null +++ b/bzipper_macros/src/impls/decode_enum.rs @@ -0,0 +1,67 @@ +// 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::DiscriminantIter; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DataEnum, Fields, Token, Variant}; +use syn::punctuated::Punctuated; + +#[must_use] +pub fn decode_enum(data: &DataEnum) -> TokenStream { + let mut match_arms = Punctuated::<TokenStream, Token![,]>::new(); + + for (discriminant, variant) in DiscriminantIter::new(&data.variants) { + let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); + + for field in &variant.fields { + let command = field.ident + .as_ref() + .map_or_else( + || quote! { ::bzipper::Decode::decode(stream)? }, + |field_name| quote! { #field_name: ::bzipper::Decode::decode(stream)? }, + ); + + chain_commands.push(command); + } + + let value = match *variant { + Variant { ident: ref variant_name, fields: Fields::Named( ..), .. } => quote! { Self::#variant_name { #chain_commands } }, + Variant { ident: ref variant_name, fields: Fields::Unnamed(..), .. } => quote! { Self::#variant_name(#chain_commands) }, + Variant { ident: ref variant_name, fields: Fields::Unit, .. } => quote! { Self::#variant_name }, + }; + + match_arms.push(quote! { #discriminant => #value }); + } + + match_arms.push(quote! { + value => return ::core::result::Result::Err(::bzipper::error::DecodeError::InvalidDiscriminant(value)) + }); + + quote! { + #[inline] + fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> { + let value = match (<isize as ::bzipper::Decode>::decode(stream)?) { #match_arms }; + Ok(value) + } + } +} diff --git a/bzipper_macros/src/impls/decode_struct.rs b/bzipper_macros/src/impls/decode_struct.rs new file mode 100644 index 0000000..9688e91 --- /dev/null +++ b/bzipper_macros/src/impls/decode_struct.rs @@ -0,0 +1,55 @@ +// 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 decode_struct(data: &DataStruct) -> TokenStream { + let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); + + for field in &data.fields { + let command = field.ident + .as_ref() + .map_or_else( + || quote! { ::bzipper::Decode::decode(stream)? }, + |field_name| quote! { #field_name: ::bzipper::Decode::decode(stream)? }, + ); + + chain_commands.push(command); + } + + let value = match data.fields { + Fields::Named( ..) => quote! { Self { #chain_commands } }, + Fields::Unnamed(..) => quote! { Self(#chain_commands) }, + Fields::Unit => quote! { Self }, + }; + + quote! { + #[inline] + fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> { + let value = #value; + Ok(value) + } + } +} diff --git a/bzipper_macros/src/impls/deserialise_enum.rs b/bzipper_macros/src/impls/deserialise_enum.rs deleted file mode 100644 index 4c88a41..0000000 --- a/bzipper_macros/src/impls/deserialise_enum.rs +++ /dev/null @@ -1,68 +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::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 mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); - - for field in &variant.fields { - let command = field.ident - .as_ref() - .map_or_else( - || quote! { Deserialise::deserialise(stream)? }, - |field_name| quote! { #field_name: Deserialise::deserialise(stream)? } - ); - - chain_commands.push(command); - } - - let value = match variant.fields { - Fields::Named( ..) => quote! { Self::#variant_name { #chain_commands } }, - Fields::Unnamed(..) => quote! { Self::#variant_name(#chain_commands) }, - Fields::Unit => quote! { Self::#variant_name }, - }; - - match_arms.push(quote! { #discriminant => #value }); - } - - match_arms.push(quote! { value => return Err(::bzipper::Error::InvalidDiscriminant(value)) }); - - quote! { - fn deserialise(stream: &::bzipper::Dstream) -> ::bzipper::Result<Self> { - let value = match (<u32 as ::bzipper::Deserialise>::deserialise(stream)?) { #match_arms }; - Ok(value) - } - } -} diff --git a/bzipper_macros/src/impls/deserialise_struct.rs b/bzipper_macros/src/impls/deserialise_struct.rs deleted file mode 100644 index f8c167b..0000000 --- a/bzipper_macros/src/impls/deserialise_struct.rs +++ /dev/null @@ -1,61 +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 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 matches!(data.fields, Fields::Unit) { - quote! { - #[inline(always)] - fn deserialise(_stream: &::bzipper::Dstream) -> ::bzipper::Result<Self> { Ok(Self) } - } - } else { - let mut chain_commands = Punctuated::<TokenStream, Token![,]>::new(); - - for field in &data.fields { - let command = field.ident - .as_ref() - .map_or_else( - || quote! { Deserialise::deserialise(stream)? }, - |field_name| quote! { #field_name: Deserialise::deserialise(stream)? } - ); - - chain_commands.push(command); - } - - let value = if let Fields::Named(..) = data.fields { - quote! { Self { #chain_commands } } - } else { - quote! { Self(#chain_commands) } - }; - - quote! { - fn deserialise(stream: &::bzipper::Dstream) -> ::bzipper::Result<Self> { - let value = #value; - Ok(value) - } - } - } -} diff --git a/bzipper_macros/src/impls/encode_enum.rs b/bzipper_macros/src/impls/encode_enum.rs new file mode 100644 index 0000000..37acd34 --- /dev/null +++ b/bzipper_macros/src/impls/encode_enum.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 crate::DiscriminantIter; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{ + DataEnum, + Fields, + Ident, + Variant, +}; + +#[must_use] +pub fn encode_enum(data: &DataEnum) -> TokenStream { + let mut match_arms = Vec::new(); + + // Iterate over each variant and give it a unique + // encoding scheme. + for (discriminant, variant) in DiscriminantIter::new(&data.variants) { + // The original identifiers of the fields: + let mut field_names = Vec::new(); + + // The captured field identifiers: + let mut field_captures = Vec::new(); + + for (index, field) in variant.fields.iter().enumerate() { + let capture = Ident::new(&format!("v{index}"), Span::call_site()); + + field_names.push(&field.ident); + field_captures.push(capture); + } + + let pattern = match *variant { + Variant { ident: ref variant_name, fields: Fields::Named( ..), .. } => quote! { Self::#variant_name { #(#field_names: ref #field_captures, )* } }, + Variant { ident: ref variant_name, fields: Fields::Unnamed(..), .. } => quote! { Self::#variant_name(#(ref #field_captures)*) }, + Variant { ident: ref variant_name, fields: Fields::Unit, .. } => quote! { Self::#variant_name }, + }; + + match_arms.push(quote! { + #pattern => { + ::bzipper::Encode::encode(&#discriminant, stream)?; + #(::bzipper::Encode::encode(#field_captures, stream)?;)* + } + }); + } + + quote! { + #[inline] + fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + match *self { + #(#match_arms)* + } + + Ok(()) + } + } +} diff --git a/bzipper_macros/src/impls/encode_struct.rs b/bzipper_macros/src/impls/encode_struct.rs new file mode 100644 index 0000000..e853e44 --- /dev/null +++ b/bzipper_macros/src/impls/encode_struct.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 proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{DataStruct, Index}; + +#[must_use] +pub fn encode_struct(data: &DataStruct) -> TokenStream { + let mut fields = Vec::new(); + + for (index, field) in data.fields.iter().enumerate() { + let name = field.ident + .as_ref() + .map_or_else(|| Index::from(index).to_token_stream(), ToTokens::to_token_stream); + + fields.push(name); + } + + quote! { + #[inline] + fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + #(::bzipper::Encode::encode(&self.#fields, stream)?;)* + + Ok(()) + } + } +} diff --git a/bzipper_macros/src/impls/mod.rs b/bzipper_macros/src/impls/mod.rs index d61cf90..cdbc9ac 100644 --- a/bzipper_macros/src/impls/mod.rs +++ b/bzipper_macros/src/impls/mod.rs @@ -1,26 +1,28 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// er General Public License along with bZipper. If // not, see <https://www.gnu.org/licenses/>. use crate::use_mod; -use_mod!(pub deserialise_enum); -use_mod!(pub deserialise_struct); -use_mod!(pub serialise_enum); -use_mod!(pub serialise_struct); +use_mod!(pub decode_enum); +use_mod!(pub decode_struct); +use_mod!(pub encode_enum); +use_mod!(pub encode_struct); +use_mod!(pub sized_encode_enum); +use_mod!(pub sized_encode_struct); diff --git a/bzipper_macros/src/impls/serialise_enum.rs b/bzipper_macros/src/impls/serialise_enum.rs deleted file mode 100644 index 825886c..0000000 --- a/bzipper_macros/src/impls/serialise_enum.rs +++ /dev/null @@ -1,101 +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::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 variant_name = &variant.ident; - - let discriminant = u32::try_from(index) - .expect("enumeration discriminants must be representable as `u32`"); - - // Discriminant size: - serialised_size.push(quote! { <u32 as ::bzipper::Serialise>::MAX_SERIALISED_SIZE }); - - let mut captures = Punctuated::<Capture, Token![,]>::new(); - - let mut chain_commands = Punctuated::<TokenStream, Token![;]>::new(); - chain_commands.push(quote! { #discriminant.serialise(stream)? }); - - 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>::MAX_SERIALISED_SIZE }); - - captures.push(Capture { - ref_token: Token![ref](Span::call_site()), - ident: field_name.clone(), - }); - - chain_commands.push(quote! { #field_name.serialise(stream)? }); - } - - chain_commands.push_punct(Token![;](Span::call_site())); - - let arm = match variant.fields { - Fields::Named( ..) => quote! { Self::#variant_name { #captures } => { #chain_commands } }, - Fields::Unnamed(..) => quote! { Self::#variant_name(#captures) => { #chain_commands } }, - Fields::Unit => quote! { Self::#variant_name => { #chain_commands } }, - }; - - 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 MAX_SERIALISED_SIZE: usize = const { #size_tests }; - - fn serialise(&self, stream: &mut ::bzipper::Sstream) -> ::bzipper::Result<()> { - match (*self) { #match_arms } - - Ok(()) - } - } -} diff --git a/bzipper_macros/src/impls/serialise_struct.rs b/bzipper_macros/src/impls/serialise_struct.rs deleted file mode 100644 index bd81a39..0000000 --- a/bzipper_macros/src/impls/serialise_struct.rs +++ /dev/null @@ -1,69 +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 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 MAX_SERIALISED_SIZE: usize = 0x0; - - #[inline(always)] - fn serialise(&self, stream: &mut ::bzipper::Sstream) -> ::bzipper::Result<()> { 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>::MAX_SERIALISED_SIZE }); - - chain_commands.push(quote! { self.#name.serialise(stream)? }); - } - - chain_commands.push_punct(Token![;](Span::call_site())); - - quote! { - const MAX_SERIALISED_SIZE: usize = #serialised_size; - - fn serialise(&self, stream: &mut ::bzipper::Sstream) -> ::bzipper::Result<()> { - #chain_commands - - Ok(()) - } - } - } -} diff --git a/bzipper_macros/src/impls/sized_encode_enum.rs b/bzipper_macros/src/impls/sized_encode_enum.rs new file mode 100644 index 0000000..3bfc961 --- /dev/null +++ b/bzipper_macros/src/impls/sized_encode_enum.rs @@ -0,0 +1,54 @@ +// 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::DataEnum; + +#[must_use] +pub fn sized_encode_enum(data: &DataEnum) -> TokenStream { + let mut sizes = Vec::new(); + + // Iterate over each variant and give it a unique + // encoding scheme. + for variant in &data.variants { + let mut field_tys = Vec::new(); + + for field in &variant.fields { + field_tys.push(&field.ty); + } + + sizes.push(quote! { + <isize as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE + #(+ <#field_tys as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE)* + }); + } + + quote! { + const MAX_ENCODED_SIZE: usize = const { + let mut max_encoded_size = 0x0usize; + + #(if #sizes > max_encoded_size { max_encoded_size = #sizes };)* + + max_encoded_size + }; + } +} diff --git a/bzipper_macros/src/closure/mod.rs b/bzipper_macros/src/impls/sized_encode_struct.rs index 86d19d4..e194e08 100644 --- a/bzipper_macros/src/closure/mod.rs +++ b/bzipper_macros/src/impls/sized_encode_struct.rs @@ -1,41 +1,37 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// 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}; +use quote::quote; +use syn::DataStruct; -/// 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, -} +#[must_use] +pub fn sized_encode_struct(data: &DataStruct) -> TokenStream { + let mut field_tys = Vec::new(); + + for field in &data.fields { + field_tys.push(&field.ty); + } -impl ToTokens for Capture { - #[inline(always)] - fn to_tokens(&self, tokens: &mut TokenStream) { - self.ref_token.to_tokens(tokens); - self.ident.to_tokens(tokens); + quote! { + const MAX_ENCODED_SIZE: usize = 0x0 #( + <#field_tys as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE)*; } } diff --git a/bzipper_macros/src/lib.rs b/bzipper_macros/src/lib.rs index f7979c8..9bb1480 100644 --- a/bzipper_macros/src/lib.rs +++ b/bzipper_macros/src/lib.rs @@ -1,33 +1,31 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of bZipper. // -// bzipper is free software: you can redistribute +// 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 +// 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 +// 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")] +#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")] -//! Binary (de)serialisation. -//! -//! This crate implements macros for the [`bzipper`](https://crates.io/crates/bzipper/) crate. +//! This crate implements procedural macros for [`bzipper`](https://crates.io/crates/bzipper/). use proc_macro::TokenStream; use quote::quote; -use syn::{Data, DeriveInput, parse_macro_input}; +use syn::{parse_macro_input, Data, DeriveInput}; macro_rules! use_mod { ($vis:vis $name:ident) => { @@ -35,26 +33,26 @@ macro_rules! use_mod { $vis use $name::*; }; } -pub(in crate) use use_mod; +pub(crate) use use_mod; -use_mod!(closure); use_mod!(discriminant); +use_mod!(discriminant_iter); use_mod!(generic_name); mod impls; -#[proc_macro_derive(Deserialise)] -pub fn derive_deserialise(input: TokenStream) -> TokenStream { +#[proc_macro_derive(Decode)] +pub fn derive_decode(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::Enum( ref data) => impls::decode_enum( data), + Data::Struct(ref data) => impls::decode_struct(data), - Data::Union(..) => panic!("unions cannot derive `Deserialise`"), + Data::Union(..) => panic!("unions cannot derive `Decode`"), }; - let type_name = &input.ident; + let ty_name = &input.ident; let generic_params = &input.generics.params; let generic_where = &input.generics.where_clause; @@ -62,27 +60,29 @@ pub fn derive_deserialise(input: TokenStream) -> TokenStream { let generic_names = GenericName::extract_from(&input.generics); let output = quote! { - impl<#generic_params> ::bzipper::Deserialise for #type_name<#generic_names> + impl<#generic_params> ::bzipper::Decode for #ty_name<#generic_names> #generic_where { #impl_body } }; + //panic!("{output}"); + output.into() } -#[proc_macro_derive(Serialise)] -pub fn derive_serialise(input: TokenStream) -> TokenStream { +#[proc_macro_derive(Encode)] +pub fn derive_encode(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::Enum( ref data) => impls::encode_enum( data), + Data::Struct(ref data) => impls::encode_struct(data), - Data::Union(..) => panic!("unions cannot derive `Serialise`"), + Data::Union(..) => panic!("unions cannot derive `Encode`"), }; - let type_name = &input.ident; + let ty_name = &input.ident; let generic_params = &input.generics.params; let generic_where = &input.generics.where_clause; @@ -90,13 +90,55 @@ pub fn derive_serialise(input: TokenStream) -> TokenStream { let generic_names = GenericName::extract_from(&input.generics); let output = quote! { - impl<#generic_params> ::bzipper::Serialise for #type_name<#generic_names> + impl<#generic_params> ::bzipper::Encode for #ty_name<#generic_names> #generic_where { #impl_body } }; - //if let Data::Enum(..) = input.data { panic!("{output}") }; + //panic!("{output}"); + + output.into() +} + +#[proc_macro_derive(SizedEncode)] +pub fn derive_sized_encode(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let encode_impl_body = match input.data { + Data::Enum( ref data) => impls::encode_enum( data), + Data::Struct(ref data) => impls::encode_struct(data), + + Data::Union(..) => panic!("unions can neither derive `Encode` nor `SizedEncode`"), + }; + + let sized_encode_impl_body = match input.data { + Data::Enum( ref data) => impls::sized_encode_enum( data), + Data::Struct(ref data) => impls::sized_encode_struct(data), + + Data::Union(..) => unreachable!(), + }; + + let ty_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::Encode for #ty_name<#generic_names> + #generic_where { + #encode_impl_body + } + + unsafe impl<#generic_params> ::bzipper::SizedEncode for #ty_name<#generic_names> + #generic_where { + #sized_encode_impl_body + } + }; + + //panic!("{output}"); output.into() } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..cda8d17 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/doc-icon.svg b/doc-icon.svg index bfea201..d17c770 100644 --- a/doc-icon.svg +++ b/doc-icon.svg @@ -1,14 +1,6 @@ -<svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> +<svg height="72" width="72" xmlns="http://www.w3.org/2000/svg"> <mask id="z"> - <rect fill="white" height="100%" rx="8" width="100%" x="0" y="0" /> - - <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" /> + <polygon fill="white" points="12,12 72,12 60,24 48,24 42,30 66,30 54,42 48,42 42,48 72,48 60,60 0,60 12,48 24,48 30,42 5,42 18,30 24,30 30,24 0,24" /> </mask> <rect fill="#FFFFFF" height="100%" mask="url(#z)" width="100%" x="0" y="0" /> |