open-standards/wai/http-conformance/http_verify.py
Transaction Science 1cffbc6ffd wai: HTTP reports a refinement by its refines.base alone; a conforming envelope's refinement tested
- §7 step 4 as the text stands: a refinement of the pick is one whose refines.base, read
  on its own, is the pick's id (container::refinements_naming, refinements_naming in
  http_verify.py), so one whose refines is otherwise malformed is reported dropped
  (components_nonconforming). Of a conforming envelope the set is the same as before.
- Objects OCF (ONC's conforming twin: refinements [2], dropped []), ONM (refines
  {base: b} alone: reported) and ONX (refines {base: x}: not reported), on the whole and
  the range paths; fetch_OR_malformed_refines now reports its refinement; the generator
  checks each outcome.
- falsify: conforming-withheld and refines-read-whole, each with an http_verify.py
  --mutate, wired into falsify/run.sh; nonconforming-offered's must-fail list extended.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 03:11:46 -04:00

3486 lines
138 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Second verifier for http-conformance/ — HTTP delivery (extensions/http-delivery.md).
Written from the extension text and SPEC §7.1. It shares no code with wai-rs, which
generates the corpus: the head (§2, §5.1), covered_len, the head proof (§6), the
response checks (§7.1), the plans and classes of the reference client (§7, §10.1), the
byte-range procedure with its whole-object fallback (§7), the base presented as it
verifies and the refinement deadline on the caller's clock (§10.1, §10.2), SPEC §7.1's selection with
companions and refinements, the codecs grammar, union, floor and support (§4), all
written here. BLAKE3 is ../claim-conformance/_blake3.py (written from its specification,
shared with the other Python verifiers) and SHA-256 is hashlib. The registry's kinds,
roles and classes are read from ../registry/capabilities.json, the published export.
Only the standard library is needed.
Pins (SPEC §3.1) are read as far as the corpus needs: a pin applies to a capability that
decodes against a parameter set (registry class neural-int-exact, neural-entropy-exact,
neural-float or generative-exact) or that the registry does not hold; a form-less pin
resolves when the sink holds a file whose SHA-256 is the pin's; a pin in the set form
resolves to nothing here, since the corpus's sinks hold no set listing. Any other shape
stops the verifier, never passes.
python3 http_verify.py [dir] verify every case and the self-test
python3 http_verify.py [dir] --mutate NAME the same with a deliberate defect:
left-half | no-root | counter-zero | floor-as-codecs | blake3-iv | base-at-done |
tick-noop | refinement-unclipped | nonconforming-offered | conforming-withheld |
refines-read-whole | ... (falsify/run.sh runs every one)
python3 http_verify.py cases FILE [dir]
give every case of a case file (wai_http_vectors fuzz) its stated outcome
python3 http_verify.py diff FILE [--flip N --seed S]
verify every head proof `wai_http_vectors diff` wrote; with --flip, change one
chaining-value byte in N of them, chosen by S, and require exactly those refused
Exit status 0 only when every case gives its committed outcome (or, for diff, every
proof verifies, or exactly the flipped ones fail).
"""
import hashlib, json, os, random, re, sys, time
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
sys.path.insert(0, os.path.join(HERE, "..", "staged-conformance"))
import _blake3 as B3 # noqa: E402
import staged_verify as SV # noqa: E402 (strict JSON, the manifest members, refines, explode)
MUTATE = None
DOMAIN = b"moq-jwp:object\x01"
READABLE = ["wai.prior.set"]
class Refusal(Exception):
def __init__(self, code, skipped=None):
super().__init__(code)
self.code, self.skipped = code, skipped
def sha(b):
return hashlib.sha256(b).hexdigest()
def blake3(b):
return B3.blake3(b)
def content_hash(env):
return blake3(DOMAIN + env)
# ------------------------------------------------------------------ the registry
REG = {}
def load_registry():
d = json.load(open(os.path.join(HERE, "..", "registry", "capabilities.json")))
for c in d["capabilities"]:
REG[c["capability"]] = c
def kind(cap):
return REG[cap]["kind"] if cap in REG else None
def candidate(cap):
return cap in REG and REG[cap]["status"] == "candidate"
def dispatchable(cap):
k = kind(cap)
return k is None or (k == "payload" and not candidate(cap))
def enhancement(cap):
return cap in REG and REG[cap]["role"] == "enhancement"
def pin_applies(cap):
return cap not in REG or REG[cap]["replicate"] in ("neural-int-exact", "neural-entropy-exact", "neural-float", "generative-exact")
# ---------------------------------------------------------------------- the head
def u32(b, at):
return int.from_bytes(b[at:at + 4], "little")
def read_manifest(b, v2):
"""SPEC §2, §3, §7.1: the manifest read strictly, with the members SPEC requires and
their types (staged_verify's reader); `head_malformed` otherwise."""
try:
m = SV.strict_json(b)
(SV.check_manifest_v2 if v2 else SV.check_manifest_v1)(m)
except (SV.NotJSON, SV.Schema):
raise Refusal("head_malformed")
return m
def parse_head(prefix, n):
"""§2, §5.1: ("need", k) or a head dict; Refusal on a malformed or mis-sized object."""
if len(prefix) < 8:
return ("need", 8)
magic = prefix[:4]
if magic not in (b"WAI1", b"WAI2"):
raise Refusal("head_malformed")
ml = u32(prefix, 4)
if magic == b"WAI1":
head_len = 12 + ml
if len(prefix) < head_len:
return ("need", head_len)
comps = [(head_len, u32(prefix, 8 + ml))]
else:
if len(prefix) < 10 + ml:
return ("need", 10 + ml)
k = int.from_bytes(prefix[8 + ml:10 + ml], "little")
head_len = 10 + ml + 8 * k
if len(prefix) < head_len:
return ("need", head_len)
comps, expected = [], 0
for i in range(k):
off, ln = u32(prefix, 10 + ml + 8 * i), u32(prefix, 14 + ml + 8 * i)
if off != expected:
raise Refusal("head_malformed")
comps.append((head_len + off, ln))
expected += ln
if head_len + sum(c[1] for c in comps) != n:
raise Refusal("object_length_mismatch")
m = read_manifest(prefix[8:8 + ml], magic == b"WAI2")
if magic == b"WAI2" and len(m["renditions"]) != len(comps):
raise Refusal("head_malformed")
return {"version": magic.decode(), "head_len": head_len, "envelope_len": n, "components": comps, "manifest": m}
def is_layer(h):
"""§10.4: a WAI1 whose capability is refinement-only (staged delivery §3). A payload
capability that carries `refines` is an ordinary object: SPEC §4 dispatches it and
`refines` is not read."""
if h["version"] != "WAI1":
return False
return kind(h["manifest"]["model_requirement"]["capability"]) == "refinement"
def whole_head(env):
"""The head of a whole envelope; one shorter than its head is malformed."""
h = parse_head(env, len(env))
if isinstance(h, tuple):
raise Refusal("head_malformed")
return h
def covered_len(head_len, n):
return min(n, 1024 * -(-(15 + head_len) // 1024) - 15)
# ------------------------------------------------------------------ the head proof
def left(n):
if MUTATE == "left-half":
return max(1, (n // 2) // 1024) * 1024
return 1024 * 2 ** ((((n - 1) // 1024)).bit_length() - 1)
def merge(l, r, root):
if MUTATE == "no-root":
root = False
return B3.parent_cv(l, r, root)
def subtree(x, start):
return B3.subtree_cv(x, 0 if MUTATE == "counter-zero" else start)
def read_proof(b):
if len(b) < 53 or b[:4] != b"WHP1":
raise Refusal("head_proof_malformed")
k = b[52]
if k > 64 or len(b) != 53 + 32 * k:
raise Refusal("head_proof_malformed")
n, c = int.from_bytes(b[36:44], "little"), int.from_bytes(b[44:52], "little")
if n > 2 ** 64 - 1 - 15 or c > n:
raise Refusal("head_proof_malformed")
if not (c == n or (c < n and (15 + c) % 1024 == 0)) or (c == n and k):
raise Refusal("head_proof_malformed")
return {"cid": b[4:36], "n": n, "covered": c, "cvs": [b[53 + 32 * i:85 + 32 * i] for i in range(k)]}
def verify_proof(p, cid, prefix):
"""§6.3, checks 1-3, in order."""
if p["cid"] != cid or len(prefix) < p["covered"]:
raise Refusal("head_proof_malformed")
covered = prefix[:p["covered"]]
if p["covered"] == p["n"]:
if content_hash(covered) != cid:
raise Refusal("head_proof_mismatch")
else:
x, L, c = DOMAIN + covered, 15 + p["n"], 15 + p["covered"]
it = iter(p["cvs"])
def cv(start, ln):
if start >= c:
v = next(it, None)
if v is None:
raise Refusal("head_proof_mismatch")
return v
if start + ln <= c:
return subtree(x[start:start + ln], start)
l = left(ln)
return merge(cv(start, l), cv(start + l, ln - l), False)
l = left(L)
root = merge(cv(0, l), cv(l, L - l), True)
if next(it, None) is not None or root != cid:
raise Refusal("head_proof_mismatch")
try:
h = parse_head(covered, p["n"])
except Refusal as e:
if e.code == "object_length_mismatch":
raise Refusal("head_proof_malformed")
raise
if isinstance(h, tuple) or covered_len(h["head_len"], p["n"]) != p["covered"]:
raise Refusal("head_proof_malformed")
# ------------------------------------------------------------- responses (§7.1)
DIGITS = re.compile(r"[0-9]+", re.ASCII)
def digits(s):
"""RFC 9110's 1*DIGIT: ASCII digits only, no sign, separator, space or other script."""
return int(s) if DIGITS.fullmatch(s) else None
def check_response(req, status, cr, enc, body_len, n):
coded = enc is not None and enc.strip() and enc.strip().lower() != "identity"
if status == 206:
if req is None:
raise Refusal("http_status_unexpected")
if coded:
raise Refusal("content_coding_present")
field = (cr or "").strip(" \t")
if not field.startswith("bytes ") or "/" not in field:
raise Refusal("content_range_mismatch")
span, complete = field[len("bytes "):].split("/", 1)
if "-" not in span:
raise Refusal("content_range_mismatch")
a, b = span.split("-", 1)
a, b = digits(a), digits(b)
if a is None or b is None or a != req[0] or b != min(req[1], max(n - 1, 0)) or b < a or body_len != b - a + 1:
raise Refusal("content_range_mismatch")
if digits(complete) != n:
raise Refusal("complete_length_mismatch")
return (a, b)
if status == 200:
if coded:
raise Refusal("content_coding_present")
if body_len != n or n == 0:
raise Refusal("complete_length_mismatch")
return (0, n - 1)
raise Refusal("http_status_unexpected")
def origin_serve(n, rng):
"""An origin's answer to a Range header on n bytes (RFC 9110 §14), as the corpus's
origin gives it: (status, Content-Range, first, end)."""
if rng is None or not rng.startswith("bytes=") or "," in rng:
return 200, None, 0, n
a, b = rng[len("bytes="):].split("-")
if a == "":
s = int(b)
return 206, "bytes %d-%d/%d" % (max(0, n - s), n - 1, n), max(0, n - s), n
first = int(a)
last = n - 1 if b == "" else min(int(b), n - 1)
if first >= n:
return 416, "bytes */%d" % n, 0, 0
return 206, "bytes %d-%d/%d" % (first, last, n), first, last + 1
# ------------------------------------------------------------------ selection
def selectable(e):
return e.get("role") not in ("companion", "refinement") and dispatchable(e["capability"])
def resolve_pin(cap, prior, held):
if not isinstance(prior, dict) or "sha256" not in prior:
raise SystemExit("http_verify: a pin shape this verifier does not read")
form = prior.get("form")
if form is None:
return "ok" if any(sha(h) == prior["sha256"] for h in held) else "prior_not_held"
if form in READABLE:
return "prior_not_held"
raise SystemExit("http_verify: a pin form this verifier does not read")
def plaintext_v2(m):
"""SPEC §6.4 step 2 for a WAI2: `security` absent or null, or an object whose
`encryption` is absent, null or "none"; a member that is not a security object (a
string, `true`, a mistyped member) means the payloads cannot be read here."""
sec = m.get("security")
if sec is None:
return True
if not isinstance(sec, dict):
return False
enc, kid = sec.get("encryption"), sec.get("kid")
if enc is not None and not isinstance(enc, str):
return False
if kid is not None and not SV.a_u64(kid):
return False
return enc in (None, "none")
def select(m, order, adv, held, digest_ok):
"""SPEC §7.1 step 2 in `order`: (pick, skipped) or Refusal('inert', skipped)."""
entries, skipped = m["renditions"], []
if not plaintext_v2(m):
raise Refusal("inert", [[i, "unsupported_encryption"] for i in range(len(entries))])
for i in order:
if i >= len(entries):
continue
e = entries[i]
if not selectable(e):
skipped.append([i, "not_selectable"]); continue
if e["capability"] not in adv:
skipped.append([i, "missing_capability"]); continue
if not digest_ok(i, e):
skipped.append([i, "component_digest_mismatch"]); continue
if e.get("prior") is not None and pin_applies(e["capability"]):
r = resolve_pin(e["capability"], e["prior"], held)
if r != "ok":
skipped.append([i, r]); continue
return i, skipped
raise Refusal("inert", skipped)
def dispatch_wai1(m, adv, held):
"""SPEC §4 with §6.4 step 2 first: an encrypted payload is inert at a sink that
opens no scheme."""
sec = m.get("security")
if sec is not None and sec.get("encryption") not in (None, "none"):
raise Refusal("inert", [[0, "unsupported_encryption"]])
mr = m["model_requirement"]
cap, fb = mr["capability"], mr.get("fallback")
if kind(cap) in ("companion", "refinement"):
raise Refusal("inert", [[0, "missing_capability"]])
first = None
for c in [cap, fb]:
if c is None or not dispatchable(c) or c not in adv:
continue
p = m.get("conditioning", {}).get("prior")
if p is not None and pin_applies(c):
r = resolve_pin(c, p, held)
if r == "ok":
return 0
first = first or r
continue
return 0
raise Refusal("inert", [[0, first or "missing_capability"]])
def companions_of(entries, i):
base = entries[i]
out = []
for j, e in enumerate(entries):
if e.get("role") != "companion":
continue
cid = e.get("companion_of_id")
if (cid is not None and base.get("id") == cid) or (cid is None and e.get("companion_of") == base["capability"]):
out.append(j)
return out
def refinements_of(entries, i):
"""The refinement components whose well-formed `refines` (staged-delivery §3, ids in
place of hashes) names entry i's id as its base."""
bid = entries[i].get("id")
if bid is None:
return []
out = []
for j, e in enumerate(entries):
if e.get("role") != "refinement":
continue
try:
if SV.parse_refines_ids(e.get("refines"))["base"] == bid:
out.append(j)
except (SV.Malformed, KeyError, TypeError):
pass
return out
def refinements_naming(entries, i):
"""The refinement components whose `refines.base`, read on its own, is entry i's id:
refinements_of without the rest of `refines` read. Of a conforming envelope the two
agree; of one that does not, these are the refinements a plan drops and reports
(components_nonconforming, http-delivery §7 step 4), a malformed `refines` naming the
pick among them."""
if MUTATE == "refines-read-whole":
return refinements_of(entries, i)
bid = entries[i].get("id")
if bid is None:
return []
return [j for j, e in enumerate(entries) if e.get("role") == "refinement"
and isinstance(e.get("refines"), dict) and isinstance(e["refines"].get("base"), str)
and e["refines"]["base"] == bid]
def payload_ok(e, payload):
return e.get("sha256") is None or e["sha256"] == sha(payload)
# ------------------------------------------------------------------- the plan
def plan(head, wanted, held, permille):
"""§7 steps 5-6 and the threshold, over base-class bytes only:
("rest", (first, last), why) or ("ranges", [...], dropped)."""
n, cov = head["envelope_len"], covered_len(head["head_len"], head["envelope_len"])
if cov == n:
return ("ranges", [], [])
rest = (cov, n - 1)
entries = head["manifest"]["renditions"]
for i, cl in wanted:
if cl == "base" and entries[i].get("sha256") is None:
return ("rest", rest, "component_digest_absent")
pieces, dropped = [], []
for i, cl in wanted:
if entries[i].get("sha256") is None:
dropped.append([i, "component_digest_absent"])
continue
s, ln = head["components"][i]
segs = [(s, s + ln)]
for (hf, hl) in held:
nxt = []
for a, b in segs:
hs, he = hf, hl + 1
if he <= a or hs >= b:
nxt.append((a, b)); continue
if a < hs:
nxt.append((a, hs))
if he < b:
nxt.append((he, b))
segs = nxt
pieces += [(0 if cl == "base" else 1, a, b) for a, b in segs]
pieces.sort()
merged = []
for c, a, b in pieces:
if merged and merged[-1][0] == c and merged[-1][2] >= a:
merged[-1][2] = max(merged[-1][2], b)
else:
merged.append([c, a, b])
base = sum(b - a for c, a, b in merged if c == 0)
if base > 0 and base * 1000 >= permille * (n - cov):
return ("rest", rest, "range_threshold")
return ("ranges", [(a, b - 1, "base" if c == 0 else "refinement") for c, a, b in merged if b > a], dropped)
# ---------------------------------------------------------------- the codecs
def cap_ok(cap):
parts = cap.split(".")
lc = lambda s, extra="": s and all(ch in "abcdefghijklmnopqrstuvwxyz0123456789" + extra for ch in s)
return len(parts) >= 2 and lc(parts[0]) and all(lc(p, "_") for p in parts[1:])
def check_cap(c, first):
if not cap_ok(c):
raise Refusal("codecs_malformed")
full = "wai." + c
k = kind(full)
if k in ("derivation", "record"):
raise Refusal("codecs_not_media")
if candidate(full):
raise Refusal("codecs_candidate")
if first and k in ("companion", "refinement"):
raise Refusal("codecs_malformed")
if not first and k == "payload":
raise Refusal("codecs_malformed")
def check_alt(alt):
for i, c in enumerate(alt):
check_cap(c, i == 0)
def parse_element(s):
if "." not in s:
raise Refusal("codecs_malformed")
prefix, rest = s.split(".", 1)
if prefix not in ("wai1", "wai2"):
raise Refusal("codecs_malformed")
alts = []
for a in rest.split("|"):
alt = a.split("+")
check_alt(alt)
if len(set(alt)) != len(alt) or any(x[0] == alt[0] for x in alts):
raise Refusal("codecs_malformed")
alts.append(alt)
return (prefix, alts)
def render(e):
return e[0] + "." + "|".join("+".join(a) for a in e[1])
def parse_codecs(value):
out = []
for e in (x.strip(" \t") for x in value.split(",")):
if e == "":
raise Refusal("codecs_malformed")
if e.encode("utf-8")[:3].lower() == b"wai":
out.append(render(parse_element(e)))
else:
out.append(None)
return out
def short(c):
return c[4:] if c.startswith("wai.") else c
def element_of(head):
if is_layer(head):
raise Refusal("codecs_malformed")
m = head["manifest"]
if head["version"] == "WAI1":
mr = m["model_requirement"]
alts, prefix = [], "wai1"
for c in [mr["capability"], mr.get("fallback")]:
if c is not None and dispatchable(c) and short(c) not in [a[0] for a in alts]:
alts.append([short(c)])
else:
e, alts, prefix = m["renditions"], [], "wai2"
for i, r in enumerate(e):
if not selectable(r):
continue
cap = short(r["capability"])
a = next((x for x in alts if x[0] == cap), None)
if a is None:
a = [cap]; alts.append(a)
for j in sorted(companions_of(e, i) + refinements_of(e, i)):
p = short(e[j]["capability"])
if p not in a[1:]:
a.append(p)
pos = lambda p: next(k for k, r in enumerate(e) if short(r["capability"]) == p)
alts = [[a[0]] + sorted(a[1:], key=pos) for a in alts]
if not alts:
raise Refusal("codecs_not_media")
for a in alts:
check_alt(a)
return (prefix, alts)
def seq_object(env):
h = whole_head(env)
comps = []
if h["version"] == "WAI2":
try:
comps = [content_hash(x).hex() if x is not None else None for x in SV.explode_each(env)]
except (SV.NotWai2, SV.NotJSON, SV.Schema, ValueError, KeyError):
comps = []
return {"cid": content_hash(env).hex(), "head": h, "components": comps}
def base_capability(b, base):
"""The capability through which object b is the base whose content hash is `base`: a
WAI1's capability when its cid is `base`, or the capability of the selectable component
of a WAI2 whose exploded envelope has that hash; None otherwise (§4.2)."""
if is_layer(b["head"]):
return None
if b["cid"] == base and b["head"]["version"] == "WAI1":
return short(b["head"]["manifest"]["model_requirement"]["capability"])
if base in b["components"]:
r = b["head"]["manifest"]["renditions"][b["components"].index(base)]
return short(r["capability"]) if selectable(r) else None
return None
def described(objects):
els = [None if is_layer(o["head"]) else element_of(o["head"]) for o in objects]
for o in objects:
if not is_layer(o["head"]):
continue
m = o["head"]["manifest"]
cap = short(m["model_requirement"]["capability"])
check_cap(cap, False)
try:
base = SV.parse_refines(m.get("refines"))["base"]
except SV.Malformed:
raise Refusal("codecs_malformed")
target = None
for k, b in enumerate(objects):
bc = base_capability(b, base)
if bc is not None:
target = (k, bc)
break
if target is None:
raise Refusal("codecs_layer_unbound")
alt = next((a for a in els[target[0]][1] if a[0] == target[1]), None)
if alt is None:
raise Refusal("codecs_layer_unbound")
if cap not in alt[1:]:
alt.append(cap)
return [e for e in els if e is not None]
def union_of(objects):
if MUTATE == "floor-as-codecs":
return floor_of(objects)
each = described(objects)
prefix = "wai2" if any(o["head"]["version"] == "WAI2" for o in objects) else "wai1"
alts = []
for p, al in each:
for a in al:
x = next((x for x in alts if x[0] == a[0]), None)
if x is None:
alts.append(list(a))
else:
x += [c for c in a[1:] if c not in x[1:]]
if not alts:
raise Refusal("codecs_no_common_capability")
return (prefix, alts)
def floor_of(objects):
each = described(objects)
prefix = "wai2" if any(o["head"]["version"] == "WAI2" for o in objects) else "wai1"
seen = []
for e in each:
for a in e[1]:
x = next((s for s in seen if s[0] == a[0]), None)
if x is None:
seen.append(list(a))
else:
x += [c for c in a[1:] if c not in x[1:]]
out = []
for a in seen:
found = [next((x for x in e[1] if x[0] == a[0]), None) for e in each]
if any(f is None for f in found):
continue
out.append([a[0]] + [c for c in a[1:] if all(c in f[1:] for f in found)])
if not out:
raise Refusal("codecs_no_common_capability")
return (prefix, out)
def check_declared(head, union, base):
"""§4.2: a layer is declared only after `+` in the alt of its own base's capability,
the base (`base`, a sequence object) being the one its `refines.base` names."""
if is_layer(head):
m = head["manifest"]
cap = short(m["model_requirement"]["capability"])
try:
named = SV.parse_refines(m.get("refines"))["base"]
except SV.Malformed:
raise Refusal("codecs_undeclared")
bc = base_capability(base, named) if base is not None else None
if bc is None or not any(a[0] == bc and cap in a[1:] for a in union[1]):
raise Refusal("codecs_undeclared")
return
p, alts = element_of(head)
if p > union[0]:
raise Refusal("codecs_undeclared")
for a in alts:
u = next((x for x in union[1] if x[0] == a[0]), None)
if u is None or any(c not in u[1:] for c in a[1:]):
raise Refusal("codecs_undeclared")
def supports(floor, containers, caps):
reads = "WAI1" in containers and (floor[0] == "wai1" or "WAI2" in containers)
return reads and any(("wai." + a[0]) in caps for a in floor[1])
def parse_layer_tag(line):
"""§10.4's #WAI-LAYER line, as an HLS attribute list."""
bad = Refusal("layer_tag_malformed")
if not line.startswith("#WAI-LAYER:"):
raise bad
rest = line[len("#WAI-LAYER:"):].encode("utf-8")
n, uri, names = None, None, []
while True:
eq = rest.find(b"=")
if eq < 0:
raise bad
name = rest[:eq]
if not name or not re.fullmatch(rb"[A-Z0-9-]+", name) or name in names:
raise bad
names.append(name)
rest = rest[eq + 1:]
if rest[:1] == b'"':
close = rest.find(b'"', 1)
if close < 0:
raise bad
value, quoted, after = rest[1:close], True, rest[close + 1:]
if b"\r" in value or b"\n" in value:
raise bad
else:
end = rest.find(b",")
end = len(rest) if end < 0 else end
value, quoted, after = rest[:end], False, rest[end:]
if not value or b'"' in value or re.search(rb"[ \t\n\r\x0c]", value):
raise bad
if name == b"N":
if quoted or len(value) > 3 or not re.fullmatch(rb"[0-9]+", value) or not 1 <= int(value) <= 255:
raise bad
n = int(value)
elif name == b"URI":
if not quoted:
raise bad
try:
uri = value.decode("utf-8")
except UnicodeDecodeError:
raise bad
if not after:
break
if after[:1] != b",":
raise bad
rest = after[1:]
if n is None or uri is None:
raise bad
return {"n": n, "uri": uri}
# ------------------------------------------------------------- the session (§7)
class Session:
"""The reference client of §7, step by step as the requests are answered: it says
which requests to make, checks each response, and decides as each arrives."""
def __init__(self, cid, frag, permille, sink, layer=False, deadline=None):
self.cid, self.frag, self.permille, self.sink, self.deadline = cid, frag, permille, sink, deadline
self.next_id, self.queued, self.issued, self.early = 0, [], {}, []
self.proof, self.held, self.head, self.stage = None, [], None, ("head",)
self.abandoned, self.dropped, self.body, self.requests, self.result = [], [], 0, 0, None
# §10.1, §10.2: the caller's clock as last given; the base once it verified, with
# the time of the response that verified it; each verified component, frozen.
self.now, self.base, self.base_at, self.verified = 0, None, None, {}
if layer:
self.stage = ("layer",)
self.queue(("whole", "refinement"))
return
n = frag.get("n")
if n is not None and n < 1009:
self.stage = ("rest", "below_one_chunk", "base")
self.queue(("whole", "base"))
return
e = frag.get("h") or 16369
if n is not None:
e = min(e, n)
self.queue(("range", (0, e - 1), "base"))
self.queue(("proof",))
@staticmethod
def cls(w):
return "base" if w[0] == "proof" else w[-1]
def queue(self, w):
self.queued.append((self.next_id, w)); self.next_id += 1
def wants(self):
out, self.queued = self.queued, []
for i, w in out:
self.issued[i] = w
self.requests += 1
return out
def finish(self, r):
self.stage, self.queued, self.result = ("done",), [], r
def abandon(self, w):
st = self.stage[0]
if st == "layer" or (st == "rest" and self.stage[2] == "refinement"):
if 0 not in self.abandoned:
self.abandoned.append(0)
elif st == "components" and w[0] == "range" and w[2] == "refinement":
(a, b) = w[1]
for i, cl in self.stage[3]:
s, ln = self.head["components"][i]
if cl == "refinement" and s <= b and a < s + ln and i not in self.abandoned:
self.abandoned.append(i)
def on_deadline(self, i):
if self.result is not None:
return
w = self.issued.get(i)
if w is not None and self.cls(w) == "refinement":
del self.issued[i]
self.abandon(w)
self.advance()
def base_ready(self):
if self.base is not None:
return self.base
return self.result if self.result is not None and "error" not in self.result else None
def tick(self, now):
"""§10.2: the deadline runs from the response that verified the base; the sum
saturates at the end of u64, and a clock that steps back has not passed it."""
self.now = now
if self.base_at is None or self.deadline is None or MUTATE == "tick-noop":
return []
if self.result is not None or now < min(self.base_at + self.deadline, 2 ** 64 - 1):
return []
ids = sorted(i for i, w in self.issued.items() if self.cls(w) == "refinement")
for i in ids:
self.on_deadline(i)
return ids
def on_response(self, i, status, cr, enc, body, now):
self.now = now
if self.result is not None and "error" in self.result:
raise Refusal(self.result["error"], self.result.get("skipped"))
w = self.issued.pop(i, None)
if w is None or self.result is not None:
return
self.body += len(body)
try:
self.take(w, status, cr, enc, body)
self.advance()
except Refusal as e:
self.finish(refusal(e))
raise
def n(self):
return self.proof["n"] if self.proof else None
def take(self, w, status, cr, enc, body):
if w[0] == "proof":
if status != 200:
raise Refusal("http_status_unexpected")
if enc is not None and enc.strip() and enc.strip().lower() != "identity":
raise Refusal("content_coding_present")
p = read_proof(body)
if p["cid"] != self.cid:
raise Refusal("head_proof_malformed")
self.proof = p
early, self.early = self.early, []
for x in early:
self.take(*x)
return
if w[0] == "whole":
try:
check_response(None, status, cr, enc, len(body), len(body))
except Refusal:
if w[1] == "refinement":
self.abandon(w); return
raise
if content_hash(body) != self.cid:
raise Refusal("object_digest_mismatch")
if body:
self.held.append((0, len(body) - 1, body))
return
if self.proof is None:
self.early.append((w, status, cr, enc, body)); return
try:
a, b = check_response(w[1], status, cr, enc, len(body), self.n())
except Refusal:
if w[2] == "refinement":
self.abandon(w); return
raise
if w[2] == "refinement" and MUTATE != "refinement-unclipped":
# A refinement's response adds the bytes it asked for and no others (§10.1).
first, last = max(a, w[1][0]), min(b, w[1][1])
if first <= last:
self.held.append((first, last, body[first - a:last + 1 - a]))
return
self.held.append((a, b, body))
def have(self, first, end):
out, at = b"", first
while at < end:
best = max((h for h in self.held if h[0] <= at <= h[1]), key=lambda h: h[1], default=None)
if best is None:
return None
take = min(best[1] + 1, end)
out += best[2][at - best[0]:take - best[0]]
at = take
return out
def drop_from(self, frm):
kept = []
for a, b, body in self.held:
if a >= frm:
continue
kept.append((a, b, body) if b < frm else (a, frm - 1, body[:frm - a]))
self.held = kept
def prefix_len(self):
at = 0
while True:
e = max((h[1] + 1 for h in self.held if h[0] <= at <= h[1]), default=None)
if e is None:
return at
at = e
def advance(self):
if self.result is not None:
return
st = self.stage[0]
if st == "head":
self.advance_head()
elif st == "components":
self.advance_components()
elif st in ("rest", "layer"):
self.advance_rest()
def to_rest(self, why, cl):
self.stage = ("rest", why, cl)
n, cov = self.head["envelope_len"], covered_len(self.head["head_len"], self.head["envelope_len"])
if cov < n and self.have(cov, n) is None:
self.queue(("rest", (cov, n - 1), cl))
def nonconforming(self, entries, wanted):
"""§7 step 4: the refinement components among `wanted` of an envelope whose
components do not all conform (SPEC §7.1) — every one of them where it does not,
none where it does."""
conforms = SV.component_roles(entries) is None and MUTATE != "conforming-withheld"
if conforms or MUTATE == "nonconforming-offered":
return []
return [i for i, _ in wanted if entries[i].get("role") == "refinement"]
def applied(self, entries, pick):
adv, applies = self.sink[0], self.sink[1]
out = [(j, "refinement" if enhancement(entries[j]["capability"]) else "base")
for j in companions_of(entries, pick) if entries[j]["capability"] in applies]
out += [(j, "refinement") for j in refinements_naming(entries, pick) if entries[j]["capability"] in applies]
return out
def advance_head(self):
if self.proof is None:
return
cov = self.proof["covered"]
h0 = self.prefix_len()
if h0 < cov:
pending = [w for w in list(self.issued.values()) + [w for _, w in self.queued] if w[0] in ("range", "rest") and w[-1] == "base"]
if not pending:
self.queue(("range", (h0, cov - 1), "base"))
return
prefix = self.have(0, cov)
verify_proof(self.proof, self.cid, prefix)
head = parse_head(prefix, self.proof["n"])
if isinstance(head, tuple):
raise Refusal("head_malformed")
adv, applies, held_sets, order, declared, base = self.sink
if declared is not None:
check_declared(head, declared, base)
self.head = head
if is_layer(head):
self.to_rest("layer", "refinement")
elif head["version"] == "WAI1":
dispatch_wai1(head["manifest"], adv, held_sets)
self.to_rest("single_component", "base")
else:
entries = head["manifest"]["renditions"]
ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries))))
pick, skipped = select(head["manifest"], ordr, adv, held_sets, lambda i, e: True)
wanted = [(pick, "base")] + self.applied(entries, pick)
nc = self.nonconforming(entries, wanted)
planned = [(i, c) for i, c in wanted if i not in nc or entries[i].get("sha256") is None]
pl = plan(head, planned, [(a, b) for a, b, _ in self.held], self.permille)
if pl[0] == "rest":
self.to_rest(pl[2], "base")
else:
for (a, b, cl) in pl[1]:
self.queue(("range", (a, b), cl))
self.dropped = pl[2] + [[i, "components_nonconforming"] for i in nc]
gone = [i for i, _ in pl[2]]
self.stage = ("components", pick, skipped, [(i, c) for i, c in wanted if i not in gone])
if self.stage[0] == "components":
self.advance_components()
elif self.stage[0] == "rest":
self.advance_rest()
def advance_components(self):
_, pick, skipped, wanted = self.stage
entries = self.head["manifest"]["renditions"]
parts, pending, base_pending = {}, False, False
for i, cl in wanted:
if i in self.abandoned or any(d == i for d, _ in self.dropped):
continue
if i in self.verified:
parts[i] = self.verified[i]
continue
s, ln = self.head["components"][i]
bts = self.have(s, s + ln)
if bts is None:
pending = True
base_pending = base_pending or cl == "base"
elif entries[i].get("sha256") is not None and sha(bts) == entries[i]["sha256"]:
parts[i] = self.verified[i] = bts
elif cl == "refinement":
self.dropped.append([i, "component_digest_mismatch"])
else:
self.issued, self.queued, self.abandoned, self.dropped, self.verified = {}, [], [], [], {}
self.drop_from(covered_len(self.head["head_len"], self.head["envelope_len"]))
self.to_rest("component_digest_mismatch", "base")
self.advance_rest()
return
classes = dict(wanted)
def assemble(only_base):
js = [j for j, _ in wanted[1:] if j in parts and not (only_base and classes[j] == "refinement")]
comps = [j for j in js if entries[j].get("role") == "companion"]
refs = [j for j in js if entries[j].get("role") != "companion"]
return self.fetched("base", pick, parts[pick], comps, refs, self.abandoned, self.dropped, skipped, "head-proof", None)
if not (pending if MUTATE == "base-at-done" else base_pending) and self.base is None:
self.base, self.base_at = assemble(True), self.now
if pending:
return
self.finish(assemble(False))
def fetched(self, role, pick, comp, comps, refs, abandoned, dropped, skipped, by, why):
return {"role": role, "pick": pick, "verified_by": by, "why_whole": why, "body_bytes": self.body, "requests": self.requests,
"companions": comps, "refinements": refs, "abandoned": list(abandoned), "dropped": [list(x) for x in dropped],
"skipped": skipped, "component_sha256": sha(comp)}
def advance_rest(self):
layer_listed = self.stage[0] == "layer"
why = "layer" if layer_listed else self.stage[1]
if 0 in self.abandoned and (layer_listed or why == "layer"):
self.finish(self.fetched("layer", 0, b"", [], [], [0], [], [], "whole-object", "layer"))
return
if self.proof is not None:
n = self.proof["n"]
elif self.held and self.held[0][0] == 0:
n = self.held[0][1] + 1
else:
return
envb = self.have(0, n)
if envb is None:
return
if content_hash(envb) != self.cid:
raise Refusal("object_digest_mismatch")
head = parse_head(envb, n)
adv, applies, held_sets, order, declared, base = self.sink
if declared is not None:
check_declared(head, declared, base)
if is_layer(head):
s, ln = head["components"][0]
self.finish(self.fetched("layer", 0, envb[s:s + ln], [], [], [], [], [], "whole-object", "layer"))
return
if layer_listed:
raise Refusal("not_a_layer")
m = head["manifest"]
if head["version"] == "WAI1":
dispatch_wai1(m, adv, held_sets)
s, ln = head["components"][0]
self.finish(self.fetched("base", 0, envb[s:s + ln], [], [], [], [], [], "whole-object", why))
return
entries = m["renditions"]
pay = [envb[s:s + ln] for s, ln in head["components"]]
ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries))))
pick, skipped = select(m, ordr, adv, held_sets, lambda i, e: payload_ok(e, pay[i]))
comps, refs, dropped = [], [], []
applied = self.applied(entries, pick)
nc = self.nonconforming(entries, applied)
for j, cl in applied:
# §7 step 4: not offered from an envelope that does not conform; one stating no
# digest is dropped for that too, as on the range path.
if j in nc:
if entries[j].get("sha256") is None:
dropped.append([j, "component_digest_absent"])
continue
# A refinement stating no digest is dropped here as on the range path (§7 step 5).
if cl == "refinement" and entries[j].get("sha256") is None:
dropped.append([j, "component_digest_absent"])
continue
if not payload_ok(entries[j], pay[j]):
if cl == "refinement":
dropped.append([j, "component_digest_mismatch"])
continue
(comps if entries[j].get("role") == "companion" else refs).append(j)
dropped += [[j, "components_nonconforming"] for j in nc]
self.finish(self.fetched("base", pick, pay[pick], comps, refs, [], dropped, skipped, "whole-object", why))
def drive(s, answer):
"""Run a session: `answer(want)` gives (status, Content-Range, Content-Encoding,
body) or None for a request that never arrives."""
while True:
if s.result is not None:
if "error" in s.result:
raise Refusal(s.result["error"], s.result.get("skipped"))
return s.result
ws = s.wants()
if not ws:
for i in [i for i, w in s.issued.items() if Session.cls(w) == "refinement"]:
s.on_deadline(i)
if s.result is not None:
continue
if not s.queued:
if s.issued:
raise Refusal("base_unanswered")
raise SystemExit("http_verify: the session stalled")
continue
for i, w in ws:
r = answer(w)
if r is not None:
s.on_response(i, *r, s.now)
def fetch(env, served, frag, sink, permille, tamper, listed_as_layer=False):
"""The reference client against an origin serving `served` under the name of `env`,
with the corpus's tampers."""
proof_bytes = make_proof_file(env) if not listed_as_layer else b""
n_real = len(served)
kind_t, ref_t, first_flip = tamper.get("kind"), tamper.get("refinement"), tamper.get("first_range_flip")
state = {"first": True}
def answer(w):
cl = Session.cls(w)
if kind_t == "no_refinement" and cl == "refinement":
return None
if w[0] == "proof":
return None if kind_t == "no_proof" else (200, None, None, proof_bytes)
rng = None if w[0] == "whole" else ("bytes=%d-%d" % w[1] if w[0] == "range" else "bytes=%d-" % w[1][0])
status, cr, a, b = origin_serve(n_real, rng)
body = served[a:b]
enc = "gzip" if kind_t == "gzip" else None
if first_flip is not None and w[0] == "range" and state["first"]:
state["first"] = False
if a <= first_flip < b:
body = bytearray(body); body[first_flip - a] ^= 1; body = bytes(body)
if ref_t is not None and cl == "refinement" and w[0] == "range":
(ra, rb) = w[1]
if ref_t == "gzip":
enc = "gzip"
elif ref_t == "bad_range":
cr = "bytes %d-%d/%d" % (ra + 1, rb + 1, n_real)
elif ref_t == "416":
return (416, "bytes */%d" % n_real, None, b"")
elif ref_t == "complete_length":
cr = "bytes %d-%d/1" % (ra, rb)
elif ref_t == "plus_sign":
cr = cr.replace("bytes ", "bytes +", 1)
elif ref_t == "short_200":
return (200, None, None, body)
return (status, cr, enc, body)
s = Session(content_hash(env), frag, permille, sink, layer=listed_as_layer)
return drive(s, answer)
def fetch_timed(env, frag, sink, permille, deadline, script, flip):
"""The reference client on a clock (§10.1, §10.2), driven by the corpus's script."""
proof_bytes = make_proof_file(env)
n = len(env)
def answer(w):
if w[0] == "proof":
return (200, None, None, proof_bytes)
rng = None if w[0] == "whole" else ("bytes=%d-%d" % w[1] if w[0] == "range" else "bytes=%d-" % w[1][0])
status, cr, a, b = origin_serve(n, rng)
return (status, cr, None, env[a:b])
def summary(f):
return {"pick": f["pick"], "verified_by": f["verified_by"], "component_sha256": f["component_sha256"],
"companions": f["companions"], "refinements": f["refinements"]}
s = Session(content_hash(env), frag, permille, sink, deadline=deadline)
pending, ready_step, first, ticks = [], None, None, []
stop = False
for k, step in enumerate(script):
kind, t = step[0], step[1]
if kind in ("base", "base_once", "refinement"):
cl = "refinement" if kind == "refinement" else "base"
while True:
pending += s.wants()
now = [(i, w) for i, w in pending if Session.cls(w) == cl]
if not now:
break
pending = [(i, w) for i, w in pending if Session.cls(w) != cl]
for i, w in now:
if len(step) > 2 and step[2] == "whole_200":
b = bytearray(env)
if flip is not None:
b[flip] ^= 1
r = (200, None, None, bytes(b))
else:
r = answer(w)
try:
s.on_response(i, *r, t)
except Refusal:
stop = True
break
if stop or kind != "base":
break
elif kind == "tick":
ids = s.tick(t)
pending = [(i, w) for i, w in pending if i not in ids]
ticks.append([t, ids])
else:
raise SystemExit("unknown step " + kind)
if ready_step is None and s.base_ready() is not None:
ready_step, first = k, summary(s.base_ready())
if stop:
break
end = s.base_ready()
return {"ready_step": ready_step, "ready_at": s.base_at, "base": first, "base_end": summary(end) if end is not None else None,
"ticks": ticks, "outcome": s.result}
def make_proof_file(env):
"""A producer's head proof (§6.2), written from the text: the chaining values of the
subtrees at or after c, in the order §6.3 consumes them."""
h = whole_head(env)
cov = covered_len(h["head_len"], len(env))
cvs = []
if cov < len(env):
x, L, c = DOMAIN + env, 15 + len(env), 15 + cov
def walk(start, ln):
if start >= c:
cvs.append(B3.subtree_cv(x[start:start + ln], start))
elif start + ln > c:
l = 1024 * 2 ** ((((ln - 1) // 1024)).bit_length() - 1)
walk(start, l); walk(start + l, ln - l)
l = 1024 * 2 ** ((((L - 1) // 1024)).bit_length() - 1)
walk(0, l); walk(l, L - l)
return b"WHP1" + content_hash(env) + len(env).to_bytes(8, "little") + cov.to_bytes(8, "little") + bytes([len(cvs)]) + b"".join(cvs)
# ----------------------------------------------------------------- the cases
def apply_patch(b, patch):
b = bytearray(b)
for off, hx in patch or []:
bs = bytes.fromhex(hx)
b[off:off + len(bs)] = bs
return bytes(b)
def frag_of(s):
out = {}
if s and "#" in s:
for kv in s.split("#", 1)[1].split("&"):
if "=" in kv:
k, v = kv.split("=", 1)
if k in ("n", "h") and v.isdigit():
out[k] = int(v)
return out
def sink_of(d, inp, objs):
"""The sink a case describes. Its declared union is parsed, and the base a layer is
declared against read, before anything is fetched: either refuses the case."""
held = [open(os.path.join(d, p), "rb").read() for p in inp.get("held") or []]
declared = parse_element(inp["declared"]) if isinstance(inp.get("declared"), str) else None
base = None
if inp.get("layer_base") is not None or inp.get("layer_base_hex") is not None:
b = bytes.fromhex(inp["layer_base_hex"]) if isinstance(inp.get("layer_base_hex"), str) else objs[inp["layer_base"]]
base = seq_object(b)
order = inp.get("order")
if inp.get("abr") is not None:
adv, pol, st = inp.get("advertises") or [], inp["abr"]["policy"], abr_state(inp["abr"]["state"])
check_policy(pol)
check_state(st)
def order(head):
try:
return order_entries(head, adv, pol, st)
except Refusal:
return list(range(len(head["manifest"]["renditions"])))
return (inp.get("advertises") or [], inp.get("applies") or [], held, order, declared, base)
# ------------------------------------------------- adaptation (Appendix F)
LOG2_TABLE = [0, 5732, 11136, 16248, 21098, 25711, 30109, 34312, 38336, 42196, 45904, 49472, 52911, 56229, 59434, 62534, 65536]
def ilog2_q16(a, b):
"""log2(a/b) in Q16: the ratio's exponent, and the table interpolated on the
mantissa's top four fractional bits with the next twelve as the weight."""
b = max(b, 1)
if a <= b:
return 0
x = (a << 16) // b
e = x.bit_length() - 1 - 16
f = (x >> e) - 65536
i, r = f >> 12, f & 4095
if MUTATE == "ilog2-no-interp":
r = 0
return e * 65536 + LOG2_TABLE[i] + (((LOG2_TABLE[i + 1] - LOG2_TABLE[i]) * r) >> 12)
def throughput_bps(samples, n):
k = min(max(n, 1), len(samples))
tail = samples[len(samples) - k:]
t = sum(max(s[2], 1) for s in tail)
return sum(s[1] for s in tail) * 8000 // t if t else 0
def option_bps(nbytes, seg):
return -(-(nbytes * 8000) // max(seg, 1))
def highest_under(st, budget):
for k in range(len(st["option_bytes"]) - 1, -1, -1):
if option_bps(st["option_bytes"][k], st["segment_ms"]) <= budget:
return k
return 0
def utilities(st):
s0 = max(st["option_bytes"][0] if st["option_bytes"] else 1, 1)
return [ilog2_q16(max(b, 1), s0) for b in st["option_bytes"]]
def choose(p, st):
opts, seg, k = st["option_bytes"], st["segment_ms"], len(st["option_bytes"])
if k == 0:
return 0
if p["id"] == "throughput":
return highest_under(st, throughput_bps(st["samples"], p["last_n"]) * p["safety_permille"] // 1000)
if p["id"] == "buffer_based":
b, r, c = st["buffer_ms"], p["reservoir_ms"], p["cushion_ms"]
if b <= r:
return 0
if c == 0 or b >= r + c:
return k - 1
lo, hi = option_bps(opts[0], seg), option_bps(opts[-1], seg)
return highest_under(st, lo + max(hi - lo, 0) * (b - r) // c)
if p["id"] == "bola_like":
sg = max(seg, 1)
q, qmax = st["buffer_ms"] * 65536 // sg, p["max_buffer_ms"] * 65536 // sg
u = utilities(st)
vn, vd = max(qmax - 65536, 0), max(u) + 65536
score = [vn * p["v_milli"] * (x + 65536) // (vd * 1000) - q for x in u]
size = [max(b, 1) for b in opts]
best = None
if any(x > 0 for x in score):
for i in range(k):
if score[i] > 0 and (best is None or score[i] * size[best] > score[best] * size[i]):
best = i
elif MUTATE == "bola-waits":
return 0
else:
for i in range(k):
if best is None or score[i] > score[best]:
best = i
return best
if p["id"] == "mpc":
pred = throughput_bps(st["samples"], 5) * p["safety_permille"] // 1000
if pred == 0:
return 0
h = min(max(p["horizon"], 1), 5)
while h > 1 and k ** h > 65536:
h -= 1
u = utilities(st)
u_top = max(u)
best = None
for code in range(k ** h):
seq, c = [0] * h, code
for t in range(h - 1, -1, -1):
seq[t], c = c % k, c // k
b, rebuf, qoe = st["buffer_ms"], 0, 0
last = st["last"]
prev = u[last] if last is not None and last < k else None
for o in seq:
dl = -(-(opts[o] * 8000) // pred)
if dl > b:
rebuf, b = rebuf + dl - b, 0
else:
b -= dl
b += seg
qoe += 1000 * u[o]
if prev is not None:
qoe -= 1000 * abs(u[o] - prev)
prev = u[o]
qoe -= rebuf * (u_top + 65536)
if best is None or qoe > best[0]:
best = (qoe, seq[0])
return best[1]
raise SystemExit("http_verify: an unknown policy " + str(p["id"]))
def clamp_to_budget(st, choice):
"""Appendix F's budget: it never raises the choice; the highest option at or below it
whose labelled figure is within it, else option 0 unmet; not applied only when no
option has a labelled figure."""
budget = st["joule_budget_micro"]
if budget is None:
return choice, "not_set"
figs = st["option_joules_micro"]
n = max(len(st["option_bytes"]), choice + 1)
fig = lambda j: figs[j] if j < len(figs) and j < n else None
if MUTATE == "unlabelled-as-zero":
fig = lambda j: (figs[j] if j < len(figs) and j < n else None) or 0
labelled = [(j, fig(j)) for j in range(n) if fig(j) is not None]
if not labelled or (MUTATE == "unlabelled-escapes" and fig(choice) is None):
return choice, "not_applied"
if fig(choice) is not None and fig(choice) <= budget:
return choice, "within"
below = [j for j, f in labelled if j < choice and f <= budget]
if below:
return below[-1], "lowered"
if MUTATE == "budget-raises":
above = [j for j, f in labelled if j > choice and f <= budget]
if above:
return above[0], "unmet"
return 0, "unmet"
MAX_OPTIONS = 256
OUT_OF_BOUNDS = "abr_state_out_of_bounds"
def bounded(v, top):
"""An unsigned integer of at most top; out of bounds for any other number."""
if isinstance(v, bool) or not isinstance(v, (int, float)):
raise SystemExit("http_verify: an adaptation value is not a number")
if not isinstance(v, int) or not 0 <= v <= top:
raise Refusal(OUT_OF_BOUNDS)
return v
def check_policy(p):
"""Appendix F: each parameter within its type."""
tops = {"throughput": {"last_n": 255, "safety_permille": 65535}, "buffer_based": {"reservoir_ms": U32, "cushion_ms": U32},
"bola_like": {"v_milli": U32, "max_buffer_ms": U32}, "mpc": {"horizon": 255, "safety_permille": 65535}}
if p.get("id") not in tops:
raise SystemExit("http_verify: an unknown policy " + str(p.get("id")))
for k, top in tops[p["id"]].items():
bounded(p[k], top)
def check_state(st):
if (len(st["option_bytes"]) > MAX_OPTIONS or len(st["option_joules_micro"]) > MAX_OPTIONS) and MUTATE != "abr-unbounded":
raise Refusal(OUT_OF_BOUNDS)
def decide(p, st):
check_policy(p)
check_state(st)
return clamp_to_budget(st, choose(p, st))
def abr_state(v):
"""A state from its JSON form, each value within its field's type (Appendix F)."""
opt = lambda x, top: None if x is None else bounded(x, top)
return {"buffer_ms": bounded(v["buffer_ms"], U32), "samples": [tuple(bounded(x, U64) for x in s[:3]) for s in v.get("samples") or []],
"last": opt(v.get("last"), U64), "option_bytes": [bounded(x, U64) for x in v.get("option_bytes") or []],
"option_joules_micro": [opt(x, U64) for x in v.get("option_joules_micro") or []],
"joule_budget_micro": opt(v.get("joule_budget_micro"), U64), "segment_ms": bounded(v["segment_ms"], U32)}
def depth(layers_n, p, st):
check_policy(p)
check_state(st)
if len(layers_n) > MAX_OPTIONS:
raise Refusal(OUT_OF_BOUNDS)
st = dict(st)
acc, cum, jcum, fsum = 0, [], [], 0
figs = st["option_joules_micro"]
for k, n in enumerate(layers_n):
acc += n
if acc > U64 and MUTATE != "abr-unbounded":
raise Refusal(OUT_OF_BOUNDS)
cum.append(acc)
f = figs[k] if k < len(figs) else None
fsum = None if fsum is None or f is None else fsum + f
if fsum is not None and fsum > U64 and MUTATE != "abr-unbounded":
raise Refusal(OUT_OF_BOUNDS)
jcum.append(fsum)
st["option_bytes"], st["option_joules_micro"] = cum, jcum
if not layers_n:
return 0, clamp_to_budget(st, 0)[1]
return clamp_to_budget(st, choose(p, st))
def order_entries(head, adv, p, st):
"""SPEC §7.1 step 2's order under a policy: each advertised selectable capability's
entries by component length, the choice first, then those below it nearest first,
then those above; every other entry after, in table order."""
check_policy(p)
check_state(st)
if head["version"] != "WAI2":
return [0]
entries = head["manifest"]["renditions"]
ln = lambda i: head["components"][i][1]
groups = []
for i, e in enumerate(entries):
if not selectable(e) or e["capability"] not in adv:
continue
g = next((g for g in groups if g[0] == e["capability"]), None)
if g is None:
groups.append((e["capability"], [i]))
else:
g[1].append(i)
order = []
for _, g in groups:
g = sorted(g, key=lambda i: (ln(i), i))
s2 = dict(st)
figs = st["option_joules_micro"]
s2["option_bytes"] = [ln(i) for i in g]
s2["option_joules_micro"] = [figs[i] if i < len(figs) else None for i in g]
c = min(decide(p, s2)[0], len(g) - 1)
order += [g[c]] + list(reversed(g[:c])) + g[c + 1:]
return order + [i for i in range(len(entries)) if i not in order]
# ------------------------------------------------------ playlists (§10.4, §11)
U64 = 2 ** 64 - 1
U32 = 2 ** 32 - 1
DIGITS3 = re.compile(r"[0-9]+(\.[0-9]+)?", re.ASCII)
def decimal_ms(s):
"""A decimal number of seconds, exactly, in milliseconds rounded half up."""
if not DIGITS3.fullmatch(s):
return None
whole, _, frac = s.partition(".")
f = (frac + "0000")[:4]
ms = int(whole) * 1000 + int(f[:3]) + (1 if int(f[3]) >= (6 if MUTATE == "round-half-down" else 5) else 0)
return ms if ms <= U64 else None
def decimal_int(s):
if not re.fullmatch(r"[0-9]+", s, re.ASCII):
return None
v = int(s)
return v if v <= U64 else None
def attributes(s):
"""An HLS attribute list (RFC 8216 §4.2) as [(name, quoted, value)]."""
bad = Refusal("playlist_malformed")
out, rest = [], s
while True:
if "=" not in rest:
raise bad
name, after = rest.split("=", 1)
if not re.fullmatch(r"[A-Z0-9-]+", name, re.ASCII) or any(n == name for n, _, _ in out):
raise bad
if after.startswith('"'):
close = after.find('"', 1)
if close < 0:
raise bad
v = after[1:close]
if "\r" in v or "\n" in v:
raise bad
out.append((name, True, v))
after = after[close + 1:]
else:
end = after.find(",")
end = len(after) if end < 0 else end
v = after[:end]
if not v or '"' in v or any(ch in " \t\n\r\x0c" for ch in v):
raise bad
out.append((name, False, v))
after = after[end:]
if not after:
return out
if not after.startswith(","):
raise bad
rest = after[1:]
def attr(a, name):
return next(((q, v) for n, q, v in a if n == name), None)
def attr_ms(a, name):
x = attr(a, name)
if x is None:
return None
if x[0] or decimal_ms(x[1]) is None:
raise Refusal("playlist_malformed")
return decimal_ms(x[1])
def attr_quoted(a, name):
x = attr(a, name)
if x is None:
return None
if not x[0]:
raise Refusal("playlist_malformed")
return x[1]
def attr_yes(a, name):
x = attr(a, name)
if x is None:
return False
if x[0] or x[1] not in ("YES", "NO"):
raise Refusal("playlist_malformed")
return x[1] == "YES"
def new_extra():
return {"discontinuity": False, "gap": False, "pdt": None, "parts": [], "layers": [], "hint": None}
def push_layer(layers, l):
if layers and layers[-1]["n"] >= l["n"]:
raise Refusal("playlist_malformed")
layers.append(l)
def parse_media_playlist(text):
"""A media playlist, classical or WAI, read as §11 needs it."""
bad = Refusal("playlist_malformed")
lines = [l[:-1] if l.endswith("\r") else l for l in text.split("\n")]
lines = [l for l in lines if l]
if not lines or lines[0] != "#EXTM3U":
raise bad
p = {"version": None, "target": None, "seq": 0, "dseq": 0, "part_target": None, "hold_back": None, "block": False,
"segments": [], "open": None, "hint": None, "end": False, "ptype": None, "indep": False, "map": None}
pending, extinf, anchor, started = new_extra(), None, None, False
for line in lines[1:]:
if line.startswith("#WAI-LAYER:"):
l = parse_layer_tag(line)
if anchor == "extinf":
push_layer(pending["layers"], l)
elif anchor == "part":
push_layer(pending["parts"][-1]["layers"], l)
else:
raise bad
continue
anchor = None
if not line.startswith("#"):
if extinf is None:
raise bad
p["segments"].append({"ms": extinf, "uri": line, "extra": pending})
pending, extinf, started = new_extra(), None, True
continue
if not line.startswith("#EXT"):
continue
tag, _, value = line.partition(":")
if tag == "#EXTM3U":
raise bad
elif tag == "#EXT-X-VERSION":
v = decimal_int(value)
if v is None or v > 255:
raise bad
p["version"] = v
elif tag == "#EXT-X-TARGETDURATION":
v = decimal_int(value)
if v is None:
raise bad
p["target"] = v
elif tag in ("#EXT-X-MEDIA-SEQUENCE", "#EXT-X-DISCONTINUITY-SEQUENCE"):
if started or extinf is not None or pending["parts"]:
raise bad
v = decimal_int(value)
if v is None:
raise bad
p["seq" if tag == "#EXT-X-MEDIA-SEQUENCE" else "dseq"] = v
elif tag == "#EXT-X-PART-INF":
a = attributes(value)
pt = attr_ms(a, "PART-TARGET")
if pt is None:
raise bad
p["part_target"] = pt
elif tag == "#EXT-X-SERVER-CONTROL":
a = attributes(value)
p["block"] = attr_yes(a, "CAN-BLOCK-RELOAD")
p["hold_back"] = attr_ms(a, "PART-HOLD-BACK")
elif tag == "#EXT-X-DISCONTINUITY":
pending["discontinuity"] = True
elif tag == "#EXT-X-GAP":
pending["gap"] = True
elif tag == "#EXT-X-INDEPENDENT-SEGMENTS":
p["indep"] = True
elif tag == "#EXT-X-PLAYLIST-TYPE":
if value not in ("VOD", "EVENT"):
raise bad
p["ptype"] = value
elif tag == "#EXT-X-MAP":
uri = attr_quoted(attributes(value), "URI")
if uri is None:
raise bad
if not started and extinf is None and pending == new_extra() and p["map"] is None:
p["map"] = uri
elif tag == "#EXT-X-PROGRAM-DATE-TIME":
pending["pdt"] = value
elif tag == "#EXT-X-PART":
a = attributes(value)
d, uri = attr_ms(a, "DURATION"), attr_quoted(a, "URI")
if d is None or uri is None:
raise bad
pending["parts"].append({"ms": d, "independent": attr_yes(a, "INDEPENDENT"), "uri": uri, "layers": []})
anchor = "part"
elif tag == "#EXTINF":
if extinf is not None:
raise bad
extinf = decimal_ms(value.split(",", 1)[0])
if extinf is None:
raise bad
anchor = "extinf"
elif tag == "#EXT-X-PRELOAD-HINT":
a = attributes(value)
t = attr(a, "TYPE")
if t is None or t[0]:
raise bad
if t[1] == "PART":
if p["hint"] is not None:
raise bad
h = attr_quoted(a, "URI")
if h is None:
raise bad
p["hint"] = h
elif tag == "#EXT-X-ENDLIST":
p["end"] = True
elif tag in ("#EXT-X-SKIP", "#EXT-X-STREAM-INF", "#EXT-X-I-FRAME-STREAM-INF", "#EXT-X-MEDIA"):
raise bad
if extinf is not None:
raise bad
if pending != new_extra():
p["open"] = pending
return p
def secs(ms):
return "%d.%03d" % (ms // 1000, ms % 1000)
def render_hls(segs, r, extras):
"""The rendering of §8.6: header, then each segment (discontinuity, date-time, parts
with their layer lines, EXTINF, its layer lines, URI), the open segment, the hint,
the end."""
target = r["target"] if r["target"] is not None else max([-(-s["ms"] // 1000) for s in segs], default=0)
out = ["#EXTM3U", "#EXT-X-VERSION:%d" % r["version"], "#EXT-X-TARGETDURATION:%d" % target]
if r["part_target"] is not None:
out.append("#EXT-X-PART-INF:PART-TARGET=" + secs(r["part_target"]))
ctl = (["CAN-BLOCK-RELOAD=YES"] if r["block"] else []) + (["PART-HOLD-BACK=" + secs(r["hold_back"])] if r["hold_back"] is not None else [])
if ctl:
out.append("#EXT-X-SERVER-CONTROL:" + ",".join(ctl))
out.append("#EXT-X-MEDIA-SEQUENCE:%d" % r["seq"])
if r["dseq"]:
out.append("#EXT-X-DISCONTINUITY-SEQUENCE:%d" % r["dseq"])
if r.get("ptype"):
out.append("#EXT-X-PLAYLIST-TYPE:" + r["ptype"])
if r.get("indep"):
out.append("#EXT-X-INDEPENDENT-SEGMENTS")
if r.get("map") is not None:
out.append('#EXT-X-MAP:URI="%s"' % r["map"])
def parts(ps):
for q in ps:
out.append("#EXT-X-PART:DURATION=%s%s,URI=\"%s\"" % (secs(q["ms"]), ",INDEPENDENT=YES" if q["independent"] else "", q["uri"]))
out.extend("#WAI-LAYER:N=%d,URI=\"%s\"" % (l["n"], l["uri"]) for l in q["layers"])
for i, s in enumerate(segs):
e = extras[i] if i < len(extras) else new_extra()
if s.get("ad") or e["discontinuity"]:
out.append("#EXT-X-DISCONTINUITY")
if e["pdt"] is not None:
out.append("#EXT-X-PROGRAM-DATE-TIME:" + e["pdt"])
if MUTATE == "parts-after-extinf":
out.append("#EXTINF:%s," % secs(s["ms"]))
parts(e["parts"])
else:
parts(e["parts"])
if e.get("gap"):
out.append("#EXT-X-GAP")
out.append("#EXTINF:%s," % secs(s["ms"]))
out.extend("#WAI-LAYER:N=%d,URI=\"%s\"" % (l["n"], l["uri"]) for l in e["layers"])
out.append(s["uri"])
if len(extras) > len(segs):
o = extras[len(segs)]
if o["discontinuity"]:
out.append("#EXT-X-DISCONTINUITY")
if o["pdt"] is not None:
out.append("#EXT-X-PROGRAM-DATE-TIME:" + o["pdt"])
parts(o["parts"])
if extras and extras[-1]["hint"] is not None:
out.append("#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\"" % extras[-1]["hint"])
if r["end"]:
out.append("#EXT-X-ENDLIST")
return "\n".join(out) + "\n"
def uri_ok(u, quoted):
return bool(u) and not u.startswith("#") and "\r" not in u and "\n" not in u and not (quoted and '"' in u)
def check_render(segs, r, extras):
bad = Refusal("playlist_malformed")
n = len(segs)
if len(extras) not in (0, n, n + 1) or r["version"] == 0:
raise bad
if r["part_target"] is not None and (r["hold_back"] is None or r["part_target"] == 0 or r["hold_back"] < 2 * r["part_target"]):
raise bad
if any(not uri_ok(s["uri"], False) for s in segs):
raise bad
if r.get("map") is not None and not uri_ok(r["map"], True):
raise bad
def layers_ok(ls):
last = 0
for l in ls:
if l["n"] == 0 or l["n"] <= last or not uri_ok(l["uri"], True):
raise bad
last = l["n"]
for i, e in enumerate(extras):
if e["pdt"] is not None and (not e["pdt"] or not re.fullmatch(r"[A-Za-z0-9:.+-]+", e["pdt"], re.ASCII)):
raise bad
if e["parts"] and r["part_target"] is None:
raise bad
for q in e["parts"]:
if not uri_ok(q["uri"], True):
raise bad
layers_ok(q["layers"])
layers_ok(e["layers"])
if i < n and e.get("gap") and (e["parts"] or e["layers"]) and MUTATE != "gap-carries":
raise bad
if e["hint"] is not None and (i + 1 != len(extras) or r["end"] or not uri_ok(e["hint"], True)):
raise bad
if i == n:
if r["end"] or e["layers"] or e.get("gap") or (not e["parts"] and (e["discontinuity"] or e["pdt"] is not None)) or (not e["parts"] and e["hint"] is None):
raise bad
def render_inputs(p):
segs = [{"ms": s["ms"], "uri": s["uri"]} for s in p["segments"]]
r = {"version": p["version"] if p["version"] is not None else 1, "seq": p["seq"], "dseq": p["dseq"], "target": p["target"],
"part_target": p["part_target"], "hold_back": p["hold_back"], "block": p["block"], "end": p["end"],
"ptype": p["ptype"], "indep": p["indep"], "map": p["map"]}
extras = [json.loads(json.dumps(s["extra"])) for s in p["segments"]]
if p["open"] is not None:
extras.append(json.loads(json.dumps(p["open"])))
if p["hint"] is not None:
if not extras:
extras.append(new_extra())
extras[-1]["hint"] = p["hint"]
if all(e == new_extra() for e in extras):
extras = []
return segs, r, extras
def is_rendered_form(p, text):
segs, r, extras = render_inputs(p)
try:
check_render(segs, r, extras)
except Refusal:
return False
return render_hls(segs, r, extras) == text
def misaligned():
return Refusal("adjacency_misaligned")
def timelines_adjacent(w, c):
if w["seq"] != c["seq"] or (w["dseq"] != c["dseq"] and MUTATE != "dseq-ignored") or len(w["segments"]) != len(c["segments"]):
raise misaligned()
both = w["part_target"] is not None and c["part_target"] is not None
if both and w["part_target"] != c["part_target"]:
raise misaligned()
for a, b in zip(w["segments"], c["segments"]):
if a["ms"] != b["ms"] or a["extra"]["discontinuity"] != b["extra"]["discontinuity"]:
raise misaligned()
if a["extra"]["pdt"] != b["extra"]["pdt"] and MUTATE != "pdt-ignored":
raise misaligned()
pa, pb = [q["ms"] for q in a["extra"]["parts"]], [q["ms"] for q in b["extra"]["parts"]]
if both and pa and pb and pa != pb:
raise misaligned()
if w["open"] is not None and c["open"] is not None:
a, b = w["open"], c["open"]
if a["discontinuity"] != b["discontinuity"] or a["pdt"] != b["pdt"]:
raise misaligned()
if both and [q["ms"] for q in a["parts"]] != [q["ms"] for q in b["parts"]]:
raise misaligned()
def check_adjacent(wai, classical):
w = parse_media_playlist(wai)
c = parse_media_playlist(classical)
timelines_adjacent(w, c)
def wai_media_playlist(c, segs, open_, prefix, end):
"""The WAI media playlist on classical timeline `c` (§11.1-§11.4)."""
if len(segs) != len(c["segments"]):
raise misaligned()
has_parts = any(s["parts"] for s in segs) or (open_ is not None and open_["parts"])
if has_parts and c["part_target"] is None:
raise misaligned()
lay = lambda ls: [{"n": l["n"], "uri": "%s%s.wai#n=%d" % (prefix, l["cid"].hex(), l["len"])} for l in ls]
prt = lambda ps: [{"ms": q["ms"], "independent": q["independent"], "uri": "%s#cid=%s&n=%d" % (q["alias"], q["cid"].hex(), q["n"]), "layers": lay(q["layers"])} for q in ps]
out_segs, extras = [], []
for i, (w, k) in enumerate(zip(segs, c["segments"])):
if w["ms"] != k["ms"]:
raise misaligned()
if w["parts"] and k["extra"]["parts"] and [q["ms"] for q in w["parts"]] != [q["ms"] for q in k["extra"]["parts"]]:
raise misaligned()
h = "&h=%d" % w["h"] if w["h"] is not None else ""
out_segs.append({"ms": w["ms"], "uri": "%s%s.wai#n=%d%s" % (prefix, w["cid"].hex(), w["n"], h)})
e = new_extra()
e.update(discontinuity=k["extra"]["discontinuity"], gap=w.get("gap", False), pdt=k["extra"]["pdt"], parts=prt(w["parts"]), layers=lay(w["layers"]))
extras.append(e)
hint = None
if open_ is not None:
if not open_["parts"]:
hint = open_["hint"]
else:
ko = c["open"]
if ko is None:
raise misaligned()
if ko["parts"] and [q["ms"] for q in open_["parts"]] != [q["ms"] for q in ko["parts"]]:
raise misaligned()
e = new_extra()
e.update(discontinuity=ko["discontinuity"], pdt=ko["pdt"], parts=prt(open_["parts"]), hint=open_["hint"])
extras.append(e)
if all(e == new_extra() for e in extras):
extras = []
if hint is not None:
if not extras:
extras = [new_extra() for _ in range(max(len(segs), 1))]
extras[-1]["hint"] = hint
pt = c["part_target"]
r = {"version": c["version"] if c["version"] is not None else 6, "seq": c["seq"], "dseq": c["dseq"], "target": c["target"],
"part_target": pt, "hold_back": c["hold_back"] if c["hold_back"] is not None else (pt * 3 if pt is not None else None),
"block": c["block"], "end": end, "ptype": c["ptype"], "indep": c["indep"], "map": None}
check_render(out_segs, r, extras)
text = render_hls(out_segs, r, extras)
timelines_adjacent(parse_media_playlist(text), c)
return text
def bandwidths(objs):
if not objs or any(d == 0 for _, d in objs):
return None
rate = lambda n, d: min(-(-(8000 * n) // d), U64)
peak = max(rate(n, d) for n, d in objs)
sn, sd = sum(n for n, _ in objs), sum(d for _, d in objs)
return peak, (rate(sn, sd) if MUTATE != "average-floor" else 8000 * sn // sd)
SESSION_DATA = '#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="'
def session_data_position(original):
"""§11.1: the offset after the first line and the EXT-X-VERSION and
EXT-X-INDEPENDENT-SEGMENTS lines that directly follow it; None when there is none."""
at = 0
lines = original.split("\n")
lines = [l + "\n" for l in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
for i, l in enumerate(lines):
bare = l[:-1] if l.endswith("\n") else l
bare = bare[:-1] if l.endswith("\n") and bare.endswith("\r") else bare
if i > 0 and not (bare.startswith("#EXT-X-VERSION:") or bare == "#EXT-X-INDEPENDENT-SEGMENTS") and MUTATE != "session-data-anywhere":
break
if not l.endswith("\n"):
return None
at += len(l.encode("utf-8"))
if MUTATE == "session-data-anywhere":
break
return at if at > 0 else None
def classical_unchanged(original, published):
if original == published:
return
o, p = original.encode("utf-8"), published.encode("utf-8")
at = session_data_position(original)
if at is None or not (p.startswith(o[:at]) and p.endswith(o[at:]) and len(p) > len(o)):
raise Refusal("classical_changed")
inserted = p[at:len(p) - (len(o) - at)].decode("utf-8")
if not inserted.endswith("\n"):
raise Refusal("classical_changed")
bare = inserted[:-1]
bare = bare[:-1] if bare.endswith("\r") else bare
if "\n" in bare or not (bare.startswith(SESSION_DATA) and bare.endswith('"')):
raise Refusal("classical_changed")
uri = bare[len(SESSION_DATA):-1]
if not uri or any(ch in uri for ch in '"\r\n'):
raise Refusal("classical_changed")
def listing_lines(p):
"""Every part, layer and segment line of a media playlist, in the order a client
reads them for a cid (§5.2, §11.4)."""
lines = []
for s in p["segments"]:
for q in s["extra"]["parts"]:
lines.append(q["uri"])
lines += [l["uri"] for l in q["layers"]]
lines += [l["uri"] for l in s["extra"]["layers"]]
lines.append(s["uri"])
if p["open"] is not None:
for q in p["open"]["parts"]:
lines.append(q["uri"])
lines += [l["uri"] for l in q["layers"]]
return lines
def uri_cid(line):
"""The cid a listed URI names: its cid fragment, or the <cid>.wai its path ends in."""
base, _, frag = line.partition("#")
for kv in frag.split("&") if frag else []:
k, eq, v = kv.partition("=")
if eq and k == "cid" and re.fullmatch(r"[0-9a-f]{64}", v):
return bytes.fromhex(v)
name = base.rsplit("/", 1)[-1]
if name.endswith(".wai") and re.fullmatch(r"[0-9a-f]{64}", name[:-4]):
return bytes.fromhex(name[:-4])
return None
def listed_cid(p, uri):
line = next((l for l in listing_lines(p) if l.split("#", 1)[0] == uri), None)
return None if line is None else uri_cid(line)
RETENTION = 256
URI_PARTS = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$", re.S)
def remove_dot_segments(path):
"""RFC 3986 §5.2.4."""
inp, out = path, ""
while inp:
if inp.startswith("../"):
inp = inp[3:]
elif inp.startswith("./"):
inp = inp[2:]
elif inp.startswith("/./"):
inp = inp[2:]
elif inp == "/.":
inp = "/"
elif inp.startswith("/../") or inp == "/..":
inp = "/" + inp[4:] if inp.startswith("/../") else "/"
out = out[:out.rfind("/")] if "/" in out else ""
elif inp in (".", ".."):
inp = ""
else:
j = inp.find("/", 1 if inp.startswith("/") else 0)
j = len(inp) if j < 0 else j
out, inp = out + inp[:j], inp[j:]
return out
def pct_normal(s):
"""RFC 3986 §6.2.2.2: upper-case hexadecimal digits, unreserved characters decoded."""
def one(m):
c = chr(int(m.group(1), 16))
return c if re.fullmatch(r"[A-Za-z0-9._~-]", c) else "%" + m.group(1).upper()
return re.sub(r"%([0-9A-Fa-f]{2})", one, s)
def rfc3986_resolve(base, ref):
"""ref resolved against base (RFC 3986 §5.2.2, §5.2.3) and normalized (§6.2.2), its
fragment dropped."""
def parts(u):
m = URI_PARTS.match(u)
return m.group(2), m.group(4) if m.group(3) is not None else None, m.group(5), m.group(7) if m.group(6) is not None else None
bs, ba, bp, bq = parts(base)
rs, ra, rp, rq = parts(ref)
if MUTATE == "uri-unresolved":
return ref.split("#", 1)[0]
if rs is not None:
ts, ta, tp, tq = rs, ra, remove_dot_segments(rp), rq
elif ra is not None:
ts, ta, tp, tq = bs, ra, remove_dot_segments(rp), rq
elif rp == "":
ts, ta, tp, tq = bs, ba, bp, rq if rq is not None else bq
elif rp.startswith("/"):
ts, ta, tp, tq = bs, ba, remove_dot_segments(rp), rq
else:
if ba is not None and bp == "":
merged = "/" + rp
else:
merged = bp[:bp.rfind("/") + 1] + rp
ts, ta, tp, tq = bs, ba, remove_dot_segments(merged), rq
out = ""
if ts is not None:
out += ts.lower() + ":"
if ta is not None:
a = pct_normal(ta)
at = a.rfind("@")
out += "//" + (a[:at + 1] + a[at + 1:].lower() if at >= 0 else a.lower())
out += remove_dot_segments(pct_normal(tp))
if tq is not None:
out += "?" + pct_normal(tq)
return out
def new_bindings(url):
return {"base": url, "uris": {}, "seqs": {}, "rev": 0}
def bind(b, p):
"""§5.2, §11.5: the bindings b with playlist p's added as one more revision: each URL
a line names (resolved against the playlist's URL) and each media sequence number to
its cid. Refusal when p lists another cid for one bound, or a gap on a bound sequence
number; then what none of the last RETENTION revisions listed is forgotten."""
rev = b["rev"] + 1
uris, seqs = dict(b["uris"]), dict(b["seqs"])
for line in listing_lines(p):
cid = uri_cid(line)
if cid is None:
continue
key = rfc3986_resolve(b["base"], line)
if key in uris and uris[key][0] != cid and MUTATE != "rebind-uri":
raise Refusal("playlist_listing_rebound")
uris[key] = (cid, rev)
for i, sg in enumerate(p["segments"]):
n = p["seq"] + i
if n > U64:
raise Refusal("playlist_malformed")
if sg["extra"]["gap"]:
if n in seqs and MUTATE != "gap-withdraws":
raise Refusal("playlist_listing_rebound")
continue
cid = uri_cid(sg["uri"])
if cid is None:
continue
if n in seqs and seqs[n][0] != cid and MUTATE != "rebind-sequence":
raise Refusal("playlist_listing_rebound")
seqs[n] = (cid, rev)
keep = (lambda last: True) if MUTATE == "bindings-unbounded" else (lambda last: last + RETENTION > rev)
return {"base": b["base"], "uris": {k: v for k, v in uris.items() if keep(v[1])}, "seqs": {k: v for k, v in seqs.items() if keep(v[1])}, "rev": rev}
def cid_of(b, uri):
v = b["uris"].get(rfc3986_resolve(b["base"], uri))
return None if v is None else v[0]
def xml_char(c):
o = ord(c)
return o in (9, 10, 13) or 0x20 <= o <= 0xD7FF or 0xE000 <= o <= 0xFFFD or 0x10000 <= o <= 0x10FFFF
def esc_xml(s):
"""An attribute value: & < > " as entities, tab, LF and CR as character references;
Refusal for a character that is not an XML Char."""
out = []
for c in s:
if c in '&<>"\t\n\r':
out.append({"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "\t": "&#9;", "\n": "&#10;", "\r": "&#13;"}[c])
elif xml_char(c) or MUTATE == "xml-char-unchecked":
out.append(c)
else:
raise Refusal("playlist_malformed")
return "".join(out)
# The characters of Unicode's White_Space property: an MPD @id holds none.
WHITE_SPACE = set("\t\n\x0b\x0c\r \x85\xa0\u1680\u2028\u2029\u202f\u205f\u3000") | {chr(c) for c in range(0x2000, 0x200B)}
def id_ok(s):
return s != "" and not any(c in WHITE_SPACE for c in s)
def xs_seconds(ms):
return "PT%dS" % (ms // 1000) if ms % 1000 == 0 else "PT%sS" % secs(ms)
def wai_mpd(t, set_id, union, floor, rep_id, bandwidth, media):
"""wai.mpd (§11.1) on the timeline t = (period id or None, start ms, timescale,
[(t, d)]); media[i] None leaves segment i out (a gap)."""
period_id, start_ms, ts, segs = t
if not segs or ts == 0 or len(media) != len(segs) or union[0] != floor[0]:
raise Refusal("playlist_malformed")
if not id_ok(rep_id) or (period_id is not None and not id_ok(period_id)):
raise Refusal("playlist_malformed")
offered = [(sg, m) for sg, m in zip(segs, media) if m is not None]
if not offered:
raise Refusal("playlist_malformed")
to_ms = lambda d: min(d * 1000 // ts, U64)
total = min(sum(to_ms(d) for _, d in segs), U64)
longest = max(to_ms(d) for _, d in segs)
period = (' <Period id="%s" start="%s">' % (esc_xml(period_id), xs_seconds(start_ms))) if period_id is not None else (' <Period start="%s">' % xs_seconds(start_ms))
out = ['<?xml version="1.0" encoding="UTF-8"?>',
'<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT%sS" minBufferTime="PT%dS">' % (secs(total), -(-longest // 1000)),
period,
' <AdaptationSet id="%d" mimeType="application/wai" codecs="%s" segmentAlignment="true">' % (set_id, esc_xml(render(union))),
' <EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>',
' <SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/floor" value="%s"/>' % esc_xml(render(floor)),
' <Representation id="%s" bandwidth="%d">' % (esc_xml(rep_id), bandwidth),
' <SegmentList timescale="%d">' % ts, ' <SegmentTimeline>']
out += [' <S t="%d" d="%d"/>' % sg for sg, _ in offered]
out += [' </SegmentTimeline>'] + [' <SegmentURL media="%s"/>' % esc_xml(m) for _, m in offered]
out += [' </SegmentList>', ' </Representation>', ' </AdaptationSet>', ' </Period>', '</MPD>']
return "\n".join(out) + "\n"
# ---------------------------------------------- reading an MPD (Appendix I)
MAX_TIMELINES = 256
MAX_DEPTH = 64
NAME = re.compile(r"[A-Za-z_:][A-Za-z0-9_:.\-]*", re.ASCII)
class Xml:
"""A reader of the XML subset of Appendix I over a normalised text."""
def __init__(self, s):
self.s, self.at = s, 0
def bad(self):
return Refusal("playlist_malformed")
def rest(self):
return self.s[self.at:]
def eat(self, lit):
if self.s.startswith(lit, self.at):
self.at += len(lit)
return True
return False
def space(self):
n = 0
while self.at + n < len(self.s) and self.s[self.at + n] in " \t\n":
n += 1
self.at += n
return n > 0
def name(self):
m = NAME.match(self.s, self.at)
if m is None:
raise self.bad()
self.at = m.end()
return m.group(0)
def reference(self):
end = self.s.find(";", self.at)
if end < 0:
raise self.bad()
body = self.s[self.at:end]
named = {"amp": "&", "lt": "<", "gt": ">", "quot": '"', "apos": "'"}
if body in named:
c = named[body]
else:
if body.startswith("#x") and re.fullmatch(r"[0-9A-Fa-f]{1,8}", body[2:], re.ASCII):
n = int(body[2:], 16)
elif body.startswith("#") and re.fullmatch(r"[0-9]{1,10}", body[1:], re.ASCII) and int(body[1:]) <= 0xFFFFFFFF:
n = int(body[1:])
else:
raise self.bad()
if 0xD800 <= n <= 0xDFFF or n > 0x10FFFF or not xml_char(chr(n)):
raise self.bad()
c = chr(n)
self.at = end + 1
return c
def attr_value(self):
if self.at >= len(self.s) or self.s[self.at] not in "\"'":
raise self.bad()
q = self.s[self.at]
self.at += 1
out = []
while True:
if self.at >= len(self.s):
raise self.bad()
c = self.s[self.at]
self.at += 1
if c == q:
return "".join(out)
if c == "<":
raise self.bad()
if c == "&":
out.append(self.reference())
elif c in "\t\n":
out.append(" ")
else:
out.append(c)
def comment(self):
self.at += 4
end = self.s.find("--", self.at)
if end < 0 or not self.s.startswith("-->", end):
raise self.bad()
self.at = end + 3
def misc(self):
while True:
if self.space():
continue
if self.s.startswith("<!--", self.at):
self.comment()
continue
return
def start_tag(self):
name = self.name()
attrs, names = [], set()
while True:
spaced = self.space()
if self.eat("/>"):
return {"name": name, "attrs": attrs, "children": []}, True
if self.eat(">"):
return {"name": name, "attrs": attrs, "children": []}, False
if not spaced:
raise self.bad()
k = self.name()
self.space()
if not self.eat("="):
raise self.bad()
self.space()
v = self.attr_value()
seen = any(a == k for a, _ in attrs) if MUTATE == "xml-attributes-scanned" else k in names
if seen and MUTATE != "xml-attribute-twice":
raise self.bad()
names.add(k)
attrs.append((k, v))
def read_xml(text):
"""The root element of a document in the XML subset of Appendix I:
{"name", "attrs", "children"}; Refusal("playlist_malformed") otherwise."""
if text.startswith("\ufeff"):
text = text[1:]
text = text.replace("\r\n", "\n").replace("\r", "\n")
if not all(xml_char(c) for c in text):
raise Refusal("playlist_malformed")
r = Xml(text)
if text.startswith("<?xml") and text[5:6] in (" ", "\t", "\n", "?") and text[5:6] != "":
end = text.find("?>")
if end < 0:
raise r.bad()
r.at = end + 2
r.misc()
if not r.eat("<"):
raise r.bad()
root, closed = r.start_tag()
stack, done = ([], root) if closed else ([root], None)
while done is None:
if r.at >= len(text):
raise r.bad()
c = text[r.at]
if c == "<":
if text.startswith("<!--", r.at):
r.comment()
elif r.eat("</"):
name = r.name()
r.space()
if not r.eat(">"):
raise r.bad()
e = stack.pop()
if e["name"] != name:
raise r.bad()
if stack:
stack[-1]["children"].append(e)
else:
done = e
elif text.startswith("<!", r.at) or text.startswith("<?", r.at):
raise r.bad()
else:
r.at += 1
e, closed = r.start_tag()
if closed:
stack[-1]["children"].append(e)
else:
if len(stack) >= MAX_DEPTH:
raise r.bad()
stack.append(e)
elif c == "&":
r.at += 1
r.reference()
else:
if text.startswith("]]>", r.at):
raise r.bad()
r.at += 1
r.misc()
if r.at != len(text):
raise r.bad()
return done
def local(e):
return e["name"].rsplit(":", 1)[-1]
def xattr(e, k):
return next((v for a, v in e["attrs"] if a == k), None)
def duration_ms(s):
"""An xs:duration of days, hours, minutes and seconds in milliseconds; None otherwise."""
if not s.startswith("P"):
return None
rest = s[1:]
date, sep, time = rest.partition("T")
time = time if sep else None
integer = lambda v: int(v) if re.fullmatch(r"[0-9]+", v, re.ASCII) else None
ms, anything = 0, False
if date:
if not date.endswith("D") or integer(date[:-1]) is None:
return None
ms, anything = integer(date[:-1]) * 86400000, True
if time is not None:
if time == "":
return None
for unit, scale in (("H", 3600000), ("M", 60000)):
i = time.find(unit)
if i >= 0:
v = integer(time[:i])
if v is None:
return None
ms += v * scale
time, anything = time[i + 1:], True
if time:
if not time.endswith("S"):
return None
v = decimal_ms(time[:-1])
if v is None:
return None
ms, anything = ms + v, True
return ms if anything and ms <= U64 else None
def unsigned(v):
if v is None or not re.fullmatch(r"[0-9]+", v, re.ASCII) or int(v) > U64:
raise Refusal("playlist_malformed")
return int(v)
def timeline_of(e, inherited):
"""A timeline as written: {"timescale", "runs": [(t, d, count)]}, one run per S, never
expanded; None without a SegmentTimeline."""
st = next((c for c in e["children"] if local(c) == "SegmentTimeline"), None)
if st is None:
return None
ts = next((xattr(x, "timescale") for x in [e] + inherited if xattr(x, "timescale") is not None), None)
timescale = unsigned(ts) if ts is not None else 1
if timescale == 0:
raise Refusal("playlist_malformed")
runs, nxt = [], 0
for sel in (c for c in st["children"] if local(c) == "S"):
d = unsigned(xattr(sel, "d"))
if d == 0:
raise Refusal("playlist_malformed")
t = unsigned(xattr(sel, "t")) if xattr(sel, "t") is not None else nxt
r = unsigned(xattr(sel, "r")) if xattr(sel, "r") is not None else 0
end = t + d * (r + 1)
if end > U64:
raise Refusal("playlist_malformed")
if MUTATE == "dash-r-expanded" and r < 1 << 20:
runs += [(t + i * d, d, 1) for i in range(r + 1)]
else:
runs.append((t, d, r + 1))
nxt = end
return {"timescale": timescale, "runs": runs}
SEGMENT_INFO = ("SegmentTemplate", "SegmentList")
def read_mpd(text):
"""An MPD's periods: [{"id", "start_ms", "sets": [{"id", "timelines"}]}]."""
root = read_xml(text)
if local(root) != "MPD":
raise Refusal("playlist_malformed")
periods, held = [], 0
for i, p in enumerate(c for c in root["children"] if local(c) == "Period"):
st = xattr(p, "start")
if st is not None:
start = duration_ms(st)
if start is None:
raise Refusal("playlist_malformed")
elif i == 0:
start = 0
else:
raise Refusal("playlist_malformed")
p_info = [c for c in p["children"] if local(c) in SEGMENT_INFO]
sets = []
for a in (c for c in p["children"] if local(c) == "AdaptationSet"):
tls = []
a_info = [c for c in a["children"] if local(c) in SEGMENT_INFO]
for e in a_info:
tl = timeline_of(e, [x for x in p_info if local(x) == local(e)])
if tl is not None:
tls.append(tl)
for rep in (c for c in a["children"] if local(c) == "Representation"):
for e in (c for c in rep["children"] if local(c) in SEGMENT_INFO):
tl = timeline_of(e, [x for x in a_info + p_info if local(x) == local(e)])
if tl is not None:
tls.append(tl)
held += len(tls)
if held > MAX_TIMELINES and MUTATE != "dash-timelines-unbounded":
raise Refusal("playlist_malformed")
sets.append({"id": xattr(a, "id"), "timelines": tls})
periods.append({"id": xattr(p, "id"), "start_ms": start, "sets": sets})
if not periods:
raise Refusal("playlist_malformed")
return periods
def within(wai, classical):
"""Whether the WAI runs list only segments of the classical runs, each with its t and
d, in order: for each WAI segment, the first classical segment at or after the one
matched before with its t has its d too. Run by run, never expanded."""
ci, co = 0, 0
for wt, wd, wn in wai:
t, left = wt, wn
while left > 0:
while True:
if ci == len(classical):
return False
ct, cd, cn = classical[ci]
if t >= ct + co * cd and (t - ct) % cd == 0 and (t - ct) // cd < cn:
co = (t - ct) // cd
break
ci, co = ci + 1, 0
ct, cd, cn = classical[ci]
if cd != wd:
return False
take = min(left, cn - co)
left, co, t = left - take, co + take, t + take * wd
if co == cn:
ci, co = ci + 1, 0
return True
def check_dash_adjacent(wai, classical):
"""§11.2 in DASH: Refusal("playlist_malformed") when either does not read,
Refusal("adjacency_misaligned") when not adjacent."""
w, k = read_mpd(wai), read_mpd(classical)
mis = Refusal("adjacency_misaligned")
for wp in w:
kp = next((p for p in k if p["id"] == wp["id"]), None)
if kp is None or (kp["start_ms"] != wp["start_ms"] and MUTATE != "dash-start-ignored"):
raise mis
if not wp["sets"]:
raise mis
for st in wp["sets"]:
if not st["timelines"]:
raise mis
for tl in st["timelines"]:
if not tl["runs"] or not any((c["timescale"] == tl["timescale"] or MUTATE == "dash-timescale-ignored") and within(tl["runs"], c["runs"])
for cs in kp["sets"] for c in cs["timelines"]):
raise mis
def mpd_timeline(text, period_id, set_id, max_segments):
"""The classical MPD's timeline (§11.1): (period id, start ms, timescale, segments),
expanded only when it lists at most max_segments segments."""
periods = read_mpd(text)
p = next((x for x in periods if x["id"] == period_id), None) if period_id is not None else periods[0]
if p is None:
raise Refusal("playlist_malformed")
st = next((x for x in p["sets"] if x["id"] == set_id), None) if set_id is not None else next((x for x in p["sets"] if x["timelines"]), None)
if st is None or not st["timelines"]:
raise Refusal("playlist_malformed")
tl = st["timelines"][0]
if sum(n for _, _, n in tl["runs"]) > max_segments:
raise Refusal("playlist_malformed")
return (p["id"], p["start_ms"], tl["timescale"], [(t + i * d, d) for t, d, n in tl["runs"] for i in range(n)])
# ------------------------------------------------ playlist receipts (§8.6)
def jcs(v):
"""RFC 8785 over the subset the render digest uses."""
if v is None:
return "null"
if v is True or v is False:
return "true" if v else "false"
if isinstance(v, int):
if not 0 <= v <= 2 ** 53 - 1:
raise ValueError("outside the canonical subset")
return str(v)
if isinstance(v, str):
return json.dumps(v, ensure_ascii=False)
if isinstance(v, list):
return "[" + ",".join(jcs(x) for x in v) + "]"
keys = sorted(v, key=lambda k: k.encode("utf-16-be"))
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in keys) + "}"
# Ed25519, strictly as WAI verifies every signature (SPEC §7 *Signatures*): the key and R
# decoded with y < p (and no sign bit on x = 0), neither of small order, S < L, and
# [S]B = R + [k]A checked by encoding [S]B − [k]A and comparing it with R byte for byte.
# `_ed25519.py` writes it from RFC 8032's definitions, with the standard library only;
# --mutate small-order-key and small-order-r leave out its two order checks.
import _ed25519 as ED # noqa: E402
def ed25519_verify(pk, msg, sig):
return ED.verifies(pk, msg, sig, skip=(MUTATE,))
RECEIPT_KNOWN = {"kind", "manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", "viewers_served",
"joules_micro", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig", "render", "extras",
"energy_provenance", "energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref"}
LEGACY_CLASSES = {"HwShunt": 1, "ModelBased": 2, "Estimator": 3}
MAX_FILE_NUMBER = 2 ** 53 - 1
class ReceiptMalformed(Exception):
pass
class NotUnsigned:
"""A JSON number with a sign, a fraction or an exponent: never an unsigned integer of
the receipt file, whatever its value (so "-0" and "1.0" are not 0 and 1)."""
def read_receipt(text):
"""The receipt file (§8.6), read strictly; ReceiptMalformed otherwise. A number is an
unsigned integer written as digits alone."""
def pairs(kv):
# A member name twice in one object (I-JSON, RFC 7493 §2.3), names compared as read.
if len({k for k, _ in kv}) != len(kv) and MUTATE != "receipt-duplicates":
raise ReceiptMalformed()
return dict(kv)
try:
# A number is an unsigned integer of at most 2^53 - 1, written as digits alone.
o = json.loads(text, object_pairs_hook=pairs, parse_float=lambda t: NotUnsigned(),
parse_int=lambda t: NotUnsigned() if t.startswith("-") or (int(t) > MAX_FILE_NUMBER and MUTATE != "file-numbers-unbounded") else int(t))
except (ValueError, RecursionError):
raise ReceiptMalformed()
bad = ReceiptMalformed
def texts(v):
if isinstance(v, str):
yield v
elif isinstance(v, dict):
for k, x in v.items():
yield k
yield from texts(x)
elif isinstance(v, list):
for x in v:
yield from texts(x)
# A \u escape that leaves a surrogate unpaired denotes no Unicode text (RFC 8259 §8.2).
if any(0xD800 <= ord(c) <= 0xDFFF for t in texts(o) for c in t) and MUTATE != "receipt-surrogates":
raise bad()
def obj(v, n):
if not isinstance(v, dict) or len(v) != n:
raise bad()
return v
def u64(v):
if isinstance(v, bool) or not isinstance(v, int) or not 0 <= v <= U64:
raise bad()
return v
def s(v):
if not isinstance(v, str):
raise bad()
return v
def hx(v, n):
if not isinstance(v, str) or not re.fullmatch("[0-9a-f]{%d}" % (2 * n), v):
raise bad()
return bytes.fromhex(v)
def opt(v, f):
return None if v is None else f(v)
if not isinstance(o, dict) or not set(o) <= RECEIPT_KNOWN or o.get("kind") != "wai.video.manifest":
raise bad()
for k in ("manifest_hash", "session_class_hash", "class", "segments", "merkle_root", "work_compose_ops", "viewers_served",
"joules_micro", "grant", "parent_receipt_hash", "signer_pubkey", "signer_id", "sig"):
if k not in o:
raise bad()
cl = obj(o["class"], 6)
if not isinstance(cl.get("blackouts"), list):
raise bad()
klass = {"content_id": s(cl.get("content_id")), "entitlement": s(cl.get("entitlement")), "geo": s(cl.get("geo")),
"blackouts": [s(b) for b in cl["blackouts"]], "ad_pod_id": s(cl.get("ad_pod_id")), "window": u64(cl.get("window"))}
segs = []
if not isinstance(o["segments"], list):
raise bad()
for x in o["segments"]:
x = obj(x, 5)
if x.get("kind") not in ("content", "ad") or not (x.get("ad_id") is None or isinstance(x.get("ad_id"), str)) or "ad_id" not in x:
raise bad()
segs.append({"index": u64(x.get("index")), "ad": x["kind"] == "ad", "uri": s(x.get("uri")), "ms": u64(x.get("duration_ms")), "ad_id": x["ad_id"]})
g = obj(o["grant"], 4)
if "funds_ceiling" not in g or "joule_ceiling_micro" not in g:
raise bad()
# No joule ceiling is written null and signs as 2^64 - 1.
grant = {"hash": hx(g.get("grant_hash"), 32), "capability": s(g.get("capability")),
"ceiling": U64 if g["joule_ceiling_micro"] is None else u64(g["joule_ceiling_micro"]), "funds": opt(g["funds_ceiling"], u64)}
label = o.get("energy_provenance")
if label is None:
if any(o.get(k) is not None for k in ("energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref")):
raise bad()
prov = None
elif label in LEGACY_CLASSES and all(o.get(k) is None for k in ("energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref")):
prov = label
else:
raise SystemExit("http_verify: an energy class this verifier does not read")
render_, extras = None, []
if ("render" in o) != ("extras" in o):
raise bad()
if "render" in o:
r = obj(o["render"], 11)
if not isinstance(r.get("can_block_reload"), bool) or not isinstance(r.get("end_list"), bool) or not isinstance(r.get("independent_segments"), bool):
raise bad()
if "playlist_type" not in r or r["playlist_type"] not in (None, "VOD", "EVENT") or "map_uri" not in r:
raise bad()
render_ = {"version": u64(r.get("version")), "seq": u64(r.get("media_sequence")), "dseq": u64(r.get("discontinuity_sequence")),
"target": opt(r.get("target_duration_s"), u64), "part_target": opt(r.get("part_target_ms"), u64),
"hold_back": opt(r.get("part_hold_back_ms"), u64), "block": r["can_block_reload"], "end": r["end_list"],
"ptype": r["playlist_type"], "indep": r["independent_segments"], "map": opt(r["map_uri"], s)}
for k in ("target_duration_s", "part_target_ms", "part_hold_back_ms"):
if k not in r:
raise bad()
if render_["version"] > 255 or not isinstance(o["extras"], list):
raise bad()
def layers(v):
if not isinstance(v, list):
raise bad()
out = []
for l in v:
l = obj(l, 2)
n = u64(l.get("n"))
if n > 255:
raise bad()
out.append({"n": n, "uri": s(l.get("uri"))})
return out
for e in o["extras"]:
e = obj(e, 6)
if not isinstance(e.get("gap"), bool) or not isinstance(e.get("discontinuity"), bool) or not isinstance(e.get("parts"), list) or "program_date_time" not in e or "preload_hint" not in e:
raise bad()
parts = []
for q in e["parts"]:
q = obj(q, 4)
if not isinstance(q.get("independent"), bool):
raise bad()
parts.append({"ms": u64(q.get("duration_ms")), "independent": q["independent"], "uri": s(q.get("uri")), "layers": layers(q.get("layers"))})
extras.append({"discontinuity": e["discontinuity"], "gap": e["gap"], "pdt": opt(e["program_date_time"], s), "parts": parts,
"layers": layers(e.get("layers")), "hint": opt(e["preload_hint"], s)})
return {"manifest_hash": hx(o["manifest_hash"], 32), "class_hash": hx(o["session_class_hash"], 32), "class": klass, "segments": segs,
"merkle_root": hx(o["merkle_root"], 32), "work": u64(o["work_compose_ops"]), "viewers": u64(o["viewers_served"]),
"joules": u64(o["joules_micro"]), "prov": prov, "grant": grant, "parent": opt(o["parent_receipt_hash"], lambda v: hx(v, 32)),
"pubkey": hx(o["signer_pubkey"], 32), "signer_id": s(o["signer_id"]), "sig": hx(o["sig"], 64), "render": render_, "extras": extras,
"render_json": (o.get("render"), o.get("extras"))}
def b3(*parts):
return blake3(b"".join(parts))
def be(n, width):
return n.to_bytes(width, "big")
def lp(s):
b = s.encode("utf-8")
return be(len(b), 4) + b
def segment_leaf(sg):
ad = (b"\x01" + lp(sg["ad_id"])) if sg["ad_id"] is not None else b"\x00"
return b3(b"wai:video-manifest-seg\x01", be(sg["index"], 8), bytes([2 if sg["ad"] else 1]), be(sg["ms"], 8), lp(sg["uri"]), ad)
def merkle_root(hashes):
if not hashes:
return bytes(32)
level = [b3(b"moq-jwp:merkle-leaf\x01", h) for h in hashes]
while len(level) > 1:
if len(level) % 2:
level.append(level[-1])
level = [b3(b"moq-jwp:merkle-node\x01", level[i], level[i + 1]) for i in range(0, len(level), 2)]
return level[0]
def class_hash(c):
bs = sorted(set(c["blackouts"]), key=lambda x: x.encode("utf-8"))
out = [b"wai:video-manifest-class\x01"] + [lp(c[k]) for k in ("content_id", "entitlement", "geo", "ad_pod_id")]
out.append(be(len(bs), 4))
out += [lp(b) for b in bs]
out.append(be(c["window"], 8))
return b3(*out)
def render_digest(r):
"""BLAKE3 of the render domain and the JCS of {"render", "extras"}, as the receipt
file carries them; None without a rendering or past the canonical subset."""
if r["render"] is None:
return None
try:
c = jcs({"render": r["render_json"][0], "extras": r["render_json"][1]})
except ValueError:
return None
return b3(b"wai:video-manifest-render\x01", c.encode("utf-8"))
def signing_payload(r):
o = [b"wai:video-manifest-receipt\x01", r["manifest_hash"], r["class_hash"], r["merkle_root"], be(r["work"], 8), be(r["viewers"], 8), be(r["joules"], 8),
bytes([LEGACY_CLASSES[r["prov"]] if r["prov"] else 0]), r["grant"]["hash"], lp(r["grant"]["capability"]), be(r["grant"]["ceiling"], 8),
(b"\x01" + be(r["grant"]["funds"], 8)) if r["grant"]["funds"] is not None else b"\x00",
(b"\x01" + r["parent"]) if r["parent"] is not None else b"\x00", be(len(r["segments"]), 8)]
o += [segment_leaf(sg) for sg in r["segments"]]
o += [r["pubkey"], lp(r["signer_id"])]
if r["render"] is not None and MUTATE != "render-digest-off":
d = render_digest(r)
o.append(b"\x01" + d if d is not None else b"\x02")
return b"".join(o)
def receipt_hash(r):
return b3(b"wai:video-manifest-receipt-id\x01", signing_payload(r), r["sig"])
def rendered(r):
rr = r["render"] or {"version": 6, "seq": 0, "dseq": 0, "target": None, "part_target": None, "hold_back": None, "block": False, "end": True,
"ptype": None, "indep": False, "map": None}
return render_hls(r["segments"], rr, r["extras"])
def receipt_verifies(r):
if r["render"] is None and r["extras"]:
return False
if r["render"] is not None:
try:
check_render(r["segments"], r["render"], r["extras"])
except Refusal:
return False
if render_digest(r) is None:
return False
if blake3(rendered(r).encode("utf-8")) != r["manifest_hash"]:
return False
if merkle_root([segment_leaf(sg) for sg in r["segments"]]) != r["merkle_root"] or class_hash(r["class"]) != r["class_hash"]:
return False
if r["work"] != len(r["segments"]) or r["viewers"] == 0:
return False
if any(sg["index"] != i or sg["ad"] != (sg["ad_id"] is not None) for i, sg in enumerate(r["segments"])):
return False
if (r["prov"] is None) != (r["joules"] == 0):
return False
return ed25519_verify(r["pubkey"], signing_payload(r), r["sig"])
def check_playlist(playlist, receipt_bytes, name):
"""§8.6, §11.6, in order: the file reads; its name; the bytes; the receipt."""
try:
r = read_receipt(receipt_bytes.decode("utf-8"))
except (ReceiptMalformed, UnicodeDecodeError):
raise Refusal("playlist_receipt_malformed")
if name is not None and name != r["manifest_hash"].hex() + ".json" and MUTATE != "name-unchecked":
raise Refusal("receipt_name_mismatch")
if blake3(playlist) != r["manifest_hash"] or rendered(r).encode("utf-8") != playlist:
raise Refusal("playlist_receipt_mismatch")
if not receipt_verifies(r):
raise Refusal("playlist_receipt_invalid")
return r
WALK_BOUND = 64
LIVE_URL = "https://origin.example/live/wai-720.m3u8"
def advance(chain, cur, fetch):
"""§8.6: take the receipt cur (checked against its playlist) into the chain a client
holds, {"head", "bindings", "bound"}; fetch(h) gives the bytes at mr/<h>.json or None.
Returns ("first" | "unchanged" | "chained" | "reanchored", walked) and updates chain,
or raises Refusal and leaves it as it was."""
if chain["head"] is None:
b = bind_receipt(chain["bindings"], cur)
chain["head"], chain["bindings"] = cur, b
return "first", 0
target = receipt_hash(chain["head"])
if receipt_hash(cur) == target and MUTATE != "reload-walked":
return "unchanged", 0
if cur["pubkey"] != chain["head"]["pubkey"] and MUTATE != "key-unchecked":
raise Refusal("playlist_receipt_unchained")
walked, link = [], cur["parent"]
while MUTATE != "chain-unchecked":
if link is None:
raise Refusal("playlist_receipt_unchained")
if link == target:
break
if len(walked) == chain["bound"] and MUTATE != "walk-unbounded":
# Past the bound: a revision ahead of the head re-anchors the chain, its
# bindings kept; one that is not ahead is refused.
if last_segment(cur) <= last_segment(chain["head"]) and MUTATE != "reanchor-backwards":
raise Refusal("playlist_receipt_walk_exceeded")
if MUTATE == "walk-exceeded-wedges":
raise Refusal("playlist_receipt_walk_exceeded")
chain["bindings"] = bind_receipt(chain["bindings"], cur)
chain["head"] = cur
return "reanchored", len(walked)
raw = fetch(link)
if raw is None:
raise Refusal("playlist_receipt_parent_missing")
try:
r = read_receipt(raw.decode("utf-8"))
except (ReceiptMalformed, UnicodeDecodeError):
raise Refusal("playlist_receipt_malformed")
if receipt_hash(r) != link and MUTATE != "walk-name-unchecked":
raise Refusal("receipt_name_mismatch")
if not receipt_verifies(r) and MUTATE != "walk-verify-unchecked":
raise Refusal("playlist_receipt_invalid")
if r["pubkey"] != cur["pubkey"] and MUTATE != "key-unchecked":
raise Refusal("playlist_receipt_unchained")
link = r["parent"]
walked.append(r)
b = chain["bindings"]
for r in list(reversed(walked)) + [cur]:
b = bind_receipt(b, r)
chain["head"], chain["bindings"] = cur, b
return "chained", len(walked)
def last_segment(r):
"""The media sequence number after the last complete segment r lists."""
return (r["render"]["seq"] if r["render"] is not None else 0) + len(r["segments"])
def bind_receipt(b, r):
try:
p = parse_media_playlist(rendered(r))
except Refusal:
raise Refusal("playlist_receipt_malformed")
try:
return bind(b, p)
except Refusal as e:
raise Refusal("playlist_listing_rebound" if e.code == "playlist_listing_rebound" else "playlist_receipt_malformed")
# ---------------------------------------------------- the playlist-side cases
def edit_text(t, edits):
for e in edits or []:
if e[0] not in t:
raise SystemExit("http_verify: a case edits text its file does not hold")
t = t.replace(e[0], e[1], 1)
return t
def read_text(d, path):
return open(os.path.join(d, path), "rb").read().decode("utf-8")
def layer_of(env):
h = whole_head(env)
if not is_layer(h):
raise Refusal("not_a_layer")
try:
r = SV.parse_refines(h["manifest"].get("refines"))
except SV.Malformed:
raise Refusal("not_a_layer")
return {"n": r["layer"], "cid": content_hash(env), "len": len(env)}
def parts_of(objs, ps, cms):
out = []
for j, p in enumerate(ps or []):
env = objs[p["object"]]
ms = cms[j] if j < len(cms) else p.get("duration_ms")
if not isinstance(ms, int):
raise SystemExit("http_verify: a part with no duration")
whole_head(env)
out.append({"alias": p["alias"], "cid": content_hash(env), "n": len(env), "ms": ms, "independent": p.get("independent") is True,
"layers": [layer_of(objs[x]) for x in p.get("layers") or []]})
return out
def render_media_case(d, objs, inp):
c = parse_media_playlist(read_text(d, inp["classical"]))
segs = []
for i, s in enumerate(inp["segments"]):
k = c["segments"][i] if i < len(c["segments"]) else None
env = objs[s["object"]]
h = whole_head(env)
w = {"cid": content_hash(env), "n": len(env), "h": covered_len(h["head_len"], len(env)) if h["version"] == "WAI2" else None,
"ms": s["duration_ms"] if isinstance(s.get("duration_ms"), int) else (k["ms"] if k else 0), "gap": s.get("gap") is True}
w["layers"] = [layer_of(objs[x]) for x in s.get("layers") or []]
w["parts"] = parts_of(objs, s.get("parts"), [q["ms"] for q in k["extra"]["parts"]] if k else [])
segs.append(w)
open_ = None
if inp.get("open") is not None:
o = inp["open"]
cms = [q["ms"] for q in c["open"]["parts"]] if c["open"] else []
open_ = {"parts": parts_of(objs, o.get("parts"), cms), "hint": o.get("preload_hint")}
end = inp["end_list"] if isinstance(inp.get("end_list"), bool) else c["end"]
return wai_media_playlist(c, segs, open_, inp.get("uri_prefix") or "", end)
def resolve_uri(objs, uri):
base, _, frag = uri.partition("#")
cid = None
for kv in frag.split("&") if frag else []:
k, eq, v = kv.partition("=")
if eq and k == "cid" and re.fullmatch(r"[0-9a-f]{64}", v):
cid = v
if cid is None:
name = base.rsplit("/", 1)[-1]
if name.endswith(".wai") and re.fullmatch(r"[0-9a-f]{64}", name[:-4]):
cid = name[:-4]
b = next((b for b in objs.values() if cid is not None and content_hash(b).hex() == cid), None)
if b is None:
raise SystemExit("http_verify: %s names no corpus object" % uri)
return b
def listed_objects(objs, p):
segs, parts, every = [], [], []
def extra(e):
for q in e["parts"]:
b = resolve_uri(objs, q["uri"])
parts.append((len(b), q["ms"]))
every.append(b)
every.extend(resolve_uri(objs, l["uri"]) for l in q["layers"])
every.extend(resolve_uri(objs, l["uri"]) for l in e["layers"])
for s in p["segments"]:
if s["extra"]["gap"]:
continue
b = resolve_uri(objs, s["uri"])
every.append(b)
segs.append((len(b) + sum(len(resolve_uri(objs, l["uri"])) for l in s["extra"]["layers"]), s["ms"]))
extra(s["extra"])
if p["open"] is not None:
extra(p["open"])
return segs, parts, every
def union_floor_of(every):
so = [seq_object(e) for e in every]
return union_of(so), floor_of(so)
def multivariant_case(d, objs, inp):
out = ["#EXTM3U"]
for v in inp["variants"]:
p = parse_media_playlist(read_text(d, v["playlist"]))
segs, parts, every = listed_objects(objs, p)
union, floor = union_floor_of(every)
bw = bandwidths(segs if segs else parts)
if bw is None:
raise SystemExit("http_verify: a variant with no duration")
uri = v["uri"]
if not uri_ok(uri, False) or union[0] != floor[0]:
raise Refusal("playlist_malformed")
line = '#EXT-X-STREAM-INF:BANDWIDTH=%d,AVERAGE-BANDWIDTH=%d,CODECS="%s",X-WAI-FLOOR="%s"' % (bw[0], bw[1], render(union), render(floor))
if v.get("resolution"):
line += ",RESOLUTION=%dx%d" % tuple(v["resolution"])
if v.get("frame_rate_milli") is not None:
if v["frame_rate_milli"] == 0:
raise Refusal("playlist_malformed")
line += ",FRAME-RATE=" + secs(v["frame_rate_milli"])
out += [line, uri]
return "\n".join(out) + "\n"
def mpd_case(d, objs, inp):
p = parse_media_playlist(read_text(d, inp["playlist"]))
segs, _, every = listed_objects(objs, p)
union, floor = union_floor_of(every)
peak = bandwidths(segs)[0]
if isinstance(inp.get("classical_mpd"), str):
t = mpd_timeline(read_text(d, inp["classical_mpd"]), inp.get("period_id"), inp.get("classical_set"), len(p["segments"]))
else:
c = parse_media_playlist(read_text(d, inp["classical"]))
at, segs_t = 0, []
for s in c["segments"]:
segs_t.append((at, s["ms"]))
at += s["ms"]
if at > U64:
raise Refusal("playlist_malformed")
t = (inp.get("period_id") or "p0", 0, 1000, segs_t)
media = [None if s["extra"]["gap"] else s["uri"] for s in p["segments"]]
return wai_mpd(t, inp.get("set_id") or 1, union, floor, inp["rep_id"], peak, media)
def rendered_case(d, inp, t):
b = t.encode("utf-8")
o = {"blake3": blake3(b).hex(), "bytes": len(b)}
if "file" in inp:
o["same_as_file"] = os.path.exists(os.path.join(d, inp["file"])) and open(os.path.join(d, inp["file"]), "rb").read() == b
return o
def parsed_case(p, t):
parts = lambda ps: [[q["ms"], q["independent"], [l["n"] for l in q["layers"]]] for q in ps]
return {"version": p["version"], "target_duration_s": p["target"], "media_sequence": p["seq"], "discontinuity_sequence": p["dseq"],
"part_target_ms": p["part_target"], "part_hold_back_ms": p["hold_back"], "can_block_reload": p["block"], "end_list": p["end"],
"playlist_type": p["ptype"], "independent_segments": p["indep"], "map_uri": p["map"],
"segments": [[s["ms"], s["extra"]["discontinuity"], s["extra"]["pdt"], parts(s["extra"]["parts"]), [l["n"] for l in s["extra"]["layers"]], s["uri"], s["extra"]["gap"]] for s in p["segments"]],
"open": None if p["open"] is None else [p["open"]["discontinuity"], p["open"]["pdt"], parts(p["open"]["parts"])],
"preload_hint": p["hint"], "rendered_form": is_rendered_form(p, t)}
def live_case(d, objs, inp):
"""§8.6, §11.4, §11.6 over a live playlist's revisions in the order a client fetched
them, and parts fetched by alias."""
revisions = inp["revisions"]
parents = inp.get("parents")
if parents is None:
parents = (revisions[0]["wai"].rsplit("/", 1)[0] if "/" in revisions[0]["wai"] else "") + "/mr" if revisions else "/mr"
def fetch(h):
f = os.path.join(d, parents, h.hex() + ".json")
return open(f, "rb").read() if os.path.isfile(f) else None
url = inp["url"] if isinstance(inp.get("url"), str) else LIVE_URL
chain = {"head": None, "bindings": new_bindings(url), "bound": inp["walk_bound"] if isinstance(inp.get("walk_bound"), int) else WALK_BOUND}
listed_b = new_bindings(url)
revs, listed = [], []
for r in revisions:
w = edit_text(read_text(d, r["wai"]), r.get("edit_wai"))
k = read_text(d, r["classical"])
try:
check_adjacent(w, k)
adjacent = True
except Refusal as e:
adjacent = e.code
p = parse_media_playlist(w)
try:
listed_b = bind(listed_b, p)
except Refusal as e:
revs.append({"adjacent": adjacent, "listing": e.code, "receipt": None, "chained": None, "walked": None})
listed.append(None)
continue
listed.append(p)
rpath = r["receipt"]
name = None if rpath.startswith("cases/") else rpath.rsplit("/", 1)[-1]
try:
rr = check_playlist(w.encode("utf-8"), open(os.path.join(d, rpath), "rb").read(), name)
except Refusal as e:
revs.append({"adjacent": adjacent, "listing": True, "receipt": e.code, "chained": None, "walked": None})
continue
try:
step, walked = advance(chain, rr, fetch)
ch = step if step in ("unchanged", "reanchored") else True
except Refusal as e:
ch, walked = e.code, None
revs.append({"adjacent": adjacent, "listing": True, "receipt": "signed", "chained": ch, "walked": walked})
fetches = []
for f in inp.get("fetches") or []:
served = apply_patch(objs[f["object"]], f.get("patch_object"))
first = next(((k, c) for k, p in enumerate(listed) if p is not None for c in [listed_cid(p, f["alias"])] if c is not None), None)
if first is None:
fetches.append({"alias": f["alias"], "listed_in": None, "outcome": "not_listed", "held": None})
elif content_hash(served) == first[1]:
held = "signed" if cid_of(chain["bindings"], f["alias"]) == first[1] else "listed"
fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "accepted", "held": held})
else:
fetches.append({"alias": f["alias"], "listed_in": first[0], "outcome": "object_digest_mismatch", "held": None})
return {"revisions": revs, "fetches": fetches}
def playlist_evaluate(d, objs, op, inp, obj):
"""The playlist and adaptation ops; None for any other op."""
if op == "media_playlist":
return rendered_case(d, inp, render_media_case(d, objs, inp))
if op == "multivariant":
return rendered_case(d, inp, multivariant_case(d, objs, inp))
if op == "mpd":
return rendered_case(d, inp, mpd_case(d, objs, inp))
if op == "playlist_parse":
t = inp["text"] if "text" in inp else read_text(d, inp["file"])
return parsed_case(parse_media_playlist(t), t)
inline = lambda k: inp[k + "_text"] if isinstance(inp.get(k + "_text"), str) else read_text(d, inp[k])
if op == "uri_resolve":
return {"url": rfc3986_resolve(inp["base"], inp["reference"])}
if op == "dash_adjacency":
check_dash_adjacent(edit_text(inline("wai"), inp.get("edit_wai")), edit_text(inline("classical"), inp.get("edit_classical")))
return {"adjacent": True}
if op == "adjacency":
check_adjacent(edit_text(inline("wai"), inp.get("edit_wai")), edit_text(inline("classical"), inp.get("edit_classical")))
return {"adjacent": True}
if op == "unchanged":
classical_unchanged(inline("original"), edit_text(inline("published"), inp.get("edit_published")))
return {"unchanged": True}
if op == "render":
segs = [{"ms": x[0], "uri": x[1], "ad": x[2] is True} for x in inp["segments"]]
r = inp["render"]
rr = {"version": r["version"] % 256, "seq": r["media_sequence"], "dseq": r["discontinuity_sequence"], "target": r["target_duration_s"],
"part_target": r["part_target_ms"], "hold_back": r["part_hold_back_ms"], "block": r["can_block_reload"] is True, "end": r["end_list"] is True,
"ptype": r.get("playlist_type") if r.get("playlist_type") in ("VOD", "EVENT") else None, "indep": r.get("independent_segments") is True,
"map": r.get("map_uri") if isinstance(r.get("map_uri"), str) else None}
txt = lambda v: v if isinstance(v, str) else None
lays = lambda ls: [{"n": l["n"] % 256, "uri": l["uri"]} for l in ls or []]
extras = [{"discontinuity": e.get("discontinuity") is True, "gap": e.get("gap") is True, "pdt": txt(e.get("program_date_time")),
"parts": [{"ms": q["duration_ms"], "independent": q.get("independent") is True, "uri": q["uri"], "layers": lays(q.get("layers"))} for q in e.get("parts") or []],
"layers": lays(e.get("layers")), "hint": txt(e.get("preload_hint"))} for e in inp.get("extras") or []]
check_render(segs, rr, extras)
t = render_hls(segs, rr, extras).encode("utf-8")
return {"blake3": blake3(t).hex(), "bytes": len(t)}
if op == "ilog2":
return {"log2_q16": ilog2_q16(inp["a"], inp["b"])}
if op == "playlist_receipt":
pl = edit_text(read_text(d, inp["playlist"]), inp.get("edit_playlist")).encode("utf-8")
rc = edit_text(read_text(d, inp["receipt"]), inp.get("edit_receipt")).encode("utf-8")
name = inp["name"] if isinstance(inp.get("name"), str) else (inp["receipt"].rsplit("/", 1)[-1] if inp.get("name") is None else None)
r = check_playlist(pl, rc, name)
return {"signed": receipt_hash(r).hex(), "parent": r["parent"].hex() if r["parent"] is not None else None}
if op == "live":
return live_case(d, objs, inp)
if op == "abr":
k, b = decide(inp["policy"], abr_state(inp["state"]))
return {"choice": k, "budget": b}
if op == "abr_order":
return {"order": order_entries(whole_head(obj(inp)), inp.get("advertises") or [], inp["policy"], abr_state(inp["state"]))}
if op == "depth":
layers = [len(objs[i]) for i in inp["objects"]] if "objects" in inp else inp["layers_n"]
k, b = depth(layers, inp["policy"], abr_state(inp["state"]))
return {"depth": k, "budget": b}
return None
def refusal(e):
out = {"error": e.code}
if e.skipped is not None:
out["skipped"] = e.skipped
return out
def evaluate(d, objs, op, inp):
def obj(i, patch=True):
if "envelope_hex" in i:
b = bytes.fromhex(i["envelope_hex"])
elif "object" in i:
b = objs[i["object"]]
else:
b = open(os.path.join(d, i["file"]), "rb").read()
return apply_patch(b, i.get("patch_object")) if patch else b
try:
if op == "parse_head":
b = obj(inp)
n = inp.get("envelope_len", len(b))
p = min(inp.get("prefix", len(b)), len(b))
h = parse_head(b[:p], n)
if isinstance(h, tuple):
return {"need": h[1]}
return {"version": h["version"], "head_len": h["head_len"], "envelope_len": h["envelope_len"],
"covered_len": covered_len(h["head_len"], n), "components": [list(c) for c in h["components"]]}
if op == "head_proof":
b = obj(inp)
original = obj(inp, patch=False)
cid = content_hash(original)
pf = open(os.path.join(d, inp["proof"]), "rb").read() if "proof" in inp else make_proof_file(original)
p = read_proof(pf)
verify_proof(p, cid, b)
return {"covered_len": p["covered"], "n_cv": len(p["cvs"]), "envelope_len": p["n"]}
if op == "plan":
b = obj(inp)
h = parse_head(b, len(b))
pl = plan(h, [tuple(w) for w in inp["wanted"]], [tuple(r) for r in inp.get("held", [])], inp.get("threshold_permille", 900))
if pl[0] == "rest":
return {"whole_rest": list(pl[1]), "why": pl[2]}
return {"requests": [list(r) for r in pl[1]], "dropped": pl[2]}
if op == "response":
r = check_response(inp.get("request"), inp["status"], inp.get("content_range"), inp.get("content_encoding"), inp["body_len"], inp["envelope_len"])
return {"ok": list(r)}
if op == "select":
b = obj(inp)
h = whole_head(b)
pay = [b[s:s + ln] for s, ln in h["components"]]
adv, _, held, order, _, _ = sink_of(d, inp, objs)
order = order(h) if callable(order) else (order if order is not None else list(range(len(pay))))
try:
i, sk = select(h["manifest"], order, adv, held, lambda i, e: payload_ok(e, pay[i]))
return {"pick": i, "skipped": sk}
except Refusal as e:
return {"inert": e.skipped}
if op in ("fetch", "same_pick"):
env = obj(inp, patch=False)
t = inp.get("tamper") or {}
served = apply_patch(env, t.get("patch_object"))
r = fetch(env, served, frag_of(inp.get("fragment")), sink_of(d, inp, objs), inp.get("threshold_permille", 900), t,
inp.get("listed_as_layer", False))
if op == "fetch":
return r
whole_pick = evaluate(d, objs, "select", inp)
if "pick" not in whole_pick or whole_pick["pick"] != r["pick"]:
return {"paths_differ": [whole_pick, r]}
return {"pick": r["pick"]}
if op == "fetch_timed":
env = obj(inp, patch=False)
sk = sink_of(d, inp, objs)
return fetch_timed(env, frag_of(inp.get("fragment")), sk, inp.get("threshold_permille", 900), inp.get("deadline_ms"),
inp["script"], inp.get("flip"))
if op == "codecs":
return {"elements": parse_codecs(inp["value"])}
if op == "union_floor":
envs = [bytes.fromhex(x) for x in inp["envelopes_hex"]] if "envelopes_hex" in inp else [objs[i] for i in inp["objects"]]
os_ = [seq_object(e) for e in envs]
return {"union": render(union_of(os_)), "floor": render(floor_of(os_))}
if op == "supports":
return {"supports": supports(parse_element(inp["floor"]), inp["containers"], inp["caps"])}
if op == "layer_tag":
return parse_layer_tag(inp["line"])
r = playlist_evaluate(d, objs, op, inp, obj)
if r is not None:
return r
except Refusal as e:
if op == "same_pick" and e.code == "inert":
try:
evaluate(d, objs, "select", inp)["inert"]
return {"inert": True}
except KeyError:
return {"paths_differ": "range inert, whole picks"}
return refusal(e)
raise SystemExit("unknown op " + op)
# ------------------------------------------------------------------ self-test
def self_test():
v = json.load(open(os.path.join(HERE, "blake3_vectors.json")))
for c in v["cases"]:
data = bytes(i % 251 for i in range(c["input_len"]))
got = blake3(data)
if MUTATE == "blake3-iv":
got = bytes([got[0] ^ 1]) + got[1:]
if got.hex() != c["hash"]:
print("FAIL self-test: BLAKE3 of %d bytes" % c["input_len"])
return False
if c["input_len"] > 1024:
n = c["input_len"]
l = 1024 * 2 ** ((((n - 1) // 1024)).bit_length() - 1)
if B3.parent_cv(B3.subtree_cv(data[:l], 0), B3.subtree_cv(data[l:], l), True) != got:
print("FAIL self-test: the tree functions over %d bytes" % n)
return False
return mpd_bounds_hold()
def mpd_bounds_hold():
"""Appendix I's reader holds what a document writes, not what its @r says, and reads
an element's attributes in linear time: 8 timelines of 2^16 segments each in a
kilobyte, read in a footprint bounded by its size (tracemalloc's peak); and one
element of 40000 attributes, read in bounded CPU time of this thread (never wall
time)."""
import tracemalloc
bomb = "<MPD><Period id=\"p\"><AdaptationSet>%s</AdaptationSet></Period></MPD>" % "".join(
'<Representation id="r%d"><SegmentList timescale="1"><SegmentTimeline><S t="0" d="1" r="65535"/></SegmentTimeline></SegmentList></Representation>' % i
for i in range(8))
tracemalloc.start()
try:
periods = read_mpd(bomb)
peak = tracemalloc.get_traced_memory()[1]
finally:
tracemalloc.stop()
if sum(n for tl in periods[0]["sets"][0]["timelines"] for _, _, n in tl["runs"]) != 8 << 16 or peak > 64 * len(bomb) + (1 << 20):
print("FAIL self-test: an MPD of %d bytes held %d bytes" % (len(bomb), peak))
return False
wide = "<MPD %s/>" % " ".join('a%d=""' % i for i in range(40000))
t0 = time.thread_time()
try:
read_mpd(wide)
except Refusal:
pass
spent = time.thread_time() - t0
if spent > 2.0:
print("FAIL self-test: one element of 40000 attributes read in %.2f s of CPU" % spent)
return False
return bindings_retention_holds()
def bindings_retention_holds():
"""§11.5: a binding is kept while one of the last RETENTION revisions lists it, then
forgotten, so a rebinding is refused at the span's last revision and not after it."""
cid = lambda b: bytes([b]) * 32
first = parse_media_playlist('#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-PART:DURATION=0.5,URI="p.wai#cid=%s"\n' % cid(1).hex())
again = parse_media_playlist('#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXT-X-PART-INF:PART-TARGET=0.5\n#EXT-X-PART:DURATION=0.5,URI="./p.wai#cid=%s"\n' % cid(2).hex())
other = parse_media_playlist("#EXTM3U\n#EXT-X-TARGETDURATION:1\n#EXTINF:1,\n%s.wai\n" % cid(3).hex())
b = bind(new_bindings("https://origin.example/live/p.m3u8"), first)
for _ in range(RETENTION - 1):
b = bind(b, other)
try:
bind(b, again)
print("FAIL self-test: a rebinding within the retention was taken")
return False
except Refusal:
pass
b = bind(b, other)
try:
bind(b, again)
except Refusal:
print("FAIL self-test: a binding outlived the retention")
return False
return True
def run_cases(d, path):
"""Evaluate every case of a case file (the corpus's format) against the corpus at d,
and require each committed outcome."""
load_registry()
objs = {i: open(os.path.join(d, o["path"]), "rb").read() for i, o in json.load(open(os.path.join(d, "objects.json"))).items()}
cases = json.load(open(path))
bad = 0
for c in cases:
got = evaluate(d, objs, c["op"], c["input"])
if got != c["expect"]:
bad += 1
if bad <= 20:
print("DIFF %s: rust %s, python %s" % (c["name"], json.dumps(c["expect"]), json.dumps(got)))
print("%d/%d cases agree" % (len(cases) - bad, len(cases)))
return bad == 0
def run(d):
load_registry()
ok = self_test()
listing = json.load(open(os.path.join(d, "objects.json")))
objs = {}
for i, o in listing.items():
b = open(os.path.join(d, o["path"]), "rb").read()
if content_hash(b).hex() != o["cid"]:
print("FAIL object %s: its bytes do not hash to its cid" % i)
ok = False
if open(os.path.join(d, o["path"] + ".hp"), "rb").read() != make_proof_file(b):
print("FAIL object %s: its head proof is not the one the text gives" % i)
ok = False
objs[i] = b
cases = json.load(open(os.path.join(d, "cases.json")))
passed = 0
for c in cases:
got = evaluate(d, objs, c["op"], c["input"])
if got == c["expect"]:
passed += 1
else:
print("FAIL %s: expected %s, got %s" % (c["name"], json.dumps(c["expect"]), json.dumps(got)))
ok = False
print("%d/%d cases give their outcome%s" % (passed, len(cases), " (mutation %s)" % MUTATE if MUTATE else ""))
return ok
def diff(path, flip, seed):
load_registry()
lines = [json.loads(l) for l in open(path) if l.strip()]
rng = random.Random(seed)
candidates = [i for i, l in enumerate(lines) if len(bytes.fromhex(l["proof"])) > 53]
flipped = set(rng.sample(candidates, flip)) if flip else set()
refused = set()
for i, l in enumerate(lines):
proof = bytearray(bytes.fromhex(l["proof"]))
if i in flipped:
k = proof[52]
at = 53 + 32 * rng.randrange(k) + rng.randrange(32)
proof[at] ^= 1 << rng.randrange(8)
try:
p = read_proof(bytes(proof))
if p["n"] != l["envelope_len"]:
raise Refusal("head_proof_malformed")
verify_proof(p, bytes.fromhex(l["cid"]), bytes.fromhex(l["prefix"]))
except Refusal:
refused.add(i)
print("%d proofs; %d flipped; %d refused" % (len(lines), len(flipped), len(refused)))
if refused != flipped:
print("FAIL: refused %s, flipped %s" % (sorted(refused - flipped)[:10], sorted(flipped - refused)[:10]))
return False
return True
def main(argv):
global MUTATE
if "--mutate" in argv:
i = argv.index("--mutate")
MUTATE = argv[i + 1]
argv = argv[:i] + argv[i + 2:]
if argv and argv[0] == "cases":
return 0 if run_cases(argv[2] if len(argv) > 2 else HERE, argv[1]) else 1
if argv and argv[0] == "diff":
flip = int(argv[argv.index("--flip") + 1]) if "--flip" in argv else 0
seed = int(argv[argv.index("--seed") + 1]) if "--seed" in argv else 1
return 0 if diff(argv[1], flip, seed) else 1
d = argv[0] if argv else HERE
return 0 if run(d) else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))