wire-repr is for binary formats whose bytes are the source of truth: network
packets, file headers, storage pages, firmware records, and IPC messages. A compact
layout declaration becomes direct, specialized Rust for reading existing bytes,
editing fixed-width fields, and writing new representations.
The layout owns physical facts β widths, offsets, framing, and byte ranges. Consumer code owns protocol meaning: magic values, reserved bits, checksums, and cross-field policy.
[!IMPORTANT] Generated views borrow ordinary byte slices. They do not reinterpret bytes as Rust structs, depend on alignment or ABI layout, allocate, use
unsafe, or carry a runtime descriptor.
πΊοΈ Capability map
- Three layout families: fixed sequential, dynamic sequential, and fixed absolute layouts; physical ordering can differ from declaration order.
- Framing and geometry: exact or prefix parsing,
bytes(source),bytes_to(source),remaining_bytes, and retained validated endpoints. - Typed bytes: built-in codecs, direct
FixedCodecpaths, nominal scalar codecs, totalasmappings, and unsigned LSB0 projections. - Variable encodings:
variable(PrefixCodec)preserves accepted raw prefix bytes while exposing a decoded value. - Safe change paths: immutable views, framing-safe mutable views, and builders with all-or-nothing output commits.
- Computed writers: derived fields, borrowed builder context, and infallible post-write finalizers over exact represented spans and values.
- Extension points:
FixedCodec,PrefixCodec, andEncodePlan, with malformed layout declarations rejected at compile time.
Complete executable format fixtures: PNG chunks, SQLite headers, and Wasm ULEB128.
π¦ Installation
[]
= { = "0.5", = false }
Rust 1.91 is the minimum supported version. The crate has no default features and no target-runtime dependencies.
π Start with a real format
A Bitcoin block starts with an 80-byte block header. The wire representation mixes little-endian integers with two opaque 32-byte hashes:
use wire_repr;
wire_repr!
The layout name is also the immutable borrowed type. view creates a lightweight
request; the terminal operation performs structural validation exactly once.
const GENESIS_HEADER: = ;
let header = view
.without_trailing
.expect;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Use with_remainder when the input continues after one representation:
let mut framed = ;
framed.copy_from_slice;
framed.copy_from_slice;
let = view
.with_remainder
.expect;
assert_eq!;
assert_eq!;
without_trailing validates the same representation but rejects any suffix.
as_bytes always returns exactly the represented bytes, never the remainder.
π Layouts, ordering, and physical holes
A fixed sequential layout reads entries in physical order. padding(N) and align(N)
are represented opaque spans, not fields with invented values:
wire_repr!
One-based placements let declaration/API order differ from wire order; parsing and writing still follow physical order:
wire_repr!
An absolute layout instead uses zero-based byte offsets. It is fixed width; its gaps are represented and builders preserve them. The SQLite 100-byte header fixture is the complete real-format example, including offsets, a preserved 20-byte reserved span, mutable field writes, exact framing, and consumer-owned SQLite validation.
wire_repr!
Absolute layouts do not accept padding, alignment, self-delimiting fields, or dynamic ranges. Sequential layouts come in fixed and dynamic forms.
π Dynamic geometry and framing
The PNG chunk is the ordinary relative case: its data_length physically precedes
data, and structural parsing validates and retains the resulting endpoint.
wire_repr!
png.rs parses IHDR/IEND, retains exact
ranges, and derives data_length from the builder's data input. Bounded range
mutation is exercised directly in
dynamic_sequential_writes.rs. PNG CRC
checking remains consumer validation, not structural parsing.
The other range forms are equally literal:
wire_repr!
bytes(source) takes that many bytes from the current position; bytes_to(source)
uses an exclusive endpoint from representation byte zero; remaining_bytes has no
external framing magic and cannot discover a packet boundary for you. Eligible sources
are physically preceding fixed integers (including a total-mapped integer). Dynamic
ranges may be empty. The view retains validated endpoints so getters do not re-scan or
reframe the bytes.
π§© Typed fields without losing raw bytes
Use a direct FixedCodec path when a field already has one, and a top-level scalar
when a protocol needs a nominal fixed-width type. Total as mappings expose semantic
and raw getters. Unsigned projections use decoded-integer LSB0 numbering regardless of
wire byte order:
wire_repr!
That declaration yields the declared semantic getters plus services_raw(); it does
not invent domain validation. See executable
scalar/direct-codec coverage,
mapping coverage, and
projection coverage.
π Self-delimiting prefixes are fields, not range sources
variable(path) uses a PrefixCodec to discover one structural field. A Wasm u32
ULEB128 is a natural example:
wire_repr!
The generated index() decodes the value and index_raw() returns the exact accepted
wire bytes. Legal noncanonical input remains preserved by _raw() β for example, ULEB128
[0x85, 0x00] still means 5 β while a builder plan may write the codec's canonical
encoding. The complete codec is in the Wasm fixture;
prefix layout tests exercise raw spans,
multiple prefixes, errors, and borrowed decoded values.
Bitcoin CompactSize is another honest prefix-codec use for one count/value. It is not a dynamic-range source, and a repeated transaction sequence remains consumer-owned: keep the cursor, bound each item, and parse it separately. Tagged unions, arbitrary conditional/version-selected fields, repeated sequences, and nested schemas are not layout features in 0.5.
Versioned formats use the same ownership boundary: parse a stable prefix containing the
version, then let consumer code select a separate nominal V1Body or V2Body layout for
the bounded remainder. The macro does not hide that dispatch inside a generated union or
silently merge version-specific policy into structural parsing.
βοΈ Mutable views and builders
Mutable views borrow exclusively and can change only same-width fixed fields. They do
not offer setters for range sources, dynamic ranges, remaining_bytes, or
self-delimiting prefixes β changing those could reframe later bytes. A dynamic range has
a bounded mutable-slice accessor over its validated span.
let mut chunk_bytes = ;
let mut chunk = parse_exact_mut
.expect;
chunk.data_mut.copy_from_slice;
assert_eq!;
Builders use caller-owned output. They derive range sources implicitly, plan custom codecs, perform fallible derivations and all geometry/capacity checks, then commit. Every builder error leaves the entire supplied output slice unchanged; successful writes preserve suffixes, padding, alignment spans, absolute gaps, and existing ranges unless an explicit field covers them.
For a dynamic range that is already populated in the destination, the generated
body_existing(length) form retains that span instead of copying new bytes. Its length
still participates in geometry, derivation, and finalizer spans, while the builder never
rewrites the retained range.
The following compact fixture syntax shows explicit fallible derivation, borrowed builder-only context, and an infallible finalization policy:
wire_repr!
A finalizer runs after the ordinary commit inputs and is an infallible, consumer-supplied
policy. This fixture reads the zeroed target span; a real Bitcoin checksum would pass the
payload span and use consumer-supplied crypto. The structural parser does not validate
it. Finalizers can consume byte spans, semantic values, and borrowed context, and their
dependencies are resolved before calls. Complete derivation,
existing-range, finalizer, ordering, and atomicity cases are in
dynamic_sequential_builders.rs.
A fixed Bitcoin header builder stays pleasantly boring:
let zero_hash = ;
let merkle_root = ;
let mut output = ;
let = new
.version
.previous_block_hash
.merkle_root
.timestamp
.target_bits
.nonce
.build_into
.expect;
assert_eq!;
assert!;
π§ Custom codecs and diagnostics
Implement FixedCodec for a compile-time-width field, or PrefixCodec for a
self-delimiting field. Both produce an EncodePlan: planning may fail, while
write_into is the infallible commit step. The compiler also rejects invalid physical
placements, unsupported layout/form combinations, unsuitable dynamic sources, invalid
projection declarations, and incompatible derive/finalize contracts at compile time.
Read the codec contracts and the complete
wire_repr! reference for grammar and diagnostic
surface. The linked executable fixtures above are the runnable behavior, not decorative
pseudocode.
𧬠Generated API and cost model
For pub layout Packet, wire_repr! generates Packet<'wire> (immutable view),
PacketViewMut<'wire> (restricted mutable view), PacketBuilder<'value>, and
structural parse/mutation/write error types. The immutable owner is the layout stem;
fixed layouts additionally expose Packet::WIDTH.
Generated operations are direct safe Rust: bounded slice access, endian conversion, shifts, masks, and copies. There are no schema walks, erased codecs, allocation, or dynamic dispatch. Release probes in wire-repr/tests/codegen.rs compare these paths with handwritten safe Rust.
The following x86-64 snippets were captured with Rust 1.91.0 (f8297e351), LLVM
21.1.2, --release, targeting x86_64-unknown-linux-gnu. Compiler-local labels were
shortened; instructions were not changed.
cmpq $2, %rsi
jne .invalid
movzwl (%rdi), %edx
rolw $8, %dx
movw $1, %ax
retq
.invalid:
xorl %eax, %eax
retq
movb $2, %al
cmpq $2, %rsi
jne .done
movzbl 1(%rdi), %eax
andb $1, %al
.done:
retq
cmpq $2, %rsi
jne .done
rolw $8, %dx
movw %dx, (%rdi)
.done:
cmpq $2, %rsi
sete %al
retq
cmpq $2, %rsi
jb .done
rolw $8, %dx
movw %dx, (%rdi)
.done:
cmpq $2, %rsi
setae %al
retq
xorl %eax, %eax
testq %rsi, %rsi
je .done
cmpq $1, %rsi
je .done
decq %rsi
movzbl (%rdi), %ecx
cmpq %rcx, %rsi
jne .done
movzbl 1(%rdi), %edx
movb $1, %al
.done:
retq
Assembly snapshots are compiler-, target-, and probe-specific. The normative gate is
ci/check-codegen.py, which compares generated operations with
handwritten safe Rust and rejects unwanted calls, panic paths, allocation, dynamic
dispatch, and excess instruction shape.
β Contract and limits
Guaranteed: safe Rust, no_std, no allocation, borrowed exact represented bytes,
explicit exact/prefix framing, retained validated dynamic boundaries, framing-safe
mutation, and builder preflight before commit.
Intentionally outside the crate: domain and protocol validation; repeated sequences; tagged unions and arbitrary conditional fields; nested runtime schemas or reflection; I/O, buffering, transport state, and allocation policy; cryptography and checksum policy.
The normative ownership and safety rules are in ARCHITECTURE.md. The published API reference is on docs.rs.
π¦ Workspace
wire-repris the public runtime facade and macro re-export.wire-repr-macrosis the host-side schema compiler.
Both packages are version 0.5.0, use edition 2024, and support Rust 1.91. The target
runtime has empty default features, no dependencies, and unsafe_code = "deny".
π License
MIT Β© 2026 SilentBless. See LICENSE.