summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md277
1 files changed, 223 insertions, 54 deletions
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** → | 7.065 | 3.567 | 3.286 | 17.937 | 3.625 |
+| **Total deviation (p.c.)** → | +115 | +9 | ±0 | +446 | +10 |
+
+[Bincode]: https://crates.io/crates/bincode/
+[Borsh]: https://crates.io/crates/borsh/
+[Ciborium]: https://crates.io/crates/ciborium/
+[Postcard]: https://crates.io/crates/postcard/
+
+All quantities are measured in seconds unless otherwise noted.
+Please feel free to conduct your own tests of bZipper.
+
## Data model
-Most primitive types serialise losslessly, with the exception being `usize` and `isize`.
-These serialise as `u32` and `i32`, respectively, for portability reasons.
+Most primitives encode losslessly, with the main exceptions being `usize` and `isize`.
+These are instead first cast as `u16` and `i16`, respectively, due to portability concerns (with respect to embedded systems).
-Unsized types, such as `str` and slices, are not supported.
-Instead, arrays should be used.
-For strings, the `FixedString` type is also provided.
+See specific types' implementations for notes on their data models.
+
+**Note that the data model is currently not stabilised,** and may not necessarily be in the near future (before [specialisation](https://github.com/rust-lang/rust/issues/31844/)).
+It may therefore be undesired to store encodings long-term.
## Usage
-This crate revolves around the `Serialise` and `Deserialise` traits, both of which use *streams* – or more specifically – s-streams and d-streams.
+This crate revolves around the `Encode` and `Decode` traits which both handle conversions to and from byte streams.
-Many core types come implemented with bzipper, including primitives as well as some standard library types such as `Option` and `Result`.
+Many standard types come implemented with bZipper, including most primitives as well as some standard library types such as `Option` and `Result`.
+Some [features](#features-flags) enable an extended set of implementations.
-It is recommended in most cases to just derive these two traits for custom types (although this is only supported with enumerations and structures).
+It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures – not untagged unions).
Here, each field is *chained* according to declaration order:
```rust
-use bzipper::{Buffer, Deserialise, Serialise};
+use bzipper::{Buf, Decode, Encode, SizedEncode};
-#[derive(Debug, Deserialise, PartialEq, Serialise)]
+#[derive(Debug, Decode, PartialEq, SizedEncode)]
struct IoRegister {
addr: u32,
value: u16,
}
-let mut buf = Buffer::new();
+let mut buf = Buf::new();
buf.write(IoRegister { addr: 0x04000000, value: 0x0402 }).unwrap();
assert_eq!(buf.len(), 0x6);
-assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02]);
+assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02].as_slice());
assert_eq!(buf.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 });
```
-### Serialisation
+### Buffer types
+
+The `Encode` and `Decode` traits both rely on streams for carrying the manipulated byte streams.
-To serialise an object implementing `Serialise`, simply allocate a buffer for the serialisation and wrap it in an s-stream (*serialisation stream*) with the `Sstream` type.
+These streams are separated into two type: *O-streams* (output streams) and *i-streams* (input streams).
+Often, but not always, the `Buf` type is preferred over directly calling the `encode` and `decode` methods.
+
+### Encoding
+
+To encode an object directly using the `Encode` trait, simply allocate a buffer for the encoding and wrap it in an `OStream` object:
```rust
-use bzipper::{Serialise, Sstream};
+use bzipper::{Encode, OStream, SizedEncode};
-let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE];
-let mut stream = Sstream::new(&mut buf);
+let mut buf = [0x00; char::MAX_ENCODED_SIZE];
+let mut stream = OStream::new(&mut buf);
-'Ж'.serialise(&mut stream).unwrap();
+'Ж'.encode(&mut stream).unwrap();
-assert_eq!(stream, [0x00, 0x00, 0x04, 0x16]);
+assert_eq!(buf, [0x00, 0x00, 0x04, 0x16].as_slice());
```
-The maximum size of any given serialisation is specified by the `MAX_SERIALISED_SIZE` constant.
-
-We can also use streams to chain multiple elements together:
+Streams can also be used to chain multiple objects together:
```rust
-use bzipper::{Serialise, Sstream};
+use bzipper::{Encode, OStream, SizedEncode};
-let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE * 0x5];
-let mut stream = Sstream::new(&mut buf);
+let mut buf = [0x0; char::MAX_ENCODED_SIZE * 0x5];
+let mut stream = OStream::new(&mut buf);
// Note: For serialising multiple characters, the
-// `FixedString` type is usually preferred.
+// `String` and `SizedStr` types are usually
+// preferred.
-'ل'.serialise(&mut stream).unwrap();
-'ا'.serialise(&mut stream).unwrap();
-'م'.serialise(&mut stream).unwrap();
-'د'.serialise(&mut stream).unwrap();
-'ا'.serialise(&mut stream).unwrap();
+'ل'.encode(&mut stream).unwrap();
+'ا'.encode(&mut stream).unwrap();
+'م'.encode(&mut stream).unwrap();
+'د'.encode(&mut stream).unwrap();
+'ا'.encode(&mut stream).unwrap();
assert_eq!(buf, [
0x00, 0x00, 0x06, 0x44, 0x00, 0x00, 0x06, 0x27,
@@ -87,38 +122,172 @@ assert_eq!(buf, [
]);
```
-When serialising primitives, the resulting byte stream is in big endian (a.k.a. network endian).
-It is recommended for implementors to adhere to this convention as well.
+If the encoded type additionally implements `SizedEncode`, then the maximum size of any encoding is guaranteed with the `MAX_ENCODED_SIZE` constant.
-### Deserialisation
+Numerical primitives are encoded in big endian (a.k.a. [network order](https://en.wikipedia.org/wiki/Endianness#Networking)) for... reasons.
+It is recommended for implementors to follow this convention as well.
-Deserialisation works with a similar syntax to serialisation.
+### Decoding
-D-streams (*deserialisation streams*) use the `Dstream` type and are constructed in a manner similar to s-streams.
-To deserialise a buffer, simply call the `deserialise` method with the strema:
+Decoding works with a similar syntax to encoding.
+To decode a byte array, simply call the `decode` method with an `IStream` object:
```rust
-use bzipper::{Deserialise, Dstream};
+use bzipper::{Decode, IStream};
let data = [0x45, 0x54];
-let stream = Dstream::new(&data);
-assert_eq!(u16::deserialise(&stream).unwrap(), 0x4554);
+let mut stream = IStream::new(&data);
+
+assert_eq!(u16::decode(&mut stream).unwrap(), 0x4554);
+
+// Data can theoretically be reinterpretred:
+
+stream = IStream::new(&data);
+
+assert_eq!(u8::decode(&mut stream).unwrap(), 0x45);
+assert_eq!(u8::decode(&mut stream).unwrap(), 0x54);
+
+// Including as tuples:
+
+stream = IStream::new(&data);
+
+assert_eq!(<(u8, u8)>::decode(&mut stream).unwrap(), (0x45, 0x54));
```
-And just like s-streams, d-streams can also be used to handle chaining:
+## Examples
+
+A UDP server/client for geographic data:
```rust
-use bzipper::{Deserialise, Dstream};
+use bzipper::{Buf, Decode, SizedEncode};
+use std::io;
+use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
+use std::thread::spawn;
+
+// City, region, etc.:
+#[derive(Clone, Copy, Debug, Decode, Eq, PartialEq, SizedEncode)]
+enum Area {
+ AlQuds,
+ Byzantion,
+ Cusco,
+ Tenochtitlan,
+ // ...
+}
-let data = [0x45, 0x54];
-let stream = Dstream::new(&data);
+// Client-to-server message:
+#[derive(Debug, Decode, PartialEq, SizedEncode)]
+enum Request {
+ AtmosphericHumidity { area: Area },
+ AtmosphericPressure { area: Area },
+ AtmosphericTemperature { area: Area },
+ // ...
+}
+
+// Server-to-client message:
+#[derive(Debug, Decode, PartialEq, SizedEncode)]
+enum Response {
+ AtmosphericHumidity(f64),
+ AtmosphericPressure(f64), // Pascal
+ AtmosphericTemperature(f64), // Kelvin
+ // ...
+}
+
+struct Party {
+ pub socket: UdpSocket,
+
+ pub request_buf: Buf::<Request>,
+ pub response_buf: Buf::<Response>,
+}
+
+impl Party {
+ pub fn new<A: ToSocketAddrs>(addr: A) -> io::Result<Self> {
+ let socket = UdpSocket::bind(addr)?;
+
+ let this = Self {
+ socket,
+
+ request_buf: Buf::new(),
+ response_buf: Buf::new(),
+ };
-assert_eq!(u8::deserialise(&stream).unwrap(), 0x45);
-assert_eq!(u8::deserialise(&stream).unwrap(), 0x54);
+ Ok(this)
+ }
+}
+
+let mut server = Party::new("127.0.0.1:27015").unwrap();
+
+let mut client = Party::new("0.0.0.0:0").unwrap();
+
+spawn(move || {
+ let Party { socket, mut request_buf, mut response_buf } = server;
+
+ // Recieve initial request from client.
+
+ let (len, addr) = socket.recv_from(&mut request_buf).unwrap();
+ request_buf.set_len(len);
+
+ let request = request_buf.read().unwrap();
+ assert_eq!(request, Request::AtmosphericTemperature { area: Area::AlQuds });
+
+ // Handle request and respond back to client.
+
+ let response = Response::AtmosphericTemperature(44.4); // For demonstration's sake.
+
+ response_buf.write(response).unwrap();
+ socket.send_to(&response_buf, addr).unwrap();
+});
+
+spawn(move || {
+ let Party { socket, mut request_buf, mut response_buf } = client;
+
+ // Send initial request to server.
+
+ socket.connect("127.0.0.1:27015").unwrap();
+
+ let request = Request::AtmosphericTemperature { area: Area::AlQuds };
+
+ request_buf.write(request);
+ socket.send(&request_buf).unwrap();
-// The data can also be deserialised as a tuple (up
-// to twelve elements).
+ // Recieve final response from server.
-let stream = Dstream::new(&data);
-assert_eq!(<(u8, u8)>::deserialise(&stream).unwrap(), (0x45, 0x54));
+ socket.recv(&mut response_buf).unwrap();
+
+ let response = response_buf.read().unwrap();
+ assert_eq!(response, Response::AtmosphericTemperature(44.4));
+});
```
+
+## Feature flags
+
+bZipper defines the following features:
+
+* `alloc` (default): Enables the `Buf` type and implementations for e.g. `Box` and `Arc`
+* `std` (default): Enables implementations for types such as `Mutex` and `RwLock`
+
+## Documentation
+
+bZipper has its documentation written in-source for use by `rustdoc`.
+See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for an on-line, rendered instance.
+
+Currently, these docs make use of some unstable features for the sake of readability.
+The nightly toolchain is therefore required when rendering them.
+
+## Contribution
+
+bZipper does not accept source code contributions at the moment.
+This is a personal choice by the maintainer and may be undone in the future.
+
+Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/bzipper/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/bzipper/issues/) if you feel the need to express any concerns over the project.
+
+## Copyright & Licence
+
+Copyright 2024 Gabriel Bjørnager Jensen.
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along with this program.
+If not, see <https://www.gnu.org/licenses/>.