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()