weir-core 4.1.0

Shared types, wire protocol, and error types for weir
Documentation
//! Conformance suite: weir-core's decoder vs. the published wire vectors.
//!
//! `docs/conformance/wire_v1_vectors.json` is the language-neutral definition of
//! the weir v1 wire format: a list of hex-encoded byte buffers, each paired with
//! the result a conformant decoder MUST produce. A non-Rust implementer loads the
//! same file and runs their decoder against it; this test runs *weir's* decoder
//! against it, proving the reference implementation matches its own published
//! vectors (and, because the vectors were generated with a different CRC-32
//! implementation, that the two agree on every byte).
//!
//! If this test fails after an intentional wire change, regenerate the vectors
//! (see docs/conformance.md) — do not edit the JSON by hand to make it pass.

use serde_json::Value;
use weir_core::{
    DecodeError, Durability, Envelope, MAX_PAYLOAD_HARD_CAP, MessageType, WIRE_VERSION,
};

/// The canonical vectors, embedded at compile time so the test is hermetic.
const VECTORS_JSON: &str = include_str!("../../../docs/conformance/wire_v1_vectors.json");

/// Decodes a lowercase-hex string to bytes. Hand-rolled to keep the test free of
/// a hex-crate dependency; the input is machine-generated so it is always valid.
fn from_hex(s: &str) -> Vec<u8> {
    assert!(
        s.len().is_multiple_of(2),
        "hex string must have even length: {s:?}"
    );
    fn nibble(c: u8) -> u8 {
        match c {
            b'0'..=b'9' => c - b'0',
            b'a'..=b'f' => c - b'a' + 10,
            _ => panic!("invalid hex digit: {:?}", c as char),
        }
    }
    s.as_bytes()
        .chunks(2)
        .map(|pair| (nibble(pair[0]) << 4) | nibble(pair[1]))
        .collect()
}

fn to_hex(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        s.push_str(&format!("{b:02x}"));
    }
    s
}

/// Maps a `DecodeError` to the stable tag used in the vectors file. This is the
/// contract surface: every rejection vector names one of these.
fn error_tag(e: &DecodeError) -> &'static str {
    match e {
        DecodeError::BadMagic => "BadMagic",
        DecodeError::VersionMismatch { .. } => "VersionMismatch",
        DecodeError::UnknownMessageType(_) => "UnknownMessageType",
        DecodeError::UnknownDurability(_) => "UnknownDurability",
        DecodeError::HeaderCrcMismatch { .. } => "HeaderCrcMismatch",
        DecodeError::PayloadCrcMismatch { .. } => "PayloadCrcMismatch",
        DecodeError::TruncatedFrame => "TruncatedFrame",
        DecodeError::PayloadTooLarge { .. } => "PayloadTooLarge",
        DecodeError::ReservedFlagsSet { .. } => "ReservedFlagsSet",
        DecodeError::TrailingBytes { .. } => "TrailingBytes",
        // DecodeError is #[non_exhaustive] (F48). A new variant must be given a
        // tag here before a vector can reference it; until then, fail loudly
        // rather than silently mis-tagging it.
        other => panic!("conformance: unmapped DecodeError variant {other:?}"),
    }
}

fn message_type_name(mt: MessageType) -> &'static str {
    match mt {
        MessageType::Push => "Push",
        MessageType::Ack => "Ack",
        MessageType::Nack => "Nack",
        MessageType::HealthCheck => "HealthCheck",
        MessageType::HealthCheckResponse => "HealthCheckResponse",
        MessageType::PushTracked => "PushTracked",
        MessageType::AckTracked => "AckTracked",
        MessageType::PushBatch => "PushBatch",
        MessageType::AckBatch => "AckBatch",
        // MessageType is #[non_exhaustive] since 4.0, because a new message
        // type is additive on the wire (bytes 0x08-0xFF are reserved) and
        // should be additive in the API too. A vector naming a type this build
        // does not know is a real failure, not something to render as "unknown".
        other => panic!("conformance: unmapped MessageType variant {other:?}"),
    }
}

fn durability_name(d: Durability) -> &'static str {
    match d {
        Durability::Durable => "Durable",
        Durability::Buffered => "Buffered",
    }
}

#[test]
fn vectors_file_pins_the_protocol_constants() {
    // If the vectors were generated against a different WIRE_VERSION or hard cap
    // than this build, every byte is suspect — fail loudly rather than run.
    let root: Value = serde_json::from_str(VECTORS_JSON).expect("vectors JSON parses");
    assert_eq!(
        root["wire_version"].as_u64().unwrap(),
        WIRE_VERSION as u64,
        "vectors file targets a different WIRE_VERSION than this build"
    );
    assert_eq!(
        root["max_payload_hard_cap"].as_u64().unwrap(),
        MAX_PAYLOAD_HARD_CAP as u64,
        "vectors file targets a different MAX_PAYLOAD_HARD_CAP than this build"
    );
}

