net-wire 0.1.0

Allocation-free no_std wire views and codecs for network protocols.
Documentation
  • Coverage
  • 100%
    1761 out of 1761 items documented0 out of 0 items with examples
  • Size
  • Source code size: 2.0 MB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 26.6 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 6s Average build duration of successful builds.
  • all releases: 6s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • SilentBless/net-wire-rs
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • SilentBless

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 with unsafe_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:

[dependencies]
net-wire = { version = "0.1.0", default-features = false, features = ["ethernet", "ipv4", "udp"] }

Parse a nested packet

Each layer first parses its own bounded bytes, then dispatches only when its discriminator agrees:

use net_wire::{
    ethernet::{EtherType, EthernetFrame},
    ipv4::Ipv4Packet,
};

fn inspect(bytes: &[u8]) -> Result<(), net_wire::ParseError> {
    let frame = EthernetFrame::parse(bytes)?;
    if frame.ether_type() != EtherType::IPV4 {
        return Ok(());
    }

    let packet = Ipv4Packet::parse(frame.payload())?;
    match packet.udp()? {
        Some(datagram) => {
            println!("UDP {} -> {}", datagram.source_port(), datagram.destination_port());
            println!("payload: {:02x?}", datagram.payload());
        }
        None => println!("not a whole UDP datagram for this IPv4 packet"),
    }
    Ok(())
}

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 net_wire::{
    ipv4::{Ipv4Address, Ipv4PacketBuilder, Ipv4Protocol},
    udp::UdpDatagramBuilder,
};

let source = Ipv4Address::new([192, 0, 2, 10]);
let destination = Ipv4Address::new([198, 51, 100, 20]);
let payload = b"ping";
let mut bytes = [0_u8; 20 + 8 + 4];

{
    let mut udp = UdpDatagramBuilder::new(&mut bytes[20..], payload.len())
        .source_port(49_152)
        .destination_port(53)
        .build()
        .unwrap();
    udp.payload_mut().copy_from_slice(payload);
    udp.update_checksum_ipv4(source, destination);
}

let packet = Ipv4PacketBuilder::new(&mut bytes, 8 + payload.len())
    .source(source)
    .destination(destination)
    .protocol(Ipv4Protocol::UDP)
    .ttl(64)
    .build()
    .unwrap();
assert!(packet.checksum_is_valid());

[!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; socket2 is an example-only dev dependency, not a production dependency of net-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.