ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
epub_open.c
Go to the documentation of this file.
1
16
17#include <stdalign.h>
18#include <stddef.h>
19#include <stdint.h>
20#include <string.h>
21
22#include "epub.h"
23#include "epub_internal.h"
24#include "epub_miniz_alloc.h"
26#include "miniz.h"
27#include "ra8_attributes.h"
28#include "ra8_decomp_limits.h"
29#include "ra8_err.h"
30
31/* ---------------------------------------------------------------------------
32 * Internal constants -- no magic numbers.
33 * ---------------------------------------------------------------------------
34 */
35
40typedef enum : uint16_t {
46
47/* ---------------------------------------------------------------------------
48 * Private contracts for the streamed XML consumers.
49 * ---------------------------------------------------------------------------
50 */
51
52/* ---------------------------------------------------------------------------
53 * Helpers.
54 * ---------------------------------------------------------------------------
55 */
56
75static void internal_byte_copy(uint8_t* dst, const uint8_t* src, size_t n)
76{
77 for (size_t i = 0U; i < n; i++) {
78 dst[i] = src[i];
79 }
80}
81
82/* see header for full description */
83RA8_PRIV size_t priv_epub_mem_read(void* ctx, uint64_t offset, void* buf, size_t len)
84{
85 const epub_mem_media_t* const mem = (const epub_mem_media_t*)ctx;
86 if ((mem == nullptr) || (buf == nullptr) || (offset > (uint64_t)mem->size) ||
87 ((uint64_t)len > ((uint64_t)mem->size - offset))) {
88 return 0U;
89 }
90 internal_byte_copy((uint8_t*)buf, &mem->data[(size_t)offset], len);
91 return len;
92}
93
108static void internal_byte_zero(uint8_t* dst, size_t n)
109{
110 for (size_t i = 0U; i < n; i++) {
111 dst[i] = 0U;
112 }
113}
114
115RA8_PRIV void priv_epub_dirname(const char* path, char* dst, size_t cap)
116{
117 if ((dst == nullptr) || (cap == 0U)) {
118 return;
119 }
120 dst[0] = '\0';
121 if (path == nullptr) {
122 return;
123 }
124 size_t len = 0U;
125 for (size_t index = 0U; path[index] != '\0'; ++index) {
126 if (path[index] == '/') {
127 len = index + 1U;
128 }
129 }
130 if (len == 0U) {
131 return;
132 }
133 if (len >= cap) {
134 len = cap - 1U;
135 }
136 internal_byte_copy((uint8_t*)dst, (const uint8_t*)path, len);
137 dst[len] = '\0';
138}
139
162static ra8_err_t
163internal_extract(mz_zip_archive* zip, const char* name, uint8_t* buf, size_t cap, size_t* got)
164{
165 *got = 0U;
166 int32_t idx = mz_zip_reader_locate_file(zip, name, nullptr, 0U);
167 if (idx < 0) {
168 return k_ra8_err_not_found;
169 }
170 mz_zip_archive_file_stat st;
171 if (mz_zip_reader_file_stat(zip, (mz_uint)idx, &st) == MZ_FALSE) {
172 return k_ra8_err_validation_failed; /* GCOVR_EXCL_LINE -- stat cannot fail: locate found it */
173 }
174 const ra8_err_t gerr = priv_epub_zip_guard_entry(&st);
175 if (gerr != k_ra8_ok) {
176 return gerr; /* lying header / declared bomb: reject before inflation */
177 }
178 if ((size_t)st.m_uncomp_size > cap) {
179 return k_ra8_err_no_mem;
180 }
181 if (mz_zip_reader_extract_to_mem(zip, (mz_uint)idx, buf, cap, 0U) == MZ_FALSE) {
183 }
184 *got = (size_t)st.m_uncomp_size;
185 return k_ra8_ok;
186}
187
188/* The book record holds an `mz_zip_archive` inline (no heap). Verify
189 * the inline storage is large enough at compile time. */
190static_assert(k_epub_zip_archive_bytes >= sizeof(mz_zip_archive),
191 "k_epub_zip_archive_bytes too small for mz_zip_archive");
192
193/* The byte buffer is declared with `alignas(max_align_t)` in the
194 * header; verify miniz's actual alignment requirement is satisfied by
195 * that choice. Catches future miniz revisions that bump alignment to
196 * something larger than the platform's max_align_t (extremely
197 * unlikely, but the cost of the check is zero). */
198static_assert(alignof(mz_zip_archive) <= alignof(max_align_t),
199 "mz_zip_archive alignment exceeds max_align_t");
200
216static void internal_zip_destroy(mz_zip_archive* zip)
217{
218 if (zip == nullptr) {
219 return; /* GCOVR_EXCL_LINE -- callsite passes &zip_archive_storage[0], never NULL */
220 }
221 mz_zip_reader_end(zip);
222}
223
248static void internal_load_toc(mz_zip_archive* zip, epub_book_t* book, uint8_t* scratch, size_t cap)
249{
250 if (book->toc_kind == (uint8_t)k_epub_toc_none) {
251 return;
252 }
253 if (book->toc_path[0] == '\0') {
254 return;
255 }
256
257 char full_path[k_epub_max_path_len];
258 priv_epub_join_path(book->opf_dir, book->toc_path, full_path, sizeof(full_path));
259
260 size_t got = 0U;
261 ra8_err_t err = internal_extract(zip, full_path, scratch, cap, &got);
262 if (err == k_ra8_err_not_found) {
263 /* Some EPUBs store the nav/NCX href already rooted at the archive. */
264 err = internal_extract(zip, book->toc_path, scratch, cap, &got);
265 }
266 if (err != k_ra8_ok) {
267 return;
268 }
269
270 if (book->toc_kind == (uint8_t)k_epub_toc_nav) {
271 (void)priv_epub_xml_parse_nav(scratch, got, book);
272 } else {
273 (void)priv_epub_xml_parse_ncx(scratch, got, book);
274 }
275}
276
299static ra8_err_t internal_parse_archive(mz_zip_archive* zip,
300 epub_book_t* out_book,
301 uint8_t* opf_scratch,
302 size_t opf_cap)
303{
305 static const char* const s_container_path = "META-INF/container.xml";
306 /* Static rather than auto: 4 KiB on the stack would blow the
307 * NASA-rule stack-usage budget. EPUB parser is single-threaded
308 * init-context-only, so the static scratch buffer is safe. */
309 static uint8_t s_container_buf[k_epub_container_xml_buf];
310 size_t got = 0U;
311 ra8_err_t err =
312 internal_extract(zip, s_container_path, s_container_buf, sizeof(s_container_buf), &got);
313 if (err != k_ra8_ok) {
314 return err;
315 }
316
317 epub_container_result_t cres = {};
318 err = priv_epub_xml_parse_container(s_container_buf, got, &cres, &out_book->xml_workspace);
319 if (err != k_ra8_ok) {
320 return err;
321 }
322
323 size_t opf_got = 0U;
324 err = internal_extract(zip, cres.opf_path, opf_scratch, opf_cap, &opf_got);
325 if (err != k_ra8_ok) {
326 return err;
327 }
328
330 err = priv_epub_xml_parse_opf(opf_scratch, opf_got, out_book);
331 if (err != k_ra8_ok) {
332 return err;
333 }
334
335 /* Optional navigation document (NCX / nav.xhtml). Reuses opf_scratch
336 * now that the OPF has been parsed into the book. Best-effort. */
337 internal_load_toc(zip, out_book, opf_scratch, opf_cap);
338 return k_ra8_ok;
339}
340
341/* see header for full description */
343{
344 if ((zip == nullptr) || (book == nullptr)) {
345 return k_ra8_err_null_ptr;
346 }
348 &book->miniz_workspace.bytes[0],
349 sizeof(book->miniz_workspace.bytes));
350 if (err != k_ra8_ok) {
351 return err; /* GCOVR_EXCL_LINE -- embedded workspace is exact and aligned */
352 }
353 zip->m_pAlloc = epub_miniz_alloc;
354 zip->m_pFree = epub_miniz_free;
355 zip->m_pRealloc = epub_miniz_realloc;
356 zip->m_pAlloc_opaque = &book->miniz_arena;
357 return k_ra8_ok;
358}
359
360RA8_PRIV size_t priv_epub_stream_read(void* opaque, mz_uint64 file_ofs, void* buf, size_t n)
361{
362 const epub_stream_media_t* sm = (const epub_stream_media_t*)opaque;
363 if ((sm == nullptr) || (sm->read == nullptr)) {
364 return 0U;
365 }
366 if (file_ofs >= sm->size) {
367 return 0U;
368 }
369 const uint64_t avail = sm->size - file_ofs;
370 const size_t want = ((uint64_t)n > avail) ? (size_t)avail : n;
371 return sm->read(sm->ctx, (uint64_t)file_ofs, buf, want);
372}
373
375{
376 if ((zip == nullptr) || (out_book == nullptr)) {
377 return k_ra8_err_null_ptr;
378 }
379 /* Archive-level decompression-limits guard: an archive whose central
380 * directory floods the policy entry cap dies before any entry work. */
381 const ra8_err_t gerr = priv_epub_zip_guard_archive(zip);
382 if (gerr != k_ra8_ok) {
384 return gerr;
385 }
386 /* Static (file-scope) OPF scratch keeps the firmware stack frame small --
387 * the OPF blob can be tens of KiB and would otherwise blow the per-thread
388 * stack budget. Single-threaded init-context use makes the static safe. */
389 static uint8_t s_opf_buf[k_epub_opf_xml_buf];
390 ra8_err_t err = internal_parse_archive(zip, out_book, s_opf_buf, sizeof(s_opf_buf));
391 if (err != k_ra8_ok) {
393 return err;
394 }
395 out_book->zip_archive_active = 1U;
396 out_book->in_use = 1U;
397 return k_ra8_ok;
398}
399
400/* ---------------------------------------------------------------------------
401 * Public API.
402 * ---------------------------------------------------------------------------
403 */
404
405ra8_err_t epub_open(const void* media, const char* path, epub_book_t* out_book)
406{
407 (void)path;
408 if ((media == nullptr) || (out_book == nullptr)) {
409 return k_ra8_err_null_ptr;
410 }
411 const epub_mem_media_t* mem = (const epub_mem_media_t*)media;
412 if ((mem->data == nullptr) || (mem->size == 0U)) {
414 }
415
416 epub_mem_media_t preflight_mem = *mem;
417 const ra8_err_t cap_err =
418 ra8_decomp_zip_entry_preflight(priv_epub_mem_read, &preflight_mem, preflight_mem.size);
419 if (cap_err != k_ra8_ok) {
420 return cap_err;
421 }
422
423 /* Zero-init the book up front so failure paths return a clean record.
424 * This also clears `zip_archive_storage` to a known state for miniz. */
425 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
426
427 /* Place the mz_zip_archive directly in the book record's inline
428 * storage. No heap allocation -- NASA Rule 3 compliance. The byte-
429 * storage punning is intentional and documented in the header. */
430 void* const zip_storage = &out_book->zip_archive_storage[0];
431 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
432 const ra8_err_t aerr = priv_epub_set_miniz_alloc(zip, out_book);
433 if (aerr != k_ra8_ok) {
434 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
435 return aerr; /* GCOVR_EXCL_LINE -- embedded workspace is exact and aligned */
436 }
437 if (mz_zip_reader_init_mem(zip, mem->data, mem->size, 0U) == MZ_FALSE) {
439 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
441 }
442
443 const ra8_err_t err = priv_epub_finish_open(zip, out_book);
444 if (err != k_ra8_ok) {
446 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
447 return err;
448 }
449 out_book->zip_bytes = mem->data;
450 out_book->zip_size = mem->size;
451 return k_ra8_ok;
452}
453
455epub_open_streamed(const epub_stream_media_t* media, const char* path, epub_book_t* out_book)
456{
457 (void)path;
458 if ((media == nullptr) || (out_book == nullptr)) {
459 return k_ra8_err_null_ptr;
460 }
461 if ((media->read == nullptr) || (media->size == 0U)) {
463 }
464
465 const ra8_err_t cap_err = ra8_decomp_zip_entry_preflight(media->read, media->ctx, media->size);
466 if (cap_err != k_ra8_ok) {
467 return cap_err;
468 }
469
470 /* Zero-init the book up front (also clears the inline miniz storage). */
471 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
472
473 /* The book owns the media descriptor so miniz's `m_pIO_opaque` has a stable
474 * address for the book's whole lifetime -- per-entry reads (chapters, cover,
475 * resources) happen long after this call returns and dereference it. */
476 out_book->stream_media = *media;
477
478 void* const zip_storage = &out_book->zip_archive_storage[0];
479 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
480 const ra8_err_t aerr = priv_epub_set_miniz_alloc(zip, out_book);
481 if (aerr != k_ra8_ok) {
482 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
483 return aerr; /* GCOVR_EXCL_LINE -- embedded workspace is exact and aligned */
484 }
485 zip->m_pRead = priv_epub_stream_read;
486 zip->m_pIO_opaque = &out_book->stream_media;
487 /* User-read reader: reads only the ZIP tail (EOCD + central directory) now;
488 * each entry is inflated on demand later through `priv_epub_stream_read`. */
489 if (mz_zip_reader_init(zip, (mz_uint64)media->size, 0U) == MZ_FALSE) {
491 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
493 }
494
495 const ra8_err_t err = priv_epub_finish_open(zip, out_book);
496 if (err != k_ra8_ok) {
498 internal_byte_zero((uint8_t*)out_book, sizeof(*out_book));
499 return err;
500 }
501 /* Streamed books hold no resident blob -- every read goes through the callback. */
502 out_book->zip_bytes = nullptr;
503 out_book->zip_size = (size_t)media->size;
504 return k_ra8_ok;
505}
506
508{
509 if (book == nullptr) {
510 return k_ra8_err_null_ptr;
511 }
512 if (book->in_use == 0U) {
514 }
515 if (book->zip_archive_active != 0U) {
516 void* const zip_storage = &book->zip_archive_storage[0];
517 internal_zip_destroy((mz_zip_archive*)zip_storage);
518 book->zip_archive_active = 0U;
519 }
521 book->in_use = 0U;
522 book->chapter_count = 0U;
523 return k_ra8_ok;
524}
EPUB (.epub) reader and chapter iterator for ra8-firmware.
@ k_epub_zip_archive_bytes
Storage for mz_zip_archive (miniz 3.0.2 sizeof=112; margin; static_assert in .c).
Definition epub.h:95
@ k_epub_max_path_len
Max href length (incl.
Definition epub.h:89
@ k_epub_toc_nav
Parsed from an EPUB 3 nav.xhtml.
Definition epub.h:127
@ k_epub_toc_none
No table of contents parsed.
Definition epub.h:125
void priv_epub_join_path(const char *dir, const char *name, char *dst, size_t cap)
Concatenate dir + name into dst, NUL-terminated.
Test-access surface for epub internal helpers (MC/DC).
ra8_err_t priv_epub_zip_guard_entry(const mz_zip_archive_file_stat *st)
Guard one ZIP entry's declared sizes against the policy.
size_t priv_epub_mem_read(void *ctx, uint64_t offset, void *buf, size_t len)
Read one bounded span from resident EPUB media.
Definition epub_open.c:83
ra8_err_t priv_epub_zip_guard_archive(mz_zip_archive *zip)
Guard a just-opened ZIP archive against the decompression policy.
size_t priv_epub_stream_read(void *opaque, mz_uint64 file_ofs, void *buf, size_t n)
Forward one bounded miniz read to streamed EPUB media.
Definition epub_open.c:360
Caller-owned bounded-arena allocator for miniz.
void epub_miniz_free(void *opaque, void *address)
miniz-compatible free (mz_free_func).
ra8_err_t epub_miniz_arena_init(epub_miniz_arena_t *arena, void *workspace, size_t workspace_bytes)
Initialise or reset a miniz arena over caller-owned storage.
void * epub_miniz_alloc(void *opaque, size_t items, size_t size)
miniz-compatible allocator (mz_alloc_func).
void * epub_miniz_realloc(void *opaque, void *address, size_t items, size_t size)
miniz-compatible realloc (mz_realloc_func).
void epub_miniz_arena_deinit(epub_miniz_arena_t *arena)
Invalidate an arena descriptor after miniz has released its blocks.
static ra8_err_t internal_parse_archive(mz_zip_archive *zip, epub_book_t *out_book, uint8_t *opf_scratch, size_t opf_cap)
Run the metadata + spine parsers given an already-open zip.
Definition epub_open.c:299
void priv_epub_dirname(const char *path, char *dst, size_t cap)
Copy the directory prefix of an EPUB package path.
Definition epub_open.c:115
ra8_err_t priv_epub_finish_open(mz_zip_archive *zip, epub_book_t *out_book)
Finish parsing one initialized ZIP reader into an EPUB book.
Definition epub_open.c:374
epub_internal_t
Implementation-only sizing constants.
Definition epub_open.c:40
@ k_epub_container_xml_buf
Stack buffer for container.xml.
Definition epub_open.c:41
@ k_epub_opf_xml_buf
Static OPF + NCX/nav scratch (48 KiB; uint16_t-capped).
Definition epub_open.c:42
static void internal_load_toc(mz_zip_archive *zip, epub_book_t *book, uint8_t *scratch, size_t cap)
Best-effort: extract and parse the book's TOC document.
Definition epub_open.c:248
ra8_err_t epub_close(epub_book_t *book)
Close a previously opened EPUB book.
Definition epub_open.c:507
static void internal_byte_zero(uint8_t *dst, size_t n)
Bounded zero-fill used in place of memset(0).
Definition epub_open.c:108
size_t priv_epub_mem_read(void *ctx, uint64_t offset, void *buf, size_t len)
Read one bounded span from resident EPUB media.
Definition epub_open.c:83
static void internal_byte_copy(uint8_t *dst, const uint8_t *src, size_t n)
Length-checked byte copy used in place of memcpy().
Definition epub_open.c:75
ra8_err_t epub_open_streamed(const epub_stream_media_t *media, const char *path, epub_book_t *out_book)
Open an EPUB book from a seekable stream, with no whole-file residency (#151).
Definition epub_open.c:455
size_t priv_epub_stream_read(void *opaque, mz_uint64 file_ofs, void *buf, size_t n)
Forward one bounded miniz read to streamed EPUB media.
Definition epub_open.c:360
static ra8_err_t internal_extract(mz_zip_archive *zip, const char *name, uint8_t *buf, size_t cap, size_t *got)
Extract a named entry from the open zip into a stack buffer.
Definition epub_open.c:163
ra8_err_t priv_epub_set_miniz_alloc(mz_zip_archive *zip, epub_book_t *book)
Bind one ZIP reader to a book's caller-owned miniz arena.
Definition epub_open.c:342
ra8_err_t epub_open(const void *media, const char *path, epub_book_t *out_book)
Open an EPUB book from an opaque media handle.
Definition epub_open.c:405
static void internal_zip_destroy(mz_zip_archive *zip)
Tear down an in-place archive on the failure path.
Definition epub_open.c:216
ra8_err_t priv_epub_xml_parse_container(const uint8_t *xml_bytes, size_t xml_len, epub_container_result_t *out, epub_xml_workspace_t *workspace)
Parse META-INF/container.xml and copy the rootfile path out.
ra8_err_t priv_epub_xml_parse_opf(const uint8_t *xml_bytes, size_t xml_len, epub_book_t *book)
Parse the OPF document into metadata, cover and spine.
Library-private contract for the bounded EPUB XML parser.
ra8_err_t priv_epub_xml_parse_nav(const uint8_t *xml_bytes, size_t xml_len, epub_book_t *book)
Parse an EPUB 3 nav document into book->toc.
ra8_err_t priv_epub_xml_parse_ncx(const uint8_t *xml_bytes, size_t xml_len, epub_book_t *book)
Parse an EPUB 2 NCX document into book->toc.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Unified decompression-limits policy: one bound set for every decoder.
ra8_err_t ra8_decomp_zip_entry_preflight(ra8_decomp_read_fn read, void *ctx, uint64_t archive_size)
Reject an over-cap ZIP from its EOCD before directory allocation.
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
Opened EPUB book.
Definition epub.h:286
char toc_path[k_epub_max_path_len]
Nav/NCX href (rel.
Definition epub.h:348
epub_miniz_workspace_t miniz_workspace
Per-book bounded allocator workspace; never shared with another book.
Definition epub.h:309
size_t zip_size
Length of the EPUB blob.
Definition epub.h:289
uint16_t chapter_count
Spine length actually stored.
Definition epub.h:321
epub_xml_workspace_t xml_workspace
Per-book XML parser state; never shared with another book.
Definition epub.h:311
uint8_t toc_kind
epub_toc_kind_t: source of the TOC.
Definition epub.h:349
epub_miniz_arena_t miniz_arena
Per-book allocator descriptor bound through miniz's opaque pointer.
Definition epub.h:307
epub_stream_media_t stream_media
Streamed media descriptor; {} for the resident path.
Definition epub.h:318
uint8_t zip_archive_storage[k_epub_zip_archive_bytes]
Zip archive storage.
Definition epub.h:304
uint8_t zip_archive_active
1 = mz_zip_reader_init succeeded.
Definition epub.h:305
uint8_t in_use
1 = open, 0 = closed.
Definition epub.h:358
char opf_dir[k_epub_max_path_len]
Directory portion of the OPF.
Definition epub.h:345
const uint8_t * zip_bytes
Pointer to the EPUB blob.
Definition epub.h:288
Out-parameter struct returned by priv_epub_xml_parse_container().
char opf_path[k_epub_max_path_len]
Rootfile path, NUL-terminated.
In-memory EPUB media descriptor.
Definition epub.h:179
const uint8_t * data
Pointer to the EPUB byte stream.
Definition epub.h:180
size_t size
Length of the EPUB byte stream, bytes.
Definition epub.h:181
uint8_t bytes[k_epub_miniz_pool_bytes]
Allocator backing bytes.
Seekable EPUB media descriptor – opens with no whole-file residency (#151).
Definition epub.h:232
void * ctx
Opaque backing passed to read (out-lives the book).
Definition epub.h:234
uint64_t size
Total archive length in bytes (> 0).
Definition epub.h:235
epub_stream_read_fn read
Seek+read callback (non-NULL).
Definition epub.h:233