use serde_json::Value;
use weir_core::{
ACK_BATCH_HEADER_LEN, ACK_BATCH_VERSION, BATCH_HEADER_LEN, BATCH_VERSION, BatchError,
Durability, Envelope, MAX_ACK_BATCH_PAYLOAD_LEN, MAX_BATCH_RECORDS_HARD_CAP,
MAX_PAYLOAD_HARD_CAP, MessageType, WIRE_VERSION, decode_ack_batch, decode_batch_body,
encode_ack_batch, encode_batch_body,
};
const VECTORS_JSON: &str = include_str!("../../../docs/conformance/wire_v1_batch_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 batch_error_tag(e: &BatchError) -> &'static str {
match e {
BatchError::Truncated { .. } => "Truncated",
BatchError::UnsupportedVersion { .. } => "UnsupportedVersion",
BatchError::EmptyBatch => "EmptyBatch",
BatchError::TooManyRecords { .. } => "TooManyRecords",
BatchError::EmptyRecord { .. } => "EmptyRecord",
BatchError::RecordTooLarge { .. } => "RecordTooLarge",
BatchError::TruncatedRecord { .. } => "TruncatedRecord",
BatchError::LengthMismatch { .. } => "LengthMismatch",
BatchError::PaddingNotZero { .. } => "PaddingNotZero",
other => panic!("conformance: unmapped BatchError variant {other:?}"),
}
}
fn message_type_name(mt: MessageType) -> &'static str {
match mt {
MessageType::PushBatch => "PushBatch",
MessageType::AckBatch => "AckBatch",
other => panic!("conformance: batch vectors carry only 0x08/0x09, got {other:?}"),
}
}
fn durability_name(d: Durability) -> &'static str {
match d {
Durability::Durable => "Durable",
Durability::Buffered => "Buffered",
}
}
fn doc() -> Value {
serde_json::from_str(VECTORS_JSON).expect("batch vectors file is valid JSON")
}
#[test]
fn vectors_file_pins_the_protocol_constants() {
let doc = doc();
assert_eq!(doc["wire_version"].as_u64().unwrap(), WIRE_VERSION as u64);
assert_eq!(doc["batch_version"].as_u64().unwrap(), BATCH_VERSION as u64);
assert_eq!(
doc["ack_batch_version"].as_u64().unwrap(),
ACK_BATCH_VERSION as u64
);
assert_eq!(
doc["batch_header_len"].as_u64().unwrap(),
BATCH_HEADER_LEN as u64
);
assert_eq!(
doc["ack_batch_header_len"].as_u64().unwrap(),
ACK_BATCH_HEADER_LEN as u64
);
assert_eq!(
doc["max_batch_records_hard_cap"].as_u64().unwrap(),
MAX_BATCH_RECORDS_HARD_CAP as u64
);
assert_eq!(
doc["max_ack_batch_payload_len"].as_u64().unwrap(),
MAX_ACK_BATCH_PAYLOAD_LEN as u64
);
}
#[test]
fn every_frame_vector_decodes_and_round_trips() {
let doc = doc();
let vectors = doc["frame_vectors"]
.as_array()
.expect("frame_vectors array");
assert!(!vectors.is_empty(), "the vectors file must not be empty");
for v in vectors {
let name = v["name"].as_str().unwrap();
let buf = from_hex(v["hex"].as_str().unwrap());
assert_eq!(
v["decode"].as_str().unwrap(),
"ok",
"{name}: only 'ok' frame vectors are defined"
);
let env = Envelope::decode(&buf).unwrap_or_else(|e| panic!("{name}: decode failed: {e:?}"));
let h = env.header();
assert_eq!(
message_type_name(h.message_type()),
v["message_type"].as_str().unwrap(),
"{name}: message_type"
);
assert_eq!(
durability_name(h.durability()),
v["durability"].as_str().unwrap(),
"{name}: durability"
);
assert_eq!(
h.flags() as u64,
v["flags"].as_u64().unwrap(),
"{name}: flags"
);
assert_eq!(
to_hex(env.payload()),
v["payload_hex"].as_str().unwrap(),
"{name}: payload"
);
assert_eq!(
to_hex(&env.encode()),
v["hex"].as_str().unwrap(),
"{name}: re-encode is not byte-identical"
);
}
}
#[test]
fn every_body_vector_matches_the_decoder() {
let doc = doc();
let vectors = doc["body_vectors"].as_array().expect("body_vectors array");
assert!(!vectors.is_empty(), "the vectors file must not be empty");
let mut ok_seen = 0;
let mut reject_seen = 0;
for v in vectors {
let name = v["name"].as_str().unwrap();
let buf = from_hex(v["hex"].as_str().unwrap());
let expected = v["decode"].as_str().unwrap();
match decode_batch_body(&buf, MAX_BATCH_RECORDS_HARD_CAP, MAX_PAYLOAD_HARD_CAP) {
Ok(records) => {
assert_eq!(expected, "ok", "{name}: expected rejection, but it decoded");
let want: Vec<String> = v["records_hex"]
.as_array()
.unwrap()
.iter()
.map(|r| r.as_str().unwrap().to_string())
.collect();
let got: Vec<String> = records.iter().map(|r| to_hex(r)).collect();
assert_eq!(got, want, "{name}: records");
assert_eq!(
to_hex(&encode_batch_body(&records)),
v["hex"].as_str().unwrap(),
"{name}: re-encode is not byte-identical"
);
ok_seen += 1;
}
Err(e) => {
assert_ne!(expected, "ok", "{name}: expected ok, got {e:?}");
assert_eq!(batch_error_tag(&e), expected, "{name}: wrong rejection tag");
reject_seen += 1;
}
}
}
assert!(
ok_seen > 0 && reject_seen > 0,
"the body vectors must cover both acceptance and rejection \
({ok_seen} ok, {reject_seen} rejected)"
);
}
#[test]
fn every_ack_vector_matches_the_decoder() {
let doc = doc();
let vectors = doc["ack_vectors"].as_array().expect("ack_vectors array");
assert!(!vectors.is_empty(), "the vectors file must not be empty");
let mut ok_seen = 0;
let mut reject_seen = 0;
for v in vectors {
let name = v["name"].as_str().unwrap();
let buf = from_hex(v["hex"].as_str().unwrap());
let expected = v["decode"].as_str().unwrap();
let count = v["expected"].as_u64().unwrap() as usize;
match decode_ack_batch(&buf, count) {
Ok(accepted) => {
assert_eq!(expected, "ok", "{name}: expected rejection, but it decoded");
assert_eq!(accepted.len(), count, "{name}: verdict count");
let want: Vec<bool> = match (v.get("accepted"), v.get("accepted_all")) {
(Some(a), None) => a
.as_array()
.unwrap()
.iter()
.map(|b| b.as_bool().unwrap())
.collect(),
(None, Some(all)) => vec![all.as_bool().unwrap(); count],
_ => panic!("{name}: give exactly one of 'accepted' / 'accepted_all'"),
};
assert_eq!(
accepted, want,
"{name}: verdicts disagree with the vector — if this is the \
N=9 asymmetric vector, the bitmap bit order is inverted"
);
assert_eq!(
to_hex(&encode_ack_batch(&accepted)),
v["hex"].as_str().unwrap(),
"{name}: re-encode is not byte-identical"
);
ok_seen += 1;
}
Err(e) => {
assert_ne!(expected, "ok", "{name}: expected ok, got {e:?}");
assert_eq!(batch_error_tag(&e), expected, "{name}: wrong rejection tag");
reject_seen += 1;
}
}
}
assert!(
ok_seen > 0 && reject_seen > 0,
"the ack vectors must cover both acceptance and rejection \
({ok_seen} ok, {reject_seen} rejected)"
);
}
#[test]
fn the_bitmap_is_lsb_first_and_a_literal_says_so() {
let accepted = [true, false, true, false, false, false, false, false, true];
let encoded = encode_ack_batch(&accepted);
assert_eq!(
encoded,
vec![ACK_BATCH_VERSION, 9, 0, 0b0000_0101, 0b0000_0001],
"LSB-first means bit i sits at mask 1 << (i % 8); MSB-first would give \
a0 80 here, which is a well-formed frame with the opposite meaning"
);
assert_eq!(decode_ack_batch(&encoded, 9).unwrap(), accepted);
}