ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
unarch_gzip.c
Go to the documentation of this file.
1
23#include "unarch_gzip.h"
24
25#include <string.h>
26
27#include "miniz.h"
28#include "ra8_attributes.h"
29#include "ra8_check.h"
30
31/* The gzip member + windowed-inflate state machines are dense sequential
32 * validators: many fail-closed guards and a bounded refill loop push several
33 * bodies past clang-tidy's statement/nesting/cognitive thresholds while each
34 * stays within the 60-line NASA Rule 4 gate. Same disposition as the other
35 * decode state machines in the tree (ra8_jpeg_sw_encode, ra8_rmac_phy). */
36
66
78typedef struct {
80 void* ctx;
81 uint64_t size;
82 uint64_t pos;
83 uint32_t crc;
84 bool track_crc;
85} gz_src_t;
86
88static tinfl_decompressor s_gz_inflator;
89
108static ra8_err_t internal_src_take(gz_src_t* s, uint8_t* dst, size_t n)
109{
110 if ((uint64_t)n > (s->size - s->pos)) {
111 return k_ra8_err_validation_failed; /* runs past the member end */
112 }
113 const size_t got = s->read(s->ctx, s->pos, dst, n);
114 if (got != n) {
115 return k_ra8_err_validation_failed; /* backing truncated */
116 }
117 if (s->track_crc) {
118 s->crc = (uint32_t)mz_crc32(s->crc, dst, n);
119 }
120 s->pos += (uint64_t)n;
121 return k_ra8_ok;
122}
123
142{
143 for (uint32_t i = 0U; i < (uint32_t)k_unarch_gzip_str_max; ++i) { /* bound: string cap */
144 uint8_t b = 0U;
145 const ra8_err_t terr = internal_src_take(s, &b, 1U);
146 if (terr != k_ra8_ok) {
147 return terr;
148 }
149 if (b == 0U) {
150 return k_ra8_ok;
151 }
152 }
153 return k_ra8_err_validation_failed; /* string exceeds the fail-closed cap */
154}
155
174{
175 uint8_t xl[k_gz_xlen_bytes] = {};
176 const ra8_err_t xerr = internal_src_take(s, xl, sizeof(xl));
177 if (xerr != k_ra8_ok) {
178 return xerr;
179 }
180 uint32_t xlen = (uint32_t)xl[0] | ((uint32_t)xl[1] << (uint32_t)k_gz_shift_byte);
181 uint8_t skip[k_gz_in_chunk];
182 while (xlen > 0U) { /* bound: xlen <= 65535, consumed in chunks */
183 const size_t n = (xlen > (uint32_t)sizeof(skip)) ? sizeof(skip) : (size_t)xlen;
184 const ra8_err_t serr = internal_src_take(s, skip, n);
185 if (serr != k_ra8_ok) {
186 return serr;
187 }
188 xlen -= (uint32_t)n;
189 }
190 return k_ra8_ok;
191}
192
214// NOLINTNEXTLINE(readability-function-size) -- wire-order guards keep cursor and header CRC mutations auditable.
216{
217 uint8_t fixed[k_gz_hdr_fixed] = {};
218 const ra8_err_t ferr = internal_src_take(s, fixed, sizeof(fixed));
219 if (ferr != k_ra8_ok) {
220 return ferr;
221 }
222 if (fixed[k_gz_idx_id1] != (uint8_t)k_gz_id1) {
224 }
225 if (fixed[k_gz_idx_id2] != (uint8_t)k_gz_id2) {
227 }
228 if (fixed[k_gz_idx_cm] != (uint8_t)k_gz_cm_deflate) {
229 return k_ra8_err_not_supported; /* only DEFLATE is defined */
230 }
231 const uint8_t flg = fixed[k_gz_idx_flg];
232 if ((flg & (uint8_t)k_gz_flg_reserved) != 0U) {
233 return k_ra8_err_not_supported; /* reserved bits: unknown format */
234 }
235 if ((flg & (uint8_t)k_gz_flg_fextra) != 0U) {
236 const ra8_err_t xerr = internal_skip_fextra(s);
237 if (xerr != k_ra8_ok) {
238 return xerr;
239 }
240 }
241 if ((flg & (uint8_t)k_gz_flg_fname) != 0U) {
242 const ra8_err_t nerr = internal_skip_string(s);
243 if (nerr != k_ra8_ok) {
244 return nerr;
245 }
246 }
247 if ((flg & (uint8_t)k_gz_flg_fcomment) != 0U) {
248 const ra8_err_t cerr = internal_skip_string(s);
249 if (cerr != k_ra8_ok) {
250 return cerr;
251 }
252 }
253 if ((flg & (uint8_t)k_gz_flg_fhcrc) != 0U) {
254 const uint32_t want = s->crc & (uint32_t)k_gz_crc16_mask;
255 uint8_t hc[k_gz_fhcrc_bytes] = {};
256 const ra8_err_t herr = internal_src_take(s, hc, sizeof(hc));
257 if (herr != k_ra8_ok) {
258 return herr;
259 }
260 const uint32_t stored = (uint32_t)hc[0] | ((uint32_t)hc[1] << (uint32_t)k_gz_shift_byte);
261 if (stored != want) {
263 }
264 }
265 s->track_crc = false; /* header ends here; the payload CRC is separate */
266 return k_ra8_ok;
267}
268
279typedef struct {
281 uint8_t* out;
282 size_t out_cap;
283 size_t total;
284 uint32_t crc;
287 size_t win_len;
288 size_t win_ofs;
289 bool done;
291
313// NOLINTNEXTLINE(readability-function-size) -- atomic miniz cursor and budget transaction.
315{
317 if (ierr != k_ra8_ok) {
318 return ierr;
319 }
320 if (st->win_ofs == st->win_len) {
321 const uint64_t remain = st->src->size - st->src->pos;
322 const size_t want = (remain > (uint64_t)sizeof(st->win)) ? sizeof(st->win) : (size_t)remain;
323 if (want == 0U) {
324 return k_ra8_err_validation_failed; /* input exhausted mid-stream */
325 }
326 const ra8_err_t rerr = internal_src_take(st->src, st->win, want);
327 if (rerr != k_ra8_ok) {
328 return rerr;
329 }
330 st->win_len = want;
331 st->win_ofs = 0U;
332 }
333 const bool more_input = (st->src->pos < st->src->size);
334 size_t in_bytes = st->win_len - st->win_ofs;
335 const size_t space = st->out_cap - st->total;
336 size_t out_bytes = (space > (size_t)k_gz_out_window) ? (size_t)k_gz_out_window : space;
337 const mz_uint32 flags = (mz_uint32)TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF |
338 (more_input ? (mz_uint32)TINFL_FLAG_HAS_MORE_INPUT : 0U);
339 const tinfl_status ts = tinfl_decompress(&s_gz_inflator,
340 &st->win[st->win_ofs],
341 &in_bytes,
342 st->out,
343 &st->out[st->total],
344 &out_bytes,
345 flags);
346 st->win_ofs += in_bytes;
347 if (out_bytes > 0U) {
348 st->crc = (uint32_t)mz_crc32(st->crc, &st->out[st->total], out_bytes);
349 }
350 st->total += out_bytes;
351 const ra8_err_t oerr =
352 ra8_decomp_budget_charge_output(&st->budget, st->src->pos, (uint64_t)out_bytes);
353 if (oerr != k_ra8_ok) {
354 return oerr;
355 }
356 if (ts == TINFL_STATUS_DONE) {
357 st->done = true;
358 return k_ra8_ok;
359 }
360 if (ts == TINFL_STATUS_NEEDS_MORE_INPUT) {
361 return k_ra8_ok; /* the next pass refills */
362 }
363 if (ts == TINFL_STATUS_HAS_MORE_OUTPUT) {
364 if (st->total == st->out_cap) {
365 return k_ra8_err_no_mem; /* arena full with the stream still open */
366 }
367 return k_ra8_ok; /* output window filled; the next pass continues */
368 }
369 return k_ra8_err_validation_failed; /* corrupt DEFLATE stream */
370}
371
392{
393 const uint64_t leftover = (uint64_t)(st->win_len - st->win_ofs);
394 const uint64_t trailer_pos = st->src->pos - leftover;
395 const uint64_t remain = st->src->size - trailer_pos;
396 if (remain < (uint64_t)k_gz_trailer_len) {
397 return k_ra8_err_validation_failed; /* truncated trailer */
398 }
399 if (remain > (uint64_t)k_gz_trailer_len) {
400 return k_ra8_err_validation_failed; /* trailing bytes: no concatenation */
401 }
402 uint8_t tr[k_gz_trailer_len] = {};
403 const size_t got = st->src->read(st->src->ctx, trailer_pos, tr, sizeof(tr));
404 if (got != sizeof(tr)) {
405 return k_ra8_err_validation_failed; /* backing truncated */
406 }
407 uint32_t crc_stored = 0U;
408 uint32_t isize_stored = 0U;
409 for (uint32_t i = 0U; i < 4U; ++i) { /* bound: fixed field width */
410 crc_stored |= (uint32_t)tr[i] << ((uint32_t)k_gz_shift_byte * i);
411 isize_stored |= (uint32_t)tr[i + 4U] << ((uint32_t)k_gz_shift_byte * i);
412 }
413 if (crc_stored != st->crc) {
415 }
416 if (isize_stored != ((uint32_t)st->total & (uint32_t)k_gz_isize_mask)) {
418 }
419 return k_ra8_ok;
420}
421
422bool unarch_gzip_magic(const uint8_t* sig, size_t sig_len)
423{
424 if (sig == nullptr) {
425 return false;
426 }
427 if (sig_len < (size_t)k_unarch_gzip_sig_len) {
428 return false;
429 }
430 if (sig[k_gz_idx_id1] != (uint8_t)k_gz_id1) {
431 return false;
432 }
433 return sig[k_gz_idx_id2] == (uint8_t)k_gz_id2;
434}
435
436// NOLINTNEXTLINE(readability-function-size) -- fail-closed member transaction.
438 void* ctx,
439 uint64_t size,
440 uint8_t* out,
441 size_t out_cap,
442 const ra8_decomp_limits_t* limits,
443 size_t* out_len)
444{
445 static const char* const tag = "unarch_gzip";
446 if (read == nullptr) {
447 ra8_log_error(tag, "unwrap: null read");
448 return k_ra8_err_null_ptr;
449 }
450 RA8_CHECK_NULL_PTR(out, tag, "unwrap: null out");
451 RA8_CHECK_NULL_PTR(out_len, tag, "unwrap: null out_len");
452 *out_len = 0U;
453 if (size == 0U) {
455 }
456 if (out_cap == 0U) {
458 }
459 gz_src_t src = {
460 .read = read,
461 .ctx = ctx,
462 .size = size,
463 .pos = 0U,
464 .crc = (uint32_t)MZ_CRC32_INIT,
465 .track_crc = true,
466 };
467 const ra8_err_t herr = internal_parse_header(&src);
468 if (herr != k_ra8_ok) {
469 return herr;
470 }
471 gz_inflate_t st = {};
472 st.src = &src;
473 st.out = out;
474 st.out_cap = out_cap;
475 st.crc = (uint32_t)MZ_CRC32_INIT;
476 const ra8_err_t berr = ra8_decomp_budget_init(&st.budget, limits);
477 if (berr != k_ra8_ok) {
478 return berr;
479 }
480 tinfl_init(&s_gz_inflator);
481 while (!st.done) { /* bound: every pass charges the policy iteration budget */
482 const ra8_err_t perr = internal_inflate_pass(&st);
483 if (perr != k_ra8_ok) {
484 return perr;
485 }
486 }
487 const ra8_err_t terr = internal_verify_trailer(&st);
488 if (terr != k_ra8_ok) {
489 return terr;
490 }
491 *out_len = st.total;
492 return k_ra8_ok;
493}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
ra8_err_t ra8_decomp_budget_charge_output(ra8_decomp_budget_t *b, uint64_t in_total, uint64_t out_delta)
Charge decompressed output against the cap and ratio bounds.
ra8_err_t ra8_decomp_budget_charge_iter(ra8_decomp_budget_t *b)
Charge one decode-loop turn against the iteration budget.
ra8_err_t ra8_decomp_budget_init(ra8_decomp_budget_t *b, const ra8_decomp_limits_t *limits)
Bind a budget to a policy (or the default policy) and zero it.
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_checksum_mismatch
Stored / transmitted checksum does not match computed value.
Definition ra8_err.h:465
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ 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_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
#define ra8_log_error(tag, message)
RA8 log error.
Definition ra8_log.h:335
Loop-carried state of the windowed DEFLATE inflation.
uint8_t * out
Destination arena.
bool done
tinfl reported stream end.
ra8_decomp_budget_t budget
Unified decompression budget.
uint8_t win[k_gz_in_chunk]
Input window.
size_t total
Payload bytes produced so far.
size_t out_cap
Arena capacity.
size_t win_len
Valid bytes in the window.
size_t win_ofs
Consumed bytes in the window.
gz_src_t * src
Byte source (cursor at the window end).
uint32_t crc
Running CRC32 of the produced payload.
Cursor-tracked exact-read byte source with a running header CRC.
Definition unarch_gzip.c:78
uint64_t pos
Next unconsumed byte offset.
Definition unarch_gzip.c:82
bool track_crc
Fold consumed bytes into crc.
Definition unarch_gzip.c:84
unarch_read_fn read
Byte reader over the member.
Definition unarch_gzip.c:79
uint64_t size
Member length in bytes.
Definition unarch_gzip.c:81
uint32_t crc
Running CRC32 of consumed bytes.
Definition unarch_gzip.c:83
void * ctx
Context for read.
Definition unarch_gzip.c:80
Running consumption tracker charged by a decoder against its policy.
One decompression policy: the five resource bounds decoders enforce.
ra8_err_t unarch_gzip_unwrap(unarch_read_fn read, void *ctx, uint64_t size, uint8_t *out, size_t out_cap, const ra8_decomp_limits_t *limits, size_t *out_len)
Decode one whole gzip member from a read seam into a caller arena.
static ra8_err_t internal_skip_string(gz_src_t *s)
Skip a NUL-terminated header string (FNAME / FCOMMENT), bounded.
static ra8_err_t internal_parse_header(gz_src_t *s)
Parse the whole RFC 1952 header, leaving the cursor at the data.
static ra8_err_t internal_verify_trailer(const gz_inflate_t *st)
Verify the gzip trailer against the produced payload.
gz_grammar_t
RFC 1952 constants and this decoder's loop windows.
Definition unarch_gzip.c:42
@ k_gz_trailer_len
CRC32 + ISIZE trailer length.
Definition unarch_gzip.c:54
@ k_gz_idx_flg
Header index of FLG.
Definition unarch_gzip.c:58
@ k_gz_cm_deflate
The only defined compression method.
Definition unarch_gzip.c:45
@ k_gz_flg_fhcrc
Header CRC16 present.
Definition unarch_gzip.c:46
@ k_gz_idx_id1
Header index of ID1.
Definition unarch_gzip.c:55
@ k_gz_isize_mask
ISIZE is the payload length mod 2^32.
Definition unarch_gzip.c:64
@ k_gz_idx_cm
Header index of CM.
Definition unarch_gzip.c:57
@ k_gz_hdr_fixed
Fixed header length in bytes.
Definition unarch_gzip.c:51
@ k_gz_xlen_bytes
FEXTRA length-field size.
Definition unarch_gzip.c:52
@ k_gz_id1
Magic byte 0.
Definition unarch_gzip.c:43
@ k_gz_shift_byte
Bits per byte for LE assembly.
Definition unarch_gzip.c:62
@ k_gz_fhcrc_bytes
FHCRC field size.
Definition unarch_gzip.c:53
@ k_gz_flg_fcomment
Comment present.
Definition unarch_gzip.c:49
@ k_gz_in_chunk
Input refill window per pass.
Definition unarch_gzip.c:59
@ k_gz_flg_fextra
Extra field present.
Definition unarch_gzip.c:47
@ k_gz_out_window
Output window per pass (charge granularity for bomb detection).
Definition unarch_gzip.c:60
@ k_gz_flg_reserved
Reserved bits: must be zero.
Definition unarch_gzip.c:50
@ k_gz_crc16_mask
FHCRC is the low 16 CRC32 bits.
Definition unarch_gzip.c:63
@ k_gz_flg_fname
Original file name present.
Definition unarch_gzip.c:48
@ k_gz_idx_id2
Header index of ID2.
Definition unarch_gzip.c:56
@ k_gz_id2
Magic byte 1.
Definition unarch_gzip.c:44
static ra8_err_t internal_skip_fextra(gz_src_t *s)
Skip the FEXTRA subfield block (RFC 1952 XLEN + XLEN bytes), bounded.
static ra8_err_t internal_inflate_pass(gz_inflate_t *st)
Run one refill + inflate pass of the DEFLATE loop.
static ra8_err_t internal_src_take(gz_src_t *s, uint8_t *dst, size_t n)
Consume exactly n bytes from the source into dst.
static tinfl_decompressor s_gz_inflator
The single-client tinfl state (too large for a task stack).
Definition unarch_gzip.c:88
bool unarch_gzip_magic(const uint8_t *sig, size_t sig_len)
Whether sig begins with the gzip member magic (1F 8B).
Clean-room gzip member decoder (RFC 1952) over the miniz DEFLATE core.
@ k_unarch_gzip_str_max
Max FNAME / FCOMMENT bytes accepted.
Definition unarch_gzip.h:68
@ k_unarch_gzip_sig_len
Magic length (1F 8B).
Definition unarch_gzip.h:67
size_t(* unarch_read_fn)(void *ctx, uint64_t offset, void *buf, size_t len)
Seek+read backing over an archive's bytes.
Definition unarch_io.h:55