open-standards/wai/live-conformance/opportunity_report_verify.py
Transaction Science d9839467e2 opportunity_report_verify.py: every refusal of the corpus accounted for by §13, its reused codes or a delegated text
So a reason left out of §13's table is caught on this side too, as the Rust test
that §13 lists exactly the reference's codes catches it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 18:45:03 -04:00

1110 lines
50 KiB
Python

#!/usr/bin/env python3
"""Second implementation of extensions/opportunity-report.md (wai.delivery.opportunity).
Written from the extension's text and sharing no code with wai-rs: it reads every opportunity
report (WOR1), statement check (WCK1), SCTE 35 section (§5) and reported statement (§9) itself;
verifies contracts, runs and signed revenue-share statements with revenue_share_verify.py, and
rooms (their events, grants, policy and the schedule of breaks their cues make) with
live_ads_verify.py; and recomputes every break, attribution, line, citation and flag.
python3 -I opportunity_report_verify.py <live-conformance/opportunity>
python3 -I opportunity_report_verify.py --document FILE
With a corpus directory it gives every vector of vectors.json its outcome and compares it with the
one the corpus states, requires every reason of §13 and every code it reuses to refuse a vector,
requires every mutated copy of the corpus to fail, and drops each of its own checks in turn and
requires the corpus to catch the weakened verifier. With --document it prints the outcome of one
report's or check's verification document, status 0 only when it is accepted. Standard library
only.
"""
import copy
import hashlib
import json
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
import revenue_share_verify as RSV # noqa: E402
import live_ads_verify as LAV # noqa: E402
import live_rooms_verify as LRV # noqa: E402
import live_events_verify as LEV # noqa: E402
import ad_presentation_verify as APV # noqa: E402
EXTENSION = os.path.join(HERE, "..", "extensions", "opportunity-report.md")
REPORT_MAGIC, CHECK_MAGIC = b"WOR1", b"WCK1" # §7.1, §10.1
REPORT_DOMAIN = b"wai:opportunity-report\x01" # §7.4
CHECK_DOMAIN = b"wai:statement-check\x01" # §10.5
CITES_DOMAIN = b"wai:opportunity-cites\x01" # §7.3
CUED, SCHEDULED = 1, 2 # §4.3
CLASS_NAMES = {CUED: "cued", SCHEDULED: "scheduled"}
RUN_KIND, CUE_KIND, POLICY_KIND = 10, 12, 13 # §7.3
PERIOD, DAY, PROGRAMME = 1, 2, 3 # §10.2
SCOPE_NAMES = {PERIOD: "period", DAY: "day", PROGRAMME: "programme"}
FLAGS = ["unproved", "above", "below", "impressions_above_signed", "impressions_below_signed", "revenue_above_signed",
"revenue_below_signed"] # §10.3
FORMS = {"json": 1, "csv": 2}
FORM_NAMES = {1: "json", 2: "csv"}
BASES = {"gross": 1, "share": 2}
BASIS_NAMES = {1: "gross", 2: "share"}
WHOLE = 10 ** 6
U64 = 2 ** 64 - 1
DAY_US = 86_400_000_000
ZERO = bytes(32)
ROOM_CARRIER = 1 # revenue-share §3.1
OPENING = (0x22, 0x30, 0x32, 0x34, 0x36) # §5.2, cue-injection §6.2
PLACEMENT = (0x34, 0x36)
SUB_SEGMENTED = (0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x44, 0x46) # §5.1 step 7
REUSED = ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "contract_mismatch", "outside_contract",
"delivery_gap", "break_overlap", "outside_window", "asset_unmapped", "line_unsourced", "citation_mismatch"]
SKIP = set()
Refuse = RSV.Refuse
# Pure steps, held once per input: a room's live-ads verification, a run's, a signed statement's.
_ROOMS, _RUNS, _STATEMENTS = {}, {}, {}
def memo(cache, value, f):
k = json.dumps(value, sort_keys=True)
if k not in cache:
cache[k] = f()
return cache[k]
def sha(*parts):
h = hashlib.sha256()
for p in parts:
h.update(p)
return h.digest()
def ed_verify(key, msg, sig):
return RSV.ed_verify(key, msg, sig)
def lowhex(v):
"""A byte field of a document: lowercase hexadecimal, or None."""
if not isinstance(v, str) or len(v) % 2 or any(c not in "0123456789abcdef" for c in v):
return None
return bytes.fromhex(v)
# ---------------------------------------------------------------- §5 the section a cue carries
class Bad(Exception):
pass
def crc32(b):
"""ISO/IEC 13818-1 Annex A: 0x04C11DB7, preset all ones, no reflection, no final inversion."""
c = 0xFFFFFFFF
for x in b:
c ^= x << 24
for _ in range(8):
c = ((c << 1) ^ 0x04C11DB7) & 0xFFFFFFFF if c & 0x80000000 else (c << 1) & 0xFFFFFFFF
return c
class Bits:
"""Big-endian bit fields of b, up to the byte `end`; running out is Bad."""
def __init__(self, b, start, end):
self.b, self.at, self.end = b, start * 8, end
def take(self, n):
if self.at + n > self.end * 8:
raise Bad()
v = 0
for _ in range(n):
v = (v << 1) | ((self.b[self.at // 8] >> (7 - self.at % 8)) & 1)
self.at += 1
return v
def skip(self, nbytes):
if self.at // 8 + nbytes > self.end:
raise Bad()
self.at += 8 * nbytes
def splice_time(r):
if r.take(1):
r.take(6)
r.take(33)
else:
r.take(7)
def segmentation(s, start, end):
"""SCTE 35-1 Table 20 after its identifier, filling [start, end) exactly."""
r = Bits(s, start, end)
d = {"event_id": r.take(32), "cancelled": r.take(1) == 1, "duration": None, "type": None, "sub": None}
r.take(7)
if d["cancelled"]:
if r.at // 8 != end:
raise Bad()
return d
program, flagged = r.take(1), r.take(1)
r.take(6) # delivery_not_restricted_flag and the restriction bits
if not program:
r.skip(6 * r.take(8))
if flagged:
d["duration"] = r.take(40)
r.take(8) # segmentation_upid_type
r.skip(r.take(8))
d["type"] = r.take(8)
r.take(8) # segment_num
r.take(8) # segments_expected
left = end - r.at // 8
if left == 2 and d["type"] in SUB_SEGMENTED:
d["sub"] = (r.take(8), r.take(8))
elif left != 0:
raise Bad()
return d
def nothing(command):
return {"command": command, "insert": None, "id": None, "duration": None, "slots": None}
def read_section(s):
"""§5.1, for what §5.2 says a section states; Bad is cue_section_invalid."""
n = len(s)
if n < 20:
raise Bad()
crc_at = n - 4
if crc32(s[:crc_at]) != int.from_bytes(s[crc_at:], "big") and "section-crc" not in SKIP:
raise Bad()
if s[3] != 0:
raise Bad()
if s[4] >> 7:
return nothing(None)
cmd_len = ((s[11] & 0x0F) << 8) | s[12]
cmd = s[13]
if cmd_len == 0xFFF and cmd not in (0x00, 0x05, 0x06):
return nothing(cmd)
end = crc_at if cmd_len == 0xFFF else 14 + cmd_len
if end > crc_at:
raise Bad()
r = Bits(s, 14, end)
st = nothing(cmd)
if cmd == 0x05: # splice_insert()
st["id"] = r.take(32)
cancel = r.take(1) == 1
r.take(7)
out = False
if not cancel:
out, program, flagged, immediate = r.take(1) == 1, r.take(1), r.take(1), r.take(1)
r.take(4)
if program and not immediate:
splice_time(r)
if not program:
for _ in range(r.take(8)):
r.take(8)
if not immediate:
splice_time(r)
if flagged:
r.take(7)
st["duration"] = r.take(33)
r.take(16)
r.take(8)
r.take(8)
st["insert"] = (cancel, out)
elif cmd == 0x06: # time_signal()
splice_time(r)
elif cmd != 0x00:
r.at = end * 8
cmd_end = r.at // 8
if cmd_len != 0xFFF and cmd_end != end:
raise Bad()
if cmd_end + 2 > crc_at:
raise Bad()
p = cmd_end + 2
if p + int.from_bytes(s[cmd_end:p], "big") != crc_at:
raise Bad()
segs = []
while p < crc_at:
if p + 2 > crc_at:
raise Bad()
tag, ln = s[p], s[p + 1]
if p + 2 + ln > crc_at:
raise Bad()
if tag == 0x02 and ln >= 4 and s[p + 2:p + 6] == b"CUEI":
segs.append(segmentation(s, p + 6, p + 2 + ln))
p += 2 + ln
live = [d for d in segs if not d["cancelled"]]
if cmd == 0x06:
opening = next((d for d in live if d["type"] in OPENING), None)
if opening is not None:
st["id"], st["duration"] = opening["event_id"], opening["duration"]
ppo = next((d for d in live if d["type"] in PLACEMENT), None)
if ppo is not None and ppo["sub"] is not None and ppo["sub"][1] > 0:
st["slots"] = ppo["sub"][1]
return st
def agrees(st, cue):
"""§5.3: the section and its opening cue's summary describe one splice."""
if st["insert"] is not None and (st["insert"][0] or not st["insert"][1]):
return False
if st["id"] is not None and st["id"] != cue["cue_id"]:
return False
return st["duration"] is None or st["duration"] // 90 == cue["duration_ms"]
def command_name(c):
return {None: "encrypted", 0x05: "splice_insert", 0x06: "time_signal", 0x00: "splice_null"}.get(c, "other")
# ---------------------------------------------------------------- §3 the evidence
def the_contract(doc):
"""§8.2 steps 1 and 2."""
c = RSV.contract_at(RSV.unhex(doc["contract"]), ["contract"])
for k, h in enumerate(doc["contracts"]):
o = RSV.contract_at(RSV.unhex(h), ["contracts", k])
if o["digest"] != c["digest"] and RSV.overlaps(o, c) and "contract-overlap" not in SKIP:
raise Refuse("contract_overlap", ["contracts", k])
return c
def the_runs(c, runs):
"""§8.2 step 4, as revenue-share §8.2 step 4."""
out = []
for k, o in enumerate(runs):
try:
r = RSV.read_run(o)
except RSV.NoRead:
raise Refuse("malformed", ["run", k])
if "run-verify" not in SKIP and not memo(_RUNS, o, lambda: RSV.run_verifies(r)):
raise Refuse("run_invalid", ["run", k])
if "run-foreign" not in SKIP and (r["channel_id"].encode() != c["channel"] or r["signer_pubkey"] != c["channel_signer"]):
raise Refuse("run_foreign", ["run", k])
if "run-overlap" not in SKIP and any(r["window_start_ms"] < x["window_end_ms"] and x["window_start_ms"] < r["window_end_ms"]
for x in out):
raise Refuse("run_overlap", ["run", k])
out.append(r)
return out
def room_reads(v):
"""§8.1: a room's object."""
if not isinstance(v, dict) or set(v) != {"descriptor", "grants", "key_log", "chains", "policy"}:
return False
if lowhex(v["descriptor"]) is None or lowhex(v["policy"]) is None:
return False
if not isinstance(v["grants"], list) or any(lowhex(g) is None for g in v["grants"]):
return False
if v["key_log"] is not None and not isinstance(v["key_log"], str):
return False
return isinstance(v["chains"], list) and all(isinstance(c, list) and all(lowhex(e) is not None for e in c) for c in v["chains"])
def room_outcome(v):
"""live-ads §9.2 steps 1 to 3 over a room with no receipts: live_ads_verify.py's outcome."""
holder = {"creatives": [], "issuers": []}
APV.held(holder)
lv = dict(v, pt0_ms=0, receipts=[], pods=[], claims=[], creatives=[], issuers=[], name="room")
return LAV.ads(lv, judge=lambda vv, d, p: APV.Judge(holder, vv, p))[0]
def the_rooms(c, rooms):
"""§8.2 step 5: each verified as live-ads §9.2 steps 1 to 3 verify one, with no receipts."""
out = []
for k, v in enumerate(rooms):
at = ["room", k]
if not room_reads(v):
raise Refuse("malformed", at)
got = memo(_ROOMS, v, lambda: room_outcome(v))
if got["outcome"] == "gap":
raise Refuse("delivery_gap", at)
if got["outcome"] != "accept":
raise Refuse(got["reason"], at + got["at"])
room_id = bytes.fromhex(got["room"])
if (ROOM_CARRIER, room_id) not in c["carriers"] and "room-foreign" not in SKIP:
raise Refuse("room_foreign", at)
if any(x["id"] == room_id for x in out) and "room-repeated" not in SKIP:
raise Refuse("room_repeated", at)
policy = LAV.read_policy(bytes.fromhex(v["policy"]))
events = {}
for chain in v["chains"]:
for h in chain:
ev = LEV.read_record(bytes.fromhex(h))
events[ev["digest"]] = ev
out.append({"id": room_id, "t0": LRV.read_descriptor(bytes.fromhex(v["descriptor"]))["t0"], "slots": policy["slots"],
"policy": sha(policy["body"]), "breaks": got["breaks"], "events": events})
return out
# ---------------------------------------------------------------- §4, §6 breaks
def share_of(c, party, split):
return next((p for k, p in RSV.parties(c, split) if k == party), None)
def attribute(c, run, rid, e, at):
"""§6: an entry's asset by the rule, and its split; the between split for none."""
asset = RSV.asset_of(run["schedule"], e, c["rule"])
split = c["between"]
if asset is not None:
split = RSV.split_of(c, asset.encode())
if split is None:
if "unmapped" not in SKIP:
raise Refuse("asset_unmapped", at)
split = c["between"]
return rid, e, asset, split
def place(runs, t):
"""§6: the run whose window holds t, and its entry whose span does."""
for k, r in enumerate(runs):
if r["window_start_ms"] * 1000 <= t < r["window_end_ms"] * 1000:
for e, x in enumerate(r["schedule"]):
if x["start_ms"] * 1000 <= t < (x["start_ms"] + x["duration_ms"]) * 1000:
return k, e
return None
return None
def the_breaks(c, start, end, runs, ids, rooms):
"""§8.2 steps 6 and 7."""
out, starts = [], []
for k, room in enumerate(rooms):
mine = []
for i, b in enumerate(room["breaks"]):
at = ["room", k, "break", i]
s = room["t0"] + b["t"]
if s > U64:
continue
starts.append(s)
if not (start <= s < end) and "period" not in SKIP:
continue
cue = LEV.read_payload(0x02, room["events"][bytes.fromhex(b["digest"])]["payload"], 65535)
st = None
if cue["form"] == 1:
try:
st = read_section(cue["scte"])
except Bad:
if "section-read" not in SKIP:
raise Refuse("cue_section_invalid", at)
if st is not None and not agrees(st, cue) and "section-agree" not in SKIP:
raise Refuse("cue_section_mismatch", at)
duration = b["duration_ms"] if b["duration_ms"] is not None else b["avail_ms"]
if "break-overlap" not in SKIP and any(o["start"] < s + duration * 1000 and s < o["start"] + o["duration_ms"] * 1000 for o in out):
raise Refuse("break_overlap", at)
where = place(runs, s)
if where is None:
if "window" in SKIP:
continue
raise Refuse("outside_window", at)
rid, e, asset, split = attribute(c, runs[where[0]], ids[where[0]], where[1], at)
if st is not None and st["slots"] is not None and "slots-section" not in SKIP:
slots, source, pol = st["slots"], "section", None
else:
slots, source, pol = room["slots"], "policy", room["policy"]
cited = {(CUE_KIND, bytes.fromhex(b["digest"])), (RUN_KIND, rid)}
if b["closed_by"] is not None:
cited.add((CUE_KIND, bytes.fromhex(b["closed_by"])))
if pol is not None:
cited.add((POLICY_KIND, pol))
mine.append({"class": CUED, "room": k, "index": i, "taken": None, "cue": b["digest"], "start": s, "duration_ms": duration,
"open": b["type"] == "out" and b["closed_by"] is None, "slots": slots, "slots_from": source,
"section": None if st is None else {"command": command_name(st["command"]), "id": st["id"],
"duration": st["duration"], "slots": st["slots"]},
"run": rid, "entry": e, "asset": asset, "split": split, "cited": cited})
out += mine
seen = set()
for k in sorted(range(len(runs)), key=lambda j: runs[j]["window_start_ms"]):
r = runs[k]
for e in r["aired"]:
x = r["schedule"][e]
if x["kind"] != "ad":
continue
key = (x["asset_id"], x["start_ms"], x["duration_ms"])
if key in seen and "dedupe" not in SKIP:
continue
seen.add(key)
s, z = x["start_ms"] * 1000, (x["start_ms"] + x["duration_ms"]) * 1000
if any(s <= t < z for t in starts) and "cued-test" not in SKIP:
continue
if not (start <= s < end) and "period" not in SKIP:
continue
rid, e2, asset, split = attribute(c, r, ids[k], e, ["run", k, "entry", e])
out.append({"class": SCHEDULED, "room": None, "index": None, "taken": {"run": k, "entry": e}, "cue": None, "start": s,
"duration_ms": x["duration_ms"], "open": False, "slots": None, "slots_from": None, "section": None,
"run": rid, "entry": e2, "asset": asset, "split": split, "cited": {(RUN_KIND, rid)}})
return out
# ---------------------------------------------------------------- §7 the report
def cites_digest(cited):
return sha(CITES_DOMAIN, *[bytes([k]) + i for k, i in sorted(cited)])
def read_report(b):
"""§7.5, or Refuse at `report`."""
at = ["report"]
bad = RSV.head(b, REPORT_MAGIC)
if bad:
raise Refuse(bad, at)
r = RSV.R(b, 5)
try:
rep = {"issuer": r.take(32), "contract": r.take(32), "party": r.take(32), "start": r.u(8), "end": r.u(8)}
rep["rooms"] = [r.take(32) for _ in range(r.u(2))]
rep["runs"] = [r.take(32) for _ in range(r.u(2))]
rep["lines"] = []
for _ in range(r.u(2)):
rep["lines"].append({"split": r.u(2), "class": r.u(1), "breaks": r.u(8), "break_ms": r.u(8), "slots": r.u(8),
"unslotted": r.u(8), "ppm": r.u(4), "cited": r.u(4), "cites": r.take(32)})
rep["totals"] = (r.u(8), r.u(8), r.u(8))
rep["sig"] = r.take(64)
except ValueError:
raise Refuse("truncated", at)
if r.p != len(b):
raise Refuse("malformed", at)
keys = [(x["split"], x["class"]) for x in rep["lines"]]
rules = (rep["start"] < rep["end"] and rep["issuer"] != ZERO and rep["party"] != ZERO and RSV.ascending(rep["rooms"])
and RSV.ascending(rep["runs"]) and RSV.ascending(keys)
and all(x["class"] in (1, 2) and x["breaks"] > 0 and x["cited"] > 0 and x["unslotted"] <= x["breaks"]
and 1 <= x["ppm"] <= WHOLE for x in rep["lines"]))
if not rules and "report-rules" not in SKIP:
raise Refuse("opportunity_report_invalid", at)
rep["body"] = b[:-64]
rep["digest"] = sha(rep["body"])
return rep
def groups_of(breaks):
g = {}
for b in breaks:
x = g.setdefault((b["split"], b["class"]), {"breaks": 0, "break_ms": 0, "slots": 0, "unslotted": 0, "cited": set()})
x["breaks"] += 1
x["break_ms"] += b["duration_ms"]
if b["slots"] is None:
x["unslotted"] += 1
else:
x["slots"] += b["slots"]
x["cited"] |= b["cited"]
return g
def lines_of(c, party, breaks):
"""§7.2, §7.3: the party's lines and totals, or None for a figure past its field."""
out = []
for (split, cls), g in sorted(groups_of(breaks).items()):
ppm = share_of(c, party, split)
if ppm is None:
continue
out.append({"split": split, "class": cls, "breaks": g["breaks"], "break_ms": g["break_ms"], "slots": g["slots"],
"unslotted": g["unslotted"], "ppm": ppm, "cited": len(g["cited"]), "cites": cites_digest(g["cited"]),
"cited_records": sorted(g["cited"])})
totals = (sum(x["breaks"] for x in out), sum(x["break_ms"] for x in out), sum(x["slots"] for x in out))
if any(x[f] > U64 for x in out for f in ("break_ms", "slots")) or any(t > U64 for t in totals):
return None, None
return out, totals
LINE_FIELDS = ("split", "class", "breaks", "break_ms", "slots", "unslotted", "ppm", "cited", "cites")
def verify_report(doc):
"""§8.2: (outcome, held) where held is what a check reads of an accepted report."""
try:
if not isinstance(doc, dict) or set(doc) != {"contract", "contracts", "report", "runs", "rooms"}:
raise Refuse("malformed", [])
if not all(isinstance(doc[k], list) for k in ("contracts", "runs", "rooms")):
raise Refuse("malformed", [])
if lowhex(doc["contract"]) is None or lowhex(doc["report"]) is None or any(lowhex(h) is None for h in doc["contracts"]):
raise Refuse("malformed", [])
c = the_contract(doc) # steps 1, 2
rep = read_report(bytes.fromhex(doc["report"])) # step 3
if "binding" not in SKIP and (rep["contract"] != c["digest"] or (rep["party"] != c["operator"] and rep["party"] not in c["owners"])):
raise Refuse("contract_mismatch", ["report"])
if "contract-period" not in SKIP and not (c["start"] <= rep["start"] and rep["end"] <= c["end"]):
raise Refuse("outside_contract", ["report"])
if "report-signature" not in SKIP:
bad = ed_verify(rep["issuer"], REPORT_DOMAIN + rep["body"], rep["sig"])
if bad:
raise Refuse(bad, ["report"])
runs = the_runs(c, doc["runs"]) # step 4
ids = [RSV.run_identity(r) for r in runs]
rooms = the_rooms(c, doc["rooms"]) # step 5
breaks = the_breaks(c, rep["start"], rep["end"], runs, ids, rooms) # steps 6, 7
groups = groups_of(breaks) # step 8
for x in rep["lines"]:
g = groups.get((x["split"], x["class"]))
holds = x["split"] < len(c["splits"]) and share_of(c, rep["party"], x["split"]) is not None
if "unsourced" not in SKIP and (g is None or not holds or x["breaks"] > g["breaks"]):
raise Refuse("line_unsourced", ["report"])
lines, totals = lines_of(c, rep["party"], breaks)
if lines is None:
raise Refuse("opportunity_report_mismatch", ["report"])
for x in rep["lines"]:
mine = [y for y in lines if (y["split"], y["class"]) == (x["split"], x["class"])]
if mine and "citations" not in SKIP and (x["cited"] != mine[0]["cited"] or x["cites"] != mine[0]["cites"]):
raise Refuse("citation_mismatch", ["report"])
same = (rep["rooms"] == sorted(r["id"] for r in rooms) and rep["runs"] == sorted(ids)
and [{k: x[k] for k in LINE_FIELDS} for x in rep["lines"]] == [{k: y[k] for k in LINE_FIELDS} for y in lines]
and rep["totals"] == totals)
if not same and "mismatch" not in SKIP:
raise Refuse("opportunity_report_mismatch", ["report"])
except Refuse as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}, None
counted = [b for b in breaks if share_of(c, rep["party"], b["split"]) is not None]
return accepted_report(c, rep, breaks, lines), {"contract": c, "report": rep, "counted": counted}
def accepted_report(c, rep, breaks, lines):
"""§8.3."""
return {
"outcome": "accept",
"report": rep["digest"].hex(),
"contract": rep["contract"].hex(),
"issuer": rep["issuer"].hex(),
"party": rep["party"].hex(),
"role": "operator" if rep["party"] == c["operator"] else "owner",
"start": rep["start"],
"end": rep["end"],
"rooms": [d.hex() for d in rep["rooms"]],
"runs": [d.hex() for d in rep["runs"]],
"breaks": [{"class": CLASS_NAMES[b["class"]], "room": b["room"], "index": b["index"], "taken": b["taken"], "cue": b["cue"],
"start": b["start"], "duration_ms": b["duration_ms"], "open": b["open"], "slots": b["slots"],
"slots_from": b["slots_from"], "section": b["section"], "run": b["run"].hex(), "entry": b["entry"],
"asset": b["asset"], "split": b["split"], "cites": [[k, d.hex()] for k, d in sorted(b["cited"])]} for b in breaks],
"lines": [{"split": y["split"], "class": CLASS_NAMES[y["class"]], "breaks": y["breaks"], "break_ms": y["break_ms"],
"slots": y["slots"], "unslotted": y["unslotted"], "ppm": y["ppm"],
"cites": [[k, d.hex()] for k, d in y["cited_records"]]} for y in lines],
"totals": {"breaks": rep["totals"][0], "break_ms": rep["totals"][1], "slots": rep["totals"][2]},
}
# ---------------------------------------------------------------- §9 the reported statement
DATE = re.compile(r"(\d{4})-(\d{2})-(\d{2})")
def day_start(s):
"""§2: the start of a day `YYYY-MM-DD` from 1970 to 9999, in microseconds, or None."""
m = DATE.fullmatch(s) if isinstance(s, str) and s.isascii() else None
if m is None:
return None
y, mo, d = (int(x) for x in m.groups())
leap = (y % 4 == 0 and y % 100 != 0) or y % 400 == 0
days = [31, 29 if leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if not (1970 <= y <= 9999 and 1 <= mo <= 12 and 1 <= d <= days[mo - 1]):
return None
ordinal = sum(366 if (x % 4 == 0 and x % 100 != 0) or x % 400 == 0 else 365 for x in range(1970, y)) + sum(days[:mo - 1]) + d - 1
return ordinal * DAY_US
def an_int(v):
return isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= U64
def utf8(s):
try:
return s.encode("utf-8")
except UnicodeEncodeError:
return None
def from_json(text):
try:
o = json.loads(text)
except ValueError:
return None
need = {"channel", "start", "end", "currency", "basis", "impressions", "revenue"}
if not isinstance(o, dict) or not need <= set(o) or not set(o) <= need | {"days", "programmes"}:
return None
if not all(isinstance(o[k], str) for k in ("channel", "currency", "basis")) or not all(an_int(o[k]) for k in ("start", "end", "impressions", "revenue")):
return None
if o["basis"] not in BASES:
return None
r = {"channel": utf8(o["channel"]), "start": o["start"], "end": o["end"], "currency": utf8(o["currency"]), "basis": BASES[o["basis"]],
"impressions": o["impressions"], "revenue": o["revenue"], "days": [], "programmes": []}
for name, key in (("days", "day"), ("programmes", "asset")):
items = o.get(name, [])
if not isinstance(items, list):
return None
for x in items:
if not isinstance(x, dict) or set(x) != {key, "impressions", "revenue"} or not isinstance(x[key], str):
return None
if not an_int(x["impressions"]) or not an_int(x["revenue"]):
return None
if name == "days":
start = day_start(x["day"])
if start is None:
return None
r["days"].append({"day": x["day"], "start": start, "impressions": x["impressions"], "revenue": x["revenue"]})
else:
r["programmes"].append({"asset": utf8(x["asset"]), "impressions": x["impressions"], "revenue": x["revenue"]})
if r["channel"] is None or r["currency"] is None or any(p["asset"] is None for p in r["programmes"]):
return None
return r
def csv_records(text):
"""RFC 4180 records, each ended by CRLF or LF, the last line break optional; None when not."""
b, n, p, out = text, len(text), 0, []
if n == 0:
return None
while True:
rec = []
while True:
f = []
if p < n and b[p] == '"':
p += 1
while True:
if p >= n:
return None
if b[p] == '"':
if p + 1 < n and b[p + 1] == '"':
f.append('"')
p += 2
else:
p += 1
break
else:
f.append(b[p])
p += 1
else:
while p < n and b[p] not in ",\r\n":
if b[p] == '"':
return None
f.append(b[p])
p += 1
rec.append("".join(f))
if p < n and b[p] == ",":
p += 1
continue
break
out.append(rec)
if p == n:
return out
if b[p] == "\r" and p + 1 < n and b[p + 1] == "\n":
p += 2
elif b[p] == "\n":
p += 1
else:
return None
if p == n:
return out
def csv_int(s):
if not re.fullmatch(r"0|[1-9][0-9]*", s or "", re.ASCII):
return None
v = int(s)
return v if v <= U64 else None
def from_csv(text):
recs = csv_records(text)
if recs is None or any(len(r) != 4 for r in recs) or not recs or recs[0] != ["scope", "key", "impressions", "revenue"]:
return None
rows = recs[1:]
meta = {}
for name in ("channel", "start", "end", "currency", "basis"):
if not rows or rows[0][0] != name or rows[0][2] != "" or rows[0][3] != "":
return None
meta[name] = rows[0][1]
rows = rows[1:]
if not rows or rows[0][0] != "period" or rows[0][1] != "":
return None
period, rows = rows[0], rows[1:]
start, end, imps, rev = csv_int(meta["start"]), csv_int(meta["end"]), csv_int(period[2]), csv_int(period[3])
if None in (start, end, imps, rev) or meta["basis"] not in BASES:
return None
r = {"channel": meta["channel"].encode(), "start": start, "end": end, "currency": meta["currency"].encode(),
"basis": BASES[meta["basis"]], "impressions": imps, "revenue": rev, "days": [], "programmes": []}
in_programmes = False
for row in rows:
i, v = csv_int(row[2]), csv_int(row[3])
if i is None or v is None:
return None
if row[0] == "day" and not in_programmes:
ds = day_start(row[1])
if ds is None:
return None
r["days"].append({"day": row[1], "start": ds, "impressions": i, "revenue": v})
elif row[0] == "programme":
in_programmes = True
r["programmes"].append({"asset": row[1].encode(), "impressions": i, "revenue": v})
else:
return None
return r
def read_reported(form, text):
"""§9: the reported statement of a form, held to §9.3's rules, or None (reported_invalid)."""
r = from_json(text) if form == 1 else from_csv(text)
if r is None:
return None
rules = (1 <= len(r["channel"]) <= 1024 and len(r["currency"]) == 3 and all(0x41 <= x <= 0x5A for x in r["currency"])
and r["start"] < r["end"]
and all(r["start"] <= d["start"] and d["start"] + DAY_US <= r["end"] for d in r["days"])
and all(a["start"] < b["start"] for a, b in zip(r["days"], r["days"][1:]))
and all(1 <= len(p["asset"]) <= 1024 for p in r["programmes"])
and all(a["asset"] < b["asset"] for a, b in zip(r["programmes"], r["programmes"][1:])))
if not rules and "reported-rules" not in SKIP:
return None
return r
# ---------------------------------------------------------------- §10 the check
def read_check(b):
"""§10.6, or Refuse at `check`."""
at = ["check"]
bad = RSV.head(b, CHECK_MAGIC)
if bad:
raise Refuse(bad, at)
r = RSV.R(b, 5)
try:
c = {"issuer": r.take(32), "report": r.take(32), "reported": r.take(32), "form": r.u(1), "statement": r.take(32),
"floor": r.u(4), "ceiling": r.u(4), "lines": []}
for _ in range(r.u(2)):
x = {"scope": r.u(1), "key": r.text()}
for f in ("breaks", "break_ms", "slots", "unslotted", "impressions", "revenue", "fill_ppm", "revenue_per_slot"):
x[f] = r.u(8)
signed = r.u(1)
if signed not in (0, 1):
raise Refuse("statement_check_invalid", at)
x["signed"] = (r.u(8), r.u(8)) if signed else None
x["flags"] = r.u(1)
c["lines"].append(x)
c["sig"] = r.take(64)
except ValueError:
raise Refuse("truncated", at)
if r.p != len(b):
raise Refuse("malformed", at)
def key_ok(x):
if x["scope"] == PERIOD:
return x["key"] == b""
try:
k = x["key"].decode("utf-8")
except UnicodeDecodeError:
return False
if x["scope"] == DAY:
return day_start(k) is not None
return x["scope"] == PROGRAMME and 1 <= len(x["key"]) <= 1024
ls = c["lines"]
rules = (c["issuer"] != ZERO and c["form"] in (1, 2) and c["floor"] <= WHOLE and c["ceiling"] >= WHOLE
and bool(ls) and ls[0]["scope"] == PERIOD and sum(1 for x in ls if x["scope"] == PERIOD) == 1
and RSV.ascending([(x["scope"], x["key"]) for x in ls])
and all(key_ok(x) and (x["signed"] is None or x["scope"] == PERIOD) and x["flags"] < 0x80 for x in ls))
if not rules and "check-rules" not in SKIP:
raise Refuse("statement_check_invalid", at)
c["body"] = b[:-64]
c["digest"] = sha(c["body"])
return c
def ratio(a, m, b):
return 0 if b == 0 else min(a * m // b, U64)
def tally(bs):
return (len(bs), min(sum(b["duration_ms"] for b in bs), U64), min(sum(b["slots"] for b in bs if b["slots"] is not None), U64),
sum(1 for b in bs if b["slots"] is None))
def check_lines(counted, reported, signed, floor, ceiling):
"""§10.2 to §10.4."""
def line(scope, key, t, imps, rev, sig):
return {"scope": scope, "key": key, "breaks": t[0], "break_ms": t[1], "slots": t[2], "unslotted": t[3], "impressions": imps,
"revenue": rev, "fill_ppm": ratio(imps, WHOLE, t[2]), "revenue_per_slot": ratio(rev, 1, t[2]), "signed": sig, "flags": 0}
out = [line(PERIOD, b"", tally(counted), reported["impressions"], reported["revenue"], signed)]
for d in reported["days"]:
out.append(line(DAY, d["day"].encode(), tally([b for b in counted if d["start"] <= b["start"] < d["start"] + DAY_US]),
d["impressions"], d["revenue"], None))
for p in reported["programmes"]:
out.append(line(PROGRAMME, p["asset"], tally([b for b in counted if b["asset"] is not None and b["asset"].encode() == p["asset"]]),
p["impressions"], p["revenue"], None))
rate, period_slots = out[0]["fill_ppm"], out[0]["slots"]
for x in out:
f = 0
if x["impressions"] > 0 and x["breaks"] == 0 and "flag-unproved" not in SKIP:
f |= 1
if x["scope"] != PERIOD and x["slots"] > 0 and period_slots > 0 and "flag-bounds" not in SKIP:
if x["fill_ppm"] * WHOLE > rate * ceiling:
f |= 2
if x["fill_ppm"] * WHOLE < rate * floor:
f |= 4
if x["signed"] is not None and "flag-signed" not in SKIP:
si, sr = x["signed"]
f |= (8 if x["impressions"] > si else 0) | (16 if x["impressions"] < si else 0)
f |= (32 if x["revenue"] > sr else 0) | (64 if x["revenue"] < sr else 0)
x["flags"] = f
return out
CHECK_LINE_FIELDS = ("scope", "key", "breaks", "break_ms", "slots", "unslotted", "impressions", "revenue", "fill_ppm", "revenue_per_slot",
"signed", "flags")
def verify_check(doc):
"""§11.2."""
try:
if not isinstance(doc, dict) or set(doc) != {"report", "reported", "statement", "check"}:
raise Refuse("malformed", [])
rp = doc["reported"]
if not isinstance(rp, dict) or set(rp) != {"form", "text"} or rp["form"] not in FORMS or not isinstance(rp["text"], str):
raise Refuse("malformed", [])
cb = lowhex(doc["check"])
if cb is None or not (doc["statement"] is None or isinstance(doc["statement"], dict)):
raise Refuse("malformed", [])
got, held = verify_report(doc["report"]) # step 1
if got["outcome"] != "accept":
raise Refuse(got["reason"], ["report"] + got["at"])
c, rep = held["contract"], held["report"]
form = FORMS[rp["form"]]
reported = read_reported(form, rp["text"]) # step 2
if reported is None:
raise Refuse("reported_invalid", ["reported"])
if "reported-foreign" not in SKIP and (reported["channel"] != c["channel"] or reported["currency"] != c["currency"]
or (reported["start"], reported["end"]) != (rep["start"], rep["end"])):
raise Refuse("reported_foreign", ["reported"])
statement = None
if doc["statement"] is not None: # step 3
sg = memo(_STATEMENTS, doc["statement"], lambda: RSV.verify(doc["statement"]))
if sg["outcome"] != "accept":
raise Refuse(sg["reason"], ["statement"] + sg["at"])
statement = RSV.read_statement(bytes.fromhex(doc["statement"]["statement"]))
if "statement-foreign" not in SKIP and (statement["contract"] != rep["contract"] or statement["party"] != rep["party"]
or (statement["start"], statement["end"]) != (rep["start"], rep["end"])):
raise Refuse("statement_foreign", ["statement"])
ch = read_check(cb) # step 4
if "check-signature" not in SKIP:
bad = ed_verify(ch["issuer"], CHECK_DOMAIN + ch["body"], ch["sig"])
if bad:
raise Refuse(bad, ["check"])
signed = None
if statement is not None:
imps = min(sum(x["impressions"] for x in statement["lines"]), U64)
rev = min(sum(x["revenue"] for x in statement["lines"]), U64) if reported["basis"] == 1 else statement["total"]
signed = (imps, rev)
lines = check_lines(held["counted"], reported, signed, ch["floor"], ch["ceiling"])
same = (ch["report"] == rep["digest"] and ch["reported"] == sha(rp["text"].encode()) and ch["form"] == form
and ch["statement"] == (ZERO if statement is None else statement["digest"])
and [{k: x[k] for k in CHECK_LINE_FIELDS} for x in ch["lines"]] == [{k: y[k] for k in CHECK_LINE_FIELDS} for y in lines])
if not same and "check-mismatch" not in SKIP:
raise Refuse("statement_check_mismatch", ["check"])
except Refuse as e:
return {"outcome": "refuse", "reason": e.reason, "at": e.at}
return {
"outcome": "accept",
"check": ch["digest"].hex(),
"issuer": ch["issuer"].hex(),
"report": ch["report"].hex(),
"party": rep["party"].hex(),
"start": rep["start"],
"end": rep["end"],
"reported": ch["reported"].hex(),
"form": FORM_NAMES[ch["form"]],
"basis": BASIS_NAMES[reported["basis"]],
"statement": None if statement is None else statement["digest"].hex(),
"floor_ppm": ch["floor"],
"ceiling_ppm": ch["ceiling"],
"lines": [{"scope": SCOPE_NAMES[x["scope"]], "key": x["key"].decode("utf-8"), "breaks": x["breaks"], "break_ms": x["break_ms"],
"slots": x["slots"], "unslotted": x["unslotted"], "impressions": x["impressions"], "revenue": x["revenue"],
"fill_ppm": x["fill_ppm"], "revenue_per_slot": x["revenue_per_slot"],
"signed": None if x["signed"] is None else {"impressions": x["signed"][0], "revenue": x["signed"][1]},
"flags": [n for i, n in enumerate(FLAGS) if x["flags"] & (1 << i)]} for x in ch["lines"]],
}
def verify(doc):
"""A report's or a check's document: a check's holds `reported`."""
if isinstance(doc, dict) and "reported" in doc:
return verify_check(doc)
return verify_report(doc)[0]
# ---------------------------------------------------------------- the corpus
def document(doc, v):
"""A vector as its verification document, its parts resolved by name."""
def report_doc(p):
return {"contract": doc["contracts"][p["contract"]], "contracts": [doc["contracts"][n] for n in p["contracts"]],
"report": p["report"], "runs": None if p["runs"] is None else [doc["runs"][n] for n in p["runs"]],
"rooms": [doc["rooms"][n] for n in p["rooms"]]}
if v["kind"] == "report":
return report_doc(v["report"])
s = v["statement"]
sd = None if s is None else {"contract": doc["contracts"][s["contract"]], "contracts": [], "statement": s["statement"],
"runs": [doc["runs"][n] for n in s["runs"]], "ledgers": [doc["ledgers"][n] for n in s["ledgers"]]}
return {"report": report_doc(v["report"]), "reported": doc["reported"][v["reported"]], "statement": sd, "check": v["check"]}
def check(doc, quiet=False):
"""The failures: every vector whose stated outcome is not the one this verifier reaches."""
fails = []
for v in doc.get("vectors", []):
try:
got = verify(document(doc, v))
except Exception as e: # a crash on a mutated copy is a failure to verify it
got = {"outcome": "crash", "error": "%s: %s" % (type(e).__name__, e)}
ok = got == v.get("outcome")
if not quiet:
what = got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if got["outcome"] == "refuse" else "")
print(("PASS" if ok else "FAIL") + " %s: %s" % (v.get("name"), what))
if not ok:
fails.append(v.get("name"))
return fails
def reasons_listed():
"""§13's table, in order."""
text = open(EXTENSION, encoding="utf-8").read()
at = text.index("\n## 13. Reasons")
end = text.find("\n## ", at + 1)
return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", text[at:end], re.M)]
def flip_hex(h, at):
b = bytearray(bytes.fromhex(h))
b[at % len(b)] ^= 0x01
return b.hex()
def mutations(doc):
"""Copies of the corpus, one defect each, that a verifier must reject."""
out = []
names = [v["name"] for v in doc["vectors"]]
def mutate(label, f):
m = copy.deepcopy(doc)
f(m)
out.append((label, m))
for k, v in enumerate(doc["vectors"]):
if v["outcome"]["outcome"] != "accept":
continue
if v["kind"] == "report":
for at in (6, 40, 100, 140, -90, -70, -1):
mutate("%s: report byte %d flipped" % (v["name"], at), lambda m, k=k, at=at: m["vectors"][k]["report"].update(
report=flip_hex(m["vectors"][k]["report"]["report"], at)))
mutate("%s: its stated totals raised" % v["name"], lambda m, k=k: m["vectors"][k]["outcome"]["totals"].update(
breaks=m["vectors"][k]["outcome"]["totals"]["breaks"] + 1))
if v["outcome"]["breaks"]:
mutate("%s: its first break moved a second" % v["name"], lambda m, k=k: m["vectors"][k]["outcome"]["breaks"][0].update(
start=m["vectors"][k]["outcome"]["breaks"][0]["start"] + 1_000_000))
mutate("%s: its first break's slots changed" % v["name"], lambda m, k=k: m["vectors"][k]["outcome"]["breaks"][0].update(
slots=7))
if v["report"]["rooms"]:
mutate("%s: its last room dropped" % v["name"], lambda m, k=k: m["vectors"][k]["report"]["rooms"].pop())
else:
for at in (40, 104, 150, -80, -66, -1):
mutate("%s: check byte %d flipped" % (v["name"], at), lambda m, k=k, at=at: m["vectors"][k].update(
check=flip_hex(m["vectors"][k]["check"], at)))
mutate("%s: a line's flags changed in its outcome" % v["name"], lambda m, k=k: m["vectors"][k]["outcome"]["lines"][-1].update(
flags=["above"]))
if v["statement"] is not None:
mutate("%s: its signed statement left out" % v["name"], lambda m, k=k: m["vectors"][k].update(statement=None))
for name, at in (("main", -1), ("main", 40), ("two_rooms", 60)):
mutate("the contract %s: byte %d flipped" % (name, at), lambda m, name=name, at=at: m["contracts"].update(
{name: flip_hex(doc["contracts"][name], at)}))
mutate("the run of the first hour: its signature flipped",
lambda m: m["runs"]["hour_1"].update(sig=flip_hex(doc["runs"]["hour_1"]["sig"], 3)))
mutate("the run of the second hour: its first programme renamed",
lambda m: m["runs"]["hour_2"]["schedule"][0].update(asset_id="a/ep-1"))
mutate("the main room: an event of its producer's flipped",
lambda m: m["rooms"]["main"]["chains"][1].__setitem__(5, flip_hex(doc["rooms"]["main"]["chains"][1][5], 130)))
mutate("the main room: its policy flipped", lambda m: m["rooms"]["main"].update(policy=flip_hex(doc["rooms"]["main"]["policy"], 70)))
mutate("owner B's reported statement: one impression more",
lambda m: m["reported"]["b_programmes"].update(text=doc["reported"]["b_programmes"]["text"].replace('"impressions":14', '"impressions":15')))
mutate("owner A's CSV: its line breaks made LF",
lambda m: m["reported"]["a_days"].update(text=doc["reported"]["a_days"]["text"].replace("\r\n", "\n")))
for k, v in enumerate(doc["vectors"]):
if v["outcome"]["outcome"] == "refuse":
mutate("%s: its stated reason changed" % v["name"], lambda m, k=k, v=v: m["vectors"][k]["outcome"].update(
reason="malformed" if v["outcome"]["reason"] != "malformed" else "truncated"))
break
assert len(names) == len(set(names)), "vector names are unique"
return out
CHECKS = ["contract-overlap", "report-rules", "binding", "contract-period", "report-signature", "run-verify", "run-foreign", "run-overlap",
"room-foreign", "room-repeated", "period", "section-crc", "section-read", "section-agree", "break-overlap", "window", "unmapped",
"slots-section", "dedupe", "cued-test", "unsourced", "citations", "mismatch", "reported-rules", "reported-foreign",
"statement-foreign", "check-rules", "check-signature", "check-mismatch", "flag-unproved", "flag-bounds", "flag-signed"]
def self_tests():
"""The standard's sample (SCTE 35-1 §14.1, as cue-injection §5.7 holds it) reads as §5 reads it."""
sample = bytes.fromhex("fc3034000000000000fffff00506fe72bd0050001e021c435545494800008e7fcf0001a599b00808000000002ca0a18a3402009ac9d17e")
st = read_section(sample)
fails = []
if (st["command"], st["id"], st["duration"], st["slots"]) != (0x06, 0x4800008E, 307 * 90_000, None):
fails.append("the standard's sample does not state its opportunity")
damaged = bytearray(sample)
damaged[-1] ^= 1
try:
read_section(bytes(damaged))
fails.append("a section with a damaged CRC reads")
except Bad:
pass
print("%s the standard's SCTE 35 sample reads, and a damaged copy does not" % ("PASS" if not fails else "FAIL"))
return fails
def main(corpus_dir):
doc = json.load(open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8"))
fails = self_tests()
fails += check(doc)
files = sorted(os.listdir(corpus_dir))
if files != ["README.md", "vectors.json"]:
fails.append("the corpus holds other files: %s" % files)
seen = {v["outcome"].get("reason") for v in doc["vectors"]}
listed = reasons_listed()
missing = [r for r in listed + REUSED if r not in seen]
if missing:
fails.append("no vector is refused %s" % missing)
print("%s every reason of §13 and every reused code refuses a vector (%d listed)" % ("PASS" if not missing else "FAIL", len(listed)))
# And no refusal is unaccounted for: each is §13's, a code §13 reuses, or a code a text it
# delegates to (revenue-share, live-ads) raises there.
delegated = set(RSV.reasons_listed()) | set(RSV.REUSED) | set(LAV.REASONS)
unexplained = sorted(r for r in seen if r is not None and r not in listed and r not in REUSED and r not in delegated)
if unexplained:
fails.append("refusals §13 does not account for: %s" % unexplained)
print("%s every refusal of the corpus is a reason of §13, a code it reuses, or one a text it delegates to raises" % (
"PASS" if not unexplained else "FAIL"))
muts = mutations(doc)
weak = [name for name, m in muts if not check(m, quiet=True)]
print("%s every mutated copy of the corpus fails (%d copies)" % ("PASS" if not weak else "FAIL", len(muts)))
fails += ["the mutated copy %r verifies" % w for w in weak]
undetected = []
for c in CHECKS:
SKIP.add(c)
try:
if not check(doc, quiet=True):
undetected.append(c)
finally:
SKIP.discard(c)
print("%s the corpus catches the verifier with each of its checks dropped (%d checks)" % ("PASS" if not undetected else "FAIL", len(CHECKS)))
fails += ["dropping the check %r goes unnoticed" % c for c in undetected]
for f in fails:
print("FAIL", f)
return 1 if fails else 0
def check_document(path):
doc = json.load(open(path, encoding="utf-8"))
got = verify(doc)
print(json.dumps(got, indent=1))
return 0 if got["outcome"] == "accept" else 1
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] == "--document":
sys.exit(check_document(sys.argv[2]))
if len(sys.argv) != 2:
print("usage: opportunity_report_verify.py <live-conformance/opportunity> | --document FILE", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))