dusk-bytes 0.1.8-rc.0

A trait that uses constant generics to implement de/serialization on sized type
Documentation

dusk-bytes

dusk-bytes is a small, no_std friendly crate that helps you implement fixed-size (de)serialization for your types using const generics.

A type that can be represented by exactly N bytes implements [Serializable<N>]. From there, the crate provides convenience traits to:

  • deserialize from slices and byte readers (DeserializableSlice).
  • parse hex strings (ParseHexStr).
  • parse hex literals at compile time (hex()).
  • and format types as hex (Hex / HexDebug).

This crate is used as the foundation for a number of Dusk types where a compact, allocation-free byte representation is desirable.

Features

  • #![no_std] (no alloc required).
  • Const-generic byte sizes (e.g. Serializable<32>).
  • Default helpers that work with custom error types via the BadLength and InvalidChar traits.
  • Built-in Serializable implementations for common integer primitives (little-endian).

Quick start

Implement [Serializable<N>] for your type:

use dusk_bytes::{DeserializableSlice, ParseHexStr, Serializable};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct Point {
    x: u16,
    y: u16,
}

impl Serializable<4> for Point {
    type Error = dusk_bytes::Error;

    fn from_bytes(buf: &[u8; 4]) -> Result<Self, Self::Error> {
        let x = u16::from_le_bytes([buf[0], buf[1]]);
        let y = u16::from_le_bytes([buf[2], buf[3]]);
        Ok(Self { x, y })
    }

    fn to_bytes(&self) -> [u8; 4] {
        let mut out = [0u8; 4];
        out[0..2].copy_from_slice(&self.x.to_le_bytes());
        out[2..4].copy_from_slice(&self.y.to_le_bytes());
        out
    }
}

// `DeserializableSlice` is auto-implemented for any `Serializable` type.
let p = Point::from_slice(&[1, 0, 2, 0]).unwrap();
assert_eq!(p, Point { x: 1, y: 2 });

// `ParseHexStr` is also auto-implemented.
let p2 = Point::from_hex_str("01000200").unwrap();
assert_eq!(p2, p);

Hex parsing

Runtime: ParseHexStr::from_hex_str

from_hex_str parses the first N * 2 characters of a string slice (two hex characters per byte).

  • If the string is shorter than N * 2, it returns a BadLength error.
  • If a non-hex character is found, it returns an InvalidChar error.
  • If the string is longer, extra characters are ignored.

Compile-time: hex()

hex() is a const fn that parses an ASCII hex byte string like b"deadbeef" into a byte array.

use dusk_bytes::hex;

const MAGIC: [u8; 4] = hex(b"deadbeef");
assert_eq!(MAGIC, [0xde, 0xad, 0xbe, 0xef]);

The input byte string must have an even length (two hex digits per output byte). Invalid characters cause a compile-time panic during const evaluation.

Hex formatting: Hex and HexDebug

dusk-bytes re-exports two derive macros from the companion derive-hex crate:

  • #[derive(Hex)] implements core::fmt::LowerHex and core::fmt::UpperHex.
  • #[derive(HexDebug)] additionally implements core::fmt::Debug and formats the value as hex for {:x?} / {:X?}.

Both derives expect your type to expose a to_bytes() method (which the [Serializable] trait already provides).

use dusk_bytes::HexDebug;

#[derive(Copy, Clone, HexDebug)]
struct IdPrefix([u8; 4]);

impl IdPrefix {
    pub fn to_bytes(&self) -> [u8; 4] {
        self.0
    }
}

let p = IdPrefix([0xde, 0xad, 0xbe, 0xef]);
assert_eq!(format!("{:x}", p), "deadbeef");
assert_eq!(format!("{:#x}", p), "0xdeadbeef");
assert_eq!(format!("{:x?}", p), "deadbeef");

Readers and writers

For embedded / no_std environments, the crate provides minimal Read / Write traits (inspired by std::io) and implements them for:

  • &[u8] (reader)
  • &mut [u8] (writer)
use dusk_bytes::{DeserializableSlice, Read, Serializable, Write};

let value: u32 = 0x01020304;

let mut buf = [0u8; 4];
{
    let mut w = &mut buf[..];
    w.write(&value.to_bytes()).unwrap();
}

let mut r = &buf[..];
let parsed = u32::from_reader(&mut r).unwrap();
assert_eq!(parsed, value);
assert!(r.is_empty());

Error handling

The crate provides a small default [Error] enum that is used by the built-in primitive implementations.

If you want to keep your own error type, implement:

  • [BadLength] (for slice/reader underflow), and
  • [InvalidChar] (for hex parsing).

Those traits are used by the default implementations of DeserializableSlice and ParseHexStr.

License

Licensed under the Mozilla Public License 2.0 (MPL-2.0).