tighten transport failure semantics
This commit is contained in:
Vendored
+12
@@ -31,6 +31,18 @@ removed before production firmware was built and flashed.
|
||||
capture application opened. That is deliberate evidence of the remaining
|
||||
distinction: USB endpoint drain is observable, but application receipt
|
||||
requires the planned acknowledgement/replay layer.
|
||||
- `direct_usb_3c95f3d.trk` — SHA-256
|
||||
`f495486f094a758bb785e145026e3934d60b52dd5083aef7f5d896195d967869`.
|
||||
This is the validated-frame output from the final exact-commit smoke capture:
|
||||
1,680 contiguous samples, sequences 0 through 1,679, with zero packet/sample
|
||||
gaps, resets, CRC failures, reported drops, loop overruns, trailing bytes, or
|
||||
timestamp saturation.
|
||||
- `direct_usb_3c95f3d.wire` — SHA-256
|
||||
`3bdaeadff7962c6eac48c4ebeda285c8eb359e439d5e1728104add2009122c03`.
|
||||
This is the byte-for-byte wire side of the same capture. It contains 563 bytes
|
||||
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.
|
||||
|
||||
`tests/test_trikke_protocol.py` verifies the hashes, parses the captures in
|
||||
fragmented chunks, and asserts these signatures so the hardware evidence remains
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -185,6 +185,7 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
"first_sequence": 0,
|
||||
"last_sequence": 2143,
|
||||
"max_dropped": 0,
|
||||
"timing_anomalies": 2,
|
||||
"gaps": [],
|
||||
},
|
||||
"forced_outage_7s.trk": {
|
||||
@@ -193,6 +194,7 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
"first_sequence": 0,
|
||||
"last_sequence": 1977,
|
||||
"max_dropped": 138,
|
||||
"timing_anomalies": 3,
|
||||
"gaps": [(511, 650, 1_390_000)],
|
||||
},
|
||||
"direct_usb_stall.trk": {
|
||||
@@ -201,8 +203,18 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
"first_sequence": 8,
|
||||
"last_sequence": 2065,
|
||||
"max_dropped": 1194,
|
||||
"timing_anomalies": 1,
|
||||
"gaps": [(511, 1706, 11_950_000)],
|
||||
},
|
||||
"direct_usb_3c95f3d.trk": {
|
||||
"sha256": "f495486f094a758bb785e145026e3934d60b52dd5083aef7f5d896195d967869",
|
||||
"sample_count": 1680,
|
||||
"first_sequence": 0,
|
||||
"last_sequence": 1679,
|
||||
"max_dropped": 0,
|
||||
"timing_anomalies": 8,
|
||||
"gaps": [],
|
||||
},
|
||||
}
|
||||
|
||||
for name, contract in expected.items():
|
||||
@@ -251,6 +263,10 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
contract["max_dropped"], integrity.sample_gap_count
|
||||
)
|
||||
self.assertEqual(0, integrity.sample_reset_count)
|
||||
self.assertEqual(
|
||||
contract["timing_anomalies"],
|
||||
integrity.timing_anomaly_count,
|
||||
)
|
||||
self.assertEqual(
|
||||
contract["max_dropped"],
|
||||
integrity.final_dropped_sample_count,
|
||||
@@ -268,6 +284,31 @@ 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(),
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -99,11 +99,16 @@ int main(void)
|
||||
TRIKKE_TRANSPORT_PENDING ||
|
||||
trikke_transport_sender_step(
|
||||
&sender, &transport, packet, sizeof(packet)) !=
|
||||
TRIKKE_TRANSPORT_RETRY ||
|
||||
sender.pending) {
|
||||
return fail(6, "backend-confirmed safe retry must return to idle");
|
||||
TRIKKE_TRANSPORT_FATAL ||
|
||||
!sender.pending || mock.begin_calls != 4 || mock.poll_calls != 3 ||
|
||||
trikke_transport_sender_step(
|
||||
&sender, &transport, packet, sizeof(packet)) !=
|
||||
TRIKKE_TRANSPORT_FATAL ||
|
||||
mock.begin_calls != 4 || mock.poll_calls != 4) {
|
||||
return fail(6, "retry after acceptance must fail closed without resubmit");
|
||||
}
|
||||
|
||||
trikke_transport_sender_init(&sender);
|
||||
if (trikke_transport_sender_step(
|
||||
NULL, &transport, packet, sizeof(packet)) !=
|
||||
TRIKKE_TRANSPORT_FATAL ||
|
||||
|
||||
Reference in New Issue
Block a user