105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate fixed-byte Phase 3C gateway input and feedback envelopes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import binascii
|
|
import pathlib
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def classify_input(raw: bytes) -> str:
|
|
if len(raw) < 6:
|
|
return "invalid:truncated"
|
|
if raw[:4] != b"VGI1":
|
|
return "invalid:magic"
|
|
kind, length = raw[4], raw[5]
|
|
if len(raw) != 6 + length:
|
|
return "invalid:length"
|
|
body = raw[6:]
|
|
if kind == 1:
|
|
return "valid" if len(body) == 4 and body[0] <= 1 and body[2:4] != b"\0\0" else "invalid:field"
|
|
if kind == 2:
|
|
return "valid" if len(body) == 3 and body[0] <= 1 and 1 <= body[1] <= 5 and body[2] == 0 else "invalid:reserved"
|
|
if kind == 3:
|
|
return "valid" if len(body) == 4 else "invalid:length"
|
|
if kind == 4:
|
|
try:
|
|
decoded = body.decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
return "invalid:utf8"
|
|
return "valid" if 1 <= len(body) <= 4 and len(decoded) == 1 else "invalid:utf8"
|
|
if kind == 5:
|
|
if len(body) != 17:
|
|
return "invalid:length"
|
|
if body[0] > 15:
|
|
return "invalid:field"
|
|
active_mask = int.from_bytes(body[1:3], "big")
|
|
return "valid" if active_mask or not any(body[3:]) else "invalid:field"
|
|
if kind == 6:
|
|
if len(body) != 8:
|
|
return "invalid:length"
|
|
x, y, width, height = (int.from_bytes(body[index:index + 2], "big") for index in range(0, 8, 2))
|
|
return "valid" if width and height and x < width and y < height else "invalid:field"
|
|
if kind == 7:
|
|
return "valid" if len(body) == 4 else "invalid:length"
|
|
return "invalid:kind"
|
|
|
|
|
|
def classify_feedback(raw: bytes) -> str:
|
|
if len(raw) < 8:
|
|
return "invalid:truncated"
|
|
if raw[:4] != b"VGF1":
|
|
return "invalid:magic"
|
|
direction, kind = raw[4], raw[5]
|
|
length = int.from_bytes(raw[6:8], "big")
|
|
if len(raw) != 8 + length:
|
|
return "invalid:length"
|
|
if direction not in (0, 1):
|
|
return "invalid:direction"
|
|
body = raw[8:]
|
|
if direction == 0:
|
|
if kind in (0x10, 0x11, 0x12):
|
|
return "invalid:direction"
|
|
if kind == 1:
|
|
return "valid" if not body else "invalid:length"
|
|
if kind == 2:
|
|
return "valid" if valid_fec_status(body) else "invalid:field"
|
|
if kind == 3:
|
|
return "valid" if not body else "invalid:length"
|
|
return "invalid:type"
|
|
if kind in (1, 2, 3):
|
|
return "invalid:direction"
|
|
if kind == 0x10:
|
|
return "valid" if len(body) == 4 else "invalid:length"
|
|
if kind == 0x11:
|
|
return "valid" if len(body) == 5 and body[0] <= 15 else "invalid:field"
|
|
if kind == 0x12:
|
|
return "valid" if len(body) == 1 and body[0] <= 1 else "invalid:field"
|
|
return "invalid:type"
|
|
|
|
|
|
def valid_fec_status(body: bytes) -> bool:
|
|
return len(body) == 21 and int.from_bytes(body[10:12], "big") > 0 and int.from_bytes(body[14:16], "big") <= int.from_bytes(body[10:12], "big") and int.from_bytes(body[16:18], "big") <= int.from_bytes(body[12:14], "big") and body[18] <= 100 and body[20] > 0 and body[19] < body[20]
|
|
|
|
|
|
def main() -> None:
|
|
lines = (ROOT / "fixtures/conformance/gateway-input-feedback-v1.tsv").read_text(encoding="utf-8").splitlines()
|
|
assert lines[0] == "id\tversion\tkind\tinput\texpected"
|
|
for line in lines[1:]:
|
|
identifier, version, kind, input_value, expected = line.split("\t")
|
|
assert version == "1"
|
|
try:
|
|
raw = binascii.unhexlify(input_value.removeprefix("hex="))
|
|
except binascii.Error:
|
|
actual = "invalid:hex"
|
|
else:
|
|
actual = classify_input(raw) if kind == "gateway_input" else classify_feedback(raw)
|
|
assert actual == expected, f"{identifier}: {actual} != {expected}"
|
|
print("Gateway input/feedback envelope validation passed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|