Expand description
Deku: Declarative binary reading and writing
Deriving a struct or enum with DekuRead
and DekuWrite
provides bit-level,
symmetric, serialization/deserialization implementations.
This allows the developer to focus on building and maintaining how the data is represented and manipulated and not on redundant, error-prone, parsing/writing code.
This approach is especially useful when dealing with binary structures such as TLVs or network protocols.
Under the hood, it makes use of the bitvec crate as the “Reader” and “Writer”
For documentation and examples on available #[deku]
attributes and features,
see attributes list
For more examples, see the examples folder!
no_std
For use in no_std
environments, alloc
is the single feature which is required on deku.
Example
Let’s read big-endian data into a struct, with fields containing different sizes, modify a value, and write it back
use deku::prelude::*;
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "big")]
struct DekuTest {
#[deku(bits = "4")]
field_a: u8,
#[deku(bits = "4")]
field_b: u8,
field_c: u16,
}
let data: Vec<u8> = vec![0b0110_1001, 0xBE, 0xEF];
let (_rest, mut val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
assert_eq!(DekuTest {
field_a: 0b0110,
field_b: 0b1001,
field_c: 0xBEEF,
}, val);
val.field_c = 0xC0FE;
let data_out = val.to_bytes().unwrap();
assert_eq!(vec![0b0110_1001, 0xC0, 0xFE], data_out);
Composing
Deku structs/enums can be composed as long as they implement DekuRead / DekuWrite traits
use deku::prelude::*;
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct DekuTest {
header: DekuHeader,
data: DekuData,
}
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct DekuHeader(u8);
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct DekuData(u16);
let data: Vec<u8> = vec![0xAA, 0xEF, 0xBE];
let (_rest, mut val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
assert_eq!(DekuTest {
header: DekuHeader(0xAA),
data: DekuData(0xBEEF),
}, val);
let data_out = val.to_bytes().unwrap();
assert_eq!(data, data_out);
Vec
Vec
bytes_read or bits_read
can also be used instead of count
to read a specific size of each.
If the length of Vec changes, the original field specified in count
will not get updated.
Calling .update()
can be used to “update” the field!
use deku::prelude::*;
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
struct DekuTest {
#[deku(update = "self.data.len()")]
count: u8,
#[deku(count = "count")]
data: Vec<u8>,
}
let data: Vec<u8> = vec![0x02, 0xBE, 0xEF, 0xFF, 0xFF];
let (_rest, mut val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
assert_eq!(DekuTest {
count: 0x02,
data: vec![0xBE, 0xEF]
}, val);
let data_out = val.to_bytes().unwrap();
assert_eq!(vec![0x02, 0xBE, 0xEF], data_out);
// Pushing an element to data
val.data.push(0xAA);
assert_eq!(DekuTest {
count: 0x02, // Note: this value has not changed
data: vec![0xBE, 0xEF, 0xAA]
}, val);
let data_out = val.to_bytes().unwrap();
// Note: `count` is still 0x02 while 3 bytes got written
assert_eq!(vec![0x02, 0xBE, 0xEF, 0xAA], data_out);
// Use `update` to update `count`
val.update().unwrap();
assert_eq!(DekuTest {
count: 0x03,
data: vec![0xBE, 0xEF, 0xAA]
}, val);
Enums
As enums can have multiple variants, each variant must have a way to match on the incoming data.
First the “type” is read using the type
, then is matched against the
variants given id
. What happens after is the same as structs!
This is implemented with the id, id_pat and type attributes. See these for more examples.
If no id
is specified, the variant will default to it’s discriminant value.
If no variant can be matched, a DekuError::Parse error will be returned.
Example:
use deku::prelude::*;
#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(type = "u8")]
enum DekuTest {
#[deku(id = "0x01")]
VariantA,
#[deku(id = "0x02")]
VariantB(u16),
}
let data: Vec<u8> = vec![0x01, 0x02, 0xEF, 0xBE];
let (rest, val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
assert_eq!(DekuTest::VariantA , val);
let (rest, val) = DekuTest::from_bytes(rest).unwrap();
assert_eq!(DekuTest::VariantB(0xBEEF) , val);
Context
Child parsers can get access to the parent’s parsed values using the ctx
attribute
For more information see ctx attribute
Example:
use deku::prelude::*;
#[derive(DekuRead, DekuWrite)]
#[deku(ctx = "a: u8")]
struct Subtype {
#[deku(map = "|b: u8| -> Result<_, DekuError> { Ok(b + a) }")]
b: u8
}
#[derive(DekuRead, DekuWrite)]
struct Root {
a: u8,
#[deku(ctx = "*a")] // `a` is a reference
sub: Subtype
}
let data: Vec<u8> = vec![0x01, 0x02];
let (rest, value) = Root::from_bytes((&data[..], 0)).unwrap();
assert_eq!(value.a, 0x01);
assert_eq!(value.sub.b, 0x01 + 0x02)
Internal variables and previously read fields
Along similar lines to Context variables, previously read variables are exposed and can be referenced:
Example:
#[derive(DekuRead)]
struct DekuTest {
num_items: u8,
#[deku(count = "num_items")]
items: Vec<u16>,
}
The following variables are internals which can be used in attributes accepting
tokens such as reader
, writer
, map
, count
, etc.
These are provided as a convenience to the user.
Always included:
deku::input: (&[u8], usize)
- The initial input byte slice and bit offset (available when using from_bytes)deku::input_bits: &BitSlice<u8, Msb0>
- The initial input in bitsdeku::rest: &BitSlice<u8, Msb0>
- Remaining bits to readdeku::output: &mut BitSlice<u8, Msb0>
- The output bit stream
Conditionally included if referenced:
deku::bit_offset: usize
- Current bit offset from the inputdeku::byte_offset: usize
- Current byte offset from the input
Example:
#[derive(DekuRead)]
#[deku(ctx = "size: u32")]
pub struct EncodedString {
encoding: u8,
#[deku(count = "size as usize - deku::byte_offset")]
data: Vec<u8>
}
Re-exports
pub use crate::error::DekuError;
Modules
Traits
container
is a type which
doesn’t need any context information.container
is a type which
doesn’t need any context information.Attribute Macros
deku_derive
proc-macro
This attribute macro is used to derive DekuRead
and DekuWrite
while removing temporary variables.