slither 0.2.0

Encrypted peer-to-peer UDP transport: reliable messages, streams and datagrams, authenticated by raw public keys - no certificates, no TLS. WireGuard-shaped handshake, QUIC-shaped frames.
Documentation
//! Exhaustiveness fence over SPEC.md §18.1's error taxonomy.
//!
//! §18.1 is explicit: "Closed and normative: every variant this
//! specification names appears here exactly once." For each of the ten
//! error types this file defines a function that takes a value of that
//! type and `match`es it with **no wildcard arm**. That absence is the
//! whole point: a `_` arm would silently keep compiling the day a variant
//! is added or removed, defeating the fence. If a variant list here ever
//! stops matching the crate's, this file stops *compiling* — do not "fix"
//! that by adding a `_` arm; it means either SPEC.md or the crate moved
//! and the taxonomy needs a ruling, per CLAUDE.md's frozen-spec rule.
//!
//! Each fence function is referenced (as a bare function pointer) from a
//! `#[test]`, rather than called on a constructed instance — several
//! variants carry fields (e.g. `ConnectionLost::PeerClosed`'s `reason`)
//! whose exact Rust type §18.1 does not spell out, and guessing one to
//! build a dummy value would risk a false-negative compile failure
//! unrelated to the taxonomy itself. Taking the function pointer is enough
//! to force the match to be type-checked (and to keep it from being
//! flagged dead code under `-D warnings`) without that risk. Where §18.1
//! *does* give an explicit field type (`WriteError::Reset(u64)`,
//! `ReadError::Reset(u64)`), the match arm below annotates the binding
//! with that type, so a field-type drift also fails to compile.
//!
//! `ConfigError` is the one exception to "from §18.1": per ruling 44
//! (grep `ConfigError` in SPEC.md), it is a **configuration** error for
//! `set_persistent_keepalive` and is deliberately kept outside §18.1's
//! protocol-error taxonomy — no peer, packet, or connection state is
//! involved. It is checked here against §16.2 instead.

use slither::error::*;

fn requires_clone<T: Clone>() {}

#[test]
fn connection_lost_is_clone() {
    // SPEC.md §16.2, ruling 46 ("A connection's death ... [is] awaitable."):
    // `closed()` is a *latched* signal, awaitable concurrently from any
    // number of tasks, and resolves with the `ConnectionLost` that ended
    // the connection every time it's polled after death — which the spec
    // says "asks only that `ConnectionLost` be `Clone`, a derive, not a
    // variant change; §18.1 stays closed."
    requires_clone::<ConnectionLost>();
}

#[test]
fn connect_error_is_exhaustively_matched() {
    let _: fn(ConnectError) = match_connect_error;
}

#[test]
fn intro_error_is_exhaustively_matched() {
    let _: fn(IntroError) = match_intro_error;
}

#[test]
fn auth_error_is_exhaustively_matched() {
    let _: fn(AuthError) = match_auth_error;
}

#[test]
fn accept_error_is_exhaustively_matched() {
    let _: fn(AcceptError) = match_accept_error;
}

#[test]
fn connection_lost_is_exhaustively_matched() {
    let _: fn(ConnectionLost) = match_connection_lost;
}

/// NOT an exhaustiveness fence, despite the shape of its neighbours.
/// `WriteError` is `#[non_exhaustive]`, so out of crate this cannot be
/// one — see the comment on `match_write_error`'s wildcard arm. The real
/// fence is `src/error.rs::tests::write_error_is_exhaustive_in_crate`.
/// Kept only to check the three known variants' payload shapes.
#[test]
fn write_error_known_variants_have_the_documented_shapes() {
    match_write_error(WriteError::Reset(0));
    match_write_error(WriteError::ConnectionLost(ConnectionLost::TimedOut));
    match_write_error(WriteError::Finished);
}

#[test]
fn read_error_is_exhaustively_matched() {
    let _: fn(ReadError) = match_read_error;
}

#[test]
fn message_error_is_exhaustively_matched() {
    let _: fn(MessageError) = match_message_error;
}

#[test]
fn datagram_error_is_exhaustively_matched() {
    let _: fn(DatagramError) = match_datagram_error;
}

#[test]
fn config_error_is_exhaustively_matched() {
    let _: fn(ConfigError) = match_config_error;
}

// =======================================================================
// The fence functions themselves.
// =======================================================================

