ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
gen_unarch_xz_fixture.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""gen_unarch_xz_fixture.py -- regenerate the unarch XZ test fixture.
5
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:
12
13 python3 scripts/gen/gen_unarch_xz_fixture.py
14
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
18 input-refill passes.
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.
30
31Copyright (c) 2026 Brighton Sikarskie
32SPDX-License-Identifier: MIT
33"""
34
35from __future__ import annotations
36
37import io
38import lzma
39import tarfile
40from pathlib import Path
41
42REPO_ROOT = Path(__file__).resolve().parents[2]
43OUT_PATH = REPO_ROOT / "apps" / "shared_libs" / "unarch" / "tests" / "inc" / "unarch_xz_fixture.h"
44
45LCG_SEED = 0x12345678
46"""Seed of the payload LCG; the unarch XZ test re-derives the
47payload with the same constants to verify decoded bytes."""
48
49LCG_MUL = 1103515245
50LCG_ADD = 12345
51
52
53def lcg_bytes(n: int, seed: int = LCG_SEED) -> bytes:
54 """The C-reproducible pseudo-random payload generator."""
55 out = bytearray()
56 s = seed
57 for _ in range(n):
58 s = (LCG_MUL * s + LCG_ADD) & 0xFFFFFFFF
59 out.append((s >> 16) & 0xFF)
60 return bytes(out)
61
62
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)
67
68
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},")
76 lines.append("};")
77 return "\n".join(lines)
78
79
80def cbt_tar() -> bytes:
81 """A deterministic two-page ustar comic (the .tar.xz fixture core)."""
82 buf = io.BytesIO()
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"),
87 ):
88 info = tarfile.TarInfo(name.decode("ascii"))
89 info.size = len(payload)
90 info.mtime = 0
91 tf.addfile(info, io.BytesIO(payload))
92 return buf.getvalue()
93
94
95def main() -> int:
96 """Emit the xz decoder fixtures covering each supported integrity check.
97
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
101 unexercised.
102 """
103 small = b"hello xz stream, bounded and fail-closed\n" * 8
104 dict_64k = 1 << 16
105 dict_8m = 1 << 23
106
107 arrays = [
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)),
114 ]
115
116 body = "\n\n".join(c_array(n, d) for n, d in arrays)
117 header = f"""/**
118 * @file unarch_xz_fixture.h
119 * @brief Committed .xz byte fixtures for the XZ decoder tests.
120 *
121 * @details
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.
128 *
129 * @copyright Copyright (c) 2026 Brighton Sikarskie
130 * SPDX-License-Identifier: MIT
131 */
132#pragma once
133
134#include <stdint.h>
135
136{body}
137"""
138 OUT_PATH.write_text(header, encoding="ascii")
139 print(f"wrote {OUT_PATH} ({OUT_PATH.stat().st_size} bytes)")
140 return 0
141
142
143if __name__ == "__main__":
144 raise SystemExit(main())
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298