4"""gen_unarch_xz_fixture.py -- regenerate the unarch XZ test fixture.
6The XZ decoder tests (apps/shared_libs/unarch/tests/src/test_unarch_xz.c) need real .xz streams
7with controlled properties: integrity-check type (CRC32 / CRC64 / SHA-256),
8LZMA2 dictionary size, and compression ratio. Those bytes cannot be built
9portably at test runtime (the C tree has no XZ *encoder*), so this script
10materialises them once with Python's lzma module and emits a committed
11C header of byte arrays. Re-run it only when a fixture needs to change:
13 python3 scripts/gen/gen_unarch_xz_fixture.py
15Fixture inventory (all payloads reproducible in C, see the header):
16 fx_xz_crc64_4k 4 KiB LCG payload, dict 64 KiB, CRC64 (the xz default)
17 -- stream > 512 B so the unwrap loop takes several
19 fx_xz_crc32 328 B text payload, dict 64 KiB, CRC32.
20 fx_xz_sha256 same payload, SHA-256 check -- xz-embedded is built
21 without SHA-256, so this must be REJECTED cleanly.
22 fx_xz_bigdict same payload, dict 8 MiB -- must be rejected by a
23 session whose scratch dictionary budget is smaller.
24 fx_xz_bomb_1m 1 MiB of zeros in a 284 B stream (~3690:1) -- breaches
25 the DEFAULT decompression-limits ratio bound
26 (1024:1 + 64 KiB grace) and must be rejected mid-decode.
27 fx_xz_cbt an XZ-wrapped ustar comic (two .png page members with
28 the payloads "PAGE-ONE" / "PAGE-TWO") for the
29 comic_open_wrapped .tar.xz path.
31Copyright (c) 2026 Brighton Sikarskie
32SPDX-License-Identifier: MIT
35from __future__
import annotations
40from pathlib
import Path
42REPO_ROOT = Path(__file__).resolve().parents[2]
43OUT_PATH = REPO_ROOT /
"apps" /
"shared_libs" /
"unarch" /
"tests" /
"inc" /
"unarch_xz_fixture.h"
46"""Seed of the payload LCG; the unarch XZ test re-derives the
47payload with the same constants to verify decoded bytes."""
53def lcg_bytes(n: int, seed: int = LCG_SEED) -> bytes:
54 """The C-reproducible pseudo-random payload generator."""
58 s = (LCG_MUL * s + LCG_ADD) & 0xFFFFFFFF
59 out.append((s >> 16) & 0xFF)
63def xz(payload: bytes, check: int, dict_size: int) -> bytes:
64 """One .xz stream with an explicit check type and LZMA2 dictionary."""
65 filters = [{
"id": lzma.FILTER_LZMA2,
"preset": 6,
"dict_size": dict_size}]
66 return lzma.compress(payload, format=lzma.FORMAT_XZ, check=check, filters=filters)
69def c_array(name: str, data: bytes) -> str:
70 """Emit one static C byte array (12 bytes per line, pure ASCII)."""
71 lines = [f
"/** @brief {name}: {len(data)} fixture bytes (see file header). */"]
72 lines.append(f
"static const uint8_t {name}[{len(data)}U] = {{")
73 for i
in range(0, len(data), 12):
74 chunk =
", ".join(f
"0x{b:02X}U" for b
in data[i : i + 12])
75 lines.append(f
" {chunk},")
77 return "\n".join(lines)
80def cbt_tar() -> bytes:
81 """A deterministic two-page ustar comic (the .tar.xz fixture core)."""
83 with tarfile.open(fileobj=buf, mode=
"w", format=tarfile.USTAR_FORMAT)
as tf:
84 for name, payload
in (
85 (b
"page2.png", b
"PAGE-TWO"),
86 (b
"page1.png", b
"PAGE-ONE"),
88 info = tarfile.TarInfo(name.decode(
"ascii"))
89 info.size = len(payload)
91 tf.addfile(info, io.BytesIO(payload))
96 """Emit the xz decoder fixtures covering each supported integrity check.
98 Generates one stream per check type (CRC32, CRC64, SHA256) and per
99 dictionary size, because the decoder's bounds handling differs by both --
100 a fixture set covering only the default would leave the fail-closed paths
103 small = b
"hello xz stream, bounded and fail-closed\n" * 8
108 (
"k_fx_xz_crc64_4k", xz(lcg_bytes(4096), lzma.CHECK_CRC64, dict_64k)),
109 (
"k_fx_xz_crc32", xz(small, lzma.CHECK_CRC32, dict_64k)),
110 (
"k_fx_xz_sha256", xz(small, lzma.CHECK_SHA256, dict_64k)),
111 (
"k_fx_xz_bigdict", xz(small, lzma.CHECK_CRC64, dict_8m)),
112 (
"k_fx_xz_bomb_1m", xz(b
"\x00" * (1 << 20), lzma.CHECK_CRC64, dict_64k)),
113 (
"k_fx_xz_cbt", xz(cbt_tar(), lzma.CHECK_CRC64, dict_64k)),
116 body =
"\n\n".join(c_array(n, d)
for n, d
in arrays)
118 * @file unarch_xz_fixture.h
119 * @brief Committed .xz byte fixtures for the XZ decoder tests.
122 * GENERATED FILE -- regenerate with:
123 * python3 scripts/gen/gen_unarch_xz_fixture.py
124 * See that script for the fixture inventory and rationale. The LCG payload
125 * of `k_fx_xz_crc64_4k` is re-derivable in C: byte i is
126 * `(state >> 16) & 0xFF` after `state = 1103515245 * state + 12345`
127 * (uint32 wrap-around) from seed 0x12345678.
129 * @copyright Copyright (c) 2026 Brighton Sikarskie
130 * SPDX-License-Identifier: MIT
138 OUT_PATH.write_text(header, encoding=
"ascii")
139 print(f
"wrote {OUT_PATH} ({OUT_PATH.stat().st_size} bytes)")
143if __name__ ==
"__main__":
144 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.