All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 9s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 10s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 9s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 9s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 7s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 8s
WAI energy receipts / does this change touch it (pull_request) Successful in 6s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 6s
wai-fleet service / does this change touch it (pull_request) Successful in 6s
WAI operation catalogue / does this change touch it (pull_request) Successful in 13s
WAI conformance / does this change touch it (pull_request) Successful in 13s
wai-key-proxy / does this change touch it (pull_request) Successful in 6s
wai-ledger service / does this change touch it (pull_request) Successful in 6s
WAI neural sink gates / does this change touch it (pull_request) Successful in 5s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 7s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 6s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 9s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 7s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 14s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 15s
WAI Studio / does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 7s
wai-web wasm32 / does this change touch it (pull_request) Successful in 13s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 3m27s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 33s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m8s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 2m56s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 33s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 3m29s
wai-fleet service / service · linux (pull_request) Successful in 2m40s
wai-key-proxy / proxy · linux (pull_request) Successful in 1m5s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m17s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m50s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m14s
wai-ledger service / service · linux (pull_request) Successful in 2m4s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 5m10s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m51s
WAI neural sink gates / the exact JPEG-AI decoder reports its path (pinned inputs, required) (pull_request) Successful in 2m38s
wai-pack / wai-pack · linux (pull_request) Successful in 1m47s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 3m57s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 2m57s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 2m31s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 8m25s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 3m32s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m36s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 5m39s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m29s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 5m56s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 11m58s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 30m34s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 38m39s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m5s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 1m47s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 10m35s
wai-fleet service / service · macos (pull_request) Successful in 42s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 10m37s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 55s
wai-ledger service / service · macos (pull_request) Successful in 1m10s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 2m1s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 16m26s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 12m18s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 6m34s
wai-pack / wai-pack · macos (pull_request) Successful in 2m57s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 7m29s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 14m38s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 11m1s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 15m43s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 15m44s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 12m18s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m24s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m11s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 3m35s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m14s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m41s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 44s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 12m22s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 16s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 32s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m3s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m38s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m53s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 17m20s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 25s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 15s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m59s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 18m2s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h12m0s
The change of wai/prior-carriage-spec (98192fe63..26834bb4c, 6 commits), on the fix branch: its changelog entries as files of wai/changes/, its wai-conformance.yml edits as step files, corpus files and test tags, and every other file as the branch has it. - e5db9b313 wai: where a parameter set is fetched — set roots, location hints, and <base> defined - 29fe43ea2 wai: a parameter set as an RFC 9842 compression dictionary, with a vector a stock client checks - faaeb6c73 wai: when a parameter set is worth holding — amortisation charged per sink, bootstrap and generations - 9a820db4a wai: a parameter set's usage statement, sealed sets released by key, leakage, and the privacy of held sets - 89a9f9924 wai: the chunk-tree prior form, wai.prior.tree — a set verified piece by piece from any peers - 26834bb4c wai: one playlist line of set roots, inner forms as the layouts list them, and a listing's usage statement read or the set refused Co-Authored-By: Claude Opus 5.5 <[email protected]>
526 lines
18 KiB
Python
526 lines
18 KiB
Python
"""A Zstandard decoder written from RFC 8878, in pure Python, for the prior-carriage verifier.
|
|
|
|
It decodes one frame against a raw-content dictionary (RFC 8878 §5: the dictionary's bytes are
|
|
history before the frame's first byte, and the repeat offsets start at 1, 4 and 8), checks the
|
|
frame's content size and its XXH64 checksum when they are stated, and refuses anything it does
|
|
not read with `ZstdError`. It shares no code with wai-rs, which decodes with the Zstandard
|
|
library. It is written for the corpus's small frames, not for speed.
|
|
"""
|
|
|
|
|
|
class ZstdError(Exception):
|
|
pass
|
|
|
|
|
|
MAGIC = 0xFD2FB528
|
|
MAX_BLOCK = 128 * 1024
|
|
|
|
|
|
def frame_window(frame):
|
|
"""(Window_Size, header length, content size or None, checksum flag) of a frame header."""
|
|
if len(frame) < 6 or int.from_bytes(frame[:4], "little") != MAGIC:
|
|
raise ZstdError("no frame magic")
|
|
fhd = frame[4]
|
|
if fhd & 0x08:
|
|
raise ZstdError("reserved bit set")
|
|
single = bool(fhd & 0x20)
|
|
did = (0, 1, 2, 4)[fhd & 3]
|
|
fcs = {0: 1 if single else 0, 1: 2, 2: 4, 3: 8}[fhd >> 6]
|
|
at = 5
|
|
window = None
|
|
if not single:
|
|
wd = frame[at]
|
|
at += 1
|
|
base = 1 << (10 + (wd >> 3))
|
|
window = base + (base // 8) * (wd & 7)
|
|
at += did
|
|
if len(frame) < at + fcs:
|
|
raise ZstdError("header truncated")
|
|
size = int.from_bytes(frame[at:at + fcs], "little") if fcs else None
|
|
if fcs == 2:
|
|
size += 256
|
|
at += fcs
|
|
if window is None:
|
|
window = size
|
|
return window, at, size, bool(fhd & 0x04)
|
|
|
|
|
|
# --- bit readers --------------------------------------------------------------------------------
|
|
|
|
class Forward:
|
|
"""Bits read from the least significant bit of each byte, first byte first (§4.1.1)."""
|
|
|
|
def __init__(self, data):
|
|
self.v = int.from_bytes(data, "little")
|
|
self.n = len(data) * 8
|
|
self.pos = 0
|
|
|
|
def read(self, k):
|
|
if self.pos + k > self.n:
|
|
raise ZstdError("table description past its end")
|
|
x = (self.v >> self.pos) & ((1 << k) - 1)
|
|
self.pos += k
|
|
return x
|
|
|
|
def peek(self, k):
|
|
return (self.v >> self.pos) & ((1 << k) - 1)
|
|
|
|
|
|
class Backward:
|
|
"""A bitstream read from its end (§4.1): the highest set bit of the last byte ends it."""
|
|
|
|
def __init__(self, data):
|
|
if not data or data[-1] == 0:
|
|
raise ZstdError("bitstream without its end mark")
|
|
self.v = int.from_bytes(data, "little")
|
|
self.left = self.v.bit_length() - 1
|
|
|
|
def read(self, k):
|
|
if k == 0:
|
|
return 0
|
|
self.left -= k
|
|
if self.left >= 0:
|
|
return (self.v >> self.left) & ((1 << k) - 1)
|
|
return (self.v << -self.left) & ((1 << k) - 1)
|
|
|
|
def peek(self, k):
|
|
if self.left >= k:
|
|
return (self.v >> (self.left - k)) & ((1 << k) - 1)
|
|
return (self.v << (k - self.left)) & ((1 << k) - 1)
|
|
|
|
def overflowed(self):
|
|
return self.left < 0
|
|
|
|
|
|
# --- FSE (§4.1) ---------------------------------------------------------------------------------
|
|
|
|
def read_distribution(br, max_symbol, max_log):
|
|
"""An FSE table description: (accuracy log, normalised counts)."""
|
|
log = br.read(4) + 5
|
|
if log > max_log:
|
|
raise ZstdError("accuracy log too large")
|
|
remaining = (1 << log) + 1
|
|
threshold = 1 << log
|
|
bits = log + 1
|
|
counts = []
|
|
previous_zero = False
|
|
while remaining > 1 and len(counts) <= max_symbol:
|
|
if previous_zero:
|
|
while True:
|
|
flag = br.read(2)
|
|
counts.extend([0] * flag)
|
|
if flag != 3:
|
|
break
|
|
if len(counts) > max_symbol:
|
|
break
|
|
top = (2 * threshold - 1) - remaining
|
|
low = br.peek(bits - 1)
|
|
if low < top:
|
|
value = low
|
|
br.read(bits - 1)
|
|
else:
|
|
value = br.peek(bits)
|
|
if value >= threshold:
|
|
value -= top
|
|
br.read(bits)
|
|
count = value - 1
|
|
remaining -= -count if count < 0 else count
|
|
counts.append(count)
|
|
previous_zero = count == 0
|
|
while remaining < threshold:
|
|
bits -= 1
|
|
threshold >>= 1
|
|
if remaining != 1 or len(counts) > max_symbol + 1:
|
|
raise ZstdError("distribution does not sum")
|
|
return log, counts
|
|
|
|
|
|
def build_table(log, counts):
|
|
"""The decoding table: per state, (symbol, bits, baseline)."""
|
|
size = 1 << log
|
|
symbol = [0] * size
|
|
high = size - 1
|
|
for s, c in enumerate(counts):
|
|
if c == -1:
|
|
symbol[high] = s
|
|
high -= 1
|
|
step = (size >> 1) + (size >> 3) + 3
|
|
pos = 0
|
|
for s, c in enumerate(counts):
|
|
for _ in range(max(c, 0)):
|
|
symbol[pos] = s
|
|
pos = (pos + step) & (size - 1)
|
|
while pos > high:
|
|
pos = (pos + step) & (size - 1)
|
|
if pos != 0:
|
|
raise ZstdError("distribution does not spread")
|
|
nxt = [1 if c == -1 else c for c in counts]
|
|
table = []
|
|
for u in range(size):
|
|
s = symbol[u]
|
|
x = nxt[s]
|
|
nxt[s] += 1
|
|
nb = log - (x.bit_length() - 1)
|
|
table.append((s, nb, (x << nb) - size))
|
|
return table
|
|
|
|
|
|
# --- Huffman literals (§4.2) --------------------------------------------------------------------
|
|
|
|
def huffman_weights(data):
|
|
"""(table, max bits, bytes read) from a Huffman tree description."""
|
|
if not data:
|
|
raise ZstdError("no tree description")
|
|
head = data[0]
|
|
if head < 128:
|
|
stream = data[1:1 + head]
|
|
if len(stream) != head:
|
|
raise ZstdError("tree description truncated")
|
|
fb = Forward(stream)
|
|
log, counts = read_distribution(fb, 255, 6)
|
|
table = build_table(log, counts)
|
|
used = (fb.pos + 7) // 8
|
|
bb = Backward(stream[used:])
|
|
s1, s2 = bb.read(log), bb.read(log)
|
|
weights = []
|
|
# Two states, alternating, until a state update reads past the stream's start; the
|
|
# other state then gives the last weight (§4.2.1.2).
|
|
while True:
|
|
sym, nb, base = table[s1]
|
|
weights.append(sym)
|
|
s1 = base + bb.read(nb)
|
|
if bb.overflowed():
|
|
weights.append(table[s2][0])
|
|
break
|
|
sym, nb, base = table[s2]
|
|
weights.append(sym)
|
|
s2 = base + bb.read(nb)
|
|
if bb.overflowed():
|
|
weights.append(table[s1][0])
|
|
break
|
|
if len(weights) > 255:
|
|
raise ZstdError("too many weights")
|
|
used = 1 + head
|
|
else:
|
|
n = head - 127
|
|
used = 1 + (n + 1) // 2
|
|
if len(data) < used:
|
|
raise ZstdError("tree description truncated")
|
|
weights = []
|
|
for i in range(n):
|
|
byte = data[1 + i // 2]
|
|
weights.append(byte >> 4 if i % 2 == 0 else byte & 15)
|
|
if any(w > 11 for w in weights):
|
|
raise ZstdError("weight too large")
|
|
total = sum(1 << (w - 1) for w in weights if w)
|
|
if total == 0:
|
|
raise ZstdError("no weights")
|
|
maxbits = total.bit_length()
|
|
rest = (1 << maxbits) - total
|
|
if rest & (rest - 1):
|
|
raise ZstdError("weights do not complete a tree")
|
|
weights.append(rest.bit_length())
|
|
if maxbits > 11:
|
|
raise ZstdError("code too long")
|
|
table = [None] * (1 << maxbits)
|
|
pos = 0
|
|
for w in range(1, 12):
|
|
for s, ws in enumerate(weights):
|
|
if ws == w:
|
|
nb = maxbits + 1 - w
|
|
for _ in range(1 << (w - 1)):
|
|
table[pos] = (s, nb)
|
|
pos += 1
|
|
if pos != len(table):
|
|
raise ZstdError("weights do not fill the table")
|
|
return (table, maxbits), used
|
|
|
|
|
|
def huffman_stream(data, tree, count):
|
|
table, maxbits = tree
|
|
br = Backward(data)
|
|
out = bytearray()
|
|
for _ in range(count):
|
|
s, nb = table[br.peek(maxbits)]
|
|
br.read(nb)
|
|
out.append(s)
|
|
if br.left != 0:
|
|
raise ZstdError("Huffman stream not consumed exactly")
|
|
return bytes(out)
|
|
|
|
|
|
def literals(block, state):
|
|
"""(literal bytes, bytes read) of a Literals_Section (§3.1.1.3.1)."""
|
|
b0 = block[0]
|
|
kind, fmt = b0 & 3, (b0 >> 2) & 3
|
|
if kind in (0, 1):
|
|
if fmt in (0, 2):
|
|
hl, size = 1, b0 >> 3
|
|
elif fmt == 1:
|
|
hl, size = 2, int.from_bytes(block[:2], "little") >> 4
|
|
else:
|
|
hl, size = 3, int.from_bytes(block[:3], "little") >> 4
|
|
if kind == 0:
|
|
if len(block) < hl + size:
|
|
raise ZstdError("raw literals truncated")
|
|
return block[hl:hl + size], hl + size
|
|
if len(block) < hl + 1:
|
|
raise ZstdError("RLE literals truncated")
|
|
return bytes([block[hl]]) * size, hl + 1
|
|
lhc = int.from_bytes(block[:5].ljust(5, b"\0"), "little")
|
|
if fmt in (0, 1):
|
|
hl, size, csize, streams = 3, (lhc >> 4) & 0x3FF, (lhc >> 14) & 0x3FF, 1 if fmt == 0 else 4
|
|
elif fmt == 2:
|
|
hl, size, csize, streams = 4, (lhc >> 4) & 0x3FFF, (lhc >> 18) & 0x3FFF, 4
|
|
else:
|
|
hl, size, csize, streams = 5, (lhc >> 4) & 0x3FFFF, (lhc >> 22) & 0x3FFFF, 4
|
|
body = block[hl:hl + csize]
|
|
if len(body) != csize:
|
|
raise ZstdError("compressed literals truncated")
|
|
at = 0
|
|
if kind == 2:
|
|
state["huffman"], at = huffman_weights(body)
|
|
elif state.get("huffman") is None:
|
|
raise ZstdError("treeless literals with no previous tree")
|
|
tree = state["huffman"]
|
|
if streams == 1:
|
|
out = huffman_stream(body[at:], tree, size)
|
|
else:
|
|
if len(body) < at + 6:
|
|
raise ZstdError("jump table truncated")
|
|
s1, s2, s3 = (int.from_bytes(body[at + 2 * i:at + 2 * i + 2], "little") for i in range(3))
|
|
at += 6
|
|
seg = (size + 3) // 4
|
|
parts = []
|
|
for i, n in enumerate((s1, s2, s3, len(body) - at - s1 - s2 - s3)):
|
|
if n < 0:
|
|
raise ZstdError("jump table past the literals")
|
|
parts.append(huffman_stream(body[at:at + n], tree, seg if i < 3 else size - 3 * seg))
|
|
at += n
|
|
out = b"".join(parts)
|
|
return out, hl + csize
|
|
|
|
|
|
# --- sequences (§3.1.1.3.2) ---------------------------------------------------------------------
|
|
|
|
LL_BASE = list(range(16)) + [16, 18, 20, 22, 24, 28, 32, 40, 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384,
|
|
32768, 65536]
|
|
LL_BITS = [0] * 16 + [1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
|
ML_BASE = [n + 3 for n in range(32)] + [35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 131, 259, 515, 1027, 2051,
|
|
4099, 8195, 16387, 32771, 65539]
|
|
ML_BITS = [0] * 32 + [1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
|
LL_DEFAULT = (6, [4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1,
|
|
-1, -1, -1, -1])
|
|
ML_DEFAULT = (6, [1, 4, 3, 2, 2, 2, 2, 2, 2] + [1] * 37 + [-1] * 7)
|
|
OF_DEFAULT = (5, [1, 1, 1, 1, 1, 1, 2, 2, 2] + [1] * 15 + [-1] * 5)
|
|
KINDS = (("ll", 35, 9, LL_DEFAULT), ("of", 31, 8, OF_DEFAULT), ("ml", 52, 9, ML_DEFAULT))
|
|
|
|
|
|
def sequences(block, state):
|
|
"""The sequences of a Sequences_Section: [(literals length, offset value, match length)]."""
|
|
if not block:
|
|
raise ZstdError("sequences section missing")
|
|
b0 = block[0]
|
|
if b0 == 0:
|
|
if len(block) != 1:
|
|
raise ZstdError("bytes after an empty sequences section")
|
|
return []
|
|
if b0 < 128:
|
|
n, at = b0, 1
|
|
elif b0 < 255:
|
|
n, at = ((b0 - 128) << 8) + block[1], 2
|
|
else:
|
|
n, at = block[1] + (block[2] << 8) + 0x7F00, 3
|
|
modes = block[at]
|
|
at += 1
|
|
if modes & 3:
|
|
raise ZstdError("reserved bits in the modes")
|
|
tables = {}
|
|
for (name, maxsym, maxlog, default), shift in zip(KINDS, (6, 4, 2)):
|
|
mode = (modes >> shift) & 3
|
|
if mode == 0:
|
|
tables[name] = build_table(*default)
|
|
elif mode == 1:
|
|
if block[at] > maxsym:
|
|
raise ZstdError("RLE symbol out of range")
|
|
tables[name] = [(block[at], 0, 0)]
|
|
at += 1
|
|
elif mode == 2:
|
|
fb = Forward(block[at:])
|
|
log, counts = read_distribution(fb, maxsym, maxlog)
|
|
tables[name] = build_table(log, counts)
|
|
at += (fb.pos + 7) // 8
|
|
else:
|
|
if state.get(name) is None:
|
|
raise ZstdError("repeat mode with no previous table")
|
|
tables[name] = state[name]
|
|
state[name] = tables[name]
|
|
br = Backward(block[at:])
|
|
|
|
def log_of(t):
|
|
return len(t).bit_length() - 1
|
|
|
|
ll, of, ml = (br.read(log_of(tables[k])) for k in ("ll", "of", "ml"))
|
|
out = []
|
|
for i in range(n):
|
|
ofc = tables["of"][of][0]
|
|
mlc = tables["ml"][ml][0]
|
|
llc = tables["ll"][ll][0]
|
|
if ofc > 31 or mlc > 52 or llc > 35:
|
|
raise ZstdError("code out of range")
|
|
offset = (1 << ofc) + br.read(ofc)
|
|
match = ML_BASE[mlc] + br.read(ML_BITS[mlc])
|
|
lit = LL_BASE[llc] + br.read(LL_BITS[llc])
|
|
out.append((lit, offset, match))
|
|
if i + 1 < n:
|
|
for k in ("ll", "ml", "of"):
|
|
s, nb, base = tables[k][{"ll": ll, "ml": ml, "of": of}[k]]
|
|
v = base + br.read(nb)
|
|
if k == "ll":
|
|
ll = v
|
|
elif k == "ml":
|
|
ml = v
|
|
else:
|
|
of = v
|
|
if br.left != 0:
|
|
raise ZstdError("sequence bitstream not consumed exactly")
|
|
return out
|
|
|
|
|
|
def execute(seqs, lits, history, reps):
|
|
"""Appends the block's output to `history` (dictionary and frame so far)."""
|
|
at = 0
|
|
for lit, value, match in seqs:
|
|
if at + lit > len(lits):
|
|
raise ZstdError("literals overrun")
|
|
history += lits[at:at + lit]
|
|
at += lit
|
|
if value > 3:
|
|
offset = value - 3
|
|
reps[:] = [offset, reps[0], reps[1]]
|
|
else:
|
|
idx = value + (1 if lit == 0 else 0)
|
|
if idx == 1:
|
|
offset = reps[0]
|
|
elif idx == 2:
|
|
offset = reps[1]
|
|
reps[:] = [offset, reps[0], reps[2]]
|
|
elif idx == 3:
|
|
offset = reps[2]
|
|
reps[:] = [offset, reps[0], reps[1]]
|
|
else:
|
|
offset = reps[0] - 1
|
|
reps[:] = [offset, reps[0], reps[1]]
|
|
if offset <= 0 or offset > len(history):
|
|
raise ZstdError("offset past the history")
|
|
start = len(history) - offset
|
|
for j in range(match):
|
|
history.append(history[start + j])
|
|
history += lits[at:]
|
|
|
|
|
|
# --- XXH64 --------------------------------------------------------------------------------------
|
|
|
|
M64 = (1 << 64) - 1
|
|
P1, P2, P3, P4, P5 = (0x9E3779B185EBCA87, 0xC2B2AE3D27D4EB4F, 0x165667B19E3779F9, 0x85EBCA77C2B2AE63,
|
|
0x27D4EB2F165667C5)
|
|
|
|
|
|
def _rotl(x, r):
|
|
return ((x << r) | (x >> (64 - r))) & M64
|
|
|
|
|
|
def _round(acc, lane):
|
|
return (_rotl((acc + lane * P2) & M64, 31) * P1) & M64
|
|
|
|
|
|
def xxh64(data, seed=0):
|
|
n = len(data)
|
|
i = 0
|
|
if n >= 32:
|
|
v = [(seed + P1 + P2) & M64, (seed + P2) & M64, seed, (seed - P1) & M64]
|
|
while i + 32 <= n:
|
|
for k in range(4):
|
|
v[k] = _round(v[k], int.from_bytes(data[i + 8 * k:i + 8 * k + 8], "little"))
|
|
i += 32
|
|
h = (_rotl(v[0], 1) + _rotl(v[1], 7) + _rotl(v[2], 12) + _rotl(v[3], 18)) & M64
|
|
for k in range(4):
|
|
h = ((h ^ _round(0, v[k])) * P1 + P4) & M64
|
|
else:
|
|
h = (seed + P5) & M64
|
|
h = (h + n) & M64
|
|
while i + 8 <= n:
|
|
h ^= _round(0, int.from_bytes(data[i:i + 8], "little"))
|
|
h = (_rotl(h, 27) * P1 + P4) & M64
|
|
i += 8
|
|
if i + 4 <= n:
|
|
h ^= (int.from_bytes(data[i:i + 4], "little") * P1) & M64
|
|
h = (_rotl(h, 23) * P2 + P3) & M64
|
|
i += 4
|
|
while i < n:
|
|
h ^= (data[i] * P5) & M64
|
|
h = (_rotl(h, 11) * P1) & M64
|
|
i += 1
|
|
h ^= h >> 33
|
|
h = (h * P2) & M64
|
|
h ^= h >> 29
|
|
h = (h * P3) & M64
|
|
h ^= h >> 32
|
|
return h
|
|
|
|
|
|
# --- a frame ------------------------------------------------------------------------------------
|
|
|
|
def decode(frame, dictionary=b"", limit=None):
|
|
"""The content of one whole frame, decoded against `dictionary` as raw content; nothing may
|
|
follow the frame. With `limit`, a frame whose content would pass it raises `TooLarge`."""
|
|
window, at, size, checksum = frame_window(frame)
|
|
history = bytearray(dictionary)
|
|
base = len(history)
|
|
reps = [1, 4, 8]
|
|
state = {}
|
|
while True:
|
|
if len(frame) < at + 3:
|
|
raise ZstdError("block header truncated")
|
|
h = int.from_bytes(frame[at:at + 3], "little")
|
|
at += 3
|
|
last, kind, bsize = h & 1, (h >> 1) & 3, h >> 3
|
|
if bsize > min(window, MAX_BLOCK) and kind != 1:
|
|
raise ZstdError("block too large")
|
|
if kind == 0:
|
|
if len(frame) < at + bsize:
|
|
raise ZstdError("raw block truncated")
|
|
history += frame[at:at + bsize]
|
|
at += bsize
|
|
elif kind == 1:
|
|
if len(frame) < at + 1:
|
|
raise ZstdError("RLE block truncated")
|
|
history += bytes([frame[at]]) * bsize
|
|
at += 1
|
|
elif kind == 2:
|
|
block = frame[at:at + bsize]
|
|
if len(block) != bsize:
|
|
raise ZstdError("compressed block truncated")
|
|
lits, used = literals(block, state)
|
|
execute(sequences(block[used:], state), lits, history, reps)
|
|
at += bsize
|
|
else:
|
|
raise ZstdError("reserved block type")
|
|
if limit is not None and len(history) - base > limit:
|
|
raise TooLarge()
|
|
if last:
|
|
break
|
|
content = bytes(history[base:])
|
|
if size is not None and len(content) != size:
|
|
raise ZstdError("content size not as stated")
|
|
if checksum:
|
|
if frame[at:at + 4] != (xxh64(content) & 0xFFFFFFFF).to_bytes(4, "little"):
|
|
raise ZstdError("checksum mismatch")
|
|
at += 4
|
|
if at != len(frame):
|
|
raise ZstdError("bytes after the frame")
|
|
return content
|
|
|
|
|
|
class TooLarge(Exception):
|
|
pass
|