120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
#!/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__])
|