Skip to main content

Crate dusk_bytes

Crate dusk_bytes 

Source
Expand description

§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, with the default derive feature, 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).

§Cargo features

  • derive (enabled by default) re-exports the Hex and HexDebug procedural macros from derive-hex.

Consumers that only need serialization and parsing can disable default features to keep dusk-bytes dependency-free:

dusk-bytes = { version = "0.1", default-features = false }

§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.

§Const-capable: hex()

hex() is a const fn that parses an ASCII hex byte string like b"deadbeef" into a byte array. It can be called at runtime or evaluated in a constant context.

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 byte). An odd trailing character is rejected even if the destination is already full; it is not ignored like extra input to from_hex_str. An odd input length or an invalid character in the portion consumed to fill the destination panics at runtime. When hex() is evaluated in a constant context, the same panic is reported during compilation.

§Hex formatting: Hex and HexDebug

With the default derive feature, 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?}.

Do not derive Hex or HexDebug for types containing secrets or other confidential material. Both expose the complete to_bytes() output: Hex through hexadecimal formatting such as {:x} and HexDebug additionally through every Debug formatting path, including ordinary {:?}.

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!("{:?}", p), "deadbeef");
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:

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

§License

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

Enums§

Error
Dusk Bytes operation error variants

Traits§

BadLength
Trait implemented by error types used by DeserializableSlice::from_slice. The method is called when the given slice is shorter than the required size.
DeserializableSlice
An optional trait used to implement DeserializableSlice::from_slice on top of types that use the Serializable trait. The default implementation makes use of Serializable trait to provide the necessary deserialization functionality without additional code from the consumer.
InvalidChar
Trait implemented by error types used by ParseHexStr::from_hex_str. The method is called when an invalid character is found in the string slice.
ParseHexStr
An optional trait used to parse a string slice for types that implements the Serializable trait. The default implementation makes use of Serializable trait to provide the necessary parsing functionality without additional code from the consumer.
Read
Implementors of the Read trait are called ‘readers’.
Serializable
The core trait used to implement Serializable::from_bytes and Serializable::to_bytes.
Write
Implementors of the Write trait are sometimes called ‘writers’.

Functions§

hex
A constant funtion to parse a bytes string representing hexadecimals (e.g. b"fe12c6" ) into bytes (e.g [0xfe, 0x12, 0xc6]). If a smaller destination buffer is provided, the value will be truncated (e.g [0xfe, 0x12]); if a bigger destination buffer is provided, it will be padded with zeroes (e.g. `[0xfe, 0x12, 0xc6, 0x0, 0x0])

Derive Macros§

Hex
Derives lower- and upper-hexadecimal formatting for a serializable type.
HexDebug
Derives hexadecimal formatting and renders the serialized value for Debug.