harden USB telemetry transport
This commit is contained in:
@@ -10,6 +10,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from trikke_protocol import ( # noqa: E402
|
||||
IntegrityTracker,
|
||||
PACKET_FLAG_TIMESTAMP_DELTA_SATURATED,
|
||||
PACKET_TYPE_METADATA,
|
||||
PACKET_TYPE_SAMPLES,
|
||||
@@ -50,6 +51,34 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
)
|
||||
cls.encoded = fixture.stdout
|
||||
|
||||
transport_executable = Path(cls.tempdir.name) / "transport_fixture"
|
||||
subprocess.run(
|
||||
[
|
||||
compiler,
|
||||
"-std=c11",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-I",
|
||||
str(ROOT / "main"),
|
||||
str(ROOT / "main" / "trikke_transport.c"),
|
||||
str(ROOT / "tests" / "transport_fixture.c"),
|
||||
"-o",
|
||||
str(transport_executable),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
transport_fixture = subprocess.run(
|
||||
[str(transport_executable)], capture_output=True
|
||||
)
|
||||
if transport_fixture.returncode != 0:
|
||||
stderr = transport_fixture.stderr.decode(errors="replace").strip()
|
||||
raise AssertionError(
|
||||
"transport fixture exited "
|
||||
f"{transport_fixture.returncode}: {stderr}"
|
||||
)
|
||||
cls.transport_fixture_passed = True
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
cls.tempdir.cleanup()
|
||||
@@ -103,6 +132,18 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
self.assertEqual((2, 1, 258), tuple(row[14:17]))
|
||||
self.assertEqual(3, row[-1])
|
||||
|
||||
def test_transport_state_machine_contract(self) -> None:
|
||||
self.assertTrue(self.transport_fixture_passed)
|
||||
|
||||
def test_integrity_sequence_wrap_classification(self) -> None:
|
||||
self.assertEqual(
|
||||
(0, 0), IntegrityTracker._classify_sequence(0xFFFFFFFF, 0)
|
||||
)
|
||||
self.assertEqual(
|
||||
(2, 0), IntegrityTracker._classify_sequence(0xFFFFFFFE, 1)
|
||||
)
|
||||
self.assertEqual((0, 1), IntegrityTracker._classify_sequence(1000, 0))
|
||||
|
||||
def test_crc_failure_resynchronizes_to_next_frame(self) -> None:
|
||||
first_size = 36 + 48
|
||||
damaged = bytearray(self.encoded[:first_size])
|
||||
@@ -141,6 +182,7 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
"forced_outage_3s.trk": {
|
||||
"sha256": "01482816cdaa668e4681c33c8baa1df331d733b9bbcbc4f448ece25e88185ad6",
|
||||
"sample_count": 2144,
|
||||
"first_sequence": 0,
|
||||
"last_sequence": 2143,
|
||||
"max_dropped": 0,
|
||||
"gaps": [],
|
||||
@@ -148,10 +190,19 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
"forced_outage_7s.trk": {
|
||||
"sha256": "2ea8a5742944bdebc13bec2ccdbceba75f0bb71e48c856b0f86285878e190cd3",
|
||||
"sample_count": 1840,
|
||||
"first_sequence": 0,
|
||||
"last_sequence": 1977,
|
||||
"max_dropped": 138,
|
||||
"gaps": [(511, 650, 1_390_000)],
|
||||
},
|
||||
"direct_usb_stall.trk": {
|
||||
"sha256": "40f874b7eaa7f705524ecdd75f832e8a724252366633116ac015fc75dfd16558",
|
||||
"sample_count": 864,
|
||||
"first_sequence": 8,
|
||||
"last_sequence": 2065,
|
||||
"max_dropped": 1194,
|
||||
"gaps": [(511, 1706, 11_950_000)],
|
||||
},
|
||||
}
|
||||
|
||||
for name, contract in expected.items():
|
||||
@@ -175,7 +226,7 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
|
||||
samples = [sample for frame in frames for sample in frame.samples]
|
||||
self.assertEqual(contract["sample_count"], len(samples))
|
||||
self.assertEqual(0, samples[0].sequence)
|
||||
self.assertEqual(contract["first_sequence"], samples[0].sequence)
|
||||
self.assertEqual(contract["last_sequence"], samples[-1].sequence)
|
||||
self.assertEqual(
|
||||
contract["max_dropped"],
|
||||
@@ -191,6 +242,21 @@ class ProtocolContractTest(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
integrity = IntegrityTracker()
|
||||
for frame in frames:
|
||||
integrity.observe(frame)
|
||||
self.assertEqual(0, integrity.packet_gap_count)
|
||||
self.assertEqual(0, integrity.packet_reset_count)
|
||||
self.assertEqual(
|
||||
contract["max_dropped"], integrity.sample_gap_count
|
||||
)
|
||||
self.assertEqual(0, integrity.sample_reset_count)
|
||||
self.assertEqual(
|
||||
contract["max_dropped"],
|
||||
integrity.final_dropped_sample_count,
|
||||
)
|
||||
self.assertEqual(0, integrity.final_loop_overrun_count)
|
||||
|
||||
gaps = [
|
||||
(
|
||||
left.sequence,
|
||||
|
||||
Reference in New Issue
Block a user