#[test]
fn every_vector_decodes_as_specified() {
    let root: Value = serde_json::from_str(VECTORS_JSON).expect("vectors JSON parses");
    let vectors = root["vectors"].as_array().expect("vectors is an array");
    assert!(!vectors.is_empty(), "vectors file is empty");

    let mut checked = 0usize;
    for v in vectors {
        let name = v["name"].as_str().expect("vector has a name");
        let bytes = from_hex(v["hex"].as_str().expect("vector has hex"));
        let expected = v["decode"].as_str().expect("vector has a decode tag");
        let result = Envelope::decode(&bytes);

        if expected == "ok" {
            let env = result.unwrap_or_else(|e| {
                panic!("vector {name}: expected Ok, got Err({e:?})");
            });
            let header = env.header();
            assert_eq!(
                message_type_name(header.message_type()),
                v["message_type"].as_str().unwrap(),
                "vector {name}: message_type mismatch"
            );
            assert_eq!(
                durability_name(header.durability()),
                v["durability"].as_str().unwrap(),
                "vector {name}: durability mismatch"
            );
            assert_eq!(
                header.flags() as u64,
                v["flags"].as_u64().unwrap(),
                "vector {name}: flags mismatch"
            );
            assert_eq!(
                to_hex(&env.payload()[..]),
                v["payload_hex"].as_str().unwrap(),
                "vector {name}: payload mismatch"
            );
            // An "ok" vector is canonical by default: re-encoding the decoded
            // frame must reproduce the exact input bytes (encode/decode are
            // inverses). A vector marked "round_trip": false is the documented
            // exception — it uses a non-canonical wire value (the retired 0x02
            // durability byte) that decodes fine but a conformant encoder never
            // re-emits, so its bytes cannot round-trip by design. See
            // retired_batched_byte_decodes_but_never_round_trips below.
            let round_trips = v.get("round_trip").and_then(Value::as_bool).unwrap_or(true);
            if round_trips {
                assert_eq!(
                    to_hex(&env.encode()),
                    v["hex"].as_str().unwrap(),
                    "vector {name}: re-encode is not byte-identical to the vector"
                );
            }
        } else {
            let err =
                result.expect_err(&format!("vector {name}: expected Err({expected}), got Ok"));
            assert_eq!(
                error_tag(&err),
                expected,
                "vector {name}: wrong rejection error ({err:?})"
            );
        }
        checked += 1;
    }
    // Guard against a silently-empty or truncated run.
    assert_eq!(checked, vectors.len(), "did not check every vector");
}

#[test]
fn retired_batched_byte_decodes_but_never_round_trips() {
    // The frozen `push_batched` vector (docs/conformance/wire_v1_vectors.json,
    // "round_trip": false) proves 0x02 still decodes. This proves we never emit
    // it back — decode permissive, encode canonical.
    let decoded = Durability::try_from(0x02).expect("0x02 must still decode");
    assert_eq!(decoded, Durability::Durable);
    assert_eq!(
        u8::from(decoded),
        0x01,
        "re-encoding must canonicalise to 0x01"
    );
}

/// The frozen set carries no extension frame — the compatibility claim for
/// every extension, executed rather than asserted.
///
/// It lives here, with the frozen set, rather than in an extension's own suite:
/// the claim is a property of *this* file, it must survive any extension being
/// removed, and one general guard beats one per extension that someone has to
/// remember to add.
///
/// Stated as `<= 0x05` rather than as a list of the types assigned today, so a
/// future extension is covered the day its byte is assigned rather than the day
/// someone remembers to widen this. `0xFF` is the single exception, and a
/// deliberate one: `reject_unknown_message_type` is this file's worked example
/// of an unassigned byte, and `0xFF` is pinned as permanently unassigned by
/// `weir-core`'s envelope tests.
///
/// If this ever fails, the five polyglot demo clients in CI fail right after
/// it — they implement `0x01..=0x05` and are *correct* to reject anything else.
#[test]
fn the_frozen_v1_vectors_contain_no_extension_frame() {
    let doc: Value = serde_json::from_str(VECTORS_JSON).unwrap();
    for v in doc["vectors"].as_array().unwrap() {
        let buf = from_hex(v["hex"].as_str().unwrap());
        // Byte 5 is message_type; short buffers (truncation vectors) have none.
        if let Some(&mt) = buf.get(5) {
            assert!(
                mt <= 0x05 || mt == 0xFF,
                "vector {:?} puts extension message type {mt:#04x} in the frozen \
                 v1 file; every v1-only decoder rejects it, and the polyglot \
                 clients in CI will fail",
                v["name"].as_str().unwrap()
            );
        }
    }
}