ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
unarch_tar.c
Go to the documentation of this file.
1
24#include "unarch_tar.h"
25
26#include <string.h>
27
28#include "ra8_attributes.h"
29#include "ra8_check.h"
30#include "unarch_tar_internal.h"
31
33static const char* const s_tag_tar = "unarch_tar";
34
35/* The streaming ustar/pax/GNU walker is a dense sequential validator (per-block
36 * type dispatch, meta-prelude handling, bounded reads): the walk and read
37 * bodies clear clang-tidy's statement/nesting/cognitive thresholds while each
38 * stays within the 60-line NASA Rule 4 gate. Same disposition as the other
39 * decode state machines in the tree (ra8_jpeg_sw_encode, ra8_rmac_phy). */
40
51typedef struct {
52 char* name_buf;
53 uint16_t name_cap;
54 uint16_t name_len;
55 bool have_path;
56 uint64_t size_ovr;
57 bool have_size;
59
60bool unarch_tar_probe(const uint8_t* block, size_t len)
61{
62 if (block == nullptr) {
63 return false;
64 }
65 if (len < (size_t)k_unarch_tar_block) {
66 return false;
67 }
68 if (!priv_unarch_tar_magic_ok(block)) {
69 return false;
70 }
71 return priv_unarch_tar_checksum_ok(block);
72}
73
75 unarch_read_fn read,
76 void* ctx,
77 uint64_t size,
78 const ra8_decomp_limits_t* limits)
79{
80 RA8_CHECK_NULL_PTR(t, s_tag_tar, "open: null t");
81 *t = (unarch_tar_t){};
82 if (read == nullptr) {
83 ra8_log_error(s_tag_tar, "open: null read");
84 return k_ra8_err_null_ptr;
85 }
86 if (size < (uint64_t)k_unarch_tar_block) {
88 }
89 const ra8_err_t berr = ra8_decomp_budget_init(&t->budget, limits);
90 if (berr != k_ra8_ok) {
91 return berr;
92 }
93 uint8_t block[k_unarch_tar_block] = {};
94 const size_t got = read(ctx, 0U, block, sizeof(block));
95 if (got != sizeof(block)) {
97 }
98 if (!unarch_tar_probe(block, sizeof(block))) {
100 }
101 t->read = read;
102 t->ctx = ctx;
103 t->size = size;
104 t->live = true;
105 return k_ra8_ok;
106}
107
126static ra8_err_t internal_read_block(const unarch_tar_t* t, uint64_t off, uint8_t* block)
127{
128 const uint64_t need = (uint64_t)k_unarch_tar_block;
129 if (off > (t->size - need)) {
130 return k_ra8_err_validation_failed; /* partial trailing block */
131 }
132 const size_t got = t->read(t->ctx, off, block, (size_t)need);
133 if ((uint64_t)got != need) {
134 return k_ra8_err_validation_failed; /* backing truncated */
135 }
136 return k_ra8_ok;
137}
138
158static ra8_err_t internal_next_off(uint64_t off, uint64_t dsize, uint64_t* next)
159{
160 const uint64_t blk = (uint64_t)k_unarch_tar_block;
161 if (dsize > (UINT64_MAX - (blk - 1U))) {
162 return k_ra8_err_validation_failed; /* rounding would wrap */
163 }
164 const uint64_t blocks = (dsize + (blk - 1U)) / blk;
165 if (blocks > ((UINT64_MAX / blk) - 1U)) {
166 return k_ra8_err_validation_failed; /* span would wrap */
167 }
168 const uint64_t span = (blocks + 1U) * blk;
169 if (off > (UINT64_MAX - span)) {
170 return k_ra8_err_validation_failed; /* next offset would wrap */
171 }
172 *next = off + span;
173 return k_ra8_ok;
174}
175
202 ra8_tar_type_t type,
203 uint64_t doff,
204 uint64_t dsize,
205 tar_meta_t* meta)
206{
207 static uint8_t s_tar_meta_scratch[k_unarch_tar_pax_max];
208 if (dsize > (uint64_t)k_unarch_tar_pax_max) {
209 return k_ra8_err_validation_failed; /* meta flood: reject the member */
210 }
211 uint8_t* const data = s_tar_meta_scratch;
212 const size_t got = t->read(t->ctx, doff, data, (size_t)dsize);
213 if ((uint64_t)got != dsize) {
214 return k_ra8_err_validation_failed; /* backing truncated */
215 }
216 if (type == k_ra8_tar_type_pax) {
217 return priv_unarch_tar_pax_parse(data,
218 (size_t)dsize,
219 meta->name_buf,
220 meta->name_cap,
221 &meta->name_len,
222 &meta->have_path,
223 &meta->size_ovr,
224 &meta->have_size);
225 }
226 /* GNU longname: the data is the member name, usually NUL-padded. */
227 size_t nlen = (size_t)dsize;
228 while (nlen > 0U) { /* bound: dsize */
229 if (data[nlen - 1U] != 0U) {
230 break;
231 }
232 nlen -= 1U;
233 }
234 const size_t want = (nlen > (size_t)meta->name_cap) ? (size_t)meta->name_cap : nlen;
235 if (want > 0U) {
236 (void)memcpy(meta->name_buf, data, want);
237 }
238 meta->name_len = (uint16_t)want;
239 meta->have_path = true;
240 return k_ra8_ok;
241}
242
259static size_t internal_field_length(const uint8_t* block, uint32_t offset, uint32_t capacity)
260{
261 size_t length = 0U;
262 while ((length < (size_t)capacity) && (block[offset + length] != 0U)) {
263 length++;
264 }
265 return length;
266}
267
282static void internal_ustar_name(const uint8_t* block, tar_meta_t* meta)
283{
284 const size_t n_len =
286 const size_t p_len =
288 size_t used = 0U;
289 if (p_len > 0U) {
290 const size_t pw = (p_len > (size_t)meta->name_cap) ? (size_t)meta->name_cap : p_len;
291 if (pw > 0U) {
292 (void)memcpy(meta->name_buf, &block[k_ra8_tar_off_prefix], pw);
293 }
294 used = pw;
295 if (used < (size_t)meta->name_cap) {
296 meta->name_buf[used] = '/';
297 used += 1U;
298 }
299 }
300 const size_t room = (size_t)meta->name_cap - used;
301 const size_t nw = (n_len > room) ? room : n_len;
302 if (nw > 0U) {
303 (void)memcpy(&meta->name_buf[used], &block[k_ra8_tar_off_name], nw);
304 }
305 meta->name_len = (uint16_t)(used + nw);
306}
307
329static ra8_err_t internal_decode_header(const uint8_t* block,
330 uint64_t off,
331 ra8_tar_type_t* type,
332 uint64_t* dsize,
333 uint64_t* next)
334{
335 if (!priv_unarch_tar_checksum_ok(block)) {
337 }
338 if (!priv_unarch_tar_magic_ok(block)) {
340 }
341 const ra8_err_t serr =
343 if (serr != k_ra8_ok) {
344 return serr;
345 }
347 return internal_next_off(off, *dsize, next);
348}
349
379 const uint8_t* block,
380 uint64_t off,
381 ra8_tar_type_t type,
382 uint64_t dsize,
383 tar_meta_t* meta,
385{
386 uint64_t msize = dsize;
387 if (meta->have_size) {
388 msize = meta->size_ovr; /* pax size record overrides the header field */
389 }
390 uint64_t next = 0U;
391 const ra8_err_t aerr = internal_next_off(off, msize, &next);
392 if (aerr != k_ra8_ok) {
393 return aerr;
394 }
395 const uint64_t doff = off + (uint64_t)k_unarch_tar_block;
396 if (msize > (t->size - doff)) {
397 return k_ra8_err_validation_failed; /* lying size: data overruns archive */
398 }
399 const ra8_err_t derr = ra8_decomp_check_declared(&t->budget.limits, msize, msize);
400 if (derr != k_ra8_ok) {
401 return derr;
402 }
404 if (eerr != k_ra8_ok) {
405 return eerr;
406 }
407 if (!meta->have_path) {
408 internal_ustar_name(block, meta);
409 }
410 out->data_off = doff;
411 out->size = msize;
412 out->next_off = next;
413 out->name_len = meta->name_len;
414 out->is_file = (type == k_ra8_tar_type_file) ? 1U : 0U;
415 out->is_dir = (type == k_ra8_tar_type_dir) ? 1U : 0U;
416 return k_ra8_ok;
417}
418// NOLINTNEXTLINE(readability-function-size,readability-function-cognitive-complexity) -- one ustar header record is parsed field-by-field in declaration order; splitting it would scatter the bounds checks away from the fields they guard.
420 uint64_t off,
421 char* name_buf,
422 uint16_t name_cap,
424{
425 RA8_CHECK_NULL_PTR(t, s_tag_tar, "next: null t");
426 RA8_CHECK_NULL_PTR(out, s_tag_tar, "next: null out");
427 *out = (unarch_tar_entry_t){};
428 if (!t->live) {
430 }
431 if (name_cap > 0U) {
432 RA8_CHECK_NULL_PTR(name_buf, s_tag_tar, "next: null name_buf");
433 }
434 tar_meta_t meta = {.name_buf = name_buf, .name_cap = name_cap};
435 uint64_t cur = off;
436 for (uint32_t m = 0U; m <= (uint32_t)k_unarch_tar_meta_max; ++m) { /* bound: meta cap */
438 if (ierr != k_ra8_ok) {
439 return ierr;
440 }
441 if (cur >= t->size) {
442 return k_ra8_err_not_found; /* clean EOF without an end marker */
443 }
444 uint8_t block[k_unarch_tar_block] = {};
445 const ra8_err_t rerr = internal_read_block(t, cur, block);
446 if (rerr != k_ra8_ok) {
447 return rerr;
448 }
449 if (priv_unarch_tar_block_zero(block)) {
450 return k_ra8_err_not_found; /* end-of-archive marker */
451 }
453 uint64_t dsize = 0U;
454 uint64_t next = 0U;
455 const ra8_err_t herr = internal_decode_header(block, cur, &type, &dsize, &next);
456 if (herr != k_ra8_ok) {
457 return herr;
458 }
459 if ((type == k_ra8_tar_type_pax) || (type == k_ra8_tar_type_longname)) {
460 const uint64_t doff = cur + (uint64_t)k_unarch_tar_block;
461 if (dsize > (t->size - doff)) {
462 return k_ra8_err_validation_failed; /* meta data overruns archive */
463 }
464 const ra8_err_t merr = internal_meta_consume(t, type, doff, dsize, &meta);
465 if (merr != k_ra8_ok) {
466 return merr;
467 }
468 cur = next;
469 continue;
470 }
471 if (type == k_ra8_tar_type_meta) {
472 cur = next; /* skipped meta ('g' global, 'K' longlink) */
473 continue;
474 }
475 const ra8_err_t ferr = internal_finish_member(t, block, cur, type, dsize, &meta, out);
476 if (ferr != k_ra8_ok) {
477 *out = (unarch_tar_entry_t){};
478 return ferr;
479 }
480 return k_ra8_ok;
481 }
482 return k_ra8_err_validation_failed; /* meta prelude longer than the cap */
483}
484
485// NOLINTNEXTLINE(readability-function-size) -- contract macros expand the linear read body.
487 const unarch_tar_entry_t* ent,
488 uint8_t* buf,
489 size_t cap,
490 size_t* got)
491{
492 RA8_CHECK_NULL_PTR(t, s_tag_tar, "read: null t");
493 RA8_CHECK_NULL_PTR(ent, s_tag_tar, "read: null ent");
494 RA8_CHECK_NULL_PTR(buf, s_tag_tar, "read: null buf");
495 RA8_CHECK_NULL_PTR(got, s_tag_tar, "read: null got");
496 *got = 0U;
497 if (!t->live) {
499 }
500 if (ent->is_file == 0U) {
502 }
503 if ((uint64_t)cap < ent->size) {
504 return k_ra8_err_no_mem;
505 }
506 if (ent->data_off > t->size) {
507 return k_ra8_err_invalid_size; /* forged entry: data area outside archive */
508 }
509 if (ent->size > (t->size - ent->data_off)) {
510 return k_ra8_err_invalid_size; /* forged entry: data area overruns archive */
511 }
512 if (ent->size == 0U) {
513 return k_ra8_ok; /* empty member: nothing to fetch */
514 }
515 const size_t n = t->read(t->ctx, ent->data_off, buf, (size_t)ent->size);
516 if ((uint64_t)n != ent->size) {
517 return k_ra8_err_invalid_size; /* backing truncated */
518 }
519 *got = n;
520 return k_ra8_ok;
521}
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_check_declared(const ra8_decomp_limits_t *limits, uint64_t comp_size, uint64_t out_size)
Header-level check of a member's declared sizes against a policy.
ra8_err_t ra8_decomp_budget_charge_entry(ra8_decomp_budget_t *b)
Charge one enumerated archive entry against the entry cap.
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_invalid_state
Module in wrong state for requested operation.
Definition ra8_err.h:161
@ 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_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
@ 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
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
#define ra8_log_error(tag, message)
RA8 log error.
Definition ra8_log.h:335
ra8_decomp_limits_t limits
Policy in force for this budget.
One decompression policy: the five resource bounds decoders enforce.
Overrides accumulated over one member's pax / GNU meta prelude.
Definition unarch_tar.c:51
bool have_size
A pax size override was applied.
Definition unarch_tar.c:57
char * name_buf
Caller name buffer (clamp target).
Definition unarch_tar.c:52
uint64_t size_ovr
pax size override value.
Definition unarch_tar.c:56
uint16_t name_len
Name bytes established so far.
Definition unarch_tar.c:54
uint16_t name_cap
Caller name buffer capacity.
Definition unarch_tar.c:53
bool have_path
A pax path / GNU longname was applied.
Definition unarch_tar.c:55
One decoded tar member: a file, a directory, or a skipped block.
Definition unarch_tar.h:122
uint64_t data_off
Absolute offset of the member's data area.
Definition unarch_tar.h:123
uint64_t size
Member data length in bytes (stored verbatim).
Definition unarch_tar.h:124
uint64_t next_off
Absolute offset of the next member's first block.
Definition unarch_tar.h:125
uint8_t is_file
1 if this member is a regular file.
Definition unarch_tar.h:127
uint16_t name_len
Name bytes copied into the caller buffer (clamped).
Definition unarch_tar.h:126
uint8_t is_dir
1 if this member is a directory.
Definition unarch_tar.h:128
One open tar archive: the backing plus the walk's running budget.
Definition unarch_tar.h:97
void * ctx
Context for read.
Definition unarch_tar.h:99
uint64_t size
Archive length in bytes.
Definition unarch_tar.h:100
bool live
Open succeeded and not superseded.
Definition unarch_tar.h:102
unarch_read_fn read
Byte reader over the archive.
Definition unarch_tar.h:98
ra8_decomp_budget_t budget
Entry / iteration budget for the walk.
Definition unarch_tar.h:101
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
ra8_err_t unarch_tar_open(unarch_tar_t *t, unarch_read_fn read, void *ctx, uint64_t size, const ra8_decomp_limits_t *limits)
Bind a walker to an archive and validate its first header block.
Definition unarch_tar.c:74
ra8_err_t unarch_tar_next(unarch_tar_t *t, uint64_t off, char *name_buf, uint16_t name_cap, unarch_tar_entry_t *out)
Decode the member at off and advance to the next member.
Definition unarch_tar.c:419
static const char *const s_tag_tar
Log tag for tar-walker diagnostics.
Definition unarch_tar.c:33
static ra8_err_t internal_next_off(uint64_t off, uint64_t dsize, uint64_t *next)
Compute the offset of the block after a member, overflow-checked.
Definition unarch_tar.c:158
static void internal_ustar_name(const uint8_t *block, tar_meta_t *meta)
Assemble a ustar member name (prefix + '/' + name), clamped.
Definition unarch_tar.c:282
static ra8_err_t internal_read_block(const unarch_tar_t *t, uint64_t off, uint8_t *block)
Read one full header block at off, bounds-checked.
Definition unarch_tar.c:126
ra8_err_t unarch_tar_read(const unarch_tar_t *t, const unarch_tar_entry_t *ent, uint8_t *buf, size_t cap, size_t *got)
Copy a file member's data area into the caller buffer.
Definition unarch_tar.c:486
static size_t internal_field_length(const uint8_t *block, uint32_t offset, uint32_t capacity)
Measure one NUL-terminated-or-full ustar header field.
Definition unarch_tar.c:259
bool unarch_tar_probe(const uint8_t *block, size_t len)
Whether a leading header block looks like a tar archive.
Definition unarch_tar.c:60
static ra8_err_t internal_finish_member(unarch_tar_t *t, const uint8_t *block, uint64_t off, ra8_tar_type_t type, uint64_t dsize, tar_meta_t *meta, unarch_tar_entry_t *out)
Finish decoding the real member header into the caller entry.
Definition unarch_tar.c:378
static ra8_err_t internal_decode_header(const uint8_t *block, uint64_t off, ra8_tar_type_t *type, uint64_t *dsize, uint64_t *next)
Validate one header block and decode its size / type / next offset.
Definition unarch_tar.c:329
static ra8_err_t internal_meta_consume(const unarch_tar_t *t, ra8_tar_type_t type, uint64_t doff, uint64_t dsize, tar_meta_t *meta)
Consume one pax / GNU-longname meta block's data area.
Definition unarch_tar.c:201
Clean-room, read-only streaming tar walker (POSIX ustar + pax + GNU).
@ k_unarch_tar_pax_max
Max pax / longname data bytes.
Definition unarch_tar.h:79
@ k_unarch_tar_block
On-disk block / header size.
Definition unarch_tar.h:77
@ k_unarch_tar_meta_max
Max meta (x/g/L/K) blocks per member.
Definition unarch_tar.h:78
ra8_err_t priv_unarch_tar_pax_parse(const uint8_t *data, size_t len, char *name_buf, uint16_t name_cap, uint16_t *name_len, bool *have_path, uint64_t *size_ovr, bool *have_size)
Parse pax extended-header records, extracting path / size.
bool priv_unarch_tar_block_zero(const uint8_t *block)
Whether a header block is all zero bytes (end-of-archive marker).
bool priv_unarch_tar_magic_ok(const uint8_t *block)
Whether a header block carries the ustar / GNU magic.
bool priv_unarch_tar_checksum_ok(const uint8_t *block)
Verify a header block's checksum (unsigned byte sum).
ra8_err_t priv_unarch_tar_num(const uint8_t *field, size_t len, uint64_t *out)
Decode a tar numeric field (octal ASCII or GNU base-256).
ra8_tar_type_t priv_unarch_tar_classify(uint8_t typeflag)
Classify a header block's typeflag byte.
Module-private tar field/record parsers shared across the tar TUs.
@ k_ra8_tar_len_prefix
prefix field length.
@ k_ra8_tar_off_prefix
prefix field offset.
@ k_ra8_tar_len_size
size field length.
@ k_ra8_tar_off_size
size field offset.
@ k_ra8_tar_off_name
name field offset.
@ k_ra8_tar_off_type
typeflag byte offset.
@ k_ra8_tar_len_name
name field length.
ra8_tar_type_t
Normalised classification of a header block's typeflag.
@ k_ra8_tar_type_pax
pax extended header ('x').
@ k_ra8_tar_type_other
Any other member kind (skipped).
@ k_ra8_tar_type_dir
Directory ('5').
@ k_ra8_tar_type_meta
Skipped meta: 'g' global, 'K' longlink.
@ k_ra8_tar_type_file
Regular file ('0' or NUL).
@ k_ra8_tar_type_longname
GNU longname data ('L').