mod common;
use cachekit::CachekitError;
use serde::de::IgnoredAny;
use serde_json::Value as Json;
use crate::common::{read_every_path, EnvelopedValue};
const VECTORS_JSON: &str = include_str!("vectors/python-frame.json");
const VECTORS_SHA256: &str = "b677d5f14de4a3cd1fa5307d4b46eae0545a20e51e9163f96495ee7ad15600d0";
fn vectors() -> Json {
serde_json::from_str(VECTORS_JSON).expect("vendored vector file must be valid JSON")
}
fn ck_frame_reason(path: &str) -> &'static str {
match path {
"serializer::deserialize" | "CacheKit::get" => "trailing",
_ => "CK frame",
}
}
#[test]
fn vendored_fixture_matches_the_pinned_sha256() {
use sha2::{Digest, Sha256};
assert_eq!(
hex::encode(Sha256::digest(VECTORS_JSON.as_bytes())),
VECTORS_SHA256,
"tests/vectors/python-frame.json differs from the pinned protocol copy: \
re-vendor it from protocol and update VECTORS_SHA256"
);
}
#[test]
fn every_ck_frame_in_the_file_is_refused_on_every_path() {
let doc = vectors();
let mut names = Vec::new();
for group in ["frame_vectors", "error_vectors", "encrypted_read_vectors"] {
for v in doc[group].as_array().expect("vector group") {
let hex = v["frame_hex"].as_str().expect("frame_hex");
if !hex.starts_with("434b") {
continue;
}
let name = v["name"].as_str().expect("name");
let bytes = hex::decode(hex).expect("frame_hex is hex");
for (path, result) in read_every_path::<IgnoredAny>(&bytes) {
let want = ck_frame_reason(path);
match result {
Err(CachekitError::Serialization(msg)) => assert!(
msg.contains(want),
"{name}: {path} must refuse a CK frame naming {want:?}, got: {msg}"
),
other => panic!("{name}: {path} decoded a CK frame (WIRE-21), got {other:?}"),
}
}
names.push(name);
}
}
assert_eq!(names.len(), 29, "CK-prefixed vectors in python-frame.json");
let paths: Vec<_> = read_every_path::<IgnoredAny>(b"\x00")
.into_iter()
.map(|(path, _)| path)
.collect();
assert_eq!(
paths,
[
"serializer::deserialize",
"interop::deserialize",
"CacheKit::get",
"CacheKit::interop_get",
"CacheKit::interop_get_swr",
],
"the sweep must cover every read path"
);
assert!(
names.contains(&"ck_frame_fed_to_interop_reader"),
"the WIRE-21 vector must be among the frames swept"
);
}
#[test]
fn bare_envelope_decodes_but_not_as_its_value() {
let doc = vectors();
let hex = doc["error_vectors"]
.as_array()
.expect("error_vectors")
.iter()
.find(|v| v["name"] == "bare_envelope_fed_to_frame_reader")
.and_then(|v| v["frame_hex"].as_str())
.expect("bare_envelope_fed_to_frame_reader");
let bytes = hex::decode(hex).expect("frame_hex is hex");
for (path, result) in read_every_path::<IgnoredAny>(&bytes) {
assert!(
result.is_ok(),
"{path}: a bare envelope is one well-formed MessagePack document, which this \
reader decodes; if it now refuses it, update this test and the WIRE-21 record \
in protocol's sdk-feature-matrix.md: {result:?}"
);
}
for (path, result) in read_every_path::<EnvelopedValue>(&bytes) {
assert!(
matches!(result, Err(CachekitError::Serialization(_))),
"{path}: a bare envelope must not decode as the value it wraps, got {result:?}"
);
}
type EnvelopeShaped = (IgnoredAny, Vec<u8>, u64, String);
for (path, result) in read_every_path::<EnvelopeShaped>(&bytes) {
assert!(
result.is_ok(),
"{path}: an envelope-shaped target reads a bare envelope: {result:?}"
);
}
}
#[cfg(feature = "encryption")]
#[test]
fn every_vector_is_refused_at_decrypt_by_an_encrypting_client() {
use crate::common::{
encrypting_client_holding, every_read, runtime, AUTH_FAILURE, SHORT_CIPHERTEXT,
};
let doc = vectors();
let reader = |k: &str| {
doc["encrypted_reader"][k]
.as_str()
.unwrap_or_else(|| panic!("encrypted_reader lacks {k}"))
};
let key = reader("cache_key");
let rt = runtime();
let mut count = 0;
let mut short = 0;
for group in ["frame_vectors", "error_vectors", "encrypted_read_vectors"] {
for v in doc[group].as_array().expect("vector group") {
let name = v["name"].as_str().expect("name");
if group == "encrypted_read_vectors" {
assert_eq!(v["outcome"], "fail_closed", "{name}");
}
let bytes =
hex::decode(v["frame_hex"].as_str().expect("frame_hex")).expect("frame_hex is hex");
let expected = if bytes.len() < 12 + 16 {
short += 1;
SHORT_CIPHERTEXT
} else {
AUTH_FAILURE
};
let client = encrypting_client_holding(
reader("master_key_hex"),
reader("tenant_id"),
None,
key,
bytes,
);
for (path, result) in rt.block_on(every_read::<IgnoredAny>(&client, key)) {
match result {
Err(CachekitError::Encryption(msg)) if msg == expected => {}
other => {
panic!("{name}: {path} must be refused with {expected:?}, got {other:?}")
}
}
}
count += 1;
}
}
assert_eq!(count, 31, "vectors in python-frame.json");
assert_eq!(short, 4, "vectors under 28 bytes");
}