More decompiling work
This commit is contained in:
154
tests/test_protocol_trace.py
Normal file
154
tests/test_protocol_trace.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import io
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from h8536.protocol_trace import (
|
||||
checksum_for,
|
||||
decode_trace,
|
||||
format_text_report,
|
||||
main,
|
||||
parse_byte_text,
|
||||
)
|
||||
|
||||
|
||||
def frame(prefix: list[int]) -> list[int]:
|
||||
return prefix + [checksum_for(prefix)]
|
||||
|
||||
|
||||
class ProtocolTraceTest(unittest.TestCase):
|
||||
def test_decodes_six_byte_frame_fields_and_checksum(self):
|
||||
decoded = decode_trace(frame([0x08, 0x85, 0x34, 0x12, 0xAB]), direction="rx")
|
||||
only = decoded["frames"][0]
|
||||
|
||||
self.assertTrue(only["checksum"]["valid"])
|
||||
self.assertEqual(only["direction"], "rx")
|
||||
self.assertEqual(only["command"]["value"], 0)
|
||||
self.assertEqual(only["index"]["byte1_low3"], 5)
|
||||
self.assertEqual(only["index"]["combined"], 0x0534)
|
||||
self.assertEqual(only["payload_value"]["word_be"], 0x12AB)
|
||||
self.assertEqual(only["payload_value"]["word_le"], 0xAB12)
|
||||
|
||||
def test_reports_bad_checksum_and_trailing_bytes(self):
|
||||
decoded = decode_trace([0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x99])
|
||||
only = decoded["frames"][0]
|
||||
|
||||
self.assertFalse(only["checksum"]["valid"])
|
||||
self.assertEqual(only["checksum"]["expected"], checksum_for([1, 2, 3, 4, 5]))
|
||||
self.assertEqual(decoded["trailing_bytes"], ["0x99"])
|
||||
|
||||
def test_loads_semantic_command_names_from_decompiler_json(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "rom.json"
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"serial_semantics": {
|
||||
"protocol_semantics": [
|
||||
{
|
||||
"command_effects": [
|
||||
{
|
||||
"command_value": 1,
|
||||
"name_candidate": "read_value",
|
||||
}
|
||||
],
|
||||
"response_schema": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
decoded = decode_trace(frame([0x09, 0, 1, 2, 3]), semantics_path=path)
|
||||
|
||||
self.assertTrue(decoded["semantics"]["loaded"])
|
||||
self.assertEqual(decoded["frames"][0]["command"]["name_candidate"], "read_value")
|
||||
|
||||
def test_marks_rx_cmd_7_as_retransmit_or_error_candidate_with_previous_frame(self):
|
||||
prior = frame([0x01, 0, 0, 0, 0])
|
||||
retry = frame([0x07, 0, 0, 0, 0])
|
||||
|
||||
decoded = decode_trace(prior + retry, direction="rx")
|
||||
annotation = decoded["frames"][1]["stateful_annotations"][0]
|
||||
|
||||
self.assertEqual(annotation["kind"], "retransmit_or_error_candidate")
|
||||
self.assertEqual(annotation["previous_valid_same_direction"]["frame_index"], 0)
|
||||
|
||||
def test_decodes_observed_tx_frames_as_reports(self):
|
||||
samples = [
|
||||
([0x00, 0x00, 0x00, 0x00, 0x80, 0xDA], 0x0000, 0x0080, "heartbeat_alive_candidate", None),
|
||||
([0x00, 0x00, 0x15, 0x80, 0x00, 0xCF], 0x0015, 0x8000, "call_button_candidate", "active"),
|
||||
([0x00, 0x00, 0x15, 0x00, 0x00, 0x4F], 0x0015, 0x0000, "call_button_candidate", "inactive"),
|
||||
([0x00, 0x00, 0x07, 0x80, 0x00, 0xDD], 0x0007, 0x8000, "cam_power_button_candidate", "active"),
|
||||
]
|
||||
|
||||
decoded = decode_trace([byte for sample, *_ in samples for byte in sample], direction="tx")
|
||||
|
||||
for actual, (_, index, value, name, state) in zip(decoded["frames"], samples):
|
||||
self.assertTrue(actual["checksum"]["valid"])
|
||||
self.assertFalse(actual["command"]["applicable"])
|
||||
self.assertIsNone(actual["command"]["name_candidate"])
|
||||
self.assertEqual(actual["response_schema_candidates"], [])
|
||||
self.assertEqual(actual["stateful_annotations"], [])
|
||||
self.assertEqual(actual["report"]["index"], index)
|
||||
self.assertEqual(actual["report"]["value"], value)
|
||||
self.assertEqual(actual["report"]["observed_candidate"]["name_candidate"], name)
|
||||
self.assertEqual(actual["report"]["observed_candidate"].get("state_candidate"), state)
|
||||
|
||||
def test_tx_text_report_does_not_render_cmd_or_set_value_acked(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "rom.json"
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"serial_semantics": {
|
||||
"command_effects": [
|
||||
{
|
||||
"command_value": 0,
|
||||
"name_candidate": "set_value_acked",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
decoded = decode_trace(
|
||||
[0x00, 0x00, 0x15, 0x80, 0x00, 0xCF],
|
||||
direction="tx",
|
||||
semantics_path=path,
|
||||
)
|
||||
|
||||
text = format_text_report(decoded)
|
||||
|
||||
self.assertIn("report_index=0x0015", text)
|
||||
self.assertIn("observed_candidate=call_button_candidate", text)
|
||||
self.assertNotIn("cmd=", text)
|
||||
self.assertNotIn("set_value_acked", text)
|
||||
|
||||
def test_auto_direction_uses_rx_tx_prefixes(self):
|
||||
events = parse_byte_text(
|
||||
"rx: 01 00 00 00 00 5B\n"
|
||||
"tx: 04 00 00 00 00 5E\n"
|
||||
)
|
||||
|
||||
decoded = decode_trace(events, direction="auto")
|
||||
|
||||
self.assertEqual(decoded["frames"][0]["direction"], "rx")
|
||||
self.assertEqual(decoded["frames"][1]["direction"], "tx")
|
||||
|
||||
def test_cli_json_output(self):
|
||||
output = io.StringIO()
|
||||
rc = main(["--json", "01", "00", "00", "00", "00", "5B"], stdout=output)
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(output.getvalue())
|
||||
self.assertEqual(payload["frames"][0]["command"]["value"], 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user