tinyklv 0.1.0

The simplest Key-Length-Value (KLV) framework in Rust
Documentation
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(clippy::unwrap_used)]
//! Example 08 - KLV inside KLV.
//!
//! Because `#[derive(Klv)]` generates `impl EncodeValue` and
//! `impl DecodeValue` on every struct it's applied to, those methods can be
//! used directly as the `dec` / `enc` functions for a field on another
//! struct. The inner struct stays usable in isolation (no sentinel needed)
//! and the outer struct treats it as an opaque variable-length value.
//!
//! Note: methods generated by the derive already take `&self` on the encode
//! side, so the outer field uses `enc = Inner::encode_value` without the
//! `&` sigil - that sigil is only needed when borrowing primitive encoders.
//!
//! Showcases:
//! * Nesting a derived struct as a field on another derived struct
//! * Using `Inner::decode_value` / `Inner::encode_value` directly
//! * Round-tripping both the outer frame and the inner stand-alone form
use tinyklv::prelude::*;            // Klv proc-macro + traits
use tinyklv::dec::binary as decb;   // binary decoders
use tinyklv::enc::binary as encb;   // binary encoders

#[derive(Klv, Debug, PartialEq, Clone, Copy)]
#[klv(
    stream = &[u8],
    key(dec = decb::u8,          enc = encb::u8),
    len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
)]
/// Engine-health sub-packet. No sentinel - it only appears inside a
/// `VehicleStatus` frame, or is decoded in isolation via `decode_value`
struct EngineHealth {
    #[klv(
        key = 0x01,
        dec = decb::be_u16,
        enc = *encb::be_u16,
    )]
    /// Engine RPM
    rpm: u16,

    #[klv(
        key = 0x02,
        dec = decb::be_u16,
        enc = *encb::be_u16,
    )]
    /// Coolant temperature in 0.1 C units
    coolant_temp_decideg: u16,

    #[klv(
        key = 0x03,
        dec = decb::u8,
        enc = *encb::u8,
    )]
    /// Oil pressure in kPa
    oil_pressure_kpa: u8,
}

#[derive(Klv, Debug, PartialEq)]
#[klv(
    stream = &[u8],
    sentinel = b"VEHICLE",
    key(dec = decb::u8,          enc = encb::u8),
    len(dec = decb::u8_as_usize, enc = encb::u8_from_usize),
)]
/// Top-level vehicle status frame embedding an `EngineHealth` sub-packet
struct VehicleStatus {
    #[klv(
        key = 0x01,
        dec = decb::be_u32,
        enc = *encb::be_u32,
    )]
    /// Unique vehicle identifier
    vehicle_id: u32,

    #[klv(
        key = 0x02,
        dec = decb::be_f32,
        enc = *encb::be_f32,
    )]
    /// Ground speed in km/h
    speed_kmh: f32,

    #[klv(
        key = 0x03,
        dec = EngineHealth::decode_value,
        enc = EngineHealth::encode_value,
    )]
    /// Nested engine sub-packet; `dec`/`enc` reference the methods generated
    /// by `#[derive(Klv)]` on `EngineHealth`
    engine: EngineHealth,
}

fn main() {
    // build - a status frame carrying a full engine-health sub-packet
    let original = VehicleStatus {
        vehicle_id: 0xDEAD_C0DE,
        speed_kmh:  112.5,
        engine: EngineHealth {
            rpm:                  3_200,
            coolant_temp_decideg:   900, // 90.0 C
            oil_pressure_kpa:       185,
        },
    };

    // encode - the engine occupies the value region of key 0x03, containing
    // its own nested KLV triples
    let frame = original.encode_frame();

    // decode - outer and inner are reconstructed in one call
    let decoded = VehicleStatus::decode_frame(
        &mut frame.as_slice(),
    ).unwrap();

    // assert - the full nested structure round-trips
    assert_eq!(decoded.vehicle_id, original.vehicle_id);
    assert!((decoded.speed_kmh - original.speed_kmh).abs() < 1e-4);
    assert_eq!(decoded.engine, original.engine);

    // the inner type can be used on its own without the outer frame
    let inner_bytes = original.engine.encode_value();
    let inner_decoded = EngineHealth::decode_value(
        &mut inner_bytes.as_slice(),
    ).unwrap();
    assert_eq!(inner_decoded, original.engine);
}