net-wire
net-wire is one no_std Rust crate for inspecting and constructing exact network-wire layouts. It exposes byte-oriented, borrowed views; leaves storage and I/O with the caller; and provides bounded protocol state where the wire format actually needs it.
It targets Rust 1.91 and edition 2024.
✨ What it promises
- No production allocation, dependencies, or
unsafe. Production code is allocation-free, has zero normal dependencies, and builds withunsafe_code = deny. - Opt in deliberately. The default feature set is empty.
- Borrowed wire views. Variable-length bytes stay borrowed from caller input; construction writes into caller-provided buffers.
- Wire fidelity. Unknown, private, reserved, GREASE, duplicate, ordered, and legal noncanonical values remain representable and preserved when the format permits them.
- Bounded, not ambient state. Protocol-local state such as compression-table accounting can be caller-owned without turning the crate into a connection runtime.
[!NOTE] A successful parse establishes the structural boundary promised by that parser. It does not silently apply an application's semantic policy.
🚫 What it is not
net-wire does not provide sockets or I/O, an async runtime, connection or application state machines, a TLS/cryptography stack, a universal codec/schema framework, or automatic semantic policy. It is deliberately a wire-layout crate, not a network stack wearing a fake moustache.
🚀 Quick start
Enable only the formats you use. For Ethernet, IPv4, and UDP dispatch:
[]
= { = "0.1.0", = false, = ["ethernet", "ipv4", "udp"] }
Parse a nested packet
Each layer first parses its own bounded bytes, then dispatches only when its discriminator agrees:
use ;
Feature-gated dispatch methods use Result<Option<T>, E> on purpose:
Ok(Some(value))— the selected protocol matches and its bounded bytes are structurally valid.Ok(None)— the discriminator does not match, or the enclosing packet is fragmented where that dispatch cannot safely claim a whole child.Err(error)— the selected child is malformed.
Build into caller storage
Write payload bytes first, then calculate the transport checksum, then build the IPv4 header (and therefore its header checksum):
use ;
let source = new;
let destination = new;
let payload = b"ping";
let mut bytes = ;
let packet = new
.source
.destination
.protocol
.ttl
.build
.unwrap;
assert!;
[!IMPORTANT] Structural validation answers whether bytes form a safely bounded layout. Semantic validation answers protocol-specific questions such as allowed values, ordering, or state transitions. Keep those decisions explicit.
Editing payloads or fields through mutable views can stale checksums and dependent lengths. Setters and raw mutable slices do not repair them automatically; recompute or rebuild in the right order.
🧭 Consumption is part of the contract
Not every protocol consumes input the same way. TlsRecord::parse returns the first complete record; record.as_bytes().len() tells you the prefix it represented. Some public APIs intentionally return (item, suffix), such as a KCP segment or QUIC frame. Other APIs validate a complete sequence, such as QPACK instruction sequences.
Use the contract of the specific parser rather than assuming every parse consumes all input. That distinction prevents a coalesced TLS record, a frame stream, or a sequence codec from being accidentally treated as one uniform blob.
🧩 Features
| Feature | Wire surface |
|---|---|
ethernet |
Ethernet II frames |
arp |
ARP packets |
ipv4 / ipv6 |
Internet packets and dispatch helpers |
icmpv4 / icmpv6 |
ICMP messages |
udp / tcp |
Transport datagrams and segments |
kcp |
KCP segments |
tls |
TLS records, handshakes, and extensions |
http1 |
HTTP/1 heads and chunked framing |
http2 |
HTTP/2 frames, sequencing, and HPACK |
quic |
QUIC primitives, packets, frames, and transport parameters |
qpack |
QPACK primitives and caller-owned bounded state |
http3 |
HTTP/3 frames and bounded protocol state; enables quic and qpack |
Features select public protocol modules. Optional cross-layer adapters are available only when both involved features are enabled; enabling a lower layer never drags in an upper one.
📚 Examples
packet-inspector— parses an Ethernet frame and follows IPv4/IPv6 payloads to UDP or TCP, including checksum reporting.dns-query— a Linux-only raw IPv4 DNS query path without Ethernet framing, with a small project-local DNS codec;socket2is an example-only dev dependency, not a production dependency ofnet-wire.tls-client-hello— walks a TLS ClientHello while preserving extension envelopes and handles RFC 7685 padding with a small local borrowed parser.
For contributor and design rules, see ARCHITECTURE.md.