Skip to content

Repository files navigation

wirecodec — Declarative, zero-copy binary codecs for Rust

wirecodec is a powerful, attribute-driven binary codec framework for Rust. It enables you to define binary-encoded protocol structures declaratively using:

  • Bit-fields
  • Byte-fields
  • Padding
  • Length- and count-prefixed fields
  • Optional fields with conditions
  • Nested structures
  • CRC / fingerprint fields
  • Verification constraints
  • Endian-aware integers
  • Zero-copy decoding with lifetimes

The companion crate wirecodec_derive generates highly optimized encode/decode implementations.

wirecodec is designed for real-world networking protocols such as:

  • STUN / ICE / TURN
  • RTP / RTCP
  • TLV-style formats
  • NTP
  • Custom packet formats

Disclaimer

Much of this code has been generated using LLM-assistance. The purpose of this project is mostly to experiment to figure out how well LLM can generate realistically useful code. I have to say I am somewhat stunned about the capabilities it is able to do. And it has taught me quite a lot about prompting and "managing" the work that the LLM does.

Features

Declarative binary layout

Define protocol structures with attribute macros:

#[derive(Codec)]
struct Header {
    #[codec(bits = 2, verify = 0b10)]
    version: u8,

    #[codec(bits = 1)]
    padding: u8,

    #[codec(bits = 1)]
    extension: u8,
}

The derive macro generates bit-level decoding/encoding automatically.

Zero-copy decoding (no_std compatible)

All decoding uses &[u8] slices and borrows data directly where possible.

fn decode<'a>(buf: &mut DecodeBuffer<'a>) -> Result<MyStruct<'a>, DecodeError>;

This allows parsing directly from network buffers without allocation.

Conditional and dependent fields

Support for:

  • length_of — encode/decode length prefix automatically
  • count_of — encode/decode list length
  • when = "expression" — conditional fields
  • skip — pad bits or bytes
  • verify = VALUE — enforce field constraints
  • bytes = N or bits = N — precise control of binary layout
  • endian = "big" | "little" — integer endianness
  • pad_to = N — byte alignment padding

Nested structures

Nested headers, messages, TLV blocks, and protocol stacks work naturally:

#[derive(Codec)]
struct StunMessage<'a> {
    header: StunHeader,               // nested
    #[codec(count_of = "attributes")]
    attribute_count: u16,
    attributes: Vec<StunAttribute<'a>>,
}

CRC32 / message fingerprint support

Useful for STUN MESSAGE-INTEGRITY and FINGERPRINT:

#[codec(crc32 = true, crc32_xor = 0x5354554e)]
fingerprint: u32,

Strong compile-time validation

The derive macro validates:

  • Missing referenced fields (length_of, count_of)
  • Invalid attribute combinations
  • Bit-width overflows
  • Forbidden types for bytes = N
  • Incorrect when conditions
  • And more…

Errors point directly to the offending field.

Example: simple framed packet

use wirecodec::fmt_utils::{ByteFormatExt, ByteFormatKind};
use wirecodec::{Codec, CodecTrait, DecodeBuffer, Encode, EncodeBuffer};

#[derive(Codec, Debug, PartialEq)]
struct Packet<'a> {
    #[codec(bits = 8)]
    kind: u8,

    #[codec(bits = 8, length_of = "payload")]
    length: u8,

    #[codec(bytes = 0, pad_to = 4)]
    payload: &'a [u8],
}

fn main() {
    let pkt = Packet {
        kind: 1,
        length: 3, // This will be automatically set, but for equality check has the correct value
        payload: &[10, 20, 30],
    };

    // Encode
    let mut enc = EncodeBuffer::new();
    pkt.encode(&mut enc).unwrap();
    let bytes = enc.as_bytes();

    // Encoded to: 01 03 0A 14 1E 00
    println!("{}", bytes.fmt_bytes(ByteFormatKind::Hex, 10));

    // Decode
    let mut dec = DecodeBuffer::new(bytes);
    let decoded = Packet::decode(&mut dec).unwrap();

    assert_eq!(decoded, pkt);
}

real-world example: STUN header (RFC 5389)

#[derive(Codec)]
struct StunHeader {
    #[codec(bits = 2, verify = 0)]         // zero bits
    zero: u8,

    #[codec(bits = 2)]                     // STUN class
    class: u8,

    #[codec(bits = 12)]                    // STUN method
    method: u16,

    #[codec(bits = 16)]
    length: u16,

    #[codec(bits = 32, verify = 0x2112A442)]
    magic_cookie: u32,

    #[codec(bytes = 12)]
    transaction_id: [u8; 12],
}

This decodes directly from a UDP receive buffer without allocation.

Installation

[dependencies]
wirecodec = "0.1"

or for no_std decode-only:

[dependencies]
wirecodec = { version = "0.1", default-features = false }

or for no_std + alloc (decode + encode):

[dependencies]
wirecodec = { version = "0.1", default-features = false, features = ["alloc"] }

Feature flags

Feature Default Description
std yes Enables std::error::Error, formatting, etc. Implies alloc.
alloc no Enables Vec and encoding. Required for Encode and EncodeBuffer.

Modes

Mode Decode Encode Description
std [X] [X] Full desktop/server use
no_std + alloc [X] [X] Embedded RTOS, WASM, etc
no_std (decode only) [X] [ ] Tiny embedded / kernel environments

Attribute reference

bits = N

Read/write N bits into an integer field.

bytes = N

Read/write an exact byte sequence into a &[u8] or [u8; N].

verify = VALUE

Field must equal VALUE during decoding.

skip

Skip bits/bytes during encoding and decoding.

length_of = "field"

Automatically populate the length prefix based on another field’s length.

count_of = "field"

Number of items in a vector.

when = "expr"

Conditionally decode or encode an optional field.

pad_to = N

Pad output until reaching byte boundary N.

endian = "big" | "little"

Specify integer byte order.

crc32 = true / crc32_xor

Compute CRC-32 over the preceding fields.

Testing and examples

The repository contains:

  • Unit tests for the derive crate
  • Integration tests
  • Fuzz tests (wirecodec_fuzz)
  • Benchmarks (benches/)
  • Example protocols:
    • STUN
    • RTP
    • NTP
    • TLV
    • CRC-framed packets

To run all tests:

cargo test

Performance

  • Bit-level operations with minimal branching
  • Zero-copy decode path over borrowed slices
  • Encode buffer grows efficiently
  • Derived code is inlined and optimized by Rust

Benchmarks included in benches/.

Roadmap (future releases)

  • Optional "decode-only" derive mode (skip Encode impl)
  • Support for slice-backed fixed-size encoding (no_std no-alloc encode)
  • More checksum algorithms
  • DSL for multi-variant tagged enums
  • Procedural derive for packed enums with overlapping bitfields

Contributing

Contributions, bug reports, and feature requests are welcome!

If you write an example protocol using wirecodec, feel free to open a PR.

License

Licensed under the same terms as Rust itself:

  • MIT OR Apache-2.0

About

A Rust library for declarative, zero-copy decoding/encoding between structs and wire-representation of binary data.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages