161 lines
7.8 KiB
Python
161 lines
7.8 KiB
Python
import json
|
|
import unittest
|
|
|
|
from h8536.model import Instruction
|
|
from h8536.serial_reconstruction import (
|
|
analyze_serial_reconstruction,
|
|
serial_reconstruction_comment_for_instruction,
|
|
serial_reconstruction_json_payload,
|
|
serial_reconstruction_metadata_for_instruction,
|
|
)
|
|
|
|
|
|
def ins(
|
|
address: int,
|
|
mnemonic: str,
|
|
operands: str = "",
|
|
references: list[int] | None = None,
|
|
kind: str = "normal",
|
|
targets: list[int] | None = None,
|
|
) -> Instruction:
|
|
return Instruction(
|
|
address,
|
|
b"",
|
|
mnemonic,
|
|
operands,
|
|
kind=kind,
|
|
targets=targets or [],
|
|
references=references or [],
|
|
)
|
|
|
|
|
|
class SerialReconstructionTest(unittest.TestCase):
|
|
def test_candidate_sci1_tx_frame_length_and_checksum_pattern(self):
|
|
instructions = {
|
|
0x3000: ins(0x3000, "MOV:G.B", "#H'02, @H'F858", [0xF858]),
|
|
0x3004: ins(0x3004, "MOV:G.B", "@H'F858, R0", [0xF858]),
|
|
0x3008: ins(0x3008, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
|
|
0x300C: ins(0x300C, "MOV:G.B", "#1, @H'F9C2", [0xF9C2]),
|
|
0x301C: ins(0x301C, "MOV:E.B", "#H'5A, R1", []),
|
|
0x3020: ins(0x3020, "XOR.B", "@H'F858, R1", [0xF858]),
|
|
0x3024: ins(0x3024, "XOR.B", "@H'F859, R1", [0xF859]),
|
|
0x3028: ins(0x3028, "XOR.B", "@H'F85A, R1", [0xF85A]),
|
|
0x302C: ins(0x302C, "XOR.B", "@H'F85B, R1", [0xF85B]),
|
|
0x3030: ins(0x3030, "XOR.B", "@H'F85C, R1", [0xF85C]),
|
|
0x3034: ins(0x3034, "MOV:G.B", "R1, @H'F85D", [0xF85D]),
|
|
0x3100: ins(0x3100, "MOV:G.B", "@H'F9C2, R2", [0xF9C2]),
|
|
0x3104: ins(0x3104, "MOV:G.B", "@(-H'07A8,R2), R0", []),
|
|
0x3108: ins(0x3108, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
|
|
0x310C: ins(0x310C, "ADD:Q.B", "#1, @H'F9C2", [0xF9C2]),
|
|
0x3110: ins(0x3110, "CMP:G.B", "#6, @H'F9C2", [0xF9C2]),
|
|
}
|
|
|
|
analysis = analyze_serial_reconstruction(instructions)
|
|
|
|
self.assertEqual(len(analysis["candidates"]), 1)
|
|
candidate = analysis["candidates"][0]
|
|
self.assertEqual(candidate["kind"], "candidate_sci1_tx_frame")
|
|
self.assertEqual(candidate["channel"], "SCI1")
|
|
self.assertEqual(candidate["frame_length"], 6)
|
|
self.assertEqual(candidate["buffer_start"], 0xF858)
|
|
self.assertEqual(candidate["checksum_address"], 0xF85D)
|
|
self.assertEqual(candidate["tx_index_address"], 0xF9C2)
|
|
self.assertEqual(candidate["checksum_seed"], 0x5A)
|
|
self.assertEqual(candidate["confidence"], "high")
|
|
self.assertEqual(candidate["confidence_score"], 0.95)
|
|
self.assertEqual(candidate["missing_evidence"], [])
|
|
|
|
evidence_addresses = candidate["evidence_addresses"]
|
|
self.assertIn(0x3034, evidence_addresses["checksum_byte"])
|
|
self.assertIn(0x3034, evidence_addresses["xor_checksum_chain"])
|
|
self.assertIn(0x3108, evidence_addresses["tx_isr_indexed_send"])
|
|
self.assertEqual(evidence_addresses["tx_index_compare_frame_length"], [0x3110])
|
|
self.assertEqual(evidence_addresses["tx_checksum_seed"], [0x301C])
|
|
|
|
checksum_comment = serial_reconstruction_comment_for_instruction(analysis, 0x3034)
|
|
self.assertIn("candidate/evidence-supported SCI1 6-byte TX frame", checksum_comment)
|
|
self.assertIn("checksum H'F85D", checksum_comment)
|
|
self.assertIn("confidence high", checksum_comment)
|
|
|
|
metadata = serial_reconstruction_metadata_for_instruction(analysis, 0x3108)
|
|
self.assertEqual(metadata[0]["candidate_id"], "sci1_tx_frame_f858_len6_candidate")
|
|
self.assertEqual(metadata[0]["evidence"], "tx_isr_indexed_send")
|
|
self.assertIn(0x3108, metadata[0]["evidence_addresses"])
|
|
|
|
payload = serial_reconstruction_json_payload(analysis)
|
|
self.assertEqual(payload["candidates"][0]["frame_length"], 6)
|
|
json.dumps(payload)
|
|
|
|
def test_candidate_sci1_rx_frame_length_and_checksum_validation_pattern(self):
|
|
instructions = {
|
|
0x4FF0: ins(0x4FF0, "BSET.B", "#7, @H'FAA4", [0xFAA4]),
|
|
0x4FF4: ins(0x4FF4, "BCLR.B", "#5, @SCI1_SSR", [0xFEDC]),
|
|
0x4FF8: ins(0x4FF8, "BCLR.B", "#4, @SCI1_SSR", [0xFEDC]),
|
|
0x4FFC: ins(0x4FFC, "BCLR.B", "#3, @SCI1_SSR", [0xFEDC]),
|
|
0x4FFE: ins(0x4FFE, "BCLR.B", "#6, @SCI1_SSR", [0xFEDC]),
|
|
0x5000: ins(0x5000, "MOV:G.B", "@SCI1_RDR, R0", [0xFEDD]),
|
|
0x5004: ins(0x5004, "MOV:G.B", "R0, @(-H'0798,R1)", []),
|
|
0x5008: ins(0x5008, "ADD:Q.B", "#1, R1", []),
|
|
0x500C: ins(0x500C, "MOV:G.B", "R1, @H'F9C3", [0xF9C3]),
|
|
0x5010: ins(0x5010, "CMP:E", "#H'06, R1", []),
|
|
0x5014: ins(0x5014, "MOV:G.B", "#H'14, @H'F9C5", [0xF9C5]),
|
|
0x5100: ins(0x5100, "CMP:G.B", "#H'06, @H'F9C3", [0xF9C3]),
|
|
0x5104: ins(0x5104, "MOV:G.W", "@H'F868, R0", [0xF868]),
|
|
0x5108: ins(0x5108, "MOV:G.W", "R0, @H'F860", [0xF860]),
|
|
0x510C: ins(0x510C, "MOV:G.W", "@H'F86A, R0", [0xF86A]),
|
|
0x5110: ins(0x5110, "MOV:G.W", "R0, @H'F862", [0xF862]),
|
|
0x5114: ins(0x5114, "MOV:G.W", "@H'F86C, R0", [0xF86C]),
|
|
0x5118: ins(0x5118, "MOV:G.W", "R0, @H'F864", [0xF864]),
|
|
0x5120: ins(0x5120, "MOV:E.B", "#H'5A, R0", []),
|
|
0x5124: ins(0x5124, "XOR.B", "@H'F860, R0", [0xF860]),
|
|
0x5128: ins(0x5128, "XOR.B", "@H'F861, R0", [0xF861]),
|
|
0x512C: ins(0x512C, "XOR.B", "@H'F862, R0", [0xF862]),
|
|
0x5130: ins(0x5130, "XOR.B", "@H'F863, R0", [0xF863]),
|
|
0x5134: ins(0x5134, "XOR.B", "@H'F864, R0", [0xF864]),
|
|
0x5138: ins(0x5138, "CMP:G.B", "@H'F865, R0", [0xF865]),
|
|
}
|
|
|
|
analysis = analyze_serial_reconstruction(instructions)
|
|
|
|
candidate = next(item for item in analysis["candidates"] if item["kind"] == "candidate_sci1_rx_frame")
|
|
self.assertEqual(candidate["frame_length"], 6)
|
|
self.assertEqual(candidate["capture_buffer_start"], 0xF868)
|
|
self.assertEqual(candidate["validation_buffer_start"], 0xF860)
|
|
self.assertEqual(candidate["checksum_address"], 0xF865)
|
|
self.assertEqual(candidate["checksum_seed"], 0x5A)
|
|
self.assertIn("no explicit header", candidate["confidence_reason"])
|
|
self.assertIn("rx_rdrf_clear_before_rdr_read", candidate["evidence_addresses"])
|
|
self.assertIn("rx_eri_falls_through_to_rxi", candidate["evidence_addresses"])
|
|
self.assertTrue(candidate["rx_error_handling"]["fallthrough_to_rx_byte_path"])
|
|
self.assertTrue(candidate["rx_error_handling"]["rdrf_clear_before_rdr_read"])
|
|
self.assertEqual(candidate["rx_error_handling"]["error_latch_address"], 0xFAA4)
|
|
|
|
comment = serial_reconstruction_comment_for_instruction(analysis, 0x5138)
|
|
self.assertIn("candidate/evidence-supported SCI1 6-byte RX frame", comment)
|
|
self.assertIn("checksum H'F865", comment)
|
|
self.assertIn("confidence high", comment)
|
|
self.assertIn(
|
|
"ROM clears SCI1 SSR.RDRF before reading SCI1_RDR",
|
|
serial_reconstruction_comment_for_instruction(analysis, 0x4FFE),
|
|
)
|
|
self.assertIn(
|
|
"SCI1 ERI latches FAA4.bit7",
|
|
serial_reconstruction_comment_for_instruction(analysis, 0x4FF0),
|
|
)
|
|
|
|
def test_lone_tdr_write_does_not_emit_reconstruction(self):
|
|
instructions = {
|
|
0x4000: ins(0x4000, "MOV:G.B", "R0, @SCI1_TDR", [0xFEDB]),
|
|
}
|
|
|
|
analysis = analyze_serial_reconstruction(instructions)
|
|
|
|
self.assertEqual(analysis["candidates"], [])
|
|
self.assertEqual(serial_reconstruction_comment_for_instruction(analysis, 0x4000), "")
|
|
self.assertEqual(serial_reconstruction_metadata_for_instruction(analysis, 0x4000), [])
|
|
self.assertEqual(serial_reconstruction_json_payload(analysis)["candidates"], [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|