ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
make_stress_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"""Generate epub_stress_fixture.h, a synthetic large-STRUCTURE EPUB3.
5
6Reproduces the bounded ZIP and XML structure pressure of a big real-world book
7(#144 bug 1) without shipping a copyrighted 7 MB novel.
8
9The insight the fixture is built on: miniz arena pressure is proportional to
10archive FILE COUNT, while XML consumer bounds are proportional to the number
11of manifest/spine/nav items. Neither comes from total byte size. So this packs many
12tiny files instead of a few large ones -- 60 chapters (the spine, deliberately
13under the k_epub_max_chapters=64 cap), 60 extra manifest-only resources, an
14NCX with 60 navPoints, and a cover. That is ~120 archive entries and a ~20 KB
15OPF, comparable to the 108-file, 41-chapter real book, in tens of KB total. It
16therefore bakes into MRAM and opens in memory like epub_parse.
17
18Raise the byte size and the fixture gets no more stressful; raise the entry
19count and it does.
20
21Output is pure 7-bit ASCII, 16 bytes per row inside a clang-format-off guard.
22
23Usage:
24 python3 examples/ek_ra8d2/hw_pending/epub_stress/scripts/make_stress_fixture.py
25"""
26
27from __future__ import annotations
28
29import io
30import zipfile
31from pathlib import Path
32
33N_CHAPTERS = 60 # spine length; must stay < k_epub_max_chapters (64)
34N_RESOURCES = 60 # extra manifest-only files (push the archive file count up)
35
36CONTAINER_XML = (
37 '<?xml version="1.0"?>\n'
38 '<container version="1.0" '
39 'xmlns="urn:oasis:names:tc:opendocument:xmlns:container">\n'
40 " <rootfiles>\n"
41 ' <rootfile full-path="OEBPS/content.opf" '
42 'media-type="application/oebps-package+xml"/>\n'
43 " </rootfiles>\n"
44 "</container>\n"
45)
46
47
48def build_opf() -> str:
49 """Build the OPF package document used to stress XML consumer bounds.
50
51 Emits every chapter as both a manifest item and a spine itemref, plus the
52 manifest-only CSS resources that inflate the file count without adding
53 spine entries. The resulting element count drives the bounded event and
54 semantic-consumer paths.
55
56 Declares EPUB3 `properties="cover-image"` and the legacy
57 `<meta name="cover">` both, so the fixture exercises either cover-resolution
58 path a reader might take.
59
60 Returns:
61 The complete OPF as ASCII-safe text.
62 """
63 items = [
64 ' <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>',
65 ' <item id="cover" href="cover.png" media-type="image/png" properties="cover-image"/>',
66 ]
67 spine = []
68 for i in range(N_CHAPTERS):
69 items.append(
70 f' <item id="ch{i}" href="ch{i}.xhtml" media-type="application/xhtml+xml"/>'
71 )
72 spine.append(f' <itemref idref="ch{i}"/>')
73 items.extend(
74 f' <item id="res{i}" href="res{i}.css" media-type="text/css"/>'
75 for i in range(N_RESOURCES)
76 )
77 return (
78 '<?xml version="1.0" encoding="UTF-8"?>\n'
79 '<package xmlns="http://www.idpf.org/2007/opf" version="3.0" '
80 'unique-identifier="id">\n'
81 ' <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">\n'
82 " <dc:title>Stress Structure Book</dc:title>\n"
83 " <dc:creator>Brighton Sikarskie</dc:creator>\n"
84 " <dc:language>en</dc:language>\n"
85 ' <dc:identifier id="id">urn:test:stress</dc:identifier>\n'
86 ' <meta name="cover" content="cover"/>\n'
87 " </metadata>\n"
88 " <manifest>\n" + "\n".join(items) + "\n </manifest>\n"
89 ' <spine toc="ncx">\n' + "\n".join(spine) + "\n </spine>\n"
90 "</package>\n"
91 )
92
93
94def build_ncx() -> str:
95 """Build the EPUB2 NCX table of contents, one navPoint per chapter.
96
97 An EPUB3 fixture does not need an NCX, and that is exactly why it is here:
98 real books ship one for backward compatibility, and it adds another
99 60-element XML document the parser must handle. `playOrder` is 1-based
100 while the chapter hrefs are 0-based, matching how real books number them.
101
102 Returns:
103 The complete NCX as ASCII-safe text.
104 """
105 points = [
106 f' <navPoint id="np{i}" playOrder="{i + 1}">\n'
107 f" <navLabel><text>Chapter {i + 1}</text></navLabel>\n"
108 f' <content src="ch{i}.xhtml"/>\n'
109 " </navPoint>"
110 for i in range(N_CHAPTERS)
111 ]
112 return (
113 '<?xml version="1.0" encoding="UTF-8"?>\n'
114 '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">\n'
115 ' <head><meta name="dtb:uid" content="urn:test:stress"/></head>\n'
116 " <docTitle><text>Stress Structure Book</text></docTitle>\n"
117 " <navMap>\n" + "\n".join(points) + "\n </navMap>\n"
118 "</ncx>\n"
119 )
120
121
122# A real 1x1 PNG (so cover resolution + a real decodable image are present).
123# Hand-aligned PNG byte table, twelve bytes per row -- keep the rows intact.
124# Suppression rationale: preserve the reviewed twelve-byte rows in this binary fixture
125# fmt: off
126COVER_PNG = bytes(
127 [
128 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D,
129 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
130 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00,
131 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8, 0xCF, 0xC0, 0x00,
132 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0x18, 0xDD, 0x8D, 0xB0, 0x00, 0x00,
133 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
134 ]
135)
136# fmt: on
137
138
139def build_epub() -> bytes:
140 """Assemble the whole EPUB archive in memory and return its bytes.
141
142 Byte-for-byte reproducible: every entry is written with a fixed 2026-01-01
143 timestamp and fixed permissions, so re-running the generator on a clean tree
144 produces an identical header and an empty diff. Without the pinned
145 timestamps the baked fixture would churn on every regeneration.
146
147 Two entries are ZIP_STORED rather than deflated. `mimetype` must be stored
148 first and uncompressed per the EPUB spec, and the 1x1 cover PNG is stored
149 because deflating an already-compressed PNG would only grow it.
150
151 Returns:
152 The complete .epub archive as bytes.
153 """
154 out = io.BytesIO()
155 fixed = (2026, 1, 1, 0, 0, 0)
156
157 def add(zf: zipfile.ZipFile, name: str, data: bytes | str, store: bool = False) -> None:
158 info = zipfile.ZipInfo(name, date_time=fixed)
159 info.compress_type = zipfile.ZIP_STORED if store else zipfile.ZIP_DEFLATED
160 info.external_attr = 0o600 << 16
161 zf.writestr(info, data)
162
163 with zipfile.ZipFile(out, "w") as zf:
164 add(zf, "mimetype", b"application/epub+zip", store=True)
165 add(zf, "META-INF/container.xml", CONTAINER_XML.encode("ascii"))
166 add(zf, "OEBPS/content.opf", build_opf().encode("ascii"))
167 add(zf, "OEBPS/toc.ncx", build_ncx().encode("ascii"))
168 add(zf, "OEBPS/cover.png", COVER_PNG, store=True)
169 for i in range(N_CHAPTERS):
170 body = (
171 f'<?xml version="1.0"?><html><body><h1>Chapter {i + 1}</h1>'
172 f"<p>Body text for chapter {i + 1}.</p></body></html>"
173 )
174 add(zf, f"OEBPS/ch{i}.xhtml", body.encode("ascii"))
175 for i in range(N_RESOURCES):
176 add(zf, f"OEBPS/res{i}.css", f".c{i} {{ margin: 0; }}\n".encode("ascii"))
177 return out.getvalue()
178
179
180def bake_header(epub: bytes) -> str:
181 """Render the EPUB bytes as a C header with a `static const uint8_t` table.
182
183 The array is sized by a generated `enum : size_t` rather than a `[]`, so the
184 firmware gets the length as a compile-time constant and the two can never
185 disagree.
186
187 The byte table is wrapped in `clang-format off`/`on`. That is load-bearing,
188 not cosmetic: the formatter would otherwise reflow tens of thousands of
189 bytes into its own line width and the generator would no longer own the
190 file's shape, making every regeneration a large spurious diff.
191
192 Args:
193 epub: The archive bytes to bake, emitted 16 per row as `0xNN`.
194
195 Returns:
196 The complete header source, pure 7-bit ASCII.
197 """
198 rows = []
199 for i in range(0, len(epub), 16):
200 chunk = epub[i : i + 16]
201 rows.append(" " + ", ".join(f"0x{b:02X}" for b in chunk) + ",")
202 body = "\n".join(rows)
203 return (
204 "/**\n"
205 " * @file epub_stress_fixture.h\n"
206 " * @brief Baked synthetic large-structure EPUB3 for the #144 pool-stress gate.\n"
207 " *\n"
208 f" * @details {N_CHAPTERS} chapters + {N_RESOURCES} manifest resources + an NCX\n"
209 f" * with {N_CHAPTERS} navPoints + a cover -- ~{N_CHAPTERS + N_RESOURCES + 5} archive\n"
210 " * entries / a large OPF, exercising bounded ZIP/XML resources. Pure ASCII.\n"
211 " *\n"
212 " * @generated by examples/ek_ra8d2/hw_pending/epub_stress/scripts/"
213 "make_stress_fixture.py -- do not edit by hand.\n"
214 " * The generator owns\n"
215 " * this file's length (a baked byte table).\n"
216 " *\n"
217 " * @copyright Copyright (c) 2026 Brighton Sikarskie\n"
218 " * SPDX-License-Identifier: MIT\n"
219 " */\n"
220 "#pragma once\n"
221 "\n"
222 "#include <stddef.h>\n"
223 "#include <stdint.h>\n"
224 "\n"
225 "/** @brief Length of the baked stress EPUB blob, bytes. */\n"
226 f"enum : size_t {{ k_epub_stress_fixture_len = {len(epub)}U "
227 "/**< EPUB stress fixture length. */ };\n"
228 "\n"
229 "/** @brief Baked synthetic large-structure EPUB3 byte stream. */\n"
230 "/* Suppression rationale: generated bytes stay at 16 per row for stable diffs. */\n"
231 "/* clang-format off */\n"
232 "static const uint8_t k_epub_stress_fixture[k_epub_stress_fixture_len] = {\n"
233 f"{body}\n"
234 "};\n"
235 "/* clang-format on */\n"
236 )
237
238
239def main() -> int:
240 """Regenerate the owning component's inc/epub_stress_fixture.h.
241
242 Writes with `encoding="ascii"`, so any non-ASCII that crept into the
243 generated text raises here rather than producing a file the encoding gate
244 would later reject.
245 """
246 epub = build_epub()
247 output = Path(__file__).resolve().parent.parent / "inc" / "epub_stress_fixture.h"
248 with output.open("w", encoding="ascii") as f:
249 f.write(bake_header(epub))
250 print(f"wrote {output} ({len(epub)} epub bytes)")
251
252
253if __name__ == "__main__":
254 main()
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298