/// SPEC.md §18.1: `ConnectError::{AlreadyConnected, TimedOut, Local}` —
/// exactly three variants, all unit. `AlreadyConnected` is returned by
/// `connect()` itself and also as the tie-break-loser resolution of an
/// in-flight `Connecting` (§6.4's PENDING branch, §6.7); `TimedOut` is
/// initial-connect give-up at `HANDSHAKE_GIVEUP` (§5.5); `Local`
/// **[RATIFIED 2026/08/15 — ruling 72]** is *our own* `Identity::open()`
/// failing — a locked enclave, a hardware fault.
fn match_connect_error(e: ConnectError) {
    match e {
        ConnectError::AlreadyConnected => {}
        ConnectError::TimedOut => {}
        ConnectError::Local => {}
    }
}

/// SPEC.md §18.1: `IntroError::{Expired, Evicted, Internal, Malformed,
/// Local, EndpointDropped}` — exactly **six** variants, all unit.
/// `Superseded` appears nowhere (§6.3) — it is still not a variant to add
/// here. `Local` **[RATIFIED 2026/08/15 — ruling 72]** is *our own*
/// provider failing, and is the opposite of `Malformed` in every way that
/// matters: the chain is left **parked** and a retry can still succeed,
/// where a `Malformed` chain is discarded and the verdict is definitive.
///
/// **[RATIFIED 2026/08/18 — ruling 261]** `Evicted` is the sixth. This
/// doc read *"exactly five variants"* and it was the fence §18.1's closure
/// is enforced by, so the count is corrected here with the enum. §6.3's two
/// cap rules displace a parked chain **before** its TTL, and every staged
/// verb reported that as `Expired` — not a vague message but a false one,
/// naming a 15 s timeout for a microsecond-scale loss of a race for a slot.
///
/// **§18.1 itself still lists five and owes the amendment** — the section
/// is normative and this file is the fence for it, so until SPEC.md carries
/// `Evicted` the two disagree by exactly this ruling.
fn match_intro_error(e: IntroError) {
    match e {
        IntroError::Expired => {}
        IntroError::Evicted => {}
        IntroError::Internal => {}
        IntroError::Malformed => {}
        IntroError::Local => {}
        IntroError::EndpointDropped => {}
    }
}

/// SPEC.md §18.1: `AuthError::{Replay, HandshakeFailed, Expired, Local,
/// EndpointDropped}` — exactly five variants, all unit. `HandshakeFailed`
/// is called out as "the only variant in the staged taxonomy that is a
/// security signal"; `Local` **[RATIFIED 2026/08/15 — ruling 78]** exists
/// so that *our own* provider failing does not fall through to it and
/// report the peer as an attacker.
fn match_auth_error(e: AuthError) {
    match e {
        AuthError::Replay => {}
        AuthError::HandshakeFailed => {}
        AuthError::Expired => {}
        AuthError::Local => {}
        AuthError::EndpointDropped => {}
    }
}

/// SPEC.md §18.1: `AcceptError::{Stale, EndpointDropped}` — exactly two
/// variants, both unit. Explicitly **not** four: `Expired` and
/// `AlreadyConnected` are both called out by name as absent ("There is no
/// `Expired` here ... There is no `AlreadyConnected`").
fn match_accept_error(e: AcceptError) {
    match e {
        AcceptError::Stale => {}
        AcceptError::EndpointDropped => {}
    }
}

/// SPEC.md §18.1: `ConnectionLost::{TimedOut, NonceExhausted,
/// LocallyClosed, PeerClosed { code, reason }, ProtocolViolation { code },
/// Replaced, EndpointDropped}` — exactly seven variants. `PeerClosed` and
/// `ProtocolViolation` are the two struct-shaped ones (the shape itself —
/// field names and arity — is what this match's patterns assert); the
/// other five are unit.
fn match_connection_lost(e: ConnectionLost) {
    match e {
        ConnectionLost::TimedOut => {}
        ConnectionLost::NonceExhausted => {}
        ConnectionLost::LocallyClosed => {}
        ConnectionLost::PeerClosed { code, reason } => {
            // Shape only (§18.1 does not pin the field types beyond
            // "carries the peer's CLOSE payload"): both fields must exist,
            // named exactly `code` and `reason`.
            let _ = code;
            let _ = reason;
        }
        ConnectionLost::ProtocolViolation { code } => {
            let _ = code;
        }
        ConnectionLost::Replaced => {}
        ConnectionLost::EndpointDropped => {}
    }
}

