tinyklv 0.1.1

The simplest Key-Length-Value (KLV) framework in Rust
Documentation

tinyklv - KLV framework in Rust

Crates.io Documentation License: MIT MSRV CI

A derive-macro framework for encoding and decoding Key-Length-Value (KLV) binary streams, built on winnow parser combinators.

What is KLV?

KLV is a generic Tag-Length-Value (TLV) framing pattern: every field in a byte stream is prefixed by a key identifying it and a length giving its size. It is the backbone of telemetry packets, video metadata streams, IoT sensor framing, and most custom binary protocols that need to evolve without breaking older parsers.

tinyklv is protocol-agnostic. It ships no baked-in standards - you declare your keys, your length encoding, your sentinel, and the codec for each field via attributes on a struct. The derive macro generates the encoder/decoder trait implementations; you control the schema.

Quick Start

cargo add tinyklv
use tinyklv::Klv;
use tinyklv::prelude::*;
use tinyklv::dec::binary as decb;
use tinyklv::enc::binary as encb;

#[derive(Klv, Debug, PartialEq)]
#[klv(
    stream = &[u8],
    sentinel = b"\x47\x48",
    key(dec = decb::u8, enc = encb::u8),
    len(dec = decb::u8_as_usize,
        enc = encb::u8_from_usize),
)]
struct Heartbeat {
    #[klv(
        key = 0x01,
        dec = decb::u8,
        enc = *encb::u8,
    )]
    sequence: u8,

    #[klv(
        key = 0x02,
        dec = decb::be_u16,
        enc = *encb::be_u16,
    )]
    temperature_centideg: u16,
}

fn main() {
    let original = Heartbeat {
        sequence: 42,
        temperature_centideg: 2350,
    };
    let frame = original.encode_frame();
    let decoded = Heartbeat::decode_frame(
        &mut frame.as_slice()
    ).unwrap();
    assert_eq!(decoded, original);
}

Full annotated version: examples/01_hello_world.rs.

Feature Highlights

  • #[derive(Klv)] generates encode and decode in one pass
  • Built-in codecs: binary (native/BE/LE for u8..u128, i8..i128, f32/f64), BER length, BER-OID keys, UTF-8 / UTF-16 / ASCII strings
  • Sentinel seeking - resync on noisy byte streams
  • Streaming partial packets - ::decoder(), iter(), next(), and DecodePartial
  • Repeated decode with user-defined break conditions
  • Nested Klv structs - compose packets from sub-packets
  • Generic structs and lifetimes supported
  • Option<T> fields, per-field and per-container defaults, trait_fallback, deny_unknown_keys
  • Stream type is user-selected - any winnow::Stream works

Traits

Trait Purpose Derived?
DecodeValue<S> Decode value body from an unframed slice yes
DecodeFrame<S> Seek sentinel, read length, subslice, then decode yes
EncodeValue<O> Encode the value body (KLV triples, no frame header) yes
EncodeFrame<O> Encode sentinel + length + value body yes
DrainFrames<S> Decode a sentinel-framed stream into Vec<T> yes
DecodePartial<S> Streaming-aware decode returning Packet<T, P> yes
Decoder<P, S> Owned-buffer streaming decoder with feed, iter, and next yes
BreakCondition<S> Per-(key, len) stop predicate for decode loops no

Documentation

Contributing

Issues and pull requests welcome at https://github.com/arpadav/tinyklv. Run cargo test --all and cargo clippy --all-targets -- -D warnings before opening a PR.

License

Licensed under the MIT License. See LICENSE for details.

Support

If tinyklv is useful to you: