summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md93
-rw-r--r--Cargo.toml10
-rw-r--r--README.md277
-rw-r--r--bzipper.svg11
-rw-r--r--bzipper/Cargo.toml8
-rw-r--r--bzipper/src/buf/mod.rs415
-rw-r--r--bzipper/src/buf/test.rs47
-rw-r--r--bzipper/src/buffer/mod.rs249
-rw-r--r--bzipper/src/buffer/test.rs36
-rw-r--r--bzipper/src/decode/mod.rs577
-rw-r--r--bzipper/src/decode/test.rs (renamed from bzipper/src/deserialise/test.rs)41
-rw-r--r--bzipper/src/decode/tuple.rs324
-rw-r--r--bzipper/src/deserialise/mod.rs235
-rw-r--r--bzipper/src/deserialise/tuple.rs298
-rw-r--r--bzipper/src/dstream/mod.rs121
-rw-r--r--bzipper/src/encode/mod.rs659
-rw-r--r--bzipper/src/encode/test.rs (renamed from bzipper/src/serialise/test.rs)61
-rw-r--r--bzipper/src/encode/tuple.rs302
-rw-r--r--bzipper/src/error/decode_error/mod.rs113
-rw-r--r--bzipper/src/error/encode_error/mod.rs89
-rw-r--r--bzipper/src/error/mod.rs128
-rw-r--r--bzipper/src/error/size_error/mod.rs42
-rw-r--r--bzipper/src/error/string_error/mod.rs75
-rw-r--r--bzipper/src/error/utf16_error/mod.rs42
-rw-r--r--bzipper/src/error/utf8_error/mod.rs43
-rw-r--r--bzipper/src/fixed_string/mod.rs480
-rw-r--r--bzipper/src/fixed_string/test.rs47
-rw-r--r--bzipper/src/i_stream/mod.rs74
-rw-r--r--bzipper/src/lib.rs397
-rw-r--r--bzipper/src/o_stream/mod.rs75
-rw-r--r--bzipper/src/serialise/mod.rs258
-rw-r--r--bzipper/src/serialise/tuple.rs376
-rw-r--r--bzipper/src/sized_encode/mod.rs342
-rw-r--r--bzipper/src/sized_encode/test.rs97
-rw-r--r--bzipper/src/sized_encode/tuple.rs253
-rw-r--r--bzipper/src/sized_iter/mod.rs161
-rw-r--r--bzipper/src/sized_slice/mod.rs552
-rw-r--r--bzipper/src/sized_slice/test.rs47
-rw-r--r--bzipper/src/sized_str/mod.rs629
-rw-r--r--bzipper/src/sized_str/test.rs95
-rw-r--r--bzipper/src/sstream/mod.rs123
-rw-r--r--bzipper_benchmarks/Cargo.toml24
-rw-r--r--bzipper_benchmarks/src/main.rs522
-rw-r--r--bzipper_macros/Cargo.toml8
-rw-r--r--bzipper_macros/src/discriminant/mod.rs88
-rw-r--r--bzipper_macros/src/discriminant_iter/mod.rs71
-rw-r--r--bzipper_macros/src/generic_name/mod.rs39
-rw-r--r--bzipper_macros/src/impls/decode_enum.rs67
-rw-r--r--bzipper_macros/src/impls/decode_struct.rs55
-rw-r--r--bzipper_macros/src/impls/deserialise_enum.rs68
-rw-r--r--bzipper_macros/src/impls/deserialise_struct.rs61
-rw-r--r--bzipper_macros/src/impls/encode_enum.rs77
-rw-r--r--bzipper_macros/src/impls/encode_struct.rs46
-rw-r--r--bzipper_macros/src/impls/mod.rs18
-rw-r--r--bzipper_macros/src/impls/serialise_enum.rs101
-rw-r--r--bzipper_macros/src/impls/serialise_struct.rs69
-rw-r--r--bzipper_macros/src/impls/sized_encode_enum.rs54
-rw-r--r--bzipper_macros/src/impls/sized_encode_struct.rs (renamed from bzipper_macros/src/closure/mod.rs)34
-rw-r--r--bzipper_macros/src/lib.rs94
-rw-r--r--clippy.toml1
-rw-r--r--doc-icon.svg12
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
diff --git a/Cargo.toml b/Cargo.toml
index 9194694..e87124c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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]
diff --git a/README.md b/README.md
index bffd266..b1f5c35 100644
--- a/README.md
+++ b/README.md
@@ -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** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 |
+| **Total deviation (p.c.)** &#8594; | +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* &ndash; or more specifically &ndash; 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 &ndash; 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** &#8594; | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 |
+//! | **Total deviation (p.c.)** &#8594; | +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" />