/// SPEC.md §18.1: `WriteError::{Reset(u64), ConnectionLost(ConnectionLost),
/// Finished}` — exactly three *documented* variants, but this is the ONE
/// type in the taxonomy ruling 61 marks `#[non_exhaustive]` (§19 reserves
/// `Stopped` for the deferred STOP_SENDING round, so this type demonstrably
/// will gain a variant). `#[non_exhaustive]` has no effect within the
/// defining crate, but this file is compiled as a separate crate (anything
/// under `tests/` is), and from outside the defining crate the compiler
/// *requires* a wildcard arm on a non_exhaustive enum's match — there is no
/// way to satisfy rustc here without one, unlike every other function in
/// this file. The `_` below is therefore load-bearing, not a shortcut: it
/// stays loud by panicking rather than silently doing nothing, so an
/// actually-new variant is still caught (at test-run time, since a compile
/// time catch is exactly what `#[non_exhaustive]` forecloses here).
fn match_write_error(e: WriteError) {
    match e {
        WriteError::Reset(code) => {
            let _: u64 = code;
        }
        WriteError::ConnectionLost(inner) => {
            let _: ConnectionLost = inner;
        }
        WriteError::Finished => {}
        // NOT a fence. `WriteError` is `#[non_exhaustive]` (ruling 61),
        // and this file is a separate crate, so the compiler REQUIRES this
        // arm — which means it silently absorbs any variant added later.
        // Proven by mutation at slice 0: an eleventh variant passed
        // `cargo build`, `cargo test`, and this very test.
        //
        // The real fence for `WriteError` is IN-CRATE, at
        // `src/error.rs::tests::write_error_is_exhaustive_in_crate`, where
        // `#[non_exhaustive]` has no effect and the match is genuinely
        // exhaustive. Do not try to restore a fence here; it cannot work.
        _ => unreachable!("see src/error.rs for this type's real fence"),
    }
}

/// SPEC.md §18.1: `ReadError::{Reset(u64), ConnectionLost(ConnectionLost)}`
/// — exactly two variants. `Reset(code)` is the peer's RESET_STREAM (§9.6).
fn match_read_error(e: ReadError) {
    match e {
        ReadError::Reset(code) => {
            let _: u64 = code;
        }
        ReadError::ConnectionLost(inner) => {
            let _: ConnectionLost = inner;
        }
    }
}

/// SPEC.md §18.1: `MessageError::{TooLarge, ConnectionLost(ConnectionLost)}`
/// — exactly two variants. `TooLarge`: payload > `MESSAGE_RECV_MAX` at the
/// handle (§9.8).
fn match_message_error(e: MessageError) {
    match e {
        MessageError::TooLarge => {}
        MessageError::ConnectionLost(inner) => {
            let _: ConnectionLost = inner;
        }
    }
}

/// SPEC.md §18.1: `DatagramError::{TooLarge,
/// ConnectionLost(ConnectionLost)}` — exactly two variants. `TooLarge`:
/// payload > `MAX_DATAGRAM_PAYLOAD` at the handle (§11.4).
fn match_datagram_error(e: DatagramError) {
    match e {
        DatagramError::TooLarge => {}
        DatagramError::ConnectionLost(inner) => {
            let _: ConnectionLost = inner;
        }
    }
}

/// `ConfigError` is deliberately **outside** §18.1's closed taxonomy
/// (ruling 44: "`ConfigError` is a **configuration** error and
/// deliberately sits outside §18.1's protocol-error taxonomy ... no peer,
/// no packet, and no connection state is involved, and nothing about it is
/// observable on the wire"). Its homes are §16.2 and §10.2:
/// `set_persistent_keepalive` returns `Result<(), ConfigError>` —
/// `KeepaliveTooShort` (below 1 s, the floor, ruling 42) and
/// `KeepaliveTooLong` (at or above `DEAD_TIMEOUT`, the ceiling, ruling
/// 40) — and `Config::with_flow_windows` returns
/// `Result<Config, ConfigError>` with ruling 259(viii)'s three window
/// refusals: `WindowTooSmall` (below the ratified initial),
/// `WindowTooLarge` (above `2^62 - 1`), `StreamWindowAboveConnection`
/// (the pair inverted). Each variant names the bound it violated.
fn match_config_error(e: ConfigError) {
    match e {
        ConfigError::KeepaliveTooShort => {}
        ConfigError::KeepaliveTooLong => {}
        ConfigError::WindowTooSmall => {}
        ConfigError::WindowTooLarge => {}
        ConfigError::StreamWindowAboveConnection => {}
    }
}