use serde_json::Value;
use weir_core::{
DecodeError, Durability, Envelope, MAX_PAYLOAD_HARD_CAP, MessageType, WIRE_VERSION,
};
const VECTORS_JSON: &str = include_str!("../../../docs/conformance/wire_v1_vectors.json");
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
}
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",
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",
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() {
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"
);
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;
}
assert_eq!(checked, vectors.len(), "did not check every vector");
}
#[test]
fn retired_batched_byte_decodes_but_never_round_trips() {
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"
);
}
#[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());
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()
);
}
}
}