Skip to content
master
Switch branches/tags
Go to file
Code

README.md

Bit-level packing and unpacking for Rust

Crates.io Documentation master

Introduction

Packing and unpacking bit-level structures is usually a programming tasks that needlessly reinvents the wheel. This library provides a meta-programming approach, using attributes to define fields and how they should be packed. The resulting trait implementations provide safe packing, unpacking and runtime debugging formatters with per-field documentation generated for each structure.

Features

  • Plain Rust structures, decorated with attributes
  • MSB or LSB integers of user-defined bit widths
  • Primitive enum code generation helper
  • MSB0 or LSB0 bit positioning
  • Documents the field's packing table
  • Runtime packing visualization
  • Nested packed types
  • Arrays of packed structures as fields
  • Reserved fields, their bits are always 0 or 1

Crate-level feature flags

  • std: use the Rust standard library. Default.
  • alloc: use the alloc crate for no_std + alloc scenarios. Requires nightly Rust.
  • use_serde: add serialization support to the built-in helper types.
  • byte_types_64, byte_types_256: enlarge the size of the generated array, byte and bit width types.

Sample usage

Cargo.toml

[dependencies]
packed_struct = "0.5"

Importing the library with the the most common traits and the derive macros

// This is only needed for pre Rust 2018
#[macro_use] extern crate packed_struct;
// Prelude import with the common imports
use packed_struct::prelude::*;

Example of a single-byte structure, with a 3 bit integer, primitive enum and a bool field.

use packed_struct::prelude::*;

#[derive(PackedStruct)]
#[packed_struct(bit_numbering="msb0")]
pub struct TestPack {
    #[packed_field(bits="0..=2")]
    tiny_int: Integer<u8, packed_bits::Bits3>,
    #[packed_field(bits="3..=4", ty="enum")]
    mode: SelfTestMode,
    #[packed_field(bits="7")]
    enabled: bool
}

#[derive(PrimitiveEnum_u8, Clone, Copy, Debug, PartialEq)]
pub enum SelfTestMode {
    NormalMode = 0,
    PositiveSignSelfTest = 1,
    NegativeSignSelfTest = 2,
    DebugMode = 3,
}

fn main() -> Result<(), PackingError> {
    let test = TestPack {
        tiny_int: 5.into(),
        mode: SelfTestMode::DebugMode,
        enabled: true
    };

    // pack into a byte array
    let packed: [u8; 1] = test.pack()?;
    assert_eq!([0b10111001], packed);

    // unpack from a byte array
    let unpacked = TestPack::unpack(&packed)?;
    assert_eq!(*unpacked.tiny_int, 5);
    assert_eq!(unpacked.mode, SelfTestMode::DebugMode);
    assert_eq!(unpacked.enabled, true);

    // or unpack from a slice
    let unpacked = TestPack::unpack_from_slice(&packed[..])?;

    Ok(())
}

Packing attributes

Syntax

use packed_struct::prelude::*;

#[derive(PackedStruct)]
#[packed_struct(attr1="val", attr2="val")]
pub struct Structure {
    #[packed_field(attr1="val", attr2="val")]
    field: u8
}

Per-structure attributes

Attribute Values Comment
size_bytes 1 ... n Size of the packed byte stream
bit_numbering msb0 or lsb0 Bit numbering for bit positioning of fields. Required if the bits attribute field is used.
endian msb or lsb Default integer endianness

Per-field attributes

Attribute Values Comment
bits 0, 0..1, ... Position of the field in the packed structure. Three modes are supported: a single bit, the starting bit, or a range of bits. See details below.
bytes 0, 0..1, ... Same as above, multiplied by 8.
size_bits 1, ... Specifies the size of the packed structure. Mandatory for certain types. Specifying a range of bits like bits="0..2" can substite the required usage of size_bits.
size_bytes 1, ... Same as above, multiplied by 8.
element_size_bits 1, ... For packed arrays, specifies the size of a single element of the array. Explicitly stating the size of the entire array can substite the usage of this attribute.
element_size_bytes 1, ... Same as above, multiplied by 8.
ty enum Packing helper for primitive enums.
endian msb or lsb Integer endianness. Applies to u16/i16 and larger types.

