14 releases (7 breaking)
0.10.1 | Nov 16, 2022 |
---|---|
0.10.0 | Sep 15, 2021 |
0.6.1 | Sep 10, 2021 |
0.6.0 | Jun 27, 2021 |
0.2.3 | Mar 29, 2018 |
#120 in Encoding
69,841 downloads per month
Used in 42 crates
(25 directly)
90KB
1.5K
SLoC
Bit-level packing and unpacking for Rust
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 thealloc
crate forno_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.10"
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::Bits::<3>>,
#[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::Bits::<24>>,
}
fn main() -> Result<(), PackingError> {
let example = LsbIntExample {
int1: 0xCCBBAA.into()
};
let packed = example.pack()?;
assert_eq!([0xAA, 0xBB, 0xCC], packed);
Ok(())
}
Nested packed types
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 {
#[packed_field(element_size_bytes="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)?;
assert_eq!(example, unpacked);
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::Bits::<4>>,
flag1: bool,
val1: Integer<u8, packed_bits::Bits::<2>>,
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
Dependencies
~2.5MB
~61K SLoC