← First Pair Library

20 Valid names instead of hopeful strings

Name is the first load-bearing abstraction. It stores a sequence of byte labels rather than a UTF-8 domain string. Its constructor is private to the module; all construction passes through parsing or from_labels, and both reach the same validation rule:

fn validate(labels: &[Vec<u8>]) -> Result<()> {
    if labels.iter().any(|l| l.is_empty() || l.len() > 63) {
        return Err(Error::InvalidName(
            "label must contain 1..=63 octets".into(),
        ));
    }
    let len = 1 + labels.iter().map(|l| l.len() + 1).sum::<usize>();
    if len > 255 {
        return Err(Error::InvalidName("wire name exceeds 255 octets".into()));
    }
    Ok(())
}

That small private function changes the rest of the codebase. Zone can use Name as a BTreeMap key without rechecking label lengths. The packet writer can calculate wire_len without wondering whether it will overflow the DNS name limit. parent, suffix, wildcard, and is_subdomain_of operate on labels rather than fragile dotted-string suffixes.

DNS identity is case-insensitive but responses should preserve the query’s case. Name therefore retains original bytes while implementing Eq, Hash, and Ord with ASCII-folded comparisons. In C this invariant must be remembered by every hash table and comparison call site. Here it belongs to the key type.

This is a zero-surprise form of abstraction. It adds allocations when a name is built, but it removes repeated parsing and validation later. The benchmarked hot paths operate on already validated values, while the network boundary absorbs the cost once.