1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! `bathy-interpret`: the pure interpretation layer.
//!
//! This crate turns the raw bytes a [`bathy_types::ProbeCapture`] recorded
//! into zero or more [`Interpretation`]s -- 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`/`getrandom` -- `getrandom`
//! *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_lossy`
//! defect 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`.
pub use ;
pub use ;