#!/usr/bin/env python3 """Independent verifier of the signal-health corpus (extensions/signal-health.md). Written from the extension's text. It shares no code with wai-rs, which generates the corpus, and never runs it. It takes Ed25519 verification as SPEC §7 *Signatures* states it from the routine every Python verifier of WAI's corpora shares (`../claim-conformance/_ed25519.py`). Everything this extension defines is written here: the record's layout and rules (§4, §6.1), its verdicts (§6.2), the profile (§6.3), each check's values recomputed from its media (§5, §6.4), the transport-stream indicators (§5.6) and the input switch (§7). For every case it requires the outcome the corpus states, field for field; that every reason of §8's table appears in the corpus; then it mutates copies of the corpus, one defect each, and requires every copy to fail; and it drops each of its own checks in turn and requires the corpus to catch the weakened verifier. python3 signal_health_verify.py Exit status 0 only if every case verifies, every mutated copy fails and every weakened verifier is caught. Python 3.8 or later and the standard library. """ import copy import hashlib import json import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) import _ed25519 as ED # noqa: E402 EXTENSION = os.path.join(HERE, "..", "extensions", "signal-health.md") MAGIC = b"WSH1" DOMAIN = b"wai:signal-health\x01" MAX_LEN = 1024 MAX_INT = (1 << 53) - 1 NAMES = ["black", "freeze", "silence", "captions", "cues", "transport"] NAME_BYTES = set(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-:") # §4.2: each code's thresholds and values, each (name, bytes). THRESHOLDS = { 1: [("luma_max", 1), ("ratio_ppm", 4), ("min_ms", 4)], 2: [("mad_milli", 4), ("min_ms", 4)], 3: [("peak_max", 2), ("min_ms", 4)], 4: [("max_gap_ms", 4)], 5: [("max_gap_ms", 4)], 6: [("table_max_gap_ms", 4), ("pid_max_gap_ms", 4)], } RUNS = [("count", 4), ("matched", 4), ("longest_ms", 8), ("longest_at_ms", 8)] SEEN = [("seen", 4), ("last_ms", 8), ("longest_gap_ms", 8)] INDICATORS = [("packets", 8), ("sync_loss", 4), ("sync_byte", 4), ("pat", 4), ("cc", 4), ("pmt", 4), ("pid", 4)] VALUES = {1: RUNS, 2: RUNS, 3: RUNS, 4: SEEN, 5: SEEN, 6: INDICATORS} # A weakened run drops one of these checks (see `WEAKENINGS`); None checks everything. DROPPED = None def drops(name): return DROPPED == name def reasons_table(): """§8's codes, in order.""" text = open(EXTENSION, encoding="utf-8").read() at = text.index("\n## 8. Reasons") end = text.find("\n## ", at + 1) rows = text[at:end if end >= 0 else len(text)].splitlines() return [r.split("`")[1] for r in rows if r.startswith("| `")] class Refused(Exception): def __init__(self, reason): super().__init__(reason) self.reason = reason # ------------------------------------------------------------------ §4, §6.1, §6.2: the record def read(b): """The record as a dict, or Refused: the layout and rules of §6.1, the signature, §6.2.""" bad = Refused("signal_health_malformed") if len(b) > MAX_LEN: raise bad at = 0 def take(n): nonlocal at if at + n > len(b): raise bad s = b[at:at + n] at += n return s def num(n): return int.from_bytes(take(n), "big") if take(4) != MAGIC or take(1) != b"\x01": raise bad monitor = take(32) n = num(1) if not 1 <= n <= 64: raise bad name = take(n) if not drops("name") and any(c not in NAME_BYTES for c in name): raise bad frm, to = num(8), num(8) if (frm >= to and not drops("window")) or to > MAX_INT: raise bad window = to - frm m = num(1) if not 1 <= m <= 6: raise bad checks = [] for _ in range(m): code = num(1) if not 1 <= code <= 6: raise bad if checks and checks[-1]["code"] >= code and not drops("order"): raise bad c = {"code": code, "media": take(32), "t": {}, "v": {}} for k, w in THRESHOLDS[code]: c["t"][k] = num(w) for k, w in VALUES[code]: c["v"][k] = num(w) c["fault"] = num(1) t, v = c["t"], c["v"] if not drops("range"): if code == 1 and not (1 <= t["ratio_ppm"] <= 1_000_000): raise bad if code == 3 and t["peak_max"] > 32_768: raise bad if any(t.get(k, 1) < 1 for k in ("min_ms", "table_max_gap_ms", "pid_max_gap_ms")): raise bad if not drops("values"): inside = lambda x: frm <= x < to # noqa: E731 if code <= 3: none = v["matched"] == 0 and v["longest_ms"] == 0 and v["longest_at_ms"] == 0 some = v["matched"] != 0 and v["longest_ms"] != 0 and inside(v["longest_at_ms"]) if v["matched"] > v["count"] or v["longest_ms"] > window or not (none or some): raise bad elif code <= 5: none = v["seen"] == 0 and v["last_ms"] == 0 and v["longest_gap_ms"] == window some = v["seen"] != 0 and inside(v["last_ms"]) if v["longest_gap_ms"] > window or not (none or some): raise bad if c["fault"] not in (0, 1): raise bad checks.append(c) signed = b[:at] sig = take(64) if at != len(b): raise bad if not drops("signature") and ED.verify(monitor, DOMAIN + signed, sig) is not None: raise Refused("signal_health_signature_invalid") if not drops("verdict") and any(fails(c) != bool(c["fault"]) for c in checks): raise Refused("signal_health_verdict_inconsistent") return {"monitor": monitor, "input": name.decode("ascii"), "from": frm, "to": to, "checks": checks, "digest": hashlib.sha256(signed).digest()} def fails(c): """§6.2: the verdict a check's values give under its thresholds.""" t, v = c["t"], c["v"] if c["code"] <= 3: return v["longest_ms"] >= t["min_ms"] if c["code"] <= 5: return t["max_gap_ms"] != 0 and v["longest_gap_ms"] > t["max_gap_ms"] return any(v[k] > 0 for k in ("sync_loss", "sync_byte", "pat", "cc", "pmt", "pid")) def faults(rec): return [NAMES[c["code"] - 1] for c in rec["checks"] if c["fault"]] def conforms(rec, profile): """§6.3: exactly the profile's checks, each with its thresholds.""" if drops("profile"): return True want = {NAMES.index(k) + 1: t for k, t in profile.items()} return len(rec["checks"]) == len(want) and all(want.get(c["code"]) == c["t"] for c in rec["checks"]) # ------------------------------------------------------------------ §5: the media def sha(*parts): return hashlib.sha256(b"".join(parts)).digest() def be(x, n): return x.to_bytes(n, "big") def longest(runs): """The longest of runs (start, duration), the earliest of equals; (0, 0) for none.""" best = (0, 0) for r in runs: if r[1] > best[1]: best = r return best def runs_of(flags): """The runs of True in flags: (first index, last index).""" out, i = [], 0 while i < len(flags): if flags[i]: j = i while j + 1 < len(flags) and flags[j + 1]: j += 1 out.append((i, j)) i = j + 1 else: i += 1 return out def frames_values(frames, code, t, frm, to): ok = all(f["width"] >= 1 and f["height"] >= 1 and len(f["luma"]) == f["width"] * f["height"] and (drops("media_window") or frm <= f["at"] < to) and (i == 0 or frames[i - 1]["at"] < f["at"]) for i, f in enumerate(frames)) if not ok: raise Refused("signal_health_media_malformed") digest = sha(b"wai:signal-health:video\x01", *[be(f["at"], 8) + be(f["width"], 2) + be(f["height"], 2) + f["luma"] for f in frames]) def end(i): return frames[i + 1]["at"] if i + 1 < len(frames) else to flags = [] for i, f in enumerate(frames): n = len(f["luma"]) if code == 1: if drops("black_ratio"): flags.append(all(y <= t["luma_max"] for y in f["luma"])) else: dark = sum(1 for y in f["luma"] if y <= t["luma_max"]) flags.append(1_000_000 * dark >= t["ratio_ppm"] * n) else: if i == 0: flags.append(False) continue p = frames[i - 1] same = (p["width"], p["height"]) == (f["width"], f["height"]) or drops("freeze_size") sad = sum(abs(a - b) for a, b in zip(f["luma"], p["luma"])) flags.append(same and 1000 * sad <= t["mad_milli"] * n) runs = [] for a, z in runs_of(flags): start = frames[a - 1]["at"] if code == 2 and not drops("freeze_hold") else frames[a]["at"] runs.append((start, end(z) - start)) at, ms = longest(runs) return digest, {"count": len(frames), "matched": sum(flags), "longest_ms": ms, "longest_at_ms": at} def audio_values(a, t, frm, to): ch, rate, s = a["channels"], a["rate"], a["samples"] if not (1 <= ch <= 32 and rate % 100 == 0 and 100 <= rate <= 384_000 and len(s) % (2 * ch) == 0): raise Refused("signal_health_media_malformed") vals = [int.from_bytes(s[i:i + 2], "little", signed=True) for i in range(0, len(s), 2)] per = rate // 100 * ch blocks = len(vals) // per if not drops("media_window") and not (a["at"] >= frm and a["at"] + 10 * blocks <= to): raise Refused("signal_health_media_malformed") digest = sha(b"wai:signal-health:audio\x01", be(a["at"], 8), be(rate, 4), be(ch, 2), s) flags = [] for b in range(blocks): block = vals[b * per:(b + 1) * per] if drops("silence_channels"): block = block[::ch] flags.append(max(abs(x) for x in block) <= t["peak_max"]) runs = [(a["at"] + 10 * x, 10 * (z - x + 1)) for x, z in runs_of(flags)] at, ms = longest(runs) return digest, {"count": blocks, "matched": sum(flags), "longest_ms": ms, "longest_at_ms": at} def instants_values(code, inst, frm, to): if not all((drops("media_window") or frm <= x < to) for x in inst) or any(a > b for a, b in zip(inst, inst[1:])): raise Refused("signal_health_media_malformed") domain = b"wai:signal-health:cues\x01" if code == 5 else b"wai:signal-health:captions\x01" digest = sha(domain, be(len(inst), 4), *[be(x, 8) for x in inst]) edges = list(inst) if drops("gap_edges") and inst else [frm] + list(inst) + [to] gaps = [b - a for a, b in zip(edges, edges[1:])] return digest, {"seen": len(inst), "last_ms": inst[-1] if inst else 0, "longest_gap_ms": max(gaps) if gaps else 0} # ------------------------------------------------------------------ §5.5, §5.6: the transport stream def transport_values(ts, t, frm, to): b, first, rate = ts["bytes"], ts["first_us"], ts["rate_bps"] n = len(b) // 188 arrive = lambda k: first + k * 188 * 8 * 1_000_000 // rate # noqa: E731 if not (len(b) % 188 == 0 and n >= 1 and rate >= 1 and (drops("media_window") or (first >= frm * 1000 and arrive(n - 1) < to * 1000))): raise Refused("signal_health_media_malformed") digest = sha(b"wai:signal-health:transport\x01", be(first, 8), be(rate, 8), b) tg, pg = t["table_max_gap_ms"] * 1000, t["pid_max_gap_ms"] * 1000 v = {"packets": n, "sync_loss": 0, "sync_byte": 0, "pat": 0, "cc": 0, "pmt": 0, "pid": 0} st = {"cc": {}, "dups": {}, "pat": frm * 1000, "pmts": {}, "ref": {}} def late(anchor, a, gap): return a - anchor > gap def rerefer(a): union = set() for _, es in st["pmts"].values(): union |= es for q in [q for q in st["ref"] if q not in union]: anchor = st["ref"].pop(q) if late(anchor, a, pg): v["pid"] += 1 for q in union: st["ref"].setdefault(q, a) def section(p, start): s = start + 1 + p[start] return s if s < 188 else None def whole(p, s, least): if s + 3 > 188: return None ln = ((p[s + 1] & 0x0F) << 8) | p[s + 2] e = s + 3 + ln return e if ln >= least and e <= 188 else None def analyse(p, a): pid = ((p[1] & 0x1F) << 8) | p[2] if pid == 0x1FFF: return scr, afc, cc, pusi = p[3] >> 6, (p[3] >> 4) & 3, p[3] & 0x0F, p[1] & 0x40 if afc == 1: start, disc = 4, False elif afc in (2, 3): start, disc = 5 + p[4], p[4] > 0 and p[5] & 0x80 != 0 else: start, disc = 188, False payload = afc in (1, 3) and start < 188 if payload: if pid in st["cc"]: last = st["cc"][pid] if disc and not drops("discontinuity"): st["dups"][pid] = 0 elif cc == last: st["dups"][pid] = st["dups"].get(pid, 0) + 1 if st["dups"][pid] >= 2 or drops("duplicate"): v["cc"] += 1 else: if cc != (last + 1) & 0x0F: v["cc"] += 1 st["dups"][pid] = 0 st["cc"][pid] = cc if pid in st["ref"]: if late(st["ref"][pid], a, pg) and not drops("pid_gap"): v["pid"] += 1 st["ref"][pid] = a if pid == 0: if scr and not drops("scrambling"): v["pat"] += 1 s = section(p, start) if pusi and payload else None if s is not None: if p[s] == 0x00: if late(st["pat"], a, tg) and not drops("table_gap"): v["pat"] += 1 st["pat"] = a e = whole(p, s, 9) if e is not None: maps, i = set(), s + 8 while i + 4 <= e - 4: if (p[i] << 8) | p[i + 1]: maps.add(((p[i + 2] & 0x1F) << 8) | p[i + 3]) i += 4 for m in [m for m in st["pmts"] if m not in maps]: anchor, _ = st["pmts"].pop(m) if late(anchor, a, tg): v["pmt"] += 1 for m in maps: st["pmts"].setdefault(m, (a, set())) rerefer(a) elif p[s] != 0xFF and not drops("pat_table"): v["pat"] += 1 if pid in st["pmts"]: if scr and not drops("scrambling"): v["pmt"] += 1 s = section(p, start) if pusi and payload else None if s is not None and p[s] == 0x02: anchor, es = st["pmts"][pid] if late(anchor, a, tg) and not drops("table_gap"): v["pmt"] += 1 st["pmts"][pid] = (a, es) e = whole(p, s, 13) if e is not None: pil = ((p[s + 10] & 0x0F) << 8) | p[s + 11] new, i = set(), s + 12 + pil while i + 5 <= e - 4: new.add(((p[i + 1] & 0x1F) << 8) | p[i + 2]) i += 5 + (((p[i + 3] & 0x0F) << 8) | p[i + 4]) st["pmts"][pid] = (a, new if not drops("es_removed") else es | new) rerefer(a) hunting, run, bad, locked = True, 0, 0, False for k in range(n): p = b[k * 188:(k + 1) * 188] good = p[0] == 0x47 if hunting: if not good: run = 0 continue run += 1 if run == 5 or drops("hysteresis"): hunting, locked, bad = False, True, 0 for j in range(k - run + 1, k + 1): analyse(b[j * 188:(j + 1) * 188], arrive(j)) elif good: bad = 0 analyse(p, arrive(k)) else: v["sync_byte"] += 1 bad += 1 if bad == 2 or drops("loss_on_two"): v["sync_loss"] += 1 hunting, run = True, 0 if n and not locked: v["sync_loss"] += 1 if not drops("window_end"): e = to * 1000 v["pat"] += late(st["pat"], e, tg) v["pmt"] += sum(1 for anchor, _ in st["pmts"].values() if late(anchor, e, tg)) v["pid"] += sum(1 for anchor in st["ref"].values() if late(anchor, e, pg)) return digest, v def measure(c, media, frm, to): """§5: (digest, values) of the media for check c, None when not held, or Refused.""" code, t = c["code"], c["t"] if code in (1, 2): return None if "frames" not in media else frames_values(media["frames"], code, t, frm, to) if code == 3: return None if "audio" not in media else audio_values(media["audio"], t, frm, to) if code in (4, 5): k = "captions" if code == 4 else "cues" return None if k not in media else instants_values(code, media[k], frm, to) return None if "transport" not in media else transport_values(media["transport"], t, frm, to) def recompute(rec, media): """§6.4: each check's label.""" labels = [] for c in rec["checks"]: try: got = measure(c, media, rec["from"], rec["to"]) except Refused as r: labels.append("reject reason=" + r.reason) continue if got is None: labels.append("not_held") elif got[0] != c["media"] and not drops("digest"): labels.append("reject reason=signal_health_media_mismatch") elif got[1] != c["v"]: labels.append("reject reason=signal_health_not_recomputed") else: labels.append("recomputed") return labels # ------------------------------------------------------------------ §7: the switch def switch(cfg, profile, records, at, current): states, read_ = [], {} for k, b in enumerate(records): try: rec = read(b) if not conforms(rec, profile): raise Refused("signal_health_profile_mismatch") except Refused as r: states.append("reject reason=" + r.reason) continue if rec["monitor"].hex() not in cfg["trusted"] and not drops("trusted"): states.append("untrusted") elif rec["to"] > at and not drops("not_yet"): states.append("not_yet") else: read_[k] = rec states.append("stands") top = {} for rec in read_.values(): top[rec["input"]] = max(top.get(rec["input"], 0), rec["to"]) for k, rec in read_.items(): if rec["to"] < top[rec["input"]] and not drops("supersede"): states[k] = "superseded" elif not drops("contradiction") and any(o["input"] == rec["input"] and o["to"] == rec["to"] and o["digest"] != rec["digest"] for o in read_.values()): states[k] = "contradicted" inputs, selected = [], None for name in cfg["inputs"]: mine = [k for k, r in read_.items() if r["input"] == name] standing = [k for k in mine if states[k] == "stands"] if any(states[k] == "contradicted" for k in mine): st = "contradicted" elif standing: rec = read_[standing[0]] if at - rec["to"] > cfg["max_age_ms"] and not drops("freshness"): st = "stale" elif faults(rec): st = "fault:" + "+".join(faults(rec)) else: st = "healthy" else: st = "none" if st == "healthy" and selected is None: selected = name inputs.append(st) return {"records": states, "inputs": inputs, "selected": selected if selected is not None else current} # ------------------------------------------------------------------ the corpus def media_of(m): out = {} if "frames" in m: out["frames"] = [{"at": f["at_ms"], "width": f["width"], "height": f["height"], "luma": bytes.fromhex(f["luma"])} for f in m["frames"]] if "audio" in m: a = m["audio"] out["audio"] = {"at": a["at_ms"], "rate": a["rate"], "channels": a["channels"], "samples": bytes.fromhex(a["samples"])} for k in ("captions", "cues"): if k in m: out[k] = list(m[k]) if "transport" in m: t = m["transport"] out["transport"] = {"first_us": t["first_us"], "rate_bps": t["rate_bps"], "bytes": bytes.fromhex(t["bytes"])} return out def run(doc, kind, c): if kind == "records": try: rec = read(bytes.fromhex(c["bytes"])) except Refused as r: return {"outcome": "refuse", "reason": r.reason} return {"outcome": "read", "digest": rec["digest"].hex(), "input": rec["input"], "faults": faults(rec)} if kind == "checks": try: rec = read(bytes.fromhex(c["record"])) if c["profile"] is not None and not conforms(rec, c["profile"]): raise Refused("signal_health_profile_mismatch") except Refused as r: return {"record": "reject reason=" + r.reason} return {"record": "stands", "checks": recompute(rec, media_of(c["media"]))} return switch(doc["switch"], doc["profile"], [bytes.fromhex(x) for x in c["records"]], c["at_ms"], c["current"]) KINDS = ("records", "checks", "switches") def strings(v, out): if isinstance(v, str): out.append(v) elif isinstance(v, list): for x in v: strings(x, out) elif isinstance(v, dict): for x in v.values(): strings(x, out) def check(doc, quiet=False): """The failures of a corpus document: each case's outcome, and every reason of §8.""" fails = [] for kind in KINDS: for c in doc[kind]: try: got = run(doc, kind, c) except (Refused, KeyError, TypeError, ValueError, IndexError, ZeroDivisionError) as e: got = {"error": repr(e)} if got != c["outcome"]: fails.append(c["name"]) if not quiet: print(f"FAIL {kind} {c['name']}\n stated {json.dumps(c['outcome'])}\n got {json.dumps(got)}") stated = [] for kind in KINDS: for c in doc[kind]: strings(c["outcome"], stated) for code in reasons_table(): if not any(s == code or s.endswith("reason=" + code) for s in stated): fails.append("no case gives " + code) return fails def mutations(doc): """Copies of the corpus, one defect each, every one of which must fail.""" def case(d, kind, name): return next(c for c in d[kind] if c["name"] == name) def m_bit(d): c = case(d, "records", "healthy") b = bytearray.fromhex(c["bytes"]) b[60] ^= 0x01 c["bytes"] = b.hex() def m_digest(d): case(d, "records", "black")["outcome"]["digest"] = "00" * 32 def m_faults(d): case(d, "records", "silence")["outcome"]["faults"] = [] def m_luma(d): f = case(d, "checks", "black")["media"]["frames"][6] f["luma"] = "ff" + f["luma"][2:] def m_threshold(d): case(d, "checks", "healthy")["profile"]["black"]["min_ms"] = 800 def m_doc_threshold(d): d["profile"]["transport"]["pid_max_gap_ms"] = 999 def m_packet(d): t = case(d, "checks", "transport_cc")["media"]["transport"] b = bytearray.fromhex(t["bytes"]) b[30 * 188 + 5] ^= 0x80 t["bytes"] = b.hex() def m_rate(d): case(d, "checks", "transport_pid")["media"]["transport"]["rate_bps"] += 1 def m_caption(d): case(d, "checks", "captions")["media"]["captions"][1] += 1 def m_at(d): case(d, "switches", "primary_stale")["at_ms"] -= 2_000 def m_inputs(d): d["switch"]["inputs"].reverse() def m_trusted(d): d["switch"]["trusted"].pop() def m_reason_gone(d): d["checks"].remove(case(d, "checks", "value_not_given")) def m_media_gone(d): del case(d, "checks", "healthy")["media"]["frames"] return [m_bit, m_digest, m_faults, m_luma, m_threshold, m_doc_threshold, m_packet, m_rate, m_caption, m_at, m_inputs, m_trusted, m_reason_gone, m_media_gone] WEAKENINGS = ["name", "window", "order", "range", "values", "signature", "verdict", "profile", "digest", "media_window", "black_ratio", "freeze_size", "freeze_hold", "silence_channels", "gap_edges", "hysteresis", "loss_on_two", "duplicate", "discontinuity", "scrambling", "table_gap", "pat_table", "pid_gap", "es_removed", "window_end", "trusted", "not_yet", "supersede", "contradiction", "freshness"] def main(argv): global DROPPED if len(argv) != 2: print(__doc__) return 2 doc = json.load(open(os.path.join(argv[1], "vectors.json"), encoding="utf-8")) extra = sorted(set(os.listdir(argv[1])) - {"vectors.json", "README.md"}) fails = check(doc) if extra: fails.append(f"files the corpus does not hold: {extra}") # §6.1's bound, which no vector carries: checked here. try: read(b"\x00" * (MAX_LEN + 1)) fails.append("a record past 1024 bytes was read") except Refused as r: if r.reason != "signal_health_malformed": fails.append("a record past the bound was refused " + r.reason) print(f"{sum(len(doc[k]) for k in KINDS)} cases ({', '.join(f'{len(doc[k])} {k}' for k in KINDS)}): {len(fails)} failure(s)") caught = 0 for m in mutations(doc): d = copy.deepcopy(doc) m(d) if check(d, quiet=True): caught += 1 else: fails.append(f"mutation {m.__name__} was not caught") print(f"{caught} of {len(mutations(doc))} mutated copies fail") weak = 0 for w in WEAKENINGS: DROPPED = w if check(doc, quiet=True): weak += 1 else: fails.append(f"a verifier without its {w} check passes the corpus") DROPPED = None print(f"{weak} of {len(WEAKENINGS)} weakened verifiers are caught") for f in fails: print("FAIL", f) return 1 if fails else 0 if __name__ == "__main__": sys.exit(main(sys.argv))