← First Pair Library

21 A bounded wire codec

DNS packets are attacker-controlled binary graphs: compression pointers can jump backward, names can share suffixes, section counts can lie, and RDATA lengths can disagree with actual bytes. packet.rs contains the codec and deliberately keeps the reader state small:

struct Reader<'a> {
    b: &'a [u8],
    p: usize,
    name_offsets: Vec<bool>,
}

The lifetime on b prevents the reader from outliving its input. Every scalar read uses slice bounds checks. Name decoding separately limits pointer hops, requires pointers to move backward, and records valid prior name boundaries. The last rule is stricter than merely checking that a pointer lands inside the packet: an interior byte can accidentally look like a valid label.

Decoded records become an RData variant. Unknown types are not discarded; they become opaque bytes paired with their numeric RecordType. This is the extension-safe behavior required by modern DNS. It also means an encoder can round-trip data it does not understand.

The writer uses compression, but optimization remains subordinate to a valid message. A last-owner cache handles repeated owners cheaply while suffix sharing reduces wire size across related names. The July 2026 benchmark shows the trade: a 64-record answer fell from 2,147 to 1,059 bytes, while compression made encoding slower than the uncompressed baseline. On DNS, fewer datagrams and less amplification surface can be worth several microseconds of local CPU.

Truncation uses a bounded search for the largest response that fits instead of repeatedly rebuilding one record at a time. The result preserves complete RRsets and required EDNS state. Performance work is therefore expressed as an algorithmic improvement behind the same Message::encode contract.