automotive-wire-codec
The L0 foundation of a layered, no_std, no-alloc automotive diagnostic protocol
suite (DoIP, UDS, later SOME/IP). It provides the shared zero-copy codec traits and
big-endian byte-level leaf helpers that every protocol core (L1) implements — and
nothing else: no framing, no concrete message types, no owned forms, no alloc.
Features
- Zero-copy decode — [
Decode] borrows directly from the input buffer; no allocation, no intermediate copies. no_std/ no-alloc — builds on bare-metal targets (verified in CI againstthumbv6m-none-eabi).- Nested encode without a staging buffer — [
Encode::encoded_size] is exact, so an outer protocol can size a header and serialize an inner value directly into the same buffer. - Generic, ergonomic errors — L1 crates keep their own rich error enum; leaf helpers
and trait defaults construct errors generically via small
Frombounds, so calls compose through?with no turbofish.
Error model
L0 defines no protocol error type. It defines two tiny error fragments —
[Incomplete] (a read ran out of bytes) and [TrailingBytes] (bytes remained after
an exact decode) — and the traits require the L1 error to be constructible From
them. This preserves each L1 crate's rich, typed error enum while letting shared
trait defaults and leaf helpers construct errors generically. Encode-side I/O
failures surface as [embedded_io::ErrorKind]; the [Encode] error bound is
From<embedded_io::ErrorKind>. Because the L1 error implements these From bounds,
helper calls (read_u8(buf)?, write_u16_be(w, x)?) compose through ? with no
turbofish and no generic error parameter at the call site.
The decode / decode_exact contract
[Decode::decode] consumes from the front of the buffer and returns the
remainder, so nested and sequential decodes thread the remainder along:
use ;
;
run.unwrap;
[Decode::decode_exact] instead requires the whole buffer to be consumed, returning
[TrailingBytes] otherwise — use it at a message boundary where framing has already
delimited the frame. L0 has no opinion on framing; that is an L1 concern.
Nested encode with no staging buffer
Because [Encode::encoded_size] is separate from [Encode::encode] and
&mut [u8] is an [embedded_io::Write] sink, an outer protocol serializes an inner
value directly into one buffer — no second allocation or copy:
use ;
;
let inner = Inner;
let mut tx_buf = ;
let payload_len = inner.encoded_size;
let header = new;
let mut writer: &mut = &mut tx_buf; // one buffer
let mut total = header.encode?; // writes header, advances `writer`
total += inner.encode?; // writes inner into the remainder
assert_eq!;
Ok::
Usage
Add the dependency:
Implement [Encode] and [Decode] for a message type using the big-endian leaf
helpers:
use ;
See the crate docs for the full API,
including the [DecodeIter] trait for repeated elements and the variable-width
[read_be_uint] helper.
no_std
This crate is no_std and does not require alloc. unsafe_code is forbidden
(#![forbid(unsafe_code)] at the workspace lint level). CI builds against a bare-metal
Cortex-M0 target (thumbv6m-none-eabi) to catch any std/alloc leaking in through a
dependency.
Minimum Supported Rust Version (MSRV)
The MSRV is tracked in Cargo.toml's rust-version field (currently 1.85) and enforced
in CI.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.