Bit and byte positioning

Used for either bits or bytes on fields. The examples are for MSB0 positioning.

Value Comment
0 A single bit or byte
0.., 0: The field starts at bit zero
0..2 Exclusive range, bits zero and one
0:1, 0..=1 Inclusive range, bits zero and one

More examples

Mixed endian integers

use packed_struct::prelude::*;

#[derive(PackedStruct)]
pub struct EndianExample {
    #[packed_field(endian="lsb")]
    int1: u16,
    #[packed_field(endian="msb")]
    int2: i32
}

fn main() -> Result<(), PackingError> {
    let example = EndianExample {
        int1: 0xBBAA,
        int2: 0x11223344
    };

    let packed = example.pack()?;
    assert_eq!([0xAA, 0xBB, 0x11, 0x22, 0x33, 0x44], packed);
    Ok(())
}

24 bit LSB integers

use packed_struct::prelude::*;

#[derive(PackedStruct)]
#[packed_struct(endian="lsb")]
pub struct LsbIntExample {
    int1: Integer<u32, packed_bits::Bits24>,
}

fn main() -> Result<(), PackingError> {
    let example = LsbIntExample {
        int1: 0xCCBBAA.into()
    };

    let packed = example.pack()?;
    assert_eq!([0xAA, 0xBB, 0xCC], packed);
    Ok(())
}

Nested packed types within arrays

use packed_struct::prelude::*;

#[derive(PackedStruct, Default, Debug, PartialEq)]
#[packed_struct(bit_numbering="msb0")]
pub struct TinyFlags {
    _reserved: ReservedZero<packed_bits::Bits4>,
    flag1: bool,
    val1: Integer<u8, packed_bits::Bits2>,
    flag2: bool
}

#[derive(PackedStruct, Debug, PartialEq)]
pub struct Settings {
    #[packed_field(element_size_bits="4")]
    values: [TinyFlags; 4]
}

fn main() -> Result<(), PackingError> {
    let example = Settings {
        values: [
            TinyFlags { flag1: true,  val1: 1.into(), flag2: false, .. TinyFlags::default() },
            TinyFlags { flag1: true,  val1: 2.into(), flag2: true,  .. TinyFlags::default() },
            TinyFlags { flag1: false, val1: 3.into(), flag2: false, .. TinyFlags::default() },
            TinyFlags { flag1: true,  val1: 0.into(), flag2: false, .. TinyFlags::default() },
        ]
    };

    let packed = example.pack()?;
    let unpacked = Settings::unpack(&packed)?;

    assert_eq!(example, unpacked);
    Ok(())
}

Primitive enums with simple discriminants

Supported backing integer types: u8, u16, u32, u64, i8, i16, i32, i64.

Explicit or implicit backing type:

use packed_struct::prelude::*;

#[derive(PrimitiveEnum, Clone, Copy, PartialEq, Debug)]
pub enum ImplicitType {
    VariantMin = 0,
    VariantMax = 255
}

#[derive(PrimitiveEnum_i16, Clone, Copy)]
pub enum ExplicitType {
    VariantMin = -32768,
    VariantMax = 32767
}

fn main() {
    use packed_struct::PrimitiveEnum;

    let t = ImplicitType::VariantMin;
    let tn: u8 = t.to_primitive();
    assert_eq!(0, tn);

    let t = ImplicitType::from_primitive(255).unwrap();
    assert_eq!(ImplicitType::VariantMax, t);
}

Primitive enum packing with support for catch-all unknown values

use packed_struct::prelude::*;

#[derive(PrimitiveEnum_u8, Debug, Clone, Copy)]
pub enum Field {
    A = 1,
    B = 2,
    C = 3
}

#[derive(PackedStruct, Debug, PartialEq)]
#[packed_struct(bit_numbering="msb0")]
pub struct Register {
    #[packed_field(bits="0..4", ty="enum")]
    field: EnumCatchAll<Field>
}

License: MIT OR Apache-2.0