#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Failure injection for the host-only Phase-1.0AX AV protocol.""" from __future__ import annotations import argparse from dataclasses import replace from pathlib import Path import sys import unittest PARSER = argparse.ArgumentParser() PARSER.add_argument("--root", type=Path, required=True) ROOT = PARSER.parse_args().root sys.path.insert(0, str(ROOT / "tools")) from phase10ax_canary_protocol_model import * # noqa: E402,F403 def frame(sequence: int, stage: int, value: int) -> bytes: return encode_frame(Frame(sequence, stage, KIND_RAW, RAW0_VALID, value, 0, 0, 0, 0)) def snapshot() -> CleanupSnapshot: return CleanupSnapshot(True, True, S15_COMPLETE, 0, 0x2f, 0, 0, 1) def trace() -> tuple[bytes, ...]: terminal = build_cleanup_terminal(4, snapshot()) assert terminal is not None return (frame(1, D07, -1), frame(2, D12, 5), frame(3, D04, -1), terminal) class CanaryProtocolTests(unittest.TestCase): def test_exact_trace_is_complete_without_visibility_claim(self) -> None: result = validate_trace(trace()) self.assertTrue(result.complete) self.assertEqual((result.submit_result, result.sdl_result), (-1, -1)) self.assertEqual(result.cleaned_mask, 0x2f) self.assertFalse(result.visible_output_proven) self.assertFalse(result.firmware_behavior_proven) self.assertFalse(result.device_action_authorized) def test_every_single_byte_mutation_breaks_frame(self) -> None: raw = frame(1, D07, -1) for index in range(FRAME_SIZE): damaged = bytearray(raw) damaged[index] ^= 1 with self.subTest(index=index), self.assertRaises(CanaryProtocolError): parse_frame(bytes(damaged)) def test_d12_cannot_be_terminal(self) -> None: with self.assertRaises(CanaryProtocolError): encode_frame(Frame(2, D12, KIND_PAIR, RAW0_VALID | RAW1_VALID | TERMINAL, 5, 104, 0, 0, 0)) def test_only_exact_cleanup_state_emits_d14(self) -> None: base = snapshot() self.assertIsNotNone(build_cleanup_terminal(1, base)) cases = (("rarch_main_returned", False), ("d04_emitted", False), ("phase", 14), ("initialized_mask", 1), ("cleanup_order_errors", 1), ("cleanup_failure_count", 1)) for field, value in cases: with self.subTest(field=field): self.assertIsNone(build_cleanup_terminal( 1, replace(base, **{field: value}))) def test_cleanup_failure_is_independent_of_first_runtime_error(self) -> None: base = replace(snapshot(), rarch_main_result=-1, cleanup_failure_count=1) self.assertIsNone(build_cleanup_terminal(1, base)) def test_trace_rejects_order_duplicates_and_post_terminal_data(self) -> None: base = trace() cases = (base[:-1], (base[2], base[0], base[3]), (base[0], base[0], base[2], base[3]), base + (frame(5, 1, 0),), (frame(2, D07, -1), frame(1, D04, -1), base[-1])) for candidate in cases: with self.subTest(candidate=candidate), self.assertRaises(CanaryProtocolError): validate_trace(candidate) def test_recomputed_crc_cannot_hide_cleanup_claim(self) -> None: bad = encode_frame(Frame(4, D14, KIND_PAIR, RAW0_VALID | RAW1_VALID | TERMINAL, 1, 0x2f, 1, 0, 0)) candidate = trace()[:-1] + (bad,) with self.assertRaises(CanaryProtocolError): validate_trace(candidate) def test_valid_crc_with_wrong_stage_semantics_fails(self) -> None: base = trace() wrong_submit = encode_frame(Frame(1, D07, KIND_PAIR, RAW0_VALID | RAW1_VALID, -1, 0, 0, 0, 0)) terminal = build_cleanup_terminal(5, snapshot()) assert terminal is not None duplicate_d12 = (frame(1, D07, -1), frame(2, D12, 5), frame(3, D12, 5), frame(4, D04, -1), terminal) for candidate in ((wrong_submit,) + base[1:], duplicate_d12): with self.subTest(candidate=candidate), self.assertRaises(CanaryProtocolError): validate_trace(candidate) def test_invalid_numeric_and_boolean_boundaries_fail(self) -> None: with self.assertRaises(CanaryProtocolError): encode_frame(Frame(0, D07, KIND_RAW, RAW0_VALID, 0, 0, 0, 0, 0)) with self.assertRaises(CanaryProtocolError): build_cleanup_terminal(1, replace(snapshot(), d04_emitted=1)) with self.assertRaises(CanaryProtocolError): build_cleanup_terminal(1, replace(snapshot(), cleaned_mask=-1)) if __name__ == "__main__": unittest.main(argv=[__file__])