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

Crate-level feature flags

Sample usage

Cargo.toml

toml [dependencies] packed_struct = "0.10"

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

```rust // This is only needed for pre Rust 2018

[macrouse] extern crate packedstruct;

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

```rust use packed_struct::prelude::*;

[derive(PackedStruct)]

[packedstruct(bitnumbering="msb0")]

pub struct TestPack { #[packedfield(bits="0..=2")] tinyint: Integer>, #[packedfield(bits="3..=4", ty="enum")] mode: SelfTestMode, #[packedfield(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

```rust 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

```rust use packed_struct::prelude::*;

[derive(PackedStruct)]

pub struct EndianExample { #[packedfield(endian="lsb")] int1: u16, #[packedfield(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

```rust use packed_struct::prelude::*;

[derive(PackedStruct)]

[packed_struct(endian="lsb")]

pub struct LsbIntExample { int1: Integer>, }

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

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

} ```

Nested packed types

```rust use packed_struct::prelude::*;

[derive(PackedStruct, Debug, PartialEq)]

[packed_struct(endian="lsb")]

pub struct Duration { minutes: u8, seconds: u8, }

[derive(PackedStruct, Debug, PartialEq)]

pub struct Record { #[packedfield(elementsizebytes="2")] span: Duration, events: u8, } fn main() -> Result<(), PackingError> { let example = Record { span: Duration { minutes: 10, seconds: 34, }, events: 3, }; let packed = example.pack()?; let unpacked = Record::unpack(&packed)?; asserteq!(example, unpacked); Ok(()) } ```

Nested packed types within arrays

```rust use packed_struct::prelude::*;

[derive(PackedStruct, Default, Debug, PartialEq)]

[packedstruct(bitnumbering="msb0")]

pub struct TinyFlags { _reserved: ReservedZero>, flag1: bool, val1: Integer>, flag2: bool }

[derive(PackedStruct, Debug, PartialEq)]

pub struct Settings { #[packedfield(elementsize_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:

```rust 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

```rust use packed_struct::prelude::*;

[derive(PrimitiveEnum_u8, Debug, Clone, Copy)]

pub enum Field { A = 1, B = 2, C = 3 }

[derive(PackedStruct, Debug, PartialEq)]

[packedstruct(bitnumbering="msb0")]

pub struct Register { #[packed_field(bits="0..4", ty="enum")] field: EnumCatchAll }

``` License: MIT OR Apache-2.0