From 00f52ecf0f9c4b5d618dd2af1b85e8246359f783 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 17 Aug 2026 17:37:39 -0400 Subject: [PATCH] preserve hardened USB smoke evidence --- README.md | 6 +- docs/transport-layer-validation-2026-08-17.md | 16 +++++ tests/fixtures/README.md | 11 +++ tests/fixtures/direct_usb_73e5680.trk | Bin 0 -> 29260 bytes tests/fixtures/direct_usb_73e5680.wire | Bin 0 -> 32849 bytes tests/test_trikke_protocol.py | 66 ++++++++++++------ tools/capture_binary.py | 16 +++++ 7 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 tests/fixtures/direct_usb_73e5680.trk create mode 100644 tests/fixtures/direct_usb_73e5680.wire diff --git a/README.md b/README.md index 0425fbf..f9e115d 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,13 @@ optional `--wire` path preserves every received byte, including startup text and damaged or partial frames, for forensic comparison: ```sh -python tools/capture_binary.py --wire captures/session.wire +python tools/capture_binary.py --reset --wire captures/session.wire ``` +`--reset` normalizes the USB DTR/RTS state, clears bytes from the prior session, +and resets the C3 while the new capture is already open. Omit it when attaching +to an intentionally uninterrupted stream. + An existing `.trk` stream can be decoded again without hardware: ```sh diff --git a/docs/transport-layer-validation-2026-08-17.md b/docs/transport-layer-validation-2026-08-17.md index 4b5da1b..c00544d 100644 --- a/docs/transport-layer-validation-2026-08-17.md +++ b/docs/transport-layer-validation-2026-08-17.md @@ -99,3 +99,19 @@ sample gaps/resets, timestamp anomalies, sensor status, saturation flags, drops, and acquisition overruns. Sequence classification is wrap-aware. The capture tool also accepts `--wire PATH` to preserve every serial byte before parsing, including startup text, corrupt frames, and trailing fragments. + +For deterministic fresh-session validation, `capture_binary.py --reset` releases +DTR/RTS, clears the prior input session, then pulses the C3 reset line while the +same reader remains open. This follows ESP-IDF monitor's USB Serial/JTAG reset +ordering and avoids a flash-to-capture port-open race. + +Commit `73e5680` was built, flashed, and then captured through this reset path. +The result contains 1,184 contiguous samples, sequences 0 through 1,183, with +zero packet/sample gaps, resets, CRC failures, reported drops, loop overruns, +trailing bytes, or timestamp saturation. Raw-wire offline decoding produced CSV +byte-for-byte identical to live rendering. Both artifacts are tracked: + +- `tests/fixtures/direct_usb_73e5680.trk`, SHA-256 + `fd34bb3bf8f92a64960024f1287e553c03076ff3714fe629ec629bec81ddf821` +- `tests/fixtures/direct_usb_73e5680.wire`, SHA-256 + `82d6d17bbf0729e9bfc53f337ec9adf70bcb5e5898b039685eda5dfa19cf4eea` diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md index dcfa6fe..d288015 100644 --- a/tests/fixtures/README.md +++ b/tests/fixtures/README.md @@ -43,6 +43,17 @@ removed before production firmware was built and flashed. of startup text before the valid frames. That text includes the literal `TRK1`, producing one rejected candidate header as designed. Extracting all valid frames reproduces `direct_usb_3c95f3d.trk` byte-for-byte. +- `direct_usb_73e5680.trk` — SHA-256 + `fd34bb3bf8f92a64960024f1287e553c03076ff3714fe629ec629bec81ddf821`. + This exact firmware-hardening capture contains 1,184 contiguous samples, + sequences 0 through 1,183, with zero packet/sample gaps, resets, CRC failures, + reported drops, loop overruns, trailing bytes, or timestamp saturation. +- `direct_usb_73e5680.wire` — SHA-256 + `82d6d17bbf0729e9bfc53f337ec9adf70bcb5e5898b039685eda5dfa19cf4eea`. + This is the byte-for-byte `--reset --wire` capture corresponding to the + validated file above. Its 3,589 skipped startup bytes and one candidate-header + rejection are deterministic, and extracting its 151 valid frames reproduces + `direct_usb_73e5680.trk` byte-for-byte. `tests/test_trikke_protocol.py` verifies the hashes, parses the captures in fragmented chunks, and asserts these signatures so the hardware evidence remains diff --git a/tests/fixtures/direct_usb_73e5680.trk b/tests/fixtures/direct_usb_73e5680.trk new file mode 100644 index 0000000000000000000000000000000000000000..d6712d60be6d237d5d9b725b3a99f954967278c2 GIT binary patch literal 29260 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/direct_usb_73e5680.wire b/tests/fixtures/direct_usb_73e5680.wire new file mode 100644 index 0000000000000000000000000000000000000000..99783071532bc960711aa22aa543603524b05add GIT binary patch literal 32849 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 literal 0 HcmV?d00001 diff --git a/tests/test_trikke_protocol.py b/tests/test_trikke_protocol.py index 148b988..1b066eb 100644 --- a/tests/test_trikke_protocol.py +++ b/tests/test_trikke_protocol.py @@ -215,6 +215,15 @@ class ProtocolContractTest(unittest.TestCase): "timing_anomalies": 8, "gaps": [], }, + "direct_usb_73e5680.trk": { + "sha256": "fd34bb3bf8f92a64960024f1287e553c03076ff3714fe629ec629bec81ddf821", + "sample_count": 1184, + "first_sequence": 0, + "last_sequence": 1183, + "max_dropped": 0, + "timing_anomalies": 2, + "gaps": [], + }, } for name, contract in expected.items(): @@ -285,29 +294,44 @@ class ProtocolContractTest(unittest.TestCase): self.assertEqual(contract["gaps"], gaps) def test_exact_usb_raw_wire_evidence(self) -> None: - wire = ( - ROOT / "tests" / "fixtures" / "direct_usb_3c95f3d.wire" - ).read_bytes() - validated = ( - ROOT / "tests" / "fixtures" / "direct_usb_3c95f3d.trk" - ).read_bytes() - self.assertEqual( - "3bdaeadff7962c6eac48c4ebeda285c8eb359e439d5e1728104add2009122c03", - hashlib.sha256(wire).hexdigest(), - ) + expected = { + "direct_usb_3c95f3d": { + "sha256": "3bdaeadff7962c6eac48c4ebeda285c8eb359e439d5e1728104add2009122c03", + "frames": 214, + "skipped": 563, + }, + "direct_usb_73e5680": { + "sha256": "82d6d17bbf0729e9bfc53f337ec9adf70bcb5e5898b039685eda5dfa19cf4eea", + "frames": 151, + "skipped": 3589, + }, + } + for stem, contract in expected.items(): + with self.subTest(fixture=stem): + wire = ( + ROOT / "tests" / "fixtures" / f"{stem}.wire" + ).read_bytes() + validated = ( + ROOT / "tests" / "fixtures" / f"{stem}.trk" + ).read_bytes() + self.assertEqual( + contract["sha256"], hashlib.sha256(wire).hexdigest() + ) - parser = StreamParser() - frames = [] - for offset in range(0, len(wire), 113): - frames.extend(parser.feed(wire[offset : offset + 113])) + parser = StreamParser() + frames = [] + for offset in range(0, len(wire), 113): + frames.extend(parser.feed(wire[offset : offset + 113])) - self.assertEqual(214, len(frames)) - self.assertEqual(validated, b"".join(frame.raw for frame in frames)) - self.assertEqual(0, parser.startup_crc_errors) - self.assertEqual(0, parser.crc_errors) - self.assertEqual(1, parser.header_errors) - self.assertEqual(563, parser.skipped_bytes) - self.assertEqual(0, parser.buffered_bytes) + self.assertEqual(contract["frames"], len(frames)) + self.assertEqual( + validated, b"".join(frame.raw for frame in frames) + ) + self.assertEqual(0, parser.startup_crc_errors) + self.assertEqual(0, parser.crc_errors) + self.assertEqual(1, parser.header_errors) + self.assertEqual(contract["skipped"], parser.skipped_bytes) + self.assertEqual(0, parser.buffered_bytes) if __name__ == "__main__": diff --git a/tools/capture_binary.py b/tools/capture_binary.py index 5d7bbb2..a8f1dcb 100644 --- a/tools/capture_binary.py +++ b/tools/capture_binary.py @@ -6,6 +6,7 @@ import csv import glob import signal import sys +import time from contextlib import ExitStack from datetime import datetime from pathlib import Path @@ -27,6 +28,11 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--port", help="serial port; auto-detected when omitted") parser.add_argument("--baud", type=int, default=115200) + parser.add_argument( + "--reset", + action="store_true", + help="hard-reset the ESP32-C3 after opening the serial port", + ) parser.add_argument("--output", type=Path, help="validated binary .trk output") parser.add_argument("--csv", type=Path, help="decoded CSV output") parser.add_argument( @@ -109,6 +115,16 @@ def main() -> int: if args.wire is not None else None ) + if args.reset: + # Match ESP-IDF monitor's USB Serial/JTAG hard-reset state: + # release DTR/RTS first, discard the old session, pulse reset, + # and keep this same reader open for the new boot stream. + sensor.dtr = False + sensor.rts = False + sensor.reset_input_buffer() + sensor.rts = True + time.sleep(0.2) + sensor.rts = False writer = csv.writer(decoded) writer.writerow(CSV_COLUMNS) while not stop_requested: