14 releases (7 breaking)

0.7.0 Aug 31, 2024
0.5.2 Aug 21, 2024
0.4.2 Jul 7, 2024

#317 in Encoding

26 downloads per month

LGPL-3.0-or-later

92KB
1.5K SLoC

bzipper

bzipper is a binary (de)serialiser for the Rust language.

In contrast to Serde/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.

Keep in mind that this project is still work-in-progress.

This crate is compatible with no_std.

Data model

Most primitive types serialise losslessly, with the exception being usize and isize. These serialise as u32 and i32, respectively, for portability reasons.

Unsized types, such as str and slices, are not supported. Instead, arrays should be used. For strings, the FixedString type is also provided.

Usage

This crate revolves around the Serialise and Deserialise traits, both of which use streams – or more specifically – s-streams and d-streams.

Many core types come implemented with bzipper, including primitives as well as some standard library types such as Option and Result.

It is recommended in most cases to just derive these two traits for custom types (although this is only supported with enumerations and structures). Here, each field is chained according to declaration order:

use bzipper::{Buffer, Deserialise, Serialise};

#[derive(Debug, Deserialise, PartialEq, Serialise)]
struct IoRegister {
    addr:  u32,
    value: u16,
}

let mut buf = Buffer::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.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 });

Serialisation

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.

use bzipper::{Serialise, Sstream};

let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE];
let mut stream = Sstream::new(&mut buf);

'Ж'.serialise(&mut stream).unwrap();

assert_eq!(stream, [0x00, 0x00, 0x04, 0x16]);

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:

use bzipper::{Serialise, Sstream};

let mut buf = [Default::default(); char::MAX_SERIALISED_SIZE * 0x5];
let mut stream = Sstream::new(&mut buf);

// Note: For serialising multiple characters, the
// `FixedString` type is usually preferred.

'ل'.serialise(&mut stream).unwrap();
'ا'.serialise(&mut stream).unwrap();
'م'.serialise(&mut stream).unwrap();
'د'.serialise(&mut stream).unwrap();
'ا'.serialise(&mut stream).unwrap();

assert_eq!(buf, [
    0x00, 0x00, 0x06, 0x44, 0x00, 0x00, 0x06, 0x27,
    0x00, 0x00, 0x06, 0x45, 0x00, 0x00, 0x06, 0x2F,
    0x00, 0x00, 0x06, 0x27
]);

When serialising primitives, the resulting byte stream is in big endian (a.k.a. network endian). It is recommended for implementors to adhere to this convention as well.

Deserialisation

Deserialisation works with a similar syntax to serialisation.

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:

use bzipper::{Deserialise, Dstream};

let data = [0x45, 0x54];
let stream = Dstream::new(&data);
assert_eq!(u16::deserialise(&stream).unwrap(), 0x4554);

And just like s-streams, d-streams can also be used to handle chaining:

use bzipper::{Deserialise, Dstream};

let data = [0x45, 0x54];
let stream = Dstream::new(&data);

assert_eq!(u8::deserialise(&stream).unwrap(), 0x45);
assert_eq!(u8::deserialise(&stream).unwrap(), 0x54);

// The data can also be deserialised as a tuple (up
// to twelve elements).

let stream = Dstream::new(&data);
assert_eq!(<(u8, u8)>::deserialise(&stream).unwrap(), (0x45, 0x54));

Dependencies

~240–680KB
~16K SLoC