feat(iso-on-tcp): add RFC 1006 TPKT header parser (STORY-184) - #466
Conversation
…proposed) with first S7comm implementation story Discharges the F4-OBLIGATION-ADR014-CLAUDEMD carry-forward from Phase-2 architect work (STORY-184, wave 87, feature-s7comm). ADR-014 was held uncommitted on develop pending the first implementation story so the ADR review artifact lands as part of the story's own review, per this repo's ADR-with-first-implementation convention. - Add docs/adr/0014-s7comm-iso-on-tcp-stream-dispatch-and-parser-design.md (status: proposed -- moves to accepted once implementation completes). Byte-identical to the canonical committed copy in the factory-artifacts mirror (architecture/decisions/ADR-014-....md). - Update CLAUDE.md docs/adr/ index note: add 0014 entry (port-102 model = Support enum, ratified; Decision 3 supersedes the original name-keyed- exclusion-list recommendation, human-ratified 2026-09-06). No src/ or test changes in this commit -- stub-architect/test-writer/ implementer pick up TPKT header parsing (BC-2.20.001..004, VP-048) next. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
Files created: src/analyzer/iso_on_tcp.rs
Files modified: src/analyzer/mod.rs (add pub mod iso_on_tcp;)
todo!() functions: 1 (parse_tpkt_header)
Stubs the SS-20 ISO-on-TCP TPKT (RFC 1006) header parser per ADR-014
Decision 1 (frozen interface) and Decision 9 (pure-core free-fn design):
- `TpktHeader { pub version: u8, pub length: u16 }` — frozen struct, no
`reserved` field surfaced.
- `pub fn parse_tpkt_header(data: &[u8]) -> Option<TpktHeader>` — body is
`todo!()`; self-check (BC-5.38.005 invariant 1) applied: "if I include
this real implementation, will the test for this function pass
trivially without any implementer work?" — yes (the function has
branching over 3+ distinct reject/accept paths per BC-2.20.001-004), so
it stays `todo!()`.
- `#[cfg(kani)] mod kani_proofs { verify_parse_tpkt_header_safety }` —
VP-048 harness skeleton, copied verbatim from STORY-184's spec; compiles
under `cargo +nightly kani` (not exercised by plain `cargo check`/
`cargo test` since it is `#[cfg(kani)]`-gated). Full proof run deferred
to STORY-194 per the story's VP-048 obligation.
Scope: STORY-184 covers parse_tpkt_header only. COTP types
(CotpHeader/CotpTpduType/parse_cotp_header) are STORY-185's VP-049
obligation per ADR-014 Decision 9's explicit scope note and are
deliberately NOT stubbed here.
No tests authored (test-writer's stage, not stub-architect's — Red Gate
temporal ordering). `tests/iso_on_tcp_tests.rs` from the story's file list
is left for test-writer.
## GREEN-BY-DESIGN
none
## WIRING-EXEMPT
none
cargo check: PASS (crate compiles with the stub).
cargo clippy --all-targets -- -D warnings: PASS, zero warnings (the
`let _ = data;` line ahead of `todo!()` avoids an unused-parameter lint
without adding any real logic).
Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
Adds tests/iso_on_tcp_tests.rs covering parse_tpkt_header per BC-2.20.001 (len < 4), BC-2.20.002 (version != 0x03), BC-2.20.003 (decoded length < 4), and BC-2.20.004 (accept path, reserved-byte independence, length=65535 ceiling, trailing-bytes tolerance). Includes an AC-184-005 four-way partition spot check and a proptest oracle cross-check. All 24 tests compile and fail against the todo!() stub in src/analyzer/iso_on_tcp.rs (Red Gate verified per BC-5.38.001). Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
Also refreshes the stale "skeleton only / todo!()" doc-comment above the VP-048 Kani proof now that parse_tpkt_header is todo!()-free. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
Per AC-158-001/PG-W71-CHANGELOG (this story modifies src/). Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
… (adversary F-184-P1-002/004) Rewrites the stale Red-phase provenance block (asserted todo!() stub still existed and tests MUST fail) into past-tense GREEN-state provenance, and softens the proptest oracle docstring to describe it accurately as a mutation-catching re-derivation rather than an independent-correctness proof. No test logic, assertions, or names changed. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…ope + EC framing (adversary P2 F-1/F-2/NIT) - F-1: reword the parse_tpkt_header doc comment so it no longer claims exhaustiveness "is proven by the VP-048 Kani harness below" -- the harness is a no-panic skeleton only; the full exhaustiveness proof is STORY-194's obligation. - F-2 (DF-CANONICAL-FRAME-HOLDOUT-001): add two RFC-1006-derived holdout tests (test_rfc1006_s5_canonical_minimal_tpkt_holdout and a 10-byte companion), citing RFC 1006 SS5 byte semantics independently of any project BC. Soften the proptest oracle rationale comment, which overstated that BC-derived vectors "guard against a shared logic error" -- both the oracle and the BC vectors trace to the same spec text, so the new RFC-1006 holdout is what provides spec-independent grounding. - NIT: reorder test_BC_2_20_004_trailing_bytes_beyond_declared_length_ still_accepted_canonical_vector so its first assertion is the genuine EC-004 trailing-bytes case; the EC-005 exact-length-match assertion now follows as a labeled companion check. No coverage removed. cargo test --test iso_on_tcp_tests: 26/26 pass (was 24, +2 new holdout tests). cargo test --all-targets, cargo fmt --check, and cargo clippy --all-targets -- -D warnings all clean. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…nce doc + VP-048 scope (adversary P3)
Remediates adversarial Pass-3 findings on the TPKT header parser:
- M1: iso_on_tcp.rs no longer claims the deferred #[cfg(kani)] harness
"proves" anything -- reworded to "scoped to check", with proof execution
explicitly attributed to STORY-194.
- M2: test file header no longer mis-cites DF-CANONICAL-FRAME-HOLDOUT-001
as requiring BC vectors verbatim -- clarified that BC vectors cover
BC-conformance tests while the policy separately requires the
spec-independent test_rfc1006_s6_* holdout set below.
- L1: corrected RFC 1006 citation from §5 to §6 ("Packet Format")
throughout the holdout tests; renamed test_rfc1006_s5_* -> test_rfc1006_s6_*.
Added a genuinely RFC-valid minimum-length (7) holdout vector, and
relabeled the existing length=4 vector as a documented wirerust/ADR-014
layering divergence rather than an RFC-conformant vector.
- L2: added an input-independence holdout using length=517 (0x0205),
a bit pattern absent from every BC-2.20.00x vector.
- N1: CHANGELOG [Unreleased] entry now says "proof harness" with execution
deferred to STORY-194, not "proof".
- Documented the length>=4 (not >=7) accept-threshold design rationale
inline at the length guard in parse_tpkt_header, pointing to the new
divergence-holdout test.
parse_tpkt_header's acceptance logic (length >= 4) is unchanged -- this is
a documentation and test-holdout correction only.
cargo test --test iso_on_tcp_tests: 28 passed, 0 failed (was 26; +2 new
holdout tests, 2 renamed).
cargo test --all-targets: all green.
cargo fmt --check / cargo clippy --all-targets -- -D warnings: clean.
Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…ing holdout ref (adversary P4 DF-SIBLING-SWEEP) Prior remediation fixed the RFC 1006 §5→§6 citation in test docstrings but missed three sibling occurrences in src/analyzer/iso_on_tcp.rs's module and TpktHeader doc comments, plus left a dangling proptest-oracle docstring reference to a non-existent test_rfc1006_s5_canonical_minimal_tpkt_holdout function. Repointed that reference to the actual test_rfc1006_s6_minimum_valid_length_holdout holdout, fixed its section number and "below"→"above" direction, and tightened the VP-048 Kani harness docstring to state its len <= 300 bound instead of claiming "any length". Citation/reference text only — no logic, test assertions, or test names changed. 28/28 iso_on_tcp_tests pass unchanged; full suite green; fmt/clippy clean. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…; tests + docs RFC-conformant
parse_tpkt_header's length-floor guard is now `length < 7` (was `< 4`),
matching RFC 1006 §6's stated minimum TPKT packet length (4-byte header +
3-byte minimum COTP). The structural read-guard (data.len() < 4) and version
check (data[0] != 0x03) are unchanged. Accept range is now [7, 65535].
Retires the earlier documented layering divergence (length=4 accepted as a
TPKT-structural-only floor); this re-opens STORY-184 from its converged state
per human ruling.
Tests (tests/iso_on_tcp_tests.rs):
- BC-2.20.003 reject set extended: length 4, 5, 6 now assert None (new tests
test_BC_2_20_003_returns_none_for_length_{four,five}_below_rfc_minimum and
the 6-vs-7 boundary test test_BC_2_20_003_returns_none_for_length_six_boundary_below_rfc_minimum).
- BC-2.20.004 accept set: dropped the length=4 canonical-accept test (moved to
the BC-2.20.003 reject set above); length=7 is now the genuine RFC-conformant
accept floor. Updated the reserved-byte and exact-length-match tests to use
length=7 instead of length=4/6.
- Renamed test_rfc1006_s6_length_four_wirerust_divergence_holdout to
test_rfc1006_s6_length_four_below_minimum_returns_none; now asserts None
instead of Some (RFC 1006 §6 min=7; length=4 has no room for COTP).
- test_rfc1006_s6_minimum_valid_length_holdout (length=7 -> Some) is unchanged
and is now the genuine RFC-conformant accept-floor vector.
- Updated the four-way-partition exhaustiveness test and the proptest oracle
(both unit-test and property-test sides) to the length >= 7 threshold.
- Removed all "documented divergence" / ADR-014-layering framing from doc
comments; replaced with accurate RFC-conformant wording throughout.
CHANGELOG.md: folded the earlier intra-dev Fixed §5->§6 entry into the Added
entry, updated to describe the [7, 65535] accept range, and removed internal
process references (adversarial Pass 4, DF-SIBLING-SWEEP-001).
cargo test --test iso_on_tcp_tests: 30 passed (was 27), 0 failed.
cargo test --all-targets: all green. cargo fmt --check and
cargo clippy --all-targets -- -D warnings: clean.
Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…versary NIT) The §5→§6 citation correction was intra-development churn (the module's INTRODUCING changelog entry never shipped with §5 citations in any release), not a user-facing change. Removed per adversarial NIT F2; substantive Added entry describing the TPKT header parser is unchanged. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…e-87) Library/test-harness demo evidence (no CLI/web surface yet at this story's scope). Captures the 30/30-green cargo test --test iso_on_tcp_tests run and maps AC-184-001..006 to their exercising tests, mirroring the STORY-167 (IEC-104 parse_apci_header) precedent this story's shape follows. Path-scrub gate (PG-W70-DEMO-SCRUB) run clean against docs/demo-evidence/STORY-184/. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
Fresh-eyes review — STORY-184 (
|
Review Cycle 1 Triage
Verdict: APPROVE (0 BLOCKING, 0 MAJOR, 2 NIT — both accepted without code change). Proceeding to CI verification and merge. |
…GATION-ADR014-CLAUDEMD RESOLVED PR #466 squash-merged to develop as 7ce0db5 (TPKT header parser, RFC-min-7 rework). Per-story adversarial CONVERGED 3/3; pr-reviewer APPROVE; security CLEAN; CI 13/13. ADR-014 + CLAUDE.md port-102 edit landed on develop via this PR, resolving the F4-OBLIGATION-ADR014-CLAUDEMD carry-forward (ADR-014 stays proposed until F7). - STORY-184.md: status ready -> delivered - STORY-INDEX.md: v4.25 -> v4.26 (status column + wave-87 delivery-progress row; totals unchanged 147/97/863) - STATE.md: v2.8 -> v2.9; develop_head 97361cd -> 7ce0db5; stories_delivered 120 -> 121; phase/current_step/EXACT RESUME POINT updated to F4 IN PROGRESS; D-562 decision recorded; F4-OBLIGATION-ADR014-CLAUDEMD marked RESOLVED; two new carry-forwards added (DEFERRED-BC-2.20.005-STALE-LEN4, BC-2.20.002-LOW-DOUBLE-GUARD) - cycles/feature-s7comm/lessons.md: new — two process-gap observations (PG-CHECK-GREEN-DOC-TENSE-BLINDSPOT, PG-CANONICAL-HOLDOUT-NOT-AC-ENFORCED) - cycles/feature-s7comm/session-checkpoints.md: archived D-561 checkpoint - code-delivery/STORY-184/: PR description + review evidence Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…-min-7 (STORY-185 pre-impl) + rehash BC-2.20.005's postcondition-4 parenthetical, EC-001, EC-002, and the empty-vector canonical row claimed a TPKT length==4 header-only frame legitimately produces an empty tpkt_payload. That is stale under BC-2.20.004's RFC-min-7 accept floor (parse_tpkt_header now rejects length < 7, STORY-184) — the smallest valid frame has length==7, a 3-byte payload, never empty. Replaced with truncated-delivery framing: an empty/short tpkt_payload here can only arise from data.len() < length at the TPKT layer, not a legitimately-parsed header-only frame. The COTP-parse behavior contract itself is unchanged. Resolves DEFERRED-BC-2.20.005-STALE-LEN4 (D-562 carry-forward), marked RESOLVED in STATE.md Active Carry-Forwards. Not a phase transition; phase/current_step otherwise left as-is (STORY-185 delivery is just starting). Rehash (canonical bin/compute-input-hash --write only): - BC-2.20.005's own input-hash unchanged (cf116b5 — inputs are ADR-014 + ARCH-INDEX.md, neither touched by this body-only edit); confirmed via direct tool invocation. - STORY-185 cites BC-2.20.005 as an input, so its hash cascades: 275ae46 -> 7f6bb1e. Repo-root --scan confirms MATCH; ADR-014 now resolves natively (landed on develop via PR #466 7ce0db5, no workaround needed); STORY-194's re-verification anchors do not include BC-2.20.005 and it remained MATCH, unaffected. - Full --scan: MATCH=125, STALE=22 — the pre-existing 22-story background-stale set is unchanged (STORY-185 was the sole newly-affected story, now back to MATCH). Burst-log note appended to cycles/feature-s7comm/burst-log.md. Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
…es delivered Session-wrap-pause checkpoint (D-564). Pipeline IN-PROGRESS→PAUSED; version 3.0→3.1. F4 delta-implementation (feature-s7comm, epic E-23) mid-cycle: STORY-184 (#466, wave 87) + STORY-185 (#467, wave 88) DELIVERED to develop e0ea30c; STORY-186 worktree created as a clean baseline (no code yet) on feature/STORY-186-iso-on-tcp-reassembly. D-563 Session Resume Checkpoint archived to cycles/feature-s7comm/session-checkpoints.md; new checkpoint written with all six required fields. Current Phase Steps: new row added, D-559 row evicted (preserved verbatim in Decisions Log D-559). regression-state.json + sidecar-learning.md hook-churn folded in. No story/spec/code content changed — bookkeeping-only wrap-pause burst, the only STATE.md mutation performed during wrap (BC-6.28.001 INV-1), single-commit burst (TD-VSDD-053). Claude-Session: https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW
[STORY-184] S7comm TPKT Core Parser:
parse_tpkt_headerPure-Core Free Function + VP-048 Kani SkeletonEpic: E-23 — feature-s7comm
Mode: feature (F4 delta-implementation)
Convergence: CONVERGED after 3 adversarial passes (per-story adversarial 3/3 clean; BC-5.39.001 satisfied)
Adds
parse_tpkt_header, the pure-core RFC 1006 TPKT header parser that anchors the newS7comm ISO-on-TCP framing layer (
src/analyzer/iso_on_tcp.rs, SS-20). This is the firstimplementation story of Epic E-23 and lands ADR-014 (S7comm ISO-on-TCP stream dispatch and
parser design,
proposed) alongside it, discharging theF4-OBLIGATION-ADR014-CLAUDEMDcarry-forward. The parser rejects under-length input, non-
0x03version bytes, anddeclared lengths below the RFC 1006 §6 minimum of 7, and accepts
TpktHeader { version: 3, length }forlengthin[7, 65535]. A#[cfg(kani)]VP-048 skeleton is included; fullproof execution is deferred to STORY-194 per ADR-014 Decision 9's scope note.
Architecture Changes
graph TD dispatcher["dispatcher.rs<br/>(protocol dispatch)"] -.->|no dependency yet| iso_on_tcp iso_on_tcp["iso_on_tcp.rs (NEW)<br/>parse_tpkt_header, TpktHeader"] -.->|consumed later by| s7comm["s7comm.rs<br/>(STORY-186, not yet created)"] style iso_on_tcp fill:#90EE90Architecture Decision Record
ADR-014: S7comm ISO-on-TCP (TPKT/COTP) Stream Dispatch and Parser Design
Context: Epic E-23 adds S7comm (Siemens, TCP/102) protocol support. S7comm PDUs are
carried inside ISO-on-TCP framing (RFC 1006 TPKT + ISO 8073/COTP), which is itself shared
by other ICS/SCADA protocols on TCP/102 (IEC 61850 MMS, ICCP/TASE.2). A stream-dispatch and
module-boundary design was needed before any parser code could be written.
Decision:
iso_on_tcp.rs(SS-20) is frozen as a standalone, protocol-agnostic framingmodule exporting pure free functions only (no
impl StreamAnalyzer, no per-flow state ofits own);
s7comm.rs(SS-21, STORY-186) is the sole consumer.TpktHeader { version: u8, length: u16 }is the frozen struct — exactly these two fields. Parsers are implementeddirectly from RFC 1006/ISO 8073 (no borrowed/GPL code — Decision 4). Port-102 protocol
identification uses a per-entry
Supportenum (Supported,KnownUnsupported,DetectionOnly) onKnownProtocol(Decision 3, human-ratified 2026-09-06).Rationale: Separating the framing layer (SS-20) from the PDU dissector (SS-21) keeps
parse_tpkt_headerand the futureparse_cotp_headerindependently Kani-provablepure-core functions, mirroring the IEC-104
parse_apci_header/VP-044 precedent(STORY-167) that this story's shape closely follows.
Alternatives Considered:
s7comm.rsmodule doing TPKT+COTP+S7 in one pass — rejected: couplesframing (shared by other TCP/102 protocols) to S7-specific PDU semantics, and produces
a much larger, harder-to-verify Kani surface.
the
Supportenum (Decision 3) for finer-grained detection-vs-support signaling.Consequences:
parse_tpkt_headerandparse_cotp_header(STORY-185) each get independent, narrowlyscoped VP-048/VP-049 Kani obligations instead of one large combined proof.
proposedpending the full SS-20/SS-21 build-out acrossSTORY-184–194; this PR ships only the SS-20 TPKT slice.
Story Dependencies
graph LR STORY184[STORY-184<br/>🟡 this PR] --> STORY185[STORY-185<br/>⬜ not started<br/>parse_cotp_header/VP-049] style STORY184 fill:#FFD700depends_on: []— STORY-184 has no upstream story dependencies (first story in EpicE-23).
blocks: [STORY-185].Spec Traceability
flowchart LR BC1[BC-2.20.001<br/>len < 4 to None] --> AC1[AC-184-001] BC2[BC-2.20.002<br/>version != 0x03 to None] --> AC2[AC-184-002] BC3[BC-2.20.003<br/>length < 7 to None] --> AC3[AC-184-003] BC4[BC-2.20.004<br/>valid input to Some] --> AC4[AC-184-004] BC4 --> AC5[AC-184-005<br/>4-way partition] AC1 --> T1[test_BC_2_20_001_*] AC2 --> T2[test_BC_2_20_002_*] AC3 --> T3[test_BC_2_20_003_*] AC4 --> T4[test_BC_2_20_004_*] AC5 --> T5[four_way_partition_is_exhaustive] T1 --> S1[src/analyzer/iso_on_tcp.rs] T2 --> S1 T3 --> S1 T4 --> S1 T5 --> S1 S1 --> VP[VP-048 Kani skeleton<br/>AC-184-006]Test Evidence
Coverage Summary
#[cfg(kani)], full proof deferred to STORY-194)Test Flow
graph LR Unit["30 Unit Tests"] Proptest["2 Proptests"] Holdout["4 RFC-1006-§6 holdouts"] Kani["VP-048 Kani skeleton"] Unit -->|100% AC coverage| Pass1["PASS"] Proptest -->|independent oracle| Pass2["PASS"] Holdout -->|canonical RFC vectors| Pass3["PASS"] Kani -->|compiles clean| Pass4["PASS"] style Pass1 fill:#90EE90 style Pass2 fill:#90EE90 style Pass3 fill:#90EE90 style Pass4 fill:#90EE90cargo test --test iso_on_tcp_tests)cargo fmt --checkclean;cargo clippy --all-targets -- -D warningscleanDetailed Test Results — Per-AC Mapping (row-verified against evidence-report.md)
Coverage Map (from
docs/demo-evidence/STORY-184/evidence-report.md)grep+cargo check/clippy)Cross-check: 5 + 5 + 9 + 9 + 2 = 30, matching
cargo test --test iso_on_tcp_testsoutput exactly (30 passed; 0 failed).
Representative Test Rows (row-verified, PG-W74-PRDESC-ROW-VERIFY)
test_BC_2_20_001_returns_none_for_three_bytes_canonical_vectortest_BC_2_20_003_returns_none_for_length_six_boundary_below_rfc_minimumtest_BC_2_20_004_valid_input_returns_some_header_length_65535_max_canonical_vectorproptests::test_BC_2_20_004_proptest_matches_independent_oracleFull raw
cargo test --test iso_on_tcp_testsoutput re-verified by pr-manager during thisPR's creation (30 passed; 0 failed; 0 ignored).
cargo fmt --checkandcargo clippy --all-targets -- -D warningsboth re-verified clean.Holdout Evaluation
N/A — evaluated at wave gate (feature-s7comm Epic E-23 has not yet reached its wave
gate). Four RFC-1006-§6 holdout unit tests (
test_rfc1006_s6_*) are included in thisstory's own test file as an independent-oracle sanity check, but formal holdout-scenario
evaluation against the >=0.85 satisfaction threshold happens at the wave/epic boundary,
not per-story.
Adversarial Review
Convergence: CONVERGED — 3/3 clean per-story adversarial passes (BC-5.39.001
satisfied). Findings were resolved across the branch's development history prior to PR
creation (commits
903a947b,953e1f14,dead410e,c253f9ea,a23fb6ba,3209e70c— doc-tense/provenance fixes, RFC-1006 §6 correction (human ruling: minimumlength 7, not the initially-drafted value), holdout-divergence documentation, and
CHANGELOG churn cleanup).
Key resolutions prior to this PR
RFC 1006 §6 minimum-length human ruling
human ruling;
BC-2.20.003/BC-2.20.004boundaries and all EC-005/006/007 edge casesupdated to match; tests and doc-comments made RFC-conformant.
test_BC_2_20_003_returns_none_for_length_six_boundary_below_rfc_minimum,test_BC_2_20_004_valid_input_returns_some_header_length_7_canonical_vectorDoc-tense / provenance sweep
TDD scaffolding.
src/analyzer/iso_on_tcp.rsandtests/iso_on_tcp_tests.rsdoc comments (adversary F-184-P1-002/004, P3DF-SIBLING-SWEEP).
Security Review
graph LR Critical["Critical: 0"] High["High: 0"] Medium["Medium: 0"] Low["Low: 0"] style Critical fill:#90EE90 style High fill:#90EE90 style Medium fill:#90EE90 style Low fill:#90EE90Security Scan Details
Dedicated security-reviewer pass completed (Step 4 of this PR's lifecycle) against
PR #466's diff (
src/analyzer/iso_on_tcp.rs::parse_tpkt_header+tests/iso_on_tcp_tests.rs). Result: NO FINDINGS at any severity(CRITICAL/HIGH/MEDIUM/LOW).
Manual/AI Security Review
if data.len() < 4 { return None }precedesevery indexed access;
data[0]/data[2]/data[3]are provably in-bounds after theguard. NOT VULNERABLE.
u16::from_be_bytesover exactly 2 bytes is atotal, non-panicking function; no arithmetic, casts, or shifts elsewhere in the
function; no
unwrap/expect/unsafe/panic!anywhere in the module. NOTVULNERABLE.
lengthfield is returned as data only — never used to size an allocation in thisstory. NOT VULNERABLE at this story's scope. Forward note (non-blocking): the
declared-length-vs-actual-buffer reassembly check must land in the STORY-185/186
COTP/S7comm consumer that actually allocates/advances buffers using this field —
tracked as that story's obligation, not a gap in this PR.
strings, queries, deserialization, authentication, or I/O surface.
only, with full proof execution deferred to STORY-194 per the module's documented
scope note — an honest, already-documented deferral, not an undisclosed gap.
Verdict: nothing blocks merge on security grounds.
Dependency Audit
Cargo.tomlchanges in this PR — no new dependency surface.Formal Verification
[u8; N]input (N <= 300)verify_parse_tpkt_header_safety)four_way_partition_is_exhaustive) + proptest oracleRisk Assessment & Deployment
Blast Radius
src/analyzer/iso_on_tcp.rsonly;src/analyzer/mod.rsgains one
pub mod iso_on_tcp;line. No existing analyzer, dispatcher, or CLI behavioris touched.
dispatcher.rsor anyStreamAnalyzer(that wiring is STORY-186's scope). Dead codefrom the CLI's perspective until then.
Performance Impact
Not applicable — new, unwired pure-core module; no existing hot path is touched.
Rollback Instructions
Immediate rollback (< 5 min):
Verification after rollback:
cargo buildsucceeds (module removal doesn't breakmod.rs— revert removes boththe file and the
pub mod iso_on_tcp;line atomically).cargo test --all-targetsgreen.Feature Flags
None — no runtime surface exists yet for this module.
Demo Evidence
Pure-core library story — no CLI/web surface exists yet for this module (the S7comm
dispatch wiring that would expose it via the CLI is STORY-186's scope, per ADR-014
Decision 1). Per the demo-recording skill's library/test-harness mode, evidence is
captured as annotated
cargo testoutput transcripts grouped by AC, plus inlinecanonical-vector tables and source-level
grep/cargo check/cargo clippyverificationfor the VP-048 Kani skeleton — mirroring the STORY-167 (IEC-104
parse_apci_header)precedent.
Location:
docs/demo-evidence/STORY-184/(committed on this branch)AC-001-short-input-rejection.mdAC-002-bad-version-byte.mdAC-003-length-floor-rejection.mdAC-004-valid-accept-path.mdAC-005-four-way-partition.mdAC-006-vp048-kani-skeleton.mdevidence-report.mdAll 6 ACs have at least one recording. Gate satisfied.
Traceability
test_BC_2_20_001_returns_none_for_three_bytes_canonical_vector(+4 more)test_BC_2_20_002_returns_none_for_version_0x04_off_by_one_canonical_vector(+4 more)test_BC_2_20_003_returns_none_for_length_three_off_by_one_canonical_vector(+8 more)test_BC_2_20_004_valid_input_returns_some_header_length_7_canonical_vector(+8 more)test_BC_2_20_004_four_way_partition_is_exhaustiveverify_parse_tpkt_header_safetyFull VSDD Contract Chain
AI Pipeline Metadata
Pipeline Details
Pre-Merge Checklist
changelog-gate, green-doc-tense, help-provenance)
docs/demo-evidence/STORY-184/, 6/6 ACs covered)[Unreleased]entry present (touchessrc/)https://claude.ai/code/session_01EQAaPvh9fwaG31jmkPicKW