deku
repository·master·Indexed 23 days ago
https://github.com/sharksforarms/dekuA declarative Rust crate for bit-level, symmetric binary serialization and deserialization of structs and enums. It uses proc-macros to automatically generate reader and writer functions via DekuRead and DekuWrite derives, allowing users to specify endianness, bit-level widths, and parsing limits through attributes. It supports no_std environments and provides tools for handling variable-length vectors, enum variant mapping, and detailed decoding errors.
What's inside deku
- Deku is a Rust crate that provides declarative, bit-level, symmetric serialization and deserialization for structs and enums. It allows you to define binary formats using attributes, automatically generating the necessary reader and writer functions to avoid manual, error-prone parsing of network headers or binary structs.
Install Deku for no_std environments
masterIf you are working in a
no_stdenvironment, disable default features and enable theallocfeature instead.[dependencies] deku = { version = "0.20", default-features = false, features = ["alloc"] }Install Deku
masterAdd
dekuto yourCargo.tomldependencies. Note that this crate requiresrustc 1.81+.[dependencies] deku = "0.20"Use Deku for declarative binary serialization
masterDeku provides bit-level, symmetric serialization and deserialization by deriving
DekuReadandDekuWriteon structs or enums. This is ideal for binary structures like TLVs or network protocols, allowing you to define the data representation while Deku handles the parsing/writing logic. It supports both byte-aligned and bit-level control (using thebitveccrate when thebitsfeature is enabled).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, }Use the Reader for bit-level binary parsing
masterThe
Readerstruct is used withfrom_reader_with_ctxto parse binary data that may not be byte-aligned. It wraps an underlying typeRthat implementsRead + Seek.When parsing fields using the
bitsattribute, theReaderhandles the complexity of reading partial bits and buffering leftover bits or bytes to ensure subsequent reads are correct. If a struct's bit-level fields do not end on a byte boundary, theReaderwill store the remaining bits in itsleftoverfield.To check for any unconsumed bits after a parsing operation, use the
rest()method. This is particularly useful when your data format uses bit-fields that don't align to the next byte.use deku::prelude::* #[cfg(feature = "bits")] #[derive(Debug, PartialEq, DekuRead, DekuWrite)] #[deku(endian = "big")] struct DekuTest { #[deku(bits = 4)] field_a: u8, #[deku(bits = 2)] field_b: u8, } // ... setup cursor and reader ... let val = DekuTest::from_reader_with_ctx(&mut reader, ()).unwrap(); // If field_a and field_b only used 6 bits, rest() returns the remaining 2 bits of that byte let unused_bits = reader.rest();Compose Deku structs using context (`ctx`)
masterWhen composing Deku types, child structs/enums may need access to configuration (like endianness) defined in the parent. Use the
#[deku(ctx = "...")]attribute to pass this context down. If you specifyendian,bit_order, orbitsat the top level, you must pass that context to children to avoid type mismatches during derivation.#[derive(Debug, PartialEq, DekuRead, DekuWrite)] #[deku(endian = "big")] struct DekuTest { header: DekuHeader, data: DekuData, } #[derive(Debug, PartialEq, DekuRead, DekuWrite)] #[deku(ctx = "endian: deku::ctx::Endian")] struct DekuHeader(u8); #[derive(Debug, PartialEq, DekuRead, DekuWrite)] #[deku(ctx = "endian: deku::ctx::Endian")] struct DekuData(u16);Deserialize Enums with `id_type` and `id`
masterTo deserialize enums, specify how the variant identifier is read using
#[deku(id_type = "...")]. Then, map specific values to variants using#[deku(id = ...)]. If noidis provided, Deku defaults to the variant's discriminant. If no variant matches, Deku returns aDekuError::Parseunless a#[deku(default = "...")]variant is specified.#[derive(Debug, PartialEq, DekuRead, DekuWrite)] #[deku(id_type = "u8")] enum DekuTest { #[deku(id = 0x01)] VariantA, #[deku(id = 0x02)] VariantB(u16), }Handle variable-length vectors with `count` and `update`
masterYou can use
Vec<T>in Deku by specifying the number of elements to read using the#[deku(count = "...")]attribute. If the length of theVecchanges during runtime (e.g., via.push()), the field used for the count will not automatically update. Use the#[deku(update = "...")]attribute and call.update()on the struct to synchronize the count field with the actual length of the vector before writing.#[derive(Debug, PartialEq, DekuRead, DekuWrite)] struct DekuTest { #[deku(update = "self.data.len()")] count: u8, #[deku(count = "count")] data: Vec<u8>, } // After modifying data: val.data.push(0xAA); val.update().unwrap();Debug decoders with the `logging` feature
masterTo troubleshoot parsing errors, enable the
loggingfeature in yourCargo.toml. You must also include thelogcrate and a compatible logger (likeenv_logger) in your project. Deku uses thetracelogging level; run your application withRUST_LOG=traceto see deserialization steps.# Cargo.toml deku = { version = "*", features = ["logging"] } log = "*" env_logger = "*"Use Deku for bit-level binary serialization
masterTo use Deku, derive
DekuReadandDekuWriteon your structs or enums. You can use the#[deku(...)]attribute to specify endianness and bit-level widths for individual fields.- Use
#[deku(endian = "...")]on the struct to set the byte order (e.g.,bigorlittle). - Use
#[deku(bits = N)]on a field to specify its size in bits. - Use
from_bytesto deserialize data. It returns a tuple containing the remaining unparsed bytes and the deserialized value. - Use
to_bytesto serialize the struct back into a byte vector.
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, } fn main() { let data: Vec<u8> = vec![0b0110_1001, 0xBE, 0xEF]; // from_bytes returns (remaining_bytes, deserialized_value) 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); }- Use
Initialize a new Reader
masterCreate a new
Readerby wrapping an existing type that implementsno_std_io::io::Readandno_std_io::io::Seek(such as aCursor).let mut reader = Reader::new(&mut cursor);Retrieve unused bits from a Reader
masterReturns the bits that were read into the internal buffer but not yet consumed by a parsing operation. This happens when a field uses the
bitsattribute and the total bits read do not align to a full byte boundary.Note: This requires the
allocfeature.