Crate packtool[][src]

Expand description

packtool is a packing library. Useful to define how serializing and deserializing data from a type level definition.

Example

Unit types

unit types can be packed. What this means is that the object is known to have the same constant value. That way it is possible to define values that are expected to be found and to be the same.

All Packed unit structures must have a #[packed(value = ...)] attribute. The value can be set to any literal except: bool, float.

use packtool::{Packed, View};

/// a unit that is always the utf8 string `"my protocol"`
/// and takes 11 bytes in the packed structure
#[derive(Packed)]
#[packed(value = "my protocol")]
pub struct ProtocolPrefix;

/// a unit that is always `4` and takes 1 byte long
#[derive(Packed)]
#[packed(value = 0b0000_0100u8)]
pub struct OtherUnit();

/// a unit that is always `0xcafe` and takes 4 bytes
/// in the packed structure
#[derive(Packed)]
#[packed(value = 0xcafeu32)]
pub struct LastButNotLeast {}

const SLICE: &[u8] = b"my protocol";
let view: View<'_, ProtocolPrefix> = View::try_from_slice(SLICE)?;

Here we are expecting the ProtocolPrefix to always have the same value in the packed representation. When serializing the ProtocolPrefix, the value will be set with these 11 characters.

Enumeration

Only enumerations without fields are allowed for now.

use packtool::{Packed, View};

#[derive(Packed)]
#[repr(u8)]
pub enum Version {
    V1 = 1,
    V2 = 2,
}

let view: View<'_, Version> = View::try_from_slice(SLICE)?;

assert!(matches!(view.unpack(), Version::V1));

the repr(...) is necessary in order to set a size to the enum.

use packtool::Packed;

#[derive(Packed)]
pub enum Color {
    Red = 1,
    Green = 2,
    Blue = -1
}

combining packed objects

It is possible to compose packed objects in named or tuple structures.

use packtool::Packed;

#[derive(Packed)]
#[packed(value = "packcoin")]
pub struct Tag;

/// 1 byte that will be used to store a version number
#[derive(Packed)]
#[repr(u8)]
pub enum Version {
    V1 = 1,
    V2 = 2,
}

/// 8 bytes that will be used to store a block number
#[derive(Packed)]
pub struct BlockNumber(u32, u32);

/// 9 bytes packed header
#[derive(Packed)]
pub struct Header {
    tag: Tag,
    version: Version,
    block_number: BlockNumber
}

Each of the packed objects have a view accessor for each fields:

  • for named fields, the name of the accessor is the name of the field
  • for tuples, the name of the accessor is the index of the field preceded by an underscore (_): _0, _1 etc.
let tag: View<'_, Tag> = Header::tag(header);
let block_number: View<'_, BlockNumber> = Header::block_number(header);

let epoch: View<'_, u32> = BlockNumber::_0(block_number);
let slot: u32  = BlockNumber::_1(block_number).unpack();

You can rename the accessor with the attribute accessor:

#[derive(Packed)]
pub struct BlockNumber(
    #[packed(accessor = "epoch")]
    u32,
    #[packed(accessor = "slot")]
    u32
);
let epoch = BlockNumber::epoch(block_number); // instead of _0
let slot = BlockNumber::slot(block_number).unpack(); // instead of _1

It is also possible to prevent the accessor to be created. You can set the accessor with a literal boolean to say if you want the accessor or not. true will simply means the default case (use the index of the field or use the name for the name of the accessor):

#[derive(Packed)]
pub struct Hash(
    #[packed(accessor = true)]
    [u8; 32]
);
let bytes = Hash::_0(hash);

However if you set it to false there will be no accessor created for you:

#[derive(Packed)]
pub struct Hash(
    #[packed(accessor = false)]
    [u8; 32]
);
let bytes = Hash::_0(hash);

Macros

helper method to create an [Error] is the assumption fails.

Structs

a owned slice of memory containing the Packed

view of a slice in memory as a packed structure of type T

Enums

error associated to unpacking or creating [View] of [Packed] types.

Traits

trait to define how a fixed size Packed object is serialized into a byte slice representation.

Derive Macros