Skip to main content

Crate bathy_interpret

Crate bathy_interpret 

Source
Expand description

bathy-interpret: the pure interpretation layer.

This crate turns the raw bytes a bathy_types::ProbeCapture recorded into zero or more Interpretations – structured claims about what service, product, and version a peer’s response is evidence for. It is the reason findings in this project are explainable and replayable:

  • Explainable. Every Interpretation names the exact rule_id that produced it and the exact byte range of the response that justified it (matched_span). explain resolves any rule id this crate can produce back to human-readable documentation and its provenance. M5’s fingerprint.explain tool is built directly on this.
  • Replayable. interpret is a pure function: no I/O, no clock, no randomness, no async runtime. Feed it the same bytes years later and it makes the same claim, because nothing except those bytes and this crate’s own source code ever fed the decision. M4 Task 4’s replay corpus depends on this holding exactly, with the network interface down; M7’s fuzz target depends on it never panicking regardless.

§Purity is enforced structurally, not just by convention

bathy-interpret depends on exactly two crates: bathy-types (for bathy_types::ProbeCapture, bathy_types::event::Observation, and bathy_types::confidence::Confidence – the shapes this crate consumes and produces) and regex (for matching text-shaped protocol banners). This crate’s own code never touches tokio, the filesystem, a clock, or a random-number generator – cargo tree -p bathy-interpret --edges normal is asserted in CI to show only bathy-types, regex, and their own transitive dependencies (AC-4.10), and no matcher in rules.rs calls anything from either crate except regex::Regex itself and plain byte/string operations.

(Narrowed claim, M4 Task 3 review round 1: an earlier version of this paragraph said “no randomness anywhere in … its dependency graph,” which overstates what AC-4.10 actually checks. bathy-types itself depends on ulid, which depends on rand/getrandomgetrandom is present in this crate’s dependency tree; bathy_types::clock is the sanctioned, sole call site for it in this workspace, and this crate’s own code never calls it. That inheritance is unavoidable and judged acceptable: it is linkable, not callable, from here, and removing it would not actually shrink the built artifact – Cargo’s feature unification means every other crate in the workspace that depends on bathy-types already pulls the same dependency in, so it is compiled into any binary that links this crate either way.)

This crate also sits below bathy-probe in this workspace’s layer order (xtask’s LAYERS), specifically so ProbeCapture fixtures can be built by hand in a test or fuzz target with no socket anywhere in the dependency graph at all.

§No panics on the byte path (Global Constraint), and how it is scoped

The #![cfg_attr(not(test), deny(...))] above is the executable form of the overview’s “No panics in parsing paths” constraint. That constraint said unwrap()/expect()/indexing-slice panics were “denied by lint” in this crate and in bathy-probe from M1, and no such lint existed anywhere in the tree until the M7 verification round. It was an aspiration written in the indicative mood for six milestones, in the one crate whose entire input is bytes a scanned peer chose.

It found real hits here, and they are not stylistic: utf8_lines sliced bytes[start..i], u16_at indexed a two-byte window it had just get-checked, mysql_handshake_v10 and dns_bind_version sliced with offsets built by unchecked +, tls_server_hello indexed a header, and every text-shaped rule computed its matched_span as a bare `line_start

  • m.start(). That last expression is the one the from_utf8_lossydefect corrupted and the one seven span mutants attacked across three review rounds; it now lives in exactly one place,rules::absolute_span, and it is checked. See rules.rs`’s own “Byte safety” note.

How test code is exempt. cfg_attr(not(test), ...), not a bare deny. Under cargo clippy --all-targets the library is compiled twice: once as the lib, where cfg(test) is off and the deny is live over every line of production code, and once as the unit-test harness, where cfg(test) is on and the deny is absent – so #[cfg(test)] mod tests keeps unwrap(). tests/replay.rs, tests/span_edge_corpus.rs and benches/interpret.rs are separate crates that never see the attribute.

A crate-level #![allow] of any of these would reproduce the exact defect being closed, so the two exceptions in this crate (Specificity::confidence and rules::static_regex, both over compile-time-constant inputs) are site-level, carry a reason, and are each backed by a test that fails if the reasoning stops being true. cargo run -p xtask -- check-panics enforces that shape, and additionally holds the overview’s constraint text to the set of crates that actually carry the attribute.

§Never guess (AC-4.13)

interpret returns an empty vector when nothing in its rule set recognizes the input. A scanner that invents a service from bytes that do not structurally support the claim is worse than one that reports nothing – see interpret::tests::unrecognized_bytes_yield_no_observation_rather_than_a_guess.

Structs§

Interpretation
One claim interpret made about a capture, and the evidence for it.
RuleDoc
Documentation for one rule, surfaced verbatim by explain (the fingerprint.explain tool’s data source in M5).

Enums§

Specificity
The confidence ladder. Every rule declares which rung it sits on, so scores across protocols mean the same thing and are auditable in one table rather than sprinkled as magic numbers through match arms (AC-4.11).

Functions§

all_rules
Every rule’s documentation, for exhaustive checks like “no rule cites Nmap” (AC-4.16) and for tools that want to list what this crate can recognize at all.
explain
Documentation for one rule by id, surfaced by the fingerprint.explain tool in M5 (AC-4.12: every rule that can fire must be explainable).
interpret
Turn one capture into zero or more observations.
known_probe_ids
Every distinct probe id this crate has at least one rule for – the “registry” M4 Task 4’s replay corpus (crates/bathy-interpret/tests/replay.rs) checks each fixture’s probe_id against, closing that task’s own “the corpus is data, so test the data” requirement.