161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
import unittest
|
|
|
|
from h8536.serial_semantics import analyze_serial_semantics
|
|
|
|
|
|
def reference(address: int) -> dict:
|
|
return {"address": address}
|
|
|
|
|
|
def instruction(
|
|
address: int,
|
|
mnemonic: str,
|
|
operands: str = "",
|
|
references: list[int] | None = None,
|
|
targets: list[int] | None = None,
|
|
) -> dict:
|
|
return {
|
|
"address": address,
|
|
"mnemonic": mnemonic,
|
|
"operands": operands,
|
|
"references": [reference(item) for item in (references or [])],
|
|
"targets": targets or [],
|
|
}
|
|
|
|
|
|
def base_payload(instructions: list[dict]) -> dict:
|
|
return {
|
|
"serial_reconstruction": {
|
|
"candidates": [
|
|
{
|
|
"kind": "candidate_sci1_rx_frame",
|
|
"channel": "SCI1",
|
|
"frame_length": 6,
|
|
"validation_buffer_start": 0xF860,
|
|
"validation_buffer_end": 0xF865,
|
|
"checksum_address": 0xF865,
|
|
"checksum_seed": 0x5A,
|
|
"confidence": "high",
|
|
},
|
|
{
|
|
"kind": "candidate_sci1_tx_frame",
|
|
"channel": "SCI1",
|
|
"frame_length": 6,
|
|
"buffer_start": 0xF850,
|
|
"buffer_end": 0xF855,
|
|
"checksum_address": 0xF855,
|
|
"checksum_seed": 0x5A,
|
|
"confidence": "high",
|
|
},
|
|
],
|
|
},
|
|
"instructions": instructions,
|
|
}
|
|
|
|
|
|
def only_semantics(testcase: unittest.TestCase, payload: dict) -> dict:
|
|
analysis = analyze_serial_semantics(payload)
|
|
testcase.assertEqual(analysis["kind"], "serial_semantics")
|
|
testcase.assertEqual(len(analysis["protocol_semantics"]), 1)
|
|
return analysis["protocol_semantics"][0]
|
|
|
|
|
|
class SerialSemanticsTest(unittest.TestCase):
|
|
def test_detects_low_three_bit_command_dispatch(self):
|
|
payload = base_payload(
|
|
[
|
|
instruction(0xBA80, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
|
instruction(0xBA84, "AND.B", "#H'07, R0"),
|
|
instruction(0xBA88, "CMP:E.B", "#H'00, R0"),
|
|
instruction(0xBA8C, "BEQ", "loc_BAA0", targets=[0xBAA0]),
|
|
instruction(0xBA90, "CMP:E.B", "#H'02, R0"),
|
|
instruction(0xBA94, "BEQ", "loc_BAC0", targets=[0xBAC0]),
|
|
instruction(0xBA98, "CMP:E.B", "#H'07, R0"),
|
|
instruction(0xBA9C, "BEQ", "loc_BAE0", targets=[0xBAE0]),
|
|
]
|
|
)
|
|
|
|
semantics = only_semantics(self, payload)
|
|
|
|
dispatch = semantics["command_dispatch"]
|
|
self.assertEqual(dispatch["source_address"], 0xF860)
|
|
self.assertEqual(dispatch["source_field"], "byte0")
|
|
self.assertEqual(dispatch["mask"], 0x07)
|
|
self.assertEqual(dispatch["field"], "command_low3")
|
|
self.assertEqual(
|
|
{(case["value"], case["target"]) for case in dispatch["cases"]},
|
|
{(0x00, 0xBAA0), (0x02, 0xBAC0), (0x07, 0xBAE0)},
|
|
)
|
|
self.assertIn(0xBA80, dispatch["evidence_addresses"])
|
|
self.assertIn(0xBA84, dispatch["evidence_addresses"])
|
|
|
|
def test_labels_likely_rx_fields_from_validation_buffer_offsets(self):
|
|
payload = base_payload(
|
|
[
|
|
instruction(0xBB00, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
|
instruction(0xBB04, "AND.B", "#H'07, R0"),
|
|
instruction(0xBB08, "MOV:G.W", "@H'F861, R1", [0xF861]),
|
|
instruction(0xBB0C, "MOV:G.W", "@H'F863, R2", [0xF863]),
|
|
]
|
|
)
|
|
|
|
semantics = only_semantics(self, payload)
|
|
|
|
fields = {field["offset"]: field for field in semantics["rx_fields"]}
|
|
self.assertEqual(fields[0]["name"], "command_low3")
|
|
self.assertEqual(fields[0]["address"], 0xF860)
|
|
self.assertEqual(fields[0]["mask"], 0x07)
|
|
self.assertEqual(fields[1]["name"], "likely_id_or_index")
|
|
self.assertEqual(fields[2]["name"], "likely_id_or_index")
|
|
self.assertEqual(fields[3]["name"], "likely_value")
|
|
self.assertEqual(fields[4]["name"], "likely_value")
|
|
self.assertIn("candidate", fields[1]["confidence"])
|
|
self.assertIn("candidate", fields[3]["confidence"])
|
|
|
|
def test_detects_response_builder_before_serial_send_call(self):
|
|
payload = base_payload(
|
|
[
|
|
instruction(0xBC00, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
|
instruction(0xBC04, "MOV:G.B", "R0, @H'F850", [0xF850]),
|
|
instruction(0xBC08, "MOV:G.B", "@H'F861, R1", [0xF861]),
|
|
instruction(0xBC0C, "MOV:G.B", "R1, @H'F851", [0xF851]),
|
|
instruction(0xBC10, "MOV:G.B", "@H'F862, R2", [0xF862]),
|
|
instruction(0xBC14, "MOV:G.B", "R2, @H'F852", [0xF852]),
|
|
instruction(0xBC18, "MOV:G.B", "#H'00, @H'F853", [0xF853]),
|
|
instruction(0xBC1C, "MOV:G.B", "#H'01, @H'F854", [0xF854]),
|
|
instruction(0xBC20, "BSR", "loc_BA26", targets=[0xBA26]),
|
|
]
|
|
)
|
|
|
|
semantics = only_semantics(self, payload)
|
|
|
|
response = semantics["response_builders"][0]
|
|
self.assertEqual(response["buffer_start"], 0xF850)
|
|
self.assertEqual(response["buffer_end"], 0xF854)
|
|
self.assertEqual(response["send_call_target"], 0xBA26)
|
|
self.assertEqual(response["call_address"], 0xBC20)
|
|
self.assertEqual(
|
|
[write["address"] for write in response["writes"]],
|
|
[0xF850, 0xF851, 0xF852, 0xF853, 0xF854],
|
|
)
|
|
|
|
def test_missing_serial_reconstruction_candidates_emit_no_protocol_semantics(self):
|
|
payload = {
|
|
"serial_reconstruction": {"candidates": []},
|
|
"instructions": [
|
|
instruction(0xBA80, "MOV:G.B", "@H'F860, R0", [0xF860]),
|
|
instruction(0xBA84, "AND.B", "#H'07, R0"),
|
|
instruction(0xBA88, "CMP:E.B", "#H'00, R0"),
|
|
instruction(0xBA8C, "BEQ", "loc_BAA0", targets=[0xBAA0]),
|
|
],
|
|
}
|
|
|
|
analysis = analyze_serial_semantics(payload)
|
|
|
|
self.assertEqual(analysis["kind"], "serial_semantics")
|
|
self.assertEqual(analysis["protocol_semantics"], [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|