use std::collections::{BTreeMap, HashSet};
use std::ops::Range;
use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64;
use bathy_interpret::{Interpretation, interpret, known_probe_ids};
use bathy_types::{ProbeCapture, Transport};
use serde::{Deserialize, Serialize};
const CORPUS_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/captures");
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Fixture {
captured_from: String,
capture: FixtureCapture,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct FixtureCapture {
probe_id: String,
transport: String,
port: u16,
request: Option<String>,
response: String,
elapsed_micros: u64,
truncated: bool,
}
fn to_probe_capture(name: &str, fc: &FixtureCapture) -> ProbeCapture {
let probe_id = known_probe_ids()
.find(|&id| id == fc.probe_id)
.unwrap_or_else(|| {
panic!(
"{name}: fixture names probe_id {:?}, which bathy-interpret has no rules for; \
known ids: {:?}",
fc.probe_id,
known_probe_ids().collect::<Vec<_>>()
)
});
let transport = match fc.transport.as_str() {
"tcp" => Transport::Tcp,
"udp" => Transport::Udp,
other => panic!("{name}: unrecognized transport {other:?}, expected \"tcp\" or \"udp\""),
};
let request = fc.request.as_deref().map(|b64| {
BASE64
.decode(b64)
.unwrap_or_else(|e| panic!("{name}: request is not valid base64: {e}"))
});
let response = BASE64
.decode(&fc.response)
.unwrap_or_else(|e| panic!("{name}: response is not valid base64: {e}"));
ProbeCapture {
probe_id,
transport,
port: fc.port,
request,
response,
elapsed_micros: fc.elapsed_micros,
truncated: fc.truncated,
}
}
fn load_corpus() -> Vec<(String, Fixture)> {
let mut entries: Vec<(String, Fixture)> = std::fs::read_dir(CORPUS_DIR)
.unwrap_or_else(|e| panic!("corpus directory {CORPUS_DIR} must exist: {e}"))
.map(|entry| entry.expect("readable directory entry").path())
.filter(|path| path.extension().and_then(|e| e.to_str()) == Some("json"))
.map(|path| {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.expect("utf8 fixture filename")
.to_string();
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let fixture: Fixture = serde_json::from_str(&text)
.unwrap_or_else(|e| panic!("{}: invalid fixture JSON: {e}", path.display()));
(name, fixture)
})
.collect();
entries.sort_by(|a, b| a.0.cmp(&b.0));
entries
}
#[derive(Serialize)]
struct SnapshotSpan {
start: usize,
end: usize,
}
#[derive(Serialize)]
struct SnapshotInterpretation {
service: String,
product: Option<String>,
version: Option<String>,
confidence: f64,
rule_id: &'static str,
matched_span: SnapshotSpan,
matched_bytes_hex: String,
rationale: String,
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn to_snapshot(response: &[u8], i: &Interpretation) -> SnapshotInterpretation {
let Range { start, end } = i.matched_span.clone();
SnapshotInterpretation {
service: i.observation.service.clone(),
product: i.observation.product.clone(),
version: i.observation.version.clone(),
confidence: i.observation.confidence.get(),
rule_id: i.rule_id,
matched_span: SnapshotSpan { start, end },
matched_bytes_hex: hex(&response[start..end]),
rationale: i.rationale.clone(),
}
}
#[test]
fn every_recorded_capture_reproduces_its_expected_findings() {
let mut checked = 0;
for (name, fixture) in load_corpus() {
let capture = to_probe_capture(&name, &fixture.capture);
let got = interpret(&capture);
let snapshot: Vec<SnapshotInterpretation> = got
.iter()
.map(|i| to_snapshot(&capture.response, i))
.collect();
insta::assert_json_snapshot!(name.as_str(), snapshot);
checked += 1;
}
assert!(
checked >= 16,
"corpus must cover at least 16 captures, found {checked}"
);
}
#[test]
fn replaying_the_corpus_twice_gives_identical_results() {
for (name, fixture) in load_corpus() {
let capture = to_probe_capture(&name, &fixture.capture);
assert_eq!(
interpret(&capture),
interpret(&capture),
"{name}: two calls to interpret() on the same capture must be byte-identical"
);
}
}
#[test]
fn every_fixture_decodes_and_names_a_probe_id_the_registry_knows() {
let known: HashSet<&str> = known_probe_ids().collect();
let mut checked = 0;
for (name, fixture) in load_corpus() {
assert!(
known.contains(fixture.capture.probe_id.as_str()),
"{name}: probe_id {:?} is not one bathy-interpret has rules for; known ids: {known:?}",
fixture.capture.probe_id
);
let _ = to_probe_capture(&name, &fixture.capture);
checked += 1;
}
assert!(checked >= 16);
}
const REAL_PREFIX: &str = "REAL CAPTURE:";
const SYNTHETIC_PREFIX: &str = "SYNTHETIC";
fn names_a_sha256_digest(s: &str) -> bool {
s.match_indices("sha256:").any(|(i, _)| {
let rest = &s[i + "sha256:".len()..];
rest.len() >= 64
&& rest[..64]
.chars()
.all(|c| c.is_ascii_digit() || ('a'..='f').contains(&c))
})
}
#[test]
fn every_fixture_states_its_provenance_and_every_real_capture_names_an_image_digest() {
let (mut real, mut synthetic) = (0usize, 0usize);
for (name, fixture) in load_corpus() {
let from = fixture.captured_from.trim();
assert!(
!from.is_empty(),
"{name}: captured_from is empty. Every fixture must record where its bytes \
came from -- either {REAL_PREFIX:?} with the lab image and its digest, or \
{SYNTHETIC_PREFIX:?} with the reason the bytes were hand-built. This is \
the evidentiary basis of the clean-room claim in README.md."
);
if let Some(detail) = from.strip_prefix(REAL_PREFIX) {
real += 1;
assert!(
names_a_sha256_digest(from),
"{name}: captured_from claims {REAL_PREFIX:?} but names no complete \
image digest (`sha256:` followed by 64 hex characters). A real capture \
must be reproducible from the exact image it came from, not merely \
asserted to exist. Got: {from:?}"
);
assert!(
detail
.split_whitespace()
.next()
.is_some_and(|w| { !w.starts_with("sha256:") && !w.starts_with("digest") }),
"{name}: captured_from names a digest but no image reference before it. \
Record both, e.g. `REAL CAPTURE: docker.io/library/nginx:1.27-alpine, \
digest sha256:...`. Got: {from:?}"
);
} else if from.starts_with(SYNTHETIC_PREFIX) {
synthetic += 1;
assert!(
from.len() > SYNTHETIC_PREFIX.len() + 20,
"{name}: captured_from is labelled {SYNTHETIC_PREFIX:?} but states no \
reason. A hand-built fixture must say what it is built to exercise and \
why no container produced it. Got: {from:?}"
);
assert!(
!names_a_sha256_digest(from),
"{name}: captured_from is labelled {SYNTHETIC_PREFIX:?} yet names an \
image digest. Synthetic bytes did not come from that image; either \
the label is wrong or the digest is fabricated provenance. Got: {from:?}"
);
} else {
panic!(
"{name}: captured_from starts with neither {REAL_PREFIX:?} nor \
{SYNTHETIC_PREFIX:?}, so nothing here can tell whether these bytes came \
off a real wire or were written by hand -- which is the one thing this \
field exists to say. Got: {from:?}"
);
}
}
assert!(
real >= 8,
"expected at least the eight real container captures M4 Task 2 recorded, found {real}"
);
assert!(
synthetic >= 1,
"expected at least one clearly-labelled synthetic fixture, found {synthetic}"
);
}
#[test]
fn corpus_covers_every_known_probe_id_at_least_twice() {
let mut counts: BTreeMap<&'static str, usize> =
known_probe_ids().map(|id| (id, 0usize)).collect();
for (name, fixture) in load_corpus() {
let capture = to_probe_capture(&name, &fixture.capture);
*counts.get_mut(capture.probe_id).expect("validated above") += 1;
}
for (probe_id, n) in &counts {
assert!(
*n >= 2,
"probe {probe_id} has only {n} corpus fixture(s), need at least 2 \
(one that matches with a version, one that matches weakly or not at all)"
);
}
}
#[test]
fn every_fixture_response_is_non_empty_unless_labelled_as_a_deliberate_empty_case() {
const LABEL: &str = "empty response";
for (name, fixture) in load_corpus() {
let capture = to_probe_capture(&name, &fixture.capture);
let labelled_empty = fixture.captured_from.to_lowercase().contains(LABEL);
if !labelled_empty {
assert!(
!capture.response.is_empty(),
"{name}: response is empty but captured_from does not contain {LABEL:?}"
);
}
}
}
#[test]
fn every_matched_span_in_the_corpus_is_a_valid_range_into_its_own_response() {
for (name, fixture) in load_corpus() {
let capture = to_probe_capture(&name, &fixture.capture);
for i in interpret(&capture) {
assert!(
i.matched_span.start <= i.matched_span.end,
"{name}: {}",
i.rule_id
);
assert!(
i.matched_span.end <= capture.response.len(),
"{name}: {} span runs past the end of the response",
i.rule_id
);
}
}
}