ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_verify_tarball.c
Go to the documentation of this file.
1
15
16#include <stddef.h>
17#include <string.h>
18
19#include "mdl_verify_internal.h"
20#include "miniz.h"
21#include "ra8_attributes.h"
22
34
45
52
54typedef struct {
56 uint64_t skip_bytes;
57 uint64_t total_bytes;
58 size_t block_used;
59 size_t pages;
60 size_t members;
61 uint8_t zero_blocks;
62 bool metadata;
63 bool ended;
65
67typedef struct {
68 mz_stream stream;
70 uint8_t* output;
71 uint32_t output_cap;
72 uint32_t crc;
73 uint64_t raw_bytes;
74 bool ended;
76
84internal_io_read_exact(mdl_verify_io_t* io, uint8_t* destination, size_t length)
85{
86 size_t got = 0U;
87 const ra8_err_t err = priv_mdl_verify_io_read_up_to(io, destination, length, &got);
88 if (err != k_ra8_ok) {
89 return err;
90 }
91 return (got == length) ? k_ra8_ok : k_ra8_err_validation_failed;
92}
93
100RA8_INTERNAL static bool internal_parse_octal(const uint8_t* field, size_t length, uint64_t* out)
101{
102 size_t index = 0U;
103 while ((index < length) && ((field[index] == (uint8_t)' ') || (field[index] == (uint8_t)'\0'))) {
104 ++index;
105 }
106 uint64_t value = 0U;
107 bool any = false;
108 for (; (index < length) && (field[index] != (uint8_t)'\0') && (field[index] != (uint8_t)' ');
109 ++index) {
110 if ((field[index] < (uint8_t)'0') || (field[index] > (uint8_t)'7') ||
111 (value > (UINT64_MAX >> 3U))) {
112 return false;
113 }
114 const uint64_t digit = (uint64_t)field[index] - (uint64_t)(uint8_t)'0';
115 value = (value << 3U) + digit;
116 any = true;
117 }
118 *out = value;
119 return any;
120}
121
129{
130 unsigned checksum = 0U;
131 for (size_t i = 0U; i < k_tar_block_bytes; ++i) {
132 checksum +=
133 ((i >= k_tar_checksum_offset) && (i < k_tar_checksum_end)) ? (unsigned)' ' : state->block[i];
134 }
135 uint64_t expected = 0U;
136 uint64_t size = 0U;
139 &expected) ||
140 ((uint64_t)checksum != expected) ||
142 (state->block[k_tar_type_offset] != (uint8_t)'0') ||
143 (size > UINT64_MAX - k_tar_padding_mask)) {
145 }
146 char name[k_tar_name_bytes + 1U];
147 (void)memcpy(name, state->block, k_tar_name_bytes);
148 name[k_tar_name_bytes] = '\0';
151 }
152 if (state->members >= k_verify_member_max) {
154 }
155 ++state->members;
156 state->pages += priv_mdl_verify_is_image(name) ? 1U : 0U;
157 state->metadata = state->metadata || (strcmp(name, "ComicInfo.xml") == 0);
159 return k_ra8_ok;
160}
161
176{
177 bool zero = true;
178 for (size_t i = 0U; i < k_tar_block_bytes; ++i) {
179 zero = zero && (state->block[i] == 0U);
180 }
181 state->block_used = 0U;
182 if (zero) {
183 state->zero_blocks += 1U;
184 state->ended = state->zero_blocks >= 2U;
185 return k_ra8_ok;
186 }
187 state->zero_blocks = 0U;
188 return internal_tar_member(state);
189}
190
198internal_tar_feed(mdl_tar_stream_t* state, const uint8_t* bytes, size_t length)
199{
200 if (state->total_bytes > (UINT64_MAX - length)) {
202 }
203 state->total_bytes += length;
204 size_t offset = 0U;
205 while (offset < length) {
206 if (state->ended) {
207 if (bytes[offset++] != 0U) {
209 }
210 continue;
211 }
212 if (state->skip_bytes != 0U) {
213 const uint64_t available = (uint64_t)length - (uint64_t)offset;
214 const size_t consumed =
215 (state->skip_bytes < available) ? (size_t)state->skip_bytes : (length - offset);
216 state->skip_bytes -= consumed;
217 offset += consumed;
218 continue;
219 }
220 const size_t missing = k_tar_block_bytes - state->block_used;
221 const size_t copied = ((length - offset) < missing) ? (length - offset) : missing;
222 (void)memcpy(&state->block[state->block_used], &bytes[offset], copied);
223 state->block_used += copied;
224 offset += copied;
225 if (state->block_used == k_tar_block_bytes) {
226 const ra8_err_t error = internal_tar_process_block(state);
227 if (error != k_ra8_ok) {
228 return error;
229 }
230 }
231 }
232 return k_ra8_ok;
233}
234
242 mdl_verify_report_t* report)
243{
244 if (!state->ended || (state->block_used != 0U) || (state->skip_bytes != 0U) ||
245 ((state->total_bytes % k_tar_block_bytes) != 0U) || (state->pages == 0U) ||
246 !state->metadata) {
248 }
249 report->page_count = state->pages;
250 report->member_count = state->members;
251 report->metadata_present = state->metadata;
252 return k_ra8_ok;
253}
254
256{
257 mdl_storage_t* storage = io->storage;
258 ra8_err_t error = k_ra8_ok;
259 mdl_tar_stream_t tar = {};
260 bool done = false;
261 while (!done) {
262 size_t got = 0U;
263 error = priv_mdl_verify_io_read_up_to(io, storage->io_buffer, storage->io_buffer_bytes, &got);
264 if (error != k_ra8_ok) {
265 done = true;
266 } else if (got == 0U) {
267 error = internal_tar_finish(&tar, report);
268 done = true;
269 } else {
270 error = internal_tar_feed(&tar, storage->io_buffer, got);
271 done = (error != k_ra8_ok);
272 }
273 }
274 return error;
275}
276
283RA8_INTERNAL static uint32_t internal_get_u32le(const uint8_t* bytes)
284{
285 return (uint32_t)bytes[0] | ((uint32_t)bytes[1] << k_u32_byte_one_shift) |
286 ((uint32_t)bytes[2] << k_u32_byte_two_shift) |
287 ((uint32_t)bytes[3] << k_u32_byte_three_shift);
288}
289
297internal_gzip_feed(mdl_gzip_stream_t* gzip, const uint8_t* bytes, uint32_t length)
298{
299 gzip->stream.next_in = bytes;
300 gzip->stream.avail_in = length;
301 do {
302 const mz_uint before_in = gzip->stream.avail_in;
303 gzip->stream.next_out = gzip->output;
304 gzip->stream.avail_out = gzip->output_cap;
305 const int status = mz_inflate(&gzip->stream, MZ_NO_FLUSH);
306 const uint32_t produced = gzip->output_cap - gzip->stream.avail_out;
307 if (gzip->raw_bytes > (uint64_t)UINT32_MAX - produced) {
309 }
310 if (produced != 0U) {
311 const ra8_err_t error = internal_tar_feed(&gzip->tar, gzip->output, produced);
312 if (error != k_ra8_ok) {
313 return error;
314 }
315 gzip->crc = (uint32_t)mz_crc32(gzip->crc, gzip->output, produced);
316 gzip->raw_bytes += produced;
317 }
318 if (status == MZ_STREAM_END) {
319 gzip->ended = true;
320 return (gzip->stream.avail_in == 0U) ? k_ra8_ok : k_ra8_err_validation_failed;
321 }
322 if ((status != MZ_OK) || ((before_in == gzip->stream.avail_in) && (produced == 0U))) {
324 }
325 } while ((gzip->stream.avail_in != 0U) || (gzip->stream.avail_out == 0U));
326 return k_ra8_ok;
327}
328
336{
337 const uint8_t empty_input = 0U;
338 while (!gzip->ended) {
339 /* miniz performs pointer arithmetic even for zero-byte input. */
340 const ra8_err_t error = internal_gzip_feed(gzip, &empty_input, 0U);
341 if (error != k_ra8_ok) {
342 return error;
343 }
344 if (!gzip->ended && (gzip->stream.avail_out != 0U)) {
346 }
347 }
348 return k_ra8_ok;
349}
350
357RA8_INTERNAL static bool internal_gzip_header_valid(const uint8_t* header)
358{
359 return (header[0] == k_gzip_id_one) && (header[1] == k_gzip_id_two) &&
360 (header[2] == k_gzip_method_deflate) && (header[3] == 0U);
361}
362
369RA8_INTERNAL static bool internal_gzip_trailer_valid(const uint8_t* trailer,
370 const mdl_gzip_stream_t* gzip)
371{
372 return (internal_get_u32le(trailer) == gzip->crc) &&
373 (internal_get_u32le(&trailer[k_gzip_isize_offset]) == (uint32_t)gzip->raw_bytes);
374}
375
392 mdl_verify_arena_t* arena,
393 mdl_gzip_stream_t* gzip)
394{
395 gzip->output =
396 (uint8_t*)priv_mdl_verify_workspace_take(workspace, gzip->output_cap, alignof(max_align_t));
397 arena->exhausted = gzip->output == nullptr;
398 gzip->stream.zalloc = priv_mdl_verify_arena_alloc;
400 gzip->stream.opaque = arena;
401 if ((gzip->output == nullptr) ||
402 (mz_inflateInit2(&gzip->stream, -MZ_DEFAULT_WINDOW_BITS) != MZ_OK)) {
404 }
405 return k_ra8_ok;
406}
407
425 mdl_verify_arena_t* arena,
426 mdl_gzip_stream_t* gzip,
427 uint64_t remaining)
428{
429 mdl_storage_t* storage = io->storage;
430 ra8_err_t error = k_ra8_ok;
431 uint64_t left = remaining;
432 while ((error == k_ra8_ok) && (left != 0U) && !gzip->ended) {
433 const uint32_t chunk =
434 (left < storage->io_buffer_bytes) ? (uint32_t)left : storage->io_buffer_bytes;
435 error = internal_io_read_exact(io, storage->io_buffer, chunk);
436 if (error == k_ra8_ok) {
437 left -= chunk;
438 error = internal_gzip_feed(gzip, storage->io_buffer, chunk);
439 }
440 }
441 if ((error == k_ra8_ok) && gzip->ended && (left != 0U)) {
443 }
444 if ((error == k_ra8_ok) && !gzip->ended) {
445 error = internal_gzip_finish_deflate(gzip);
446 }
447 uint8_t trailer[k_gzip_trailer_bytes];
448 if (error == k_ra8_ok) {
449 error = internal_io_read_exact(io, trailer, sizeof(trailer));
450 }
451 if ((error == k_ra8_ok) && !internal_gzip_trailer_valid(trailer, gzip)) {
453 }
454 if ((error == k_ra8_ok) && arena->exhausted) {
456 }
457 return error;
458}
459
461 mdl_export_workspace_t* workspace,
462 mdl_verify_report_t* report)
463{
464 ra8_err_t error;
465 uint8_t header[k_gzip_header_bytes];
466 error = (io->size_bytes < k_gzip_min_bytes) ? k_ra8_err_validation_failed
467 : internal_io_read_exact(io, header, sizeof(header));
468 if ((error == k_ra8_ok) && !internal_gzip_header_valid(header)) {
470 }
471 mdl_verify_arena_t arena = {.workspace = workspace};
472 mdl_gzip_stream_t gzip = {.output_cap = k_mdl_storage_io_bytes, .crc = MZ_CRC32_INIT};
473 if (error == k_ra8_ok) {
474 error = internal_gzip_init_inflate(workspace, &arena, &gzip);
475 }
476 if (error == k_ra8_ok) {
477 const uint64_t remaining =
478 (io->size_bytes >= k_gzip_min_bytes) ? io->size_bytes - k_gzip_min_bytes : 0U;
479 error = internal_gzip_consume(io, &arena, &gzip, remaining);
480 }
481 if (gzip.stream.state != nullptr) {
482 (void)mz_inflateEnd(&gzip.stream);
483 }
484 if (error == k_ra8_ok) {
485 error = internal_tar_finish(&gzip.tar, report);
486 }
487 return error;
488}
struct mdl_export_workspace mdl_export_workspace_t
Caller-owned bounded arena for all exporter scratch state.
@ k_gzip_header_bytes
Fixed RFC 1952 header.
@ k_mdl_storage_io_bytes
One bounded read/write chunk.
Definition mdl_storage.h:28
void priv_mdl_verify_arena_free(void *opaque, void *address)
Accept a miniz free for a monotonic arena.
Definition mdl_verify.c:162
ra8_err_t priv_mdl_verify_io_read_up_to(mdl_verify_io_t *io, uint8_t *destination, size_t length, size_t *out_read)
Read up to a requested portable span.
Definition mdl_verify.c:233
bool priv_mdl_verify_safe_member_name(const char *name)
Reject unsafe archive member paths.
Definition mdl_verify.c:86
void * priv_mdl_verify_workspace_take(mdl_export_workspace_t *workspace, size_t bytes, size_t alignment)
Reserve one aligned span from the verifier's caller-owned arena.
Definition mdl_verify.c:30
void * priv_mdl_verify_arena_alloc(void *opaque, size_t items, size_t size)
Allocate one aligned miniz span from a monotonic arena.
Definition mdl_verify.c:138
bool priv_mdl_verify_is_image(const char *name)
Recognize supported image suffixes.
Definition mdl_verify.c:79
Private bounded validator seams shared by the mdl verifiers.
@ k_verify_member_max
Maximum archive member count.
static ra8_err_t internal_io_read_exact(mdl_verify_io_t *io, uint8_t *destination, size_t length)
Read one exact structural span.
mdl_verify_gzip_frame_t
Fixed RFC 1952 framing emitted and accepted by mdl.
@ k_gzip_min_bytes
Smallest fixed-frame gzip extent.
@ k_gzip_method_deflate
RFC 1952 DEFLATE method identifier.
@ k_gzip_id_two
Second RFC 1952 magic byte.
@ k_gzip_id_one
First RFC 1952 magic byte.
@ k_gzip_trailer_bytes
CRC32 plus ISIZE trailer extent.
@ k_gzip_isize_offset
ISIZE offset inside that trailer.
ra8_err_t priv_mdl_verify_tar(mdl_verify_io_t *io, mdl_verify_report_t *report)
Validate an uncompressed CBT.
static ra8_err_t internal_gzip_consume(mdl_verify_io_t *io, mdl_verify_arena_t *arena, mdl_gzip_stream_t *gzip, uint64_t remaining)
Feed the compressed remainder into the inflater and validate the trailer.
static ra8_err_t internal_tar_process_block(mdl_tar_stream_t *state)
Process one complete 512-byte TAR block once fully buffered.
static ra8_err_t internal_gzip_feed(mdl_gzip_stream_t *gzip, const uint8_t *bytes, uint32_t length)
Inflate a raw-DEFLATE chunk.
mdl_verify_tar_layout_t
POSIX tar field layout and record sizing.
@ k_tar_checksum_offset
USTAR checksum-field byte offset.
@ k_tar_checksum_end
First byte after the checksum field.
@ k_tar_padding_mask
Mask used to round payloads to records.
@ k_tar_size_offset
USTAR size-field byte offset.
@ k_tar_type_offset
USTAR type-flag byte offset.
@ k_tar_block_bytes
TAR logical record extent.
@ k_tar_size_bytes
USTAR size-field extent.
@ k_tar_name_bytes
USTAR name-field extent.
static ra8_err_t internal_gzip_finish_deflate(mdl_gzip_stream_t *gzip)
Finish raw-DEFLATE validation.
mdl_verify_u32_shift_t
Byte shifts for little-endian decoding.
@ k_u32_byte_three_shift
Shift for byte three.
@ k_u32_byte_two_shift
Shift for byte two.
@ k_u32_byte_one_shift
Shift for byte one.
static ra8_err_t internal_tar_member(mdl_tar_stream_t *state)
Validate one TAR header.
static bool internal_gzip_header_valid(const uint8_t *header)
Validate the fixed gzip header.
ra8_err_t priv_mdl_verify_gzip_tar(mdl_verify_io_t *io, mdl_export_workspace_t *workspace, mdl_verify_report_t *report)
Validate a gzip-compressed CBT.
static ra8_err_t internal_tar_feed(mdl_tar_stream_t *state, const uint8_t *bytes, size_t length)
Feed bytes into TAR validation.
static uint32_t internal_get_u32le(const uint8_t *bytes)
Decode one little-endian word.
static ra8_err_t internal_gzip_init_inflate(mdl_export_workspace_t *workspace, mdl_verify_arena_t *arena, mdl_gzip_stream_t *gzip)
Initialize the gzip inflater bound to workspace-backed scratch.
static bool internal_gzip_trailer_valid(const uint8_t *trailer, const mdl_gzip_stream_t *gzip)
Validate gzip trailer accounting.
static ra8_err_t internal_tar_finish(const mdl_tar_stream_t *state, mdl_verify_report_t *report)
Finish TAR validation.
static bool internal_parse_octal(const uint8_t *field, size_t length, uint64_t *out)
Parse a bounded TAR octal field.
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).
@ 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_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
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
Streaming gzip inflater and nested TAR consumer.
uint32_t crc
Running decoded CRC32.
mz_stream stream
Raw-DEFLATE miniz state.
uint32_t output_cap
Extent of output.
bool ended
DEFLATE end marker observed.
mdl_tar_stream_t tar
Incremental decoded TAR state.
uint8_t * output
One bounded decoded chunk.
uint64_t raw_bytes
Exact decoded byte count.
One non-reentrant downloader filesystem dependency bundle.
Definition mdl_storage.h:40
uint8_t * io_buffer
Caller-owned stream scratch.
Definition mdl_storage.h:44
uint32_t io_buffer_bytes
Stream scratch extent.
Definition mdl_storage.h:47
Incremental TAR structural state.
bool metadata
ComicInfo.xml was found.
uint8_t zero_blocks
Consecutive terminal records.
bool ended
Two zero blocks were consumed.
size_t members
Regular member count.
uint8_t block[k_tar_block_bytes]
Partial header record.
uint64_t skip_bytes
Padded payload remaining.
uint64_t total_bytes
Total decoded TAR bytes.
size_t pages
Image member count.
size_t block_used
Bytes resident in block.
Allocation bridge retaining an explicit capacity failure.
bool exhausted
An allocation did not fit.
One open portable input and its immutable size snapshot.