diff --git a/docs/demo-evidence/STORY-180/evidence-report.md b/docs/demo-evidence/STORY-180/evidence-report.md index c496cb97..f57a6d5e 100644 --- a/docs/demo-evidence/STORY-180/evidence-report.md +++ b/docs/demo-evidence/STORY-180/evidence-report.md @@ -184,6 +184,4 @@ grep -rE '' docs/demo-evidence/STORY-180/ Result: **zero matches** — no absolute host paths present in any evidence file. -Result: **zero matches** — no absolute host paths present in any evidence file. - Gate status: **PASSED** (2026-07-24). diff --git a/tests/fixtures/E2E-PCAPS.md b/tests/fixtures/E2E-PCAPS.md index dfa5bbaf..2d4e1b46 100644 --- a/tests/fixtures/E2E-PCAPS.md +++ b/tests/fixtures/E2E-PCAPS.md @@ -355,7 +355,7 @@ Two sources used: |------|------|--------|--------|---------|----------------| | `iec104.pcap` | 10 KB | `a78aa971adc51e54413a865937f1799ef57118d397cef57ccd93a358ed5b85d6` | Wireshark SampleCaptures | local-use-only | Canonical IEC-104 reference: U-frames (STARTDT/STOPDT/TESTFR) + I-frame ASDUs + C_IC general interrogation (TypeID 100, COT 6 act/7 con/20 inrogen/10 actterm). 105 reader packets. Produces 66 findings: T1692.001 ×42 (control commands) + T0836 ×24 (parameter modification). | | `iec104-sq.pcapng` | 584 B | `f855a11326f7aa4f719b1fbb65e5f8dfe3d9d194185a8f5faf5b5dc3cb831227` | Wireshark SampleCaptures | local-use-only | **Native pcapng** (SHB magic `0x0A0D0D0A`); SQ-bit set in ASDU variable-structure qualifier (sequence-of-information-objects encoding). Only native IEC-104 pcapng found on an authoritative source. 1 reader packet; 0 findings (small link-layer exercise). | -| `iec104-iti-diverse.pcap` | 14 KB | `07b9a0879dc83e420c4cf83b37fb5830d1d8fb5f6ac6edc435896f70b0fc6bc7` | ITI/ICS-Security-Tools | CC-BY-4.0 | IEC-104 diverse-ASDU capture from the same ITI ICS corpus as the ENIP fixtures. 173 reader packets. Produces 31 findings: T1692.001 ×21 + T0836 ×10. | +| `iec104-iti-diverse.pcap` | 14 KB | `07b9a0879dc83e420c4cf83b37fb5830d1d8fb5f6ac6edc435896f70b0fc6bc7` | ITI/ICS-Security-Tools | CC-BY-4.0 | IEC-104 diverse-ASDU capture from the same ITI ICS corpus as the ENIP fixtures. 173 reader packets. Produces 66 findings: T1692.001 ×46 + T0836 ×20. (Wave-85/STORY-180: was 31 before timed TypeIDs 58–64 were detected.) | | `iec104-iti-dissect.pcap` | 11 KB | `292c18a8765db3b1bcaa9bd0b8455e4e61b8366cc5910a7363b7381eb11441b8` | ITI/ICS-Security-Tools | CC-BY-4.0 | Wireshark-dissector test capture: deliberately broad Type ID / COT coverage including control commands (C_SC/C_DC/C_SE). 147 reader packets. Produces 11 findings: T1692.001 ×9 + T0814 ×2. | ### Analyzer-level outcomes (IEC-104 --iec104 flag) @@ -364,7 +364,7 @@ Two sources used: |------|----------|-----------------|--------------| | `iec104.pcap` | 66 | T0836 ×24, T1692.001 ×42 | 0 | | `iec104-sq.pcapng` | 0 | — (benign link-management traffic only) | 0 | -| `iec104-iti-diverse.pcap` | 31 | T0836 ×10, T1692.001 ×21 | 0 | +| `iec104-iti-diverse.pcap` | 66 | T0836 ×20, T1692.001 ×46 | 0 | | `iec104-iti-dissect.pcap` | 11 | T0814 ×2, T1692.001 ×9 | 0 | All four captures parse without panics; zero parse_errors across all. diff --git a/tests/iec104_e2e_real_pcaps_tests.rs b/tests/iec104_e2e_real_pcaps_tests.rs index f99e53f9..651f513e 100644 --- a/tests/iec104_e2e_real_pcaps_tests.rs +++ b/tests/iec104_e2e_real_pcaps_tests.rs @@ -24,7 +24,7 @@ //! |------|------|-----------------| //! | `test_e2e_BC_2_19_iec104_pcap_T0836_T1692_001_interrogation` | `iec104.pcap` (Wireshark Foundation) | T0836 ×24 + T1692.001 ×42 = 66 total; flows_analyzed=1; dropped_findings=0 | //! | `test_e2e_BC_2_19_iec104_sq_pcapng_zero_findings_benign_uframes` | `iec104-sq.pcapng` (Wireshark Foundation) | 0 findings (benign STARTDT/TESTFR-only SQ-bit fixture); flows_analyzed=1; dropped_findings=0 | -//! | `test_e2e_BC_2_19_iec104_iti_diverse_T0836_T1692_001_mixed_asdu` | `iec104-iti-diverse.pcap` (ITI CC-BY-4.0) | T0836 ×10 + T1692.001 ×21 = 31 total; flows_analyzed=1; dropped_findings=0 | +//! | `test_e2e_BC_2_19_iec104_iti_diverse_T0836_T1692_001_mixed_asdu` | `iec104-iti-diverse.pcap` (ITI CC-BY-4.0) | T0836 ×20 + T1692.001 ×46 = 66 total; flows_analyzed=1; dropped_findings=0 | //! | `test_e2e_BC_2_19_iec104_iti_dissect_T0814_T1692_001_control_coverage` | `iec104-iti-dissect.pcap` (ITI CC-BY-4.0) | T0814 ×2 + T1692.001 ×9 = 11 total; flows_analyzed=6; dropped_findings=0 | //! //! ## Traces @@ -337,8 +337,17 @@ mod iec104_e2e_real_pcaps { // // Pcap: IEC-104 traffic with a diverse mix of ASDU Type IDs from the ITI ICS corpus. // 173 reader packets; 1 TCP flow on port 2404. - // Expected: 31 findings = T0836 ×10 + T1692.001 ×21. All Impact/Possible/Medium. - // flows_analyzed=1, total_findings=31, dropped_findings=0. + // Expected: 66 findings = T0836 ×20 + T1692.001 ×46. All Impact/Possible/Medium. + // flows_analyzed=1, total_findings=66, dropped_findings=0. + // + // Wave-85 change (STORY-180, BC-2.19.029/030): TypeIDs 58–64 (time-tagged control + // commands) present in this capture were silently ignored before wave-85. They are now + // detected, raising the total from 31 to 66 (+35 findings). The untimed contribution + // (31) is unchanged: TypeID=45→5×T1692.001, TypeID=46→6×T1692.001, + // TypeID=50→10×T0836+10×T1692.001. + // Timed contribution (+35): x=15 timed-switching ASDUs (TypeID=58→5, TypeID=59→10, + // each 1 finding → +15×T1692.001); y=10 timed-setpoint ASDUs (TypeID=61→5, + // TypeID=63→5, each 2 findings → +10×T1692.001 + +10×T0836); x+2y=35. ✓ // // Traces: BC-2.19 (IEC-104 analyzer pipeline). // License: ITI/ICS-Security-Tools CC-BY-4.0. Attribution: ICS Security Tools, @@ -349,15 +358,23 @@ mod iec104_e2e_real_pcaps { /// /// iec104-iti-diverse.pcap is from the same ITI ICS Security Tools corpus as the ENIP /// fixtures. It contains a diverse ASDU Type ID mix representing realistic IEC-104 - /// traffic from a SCADA deployment. + /// traffic from a SCADA deployment, including time-tagged control commands. /// - /// Postconditions asserted (ground-truth from analyzer-level validation run): - /// - `iec104.all_findings.len()` == 31. - /// - T0836 count == 10. - /// - T1692.001 count == 21. + /// Wave-85 (STORY-180, BC-2.19.029/030): TypeIDs 58–64 (time-tagged control commands) + /// present in this capture were silently ignored before wave-85; they are now detected, + /// raising the expectation from 31 to 66. The +35 timed findings decompose as: + /// x=15 from timed-switching TypeIDs 58–59 (1 finding each, T1692.001 only) + /// 2y=20 from timed-setpoint TypeIDs 61+63 (2 findings each: T1692.001 + T0836) + /// T0836 delta=y=10; T1692.001 delta=x+y=25 + /// + /// Postconditions asserted (ground-truth from wave-85 validation run): + /// - `iec104.all_findings.len()` == 66. + /// - T0836 count == 20. + /// - T1692.001 count == 46. + /// - Count of findings whose summary contains "time-tagged" == 35 (= x + 2y). /// - Every finding is Impact / Possible / Medium. /// - `iec104_summary.flows_analyzed` == 1. - /// - `iec104_summary.total_findings` == 31. + /// - `iec104_summary.total_findings` == 66. /// - `iec104_summary.dropped_findings` == 0. /// /// Traces: BC-2.19 (IEC-104 analyzer pipeline). @@ -372,9 +389,10 @@ mod iec104_e2e_real_pcaps { // ── Total findings count ────────────────────────────────────────────── assert_eq!( iec104.all_findings.len(), - 31, - "iec104-iti-diverse.pcap: expected exactly 31 findings \ - (T0836 ×10 + T1692.001 ×21); got {} findings: {:?}", + 66, + "iec104-iti-diverse.pcap: expected exactly 66 findings \ + (T0836 ×20 + T1692.001 ×46, incl. 35 time-tagged from wave-85); \ + got {} findings: {:?}", iec104.all_findings.len(), iec104 .all_findings @@ -396,12 +414,30 @@ mod iec104_e2e_real_pcaps { .count(); assert_eq!( - t0836_count, 10, - "iec104-iti-diverse.pcap: expected 10 T0836 findings; got {t0836_count}" + t0836_count, 20, + "iec104-iti-diverse.pcap: expected 20 T0836 findings \ + (10 untimed from TypeID=50 + 10 timed from TypeIDs 61+63); got {t0836_count}" ); assert_eq!( - t1692_001_count, 21, - "iec104-iti-diverse.pcap: expected 21 T1692.001 findings; got {t1692_001_count}" + t1692_001_count, 46, + "iec104-iti-diverse.pcap: expected 46 T1692.001 findings \ + (21 untimed + 25 timed: x=15 from TypeIDs 58-59, y=10 from TypeIDs 61+63); \ + got {t1692_001_count}" + ); + + // ── Time-tagged timed-arm marker (wave-85 STORY-180 guard) ─────────── + // x=15 timed-switching findings (TypeIDs 58-59) + 2y=20 timed-setpoint findings + // (TypeIDs 61+63) = 35 findings whose summary contains "time-tagged". + let time_tagged_count = iec104 + .all_findings + .iter() + .filter(|f| f.summary.contains("time-tagged")) + .count(); + assert_eq!( + time_tagged_count, 35, + "iec104-iti-diverse.pcap: expected 35 time-tagged findings (x+2y = 15+20, \ + TypeIDs 58–59 + 61+63; BC-2.19.029/030 timed-command detection from wave-85); \ + got {time_tagged_count}" ); // ── Category / verdict / confidence — all Impact/Possible/Medium ────── @@ -438,8 +474,8 @@ mod iec104_e2e_real_pcaps { ); assert_eq!( detail["total_findings"], - serde_json::json!(31u64), - "iec104-iti-diverse.pcap: total_findings must be 31; got {:?}", + serde_json::json!(66u64), + "iec104-iti-diverse.pcap: total_findings must be 66; got {:?}", detail["total_findings"] ); assert_eq!(