ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_c6link_mdl_service.c
Go to the documentation of this file.
1
9
10#include <stddef.h>
11#include <stdint.h>
12#include <string.h>
13
14#include "ra8_attributes.h"
15#include "ra8_c6link_mdl_msg.h"
18
19static_assert((uint32_t)k_mdl_format_loose == RA8__MDL__FORMAT__FORMAT_LOOSE);
20static_assert((uint32_t)k_mdl_format_cbz == RA8__MDL__FORMAT__FORMAT_CBZ);
21static_assert((uint32_t)k_mdl_format_cbt == RA8__MDL__FORMAT__FORMAT_CBT);
22static_assert((uint32_t)k_mdl_format_cbr == RA8__MDL__FORMAT__FORMAT_CBR);
25static_assert((uint32_t)k_mdl_format_epub == RA8__MDL__FORMAT__FORMAT_EPUB);
26static_assert((uint32_t)k_mdl_format_jof == RA8__MDL__FORMAT__FORMAT_JOF);
29
34
35/* The worst-case response sizing below reads one bounded run of filler through
36 * four header pointers of different lengths, so the run must be at least as
37 * long as the longest of them. */
41
58RA8_INTERNAL static bool internal_mdl_request_field_valid(const char* text, size_t cap)
59{
60 if (text == nullptr) {
61 return false;
62 }
63 const size_t length = strnlen(text, cap);
64 if (length >= cap) {
65 return false;
66 }
67 for (size_t index = 0U; index < length; index++) {
68 if ((text[index] == '\r') || (text[index] == '\n')) {
69 return false;
70 }
71 }
72 return true;
73}
74
91{
92 return (response->status >= (int32_t)k_ra8_mdl_http_status_min) &&
93 (response->status <= (int32_t)k_ra8_mdl_http_status_max) &&
94 internal_mdl_request_field_valid(response->retry_after, sizeof(response->retry_after)) &&
95 internal_mdl_request_field_valid(response->etag, sizeof(response->etag)) &&
97 sizeof(response->last_modified)) &&
99}
100
101RA8_PRIV bool priv_c6link_mdl_decode_allocation_fits(size_t used, size_t len, size_t capacity)
102{
103 if (len > (SIZE_MAX - k_mdl_decode_align_mask)) {
104 return false;
105 }
106 const size_t aligned = (len + k_mdl_decode_align_mask) & ~(size_t)k_mdl_decode_align_mask;
107 return (aligned <= capacity) && (used <= (capacity - aligned));
108}
109
123RA8_INTERNAL static void* internal_mdl_decode_alloc(void* data, size_t len)
124{
126 if (!priv_c6link_mdl_decode_allocation_fits(arena->used, len, sizeof(arena->bytes))) {
127 return nullptr;
128 }
129 const size_t aligned = (len + k_mdl_decode_align_mask) & ~(size_t)k_mdl_decode_align_mask;
130 void* out = &arena->bytes[arena->used];
131 arena->used += aligned;
132 return out;
133}
134
148RA8_INTERNAL static void internal_mdl_decode_free(void* data, void* ptr)
149{
150 (void)data;
151 (void)ptr;
152}
153
169RA8_INTERNAL static ra8_err_t internal_mdl_check_response_size(size_t len, size_t response_cap)
170{
171 if ((len == 0U) || (len > response_cap)) {
173 }
174 return k_ra8_ok;
175}
176
196 uint8_t* response,
197 size_t response_cap,
198 size_t* response_len)
199{
200 const size_t len = ra8__mdl__accepted__get_packed_size(msg);
201 const ra8_err_t capacity = internal_mdl_check_response_size(len, response_cap);
202 if (capacity != k_ra8_ok) {
203 return capacity;
204 }
205 if (ra8__mdl__accepted__pack(msg, response) != len) {
207 }
208 *response_len = len;
209 return k_ra8_ok;
210}
211
231 uint8_t* response,
232 size_t response_cap,
233 size_t* response_len)
234{
235 const size_t len = ra8__mdl__chunk__get_packed_size(msg);
236 const ra8_err_t capacity = internal_mdl_check_response_size(len, response_cap);
237 if (capacity != k_ra8_ok) {
238 return capacity;
239 }
240 if (ra8__mdl__chunk__pack(msg, response) != len) {
242 }
243 *response_len = len;
244 return k_ra8_ok;
245}
246
263{
264 if (request->url == nullptr) {
265 return false;
266 }
267 const size_t https_prefix_len = sizeof("https://") - 1U;
268 const size_t url_len = strnlen(request->url, k_ra8_mdl_url_max);
269 return (request->protocol_version == k_ra8_mdl_protocol_version) && (url_len != 0U) &&
270 (url_len < k_ra8_mdl_url_max) &&
271 (strncmp(request->url, "https://", https_prefix_len) == 0) &&
272 (request->url[https_prefix_len] != '\0') &&
273 ((uint32_t)request->format <= (uint32_t)RA8__MDL__FORMAT__FORMAT_RABOOK) &&
274 (request->timeout_ms <= k_ra8_mdl_timeout_ms_max) &&
279}
280
305 ProtobufCAllocator* alloc,
306 const uint8_t* request,
307 size_t request_len,
308 uint8_t* response,
309 size_t response_cap,
310 size_t* response_len)
311{
312 Ra8__Mdl__StartRequest* req = ra8__mdl__start_request__unpack(alloc, request_len, request);
313 if (req == nullptr) {
315 }
316 if (req->base.n_unknown_fields != 0U) {
318 }
319 if (!internal_mdl_start_valid(req)) {
321 }
322 if (service->active) {
323 return k_ra8_err_busy;
324 }
325 uint32_t next_job_id = service->next_job_id + 1U;
326 if (next_job_id == 0U) {
327 next_job_id = 1U;
328 }
331 out.job_id = next_job_id;
333 out.format = req->format;
334 const ra8_err_t prepacked =
335 internal_mdl_pack_accepted(&out, response, response_cap, response_len);
336 if (prepacked != k_ra8_ok) {
337 return prepacked;
338 }
339 const ra8_mdl_request_t backend_request = {
340 .url = req->url,
341 .format = (mdl_format_t)req->format,
342 .http = {.user_agent = req->user_agent,
343 .referer = req->referer,
344 .if_none_match = req->if_none_match,
345 .if_modified_since = req->if_modified_since,
346 .timeout_ms = req->timeout_ms},
347 };
348 const ra8_err_t begun = service->backend.begin(service->backend.ctx, &backend_request);
349 if (begun != k_ra8_ok) {
350 *response_len = 0U;
351 return begun;
352 }
353 service->next_job_id = next_job_id;
354 service->active_job_id = service->next_job_id;
355 service->next_sequence = 0U;
356 service->next_offset = 0U;
357 service->active_format = (mdl_format_t)req->format;
358 service->active = true;
359 return k_ra8_ok;
360}
361
372
390{
391 (void)service->backend.cancel(service->backend.ctx);
392 service->active = false;
393 return error;
394}
395
413static ra8_err_t internal_mdl_next_capacity(uint32_t max_data, size_t response_cap)
414{
415 /* `get_packed_size()` reads a string field only through `strlen()` and a
416 * bytes field only through its `len`, never through its `data` pointer
417 * (protobuf-c `required_field_get_packed_size`). So one filler run measures
418 * every header: a field of capacity `cap` takes the suffix at
419 * `sizeof(filler) - cap`, whose length is exactly `cap - 1`. Six
420 * separate worst-case buffers cost 1440 stack bytes and measured the same
421 * numbers. */
422 char filler[k_ra8_mdl_etag_max];
423 memset(filler, 'x', sizeof(filler) - 1U);
424 filler[sizeof(filler) - 1U] = '\0';
425 Ra8__Mdl__Chunk data;
427 data.protocol_version = UINT32_MAX;
428 data.job_id = UINT32_MAX;
429 data.sequence = UINT32_MAX;
430 data.offset = UINT64_MAX;
431 data.data = (ProtobufCBinaryData){.len = max_data, .data = (uint8_t*)filler};
432 data.total_bytes = UINT64_MAX;
434 const ra8_err_t data_fit =
436 if (data_fit != k_ra8_ok) {
437 return data_fit;
438 }
439
440 Ra8__Mdl__Chunk terminal;
441 ra8__mdl__chunk__init(&terminal);
442 terminal.protocol_version = UINT32_MAX;
443 terminal.job_id = UINT32_MAX;
444 terminal.sequence = UINT32_MAX;
445 terminal.offset = UINT64_MAX;
446 terminal.total_bytes = UINT64_MAX;
448 terminal.sha256 =
449 (ProtobufCBinaryData){.len = (size_t)k_ra8_mdl_sha256_bytes, .data = (uint8_t*)filler};
450 terminal.http_status = (int32_t)k_ra8_mdl_http_status_max;
451 terminal.retry_after = &filler[sizeof(filler) - k_ra8_mdl_retry_after_max];
452 terminal.etag = filler; /* the longest header: the whole run */
453 terminal.last_modified = &filler[sizeof(filler) - k_ra8_mdl_http_date_max];
454 terminal.content_type = &filler[sizeof(filler) - k_ra8_mdl_content_type_max];
456 response_cap);
457}
458
480 uint32_t max_data,
482{
483 const ra8_err_t read = service->backend.read(service->backend.ctx,
484 result->bytes,
485 (uint16_t)max_data,
486 &result->got,
487 &result->total,
488 &result->complete,
489 result->digest,
490 &result->response);
491 if (read != k_ra8_ok) {
492 return internal_mdl_fail_job(service, read);
493 }
494 const bool offset_overflow = service->next_offset > (UINT64_MAX - result->got);
495 result->end_offset = offset_overflow ? 0U : service->next_offset + result->got;
496 bool total_invalid = false;
497 if (result->complete) {
498 total_invalid = result->end_offset != result->total;
499 } else if (result->total != 0U) {
500 total_invalid = result->end_offset > result->total;
501 }
502 if ((result->got > max_data) || ((!result->complete) && (result->got == 0U)) ||
503 (result->complete && (result->got != 0U)) || offset_overflow || total_invalid ||
504 (service->next_sequence == UINT32_MAX) ||
505 (result->complete && !internal_mdl_response_valid(&result->response)) ||
506 (!result->complete && (result->response.status != 0))) {
508 }
509 return k_ra8_ok;
510}
511
530 Ra8__Mdl__Chunk* out)
531{
534 out->job_id = service->active_job_id;
535 out->sequence = service->next_sequence;
536 out->offset = service->next_offset;
537 out->data = (ProtobufCBinaryData){.len = result->got, .data = result->bytes};
538 out->total_bytes = result->total;
539 out->state =
541 out->status = 0;
542 if (result->complete) {
543 out->sha256 = (ProtobufCBinaryData){.len = k_ra8_mdl_sha256_bytes, .data = result->digest};
544 out->http_status = result->response.status;
545 out->retry_after = result->response.retry_after;
546 out->etag = result->response.etag;
547 out->last_modified = result->response.last_modified;
548 out->content_type = result->response.content_type;
549 }
550}
551
577 ProtobufCAllocator* alloc,
578 const uint8_t* request,
579 size_t request_len,
580 uint8_t* response,
581 size_t response_cap,
582 size_t* response_len)
583{
584 const Ra8__Mdl__NextRequest* req = ra8__mdl__next_request__unpack(alloc, request_len, request);
585 if (req == nullptr) {
587 }
588 if (req->base.n_unknown_fields != 0U) {
590 }
591 if ((req->protocol_version != k_ra8_mdl_protocol_version) || !service->active ||
592 (req->job_id != service->active_job_id) ||
593 (req->acknowledged_offset != service->next_offset) || (req->max_bytes == 0U) ||
596 }
597
598 const ra8_err_t capacity = internal_mdl_next_capacity(req->max_bytes, response_cap);
599 if (capacity != k_ra8_ok) {
600 return capacity;
601 }
602
603 internal_mdl_next_read_t result = {};
604 const ra8_err_t read = internal_mdl_read_next(service, req->max_bytes, &result);
605 if (read != k_ra8_ok) {
606 return read;
607 }
608
609 Ra8__Mdl__Chunk out;
611 internal_mdl_build_chunk(service, &result, &out);
612 const ra8_err_t packed = internal_mdl_pack_chunk(&out, response, response_cap, response_len);
613 if (packed != k_ra8_ok) {
614 return internal_mdl_fail_job(service, packed);
615 }
616 service->next_offset += result.got;
617 service->next_sequence += 1U;
618 if (result.complete) {
619 service->active = false;
620 }
621 return k_ra8_ok;
622}
623
650 ProtobufCAllocator* alloc,
651 const uint8_t* request,
652 size_t request_len,
653 uint8_t* response,
654 size_t response_cap,
655 size_t* response_len)
656{
657 const Ra8__Mdl__CancelRequest* req =
658 ra8__mdl__cancel_request__unpack(alloc, request_len, request);
659 if (req == nullptr) {
661 }
662 if (req->base.n_unknown_fields != 0U) {
664 }
665 if ((req->protocol_version != k_ra8_mdl_protocol_version) || !service->active ||
666 (req->job_id != service->active_job_id)) {
668 }
671 out.job_id = req->job_id;
672 out.status = 0;
673 const size_t len = ra8__mdl__cancelled__get_packed_size(&out);
674 const ra8_err_t capacity = internal_mdl_check_response_size(len, response_cap);
675 if (capacity != k_ra8_ok) {
676 return capacity;
677 }
678 if (ra8__mdl__cancelled__pack(&out, response) != len) {
680 }
681 const ra8_err_t cancelled = service->backend.cancel(service->backend.ctx);
682 if (cancelled != k_ra8_ok) {
683 return cancelled;
684 }
685 service->active = false;
686 *response_len = len;
687 return k_ra8_ok;
688}
689
690RA8_TEST_HELPER bool ra8_mdl_service_field_valid_test(const char* text, size_t cap)
691{
692 return internal_mdl_request_field_valid(text, cap);
693}
694
699
701{
702 return internal_mdl_check_response_size(len, response_cap);
703}
704
706{
707 if ((service == nullptr) || (backend == nullptr) || (backend->begin == nullptr) ||
708 (backend->read == nullptr) || (backend->cancel == nullptr)) {
709 return k_ra8_err_null_ptr;
710 }
711 *service = (ra8_mdl_service_t){.backend = *backend};
712 return k_ra8_ok;
713}
714
716 uint32_t operation,
717 const uint8_t* request,
718 size_t request_len,
719 uint8_t* response,
720 size_t response_cap,
721 size_t* response_len)
722{
723 if ((ctx == nullptr) || (request == nullptr) || (request_len == 0U) || (response == nullptr) ||
724 (response_len == nullptr)) {
725 return k_ra8_err_null_ptr;
726 }
727 *response_len = 0U;
728 ra8_mdl_service_t* service = (ra8_mdl_service_t*)ctx;
729 /* Cleared here, not declared here: an automatic arena of this size put this
730 * function over the 2048-byte first-party frame budget. Clearing the whole
731 * object gives every dispatch the same empty arena the automatic one did. */
732 service->arena = (ra8_mdl_decode_arena_t){};
733 ProtobufCAllocator alloc = {.alloc = internal_mdl_decode_alloc,
735 .allocator_data = &service->arena};
736 switch (operation) {
738 return internal_mdl_dispatch_start(service,
739 &alloc,
740 request,
741 request_len,
742 response,
743 response_cap,
744 response_len);
746 return internal_mdl_dispatch_next(service,
747 &alloc,
748 request,
749 request_len,
750 response,
751 response_cap,
752 response_len);
754 return internal_mdl_dispatch_cancel(service,
755 &alloc,
756 request,
757 request_len,
758 response,
759 response_cap,
760 response_len);
761 default:
763 }
764}
mdl_format_t
Artifact format selected by a media-download composition root.
Definition mdl_format.h:35
@ k_mdl_format_cbz
ZIP of images (.cbz).
Definition mdl_format.h:37
@ k_mdl_format_invalid
Unrecognized or unsupported selection.
Definition mdl_format.h:45
@ k_mdl_format_loose
Leave loose page images.
Definition mdl_format.h:36
@ k_mdl_format_cbr
Reserved RAR output.
Definition mdl_format.h:39
@ k_mdl_format_epub
Fixed-layout EPUB (.epub).
Definition mdl_format.h:42
@ k_mdl_format_cbt_gz
Gzip-compressed tar (.cbt.gz).
Definition mdl_format.h:41
@ k_mdl_format_cbt_xz
Reserved xz-compressed tar output.
Definition mdl_format.h:40
@ k_mdl_format_rabook
Chunked reader-native book (.rabook).
Definition mdl_format.h:44
@ k_mdl_format_jof
Native JOF tile atlas per page (.jof).
Definition mdl_format.h:43
@ k_mdl_format_cbt
Tar of images (.cbt).
Definition mdl_format.h:38
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_TEST_HELPER
Mark a symbol as externally-linked but only callable from tests.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ 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_busy
Resource busy – blocking operation cannot proceed.
Definition ra8_err.h:195
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ 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_protocol_error
Protocol-level error (e.g.
Definition ra8_err.h:429
@ 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 strncmp(const char *s1, const char *s2, size_t n)
Compare two strings up to a specified length.
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
size_t strnlen(const char *s, size_t maxlen)
Calculate bounded string length.
@ k_ra8_mdl_user_agent_max
Maximum User-Agent size, including NUL.
@ k_ra8_mdl_referer_max
Maximum Referer size, including NUL.
@ k_ra8_mdl_chunk_data_max
Maximum raw body bytes in one chunk.
@ k_ra8_mdl_url_max
Maximum URL buffer size, including NUL.
@ k_ra8_mdl_http_date_max
Maximum HTTP-date size, including NUL.
@ k_ra8_mdl_etag_max
Maximum ETag size, including NUL.
@ k_ra8_mdl_content_type_max
Maximum Content-Type size, including NUL.
@ k_ra8_mdl_sha256_bytes
SHA-256 digest size in bytes.
@ k_ra8_mdl_retry_after_max
Maximum Retry-After size, including NUL.
@ k_ra8_mdl_timeout_ms_max
Maximum caller-selected HTTP timeout in ms.
@ k_ra8_mdl_http_status_max
Highest well-formed HTTP status code.
@ k_ra8_mdl_http_status_min
Lowest well-formed HTTP status code.
@ k_ra8_mdl_protocol_version
Typed HTTP-artifact transfer protocol.
@ k_ra8_mdl_rpc_start
Start one typed-artifact job.
@ k_ra8_mdl_rpc_cancel
Cancel one active job.
@ k_ra8_mdl_rpc_next
Pull one ordered bounded chunk.
Generated protobuf-C declarations for the media-download protocol.
void ra8__mdl__chunk__init(Ra8__Mdl__Chunk *message)
size_t ra8__mdl__cancelled__pack(const Ra8__Mdl__Cancelled *message, uint8_t *out)
@ RA8__MDL__FORMAT__FORMAT_CBT_GZ
@ RA8__MDL__FORMAT__FORMAT_RABOOK
@ RA8__MDL__FORMAT__FORMAT_CBT_XZ
@ RA8__MDL__FORMAT__FORMAT_JOF
@ RA8__MDL__FORMAT__FORMAT_INVALID
@ RA8__MDL__FORMAT__FORMAT_LOOSE
@ RA8__MDL__FORMAT__FORMAT_CBR
@ RA8__MDL__FORMAT__FORMAT_CBZ
@ RA8__MDL__FORMAT__FORMAT_CBT
@ RA8__MDL__FORMAT__FORMAT_EPUB
size_t ra8__mdl__cancelled__get_packed_size(const Ra8__Mdl__Cancelled *message)
size_t ra8__mdl__chunk__pack(const Ra8__Mdl__Chunk *message, uint8_t *out)
#define RA8__MDL__CANCELLED__INIT
Ra8__Mdl__NextRequest * ra8__mdl__next_request__unpack(ProtobufCAllocator *allocator, size_t len, const uint8_t *data)
size_t ra8__mdl__accepted__pack(const Ra8__Mdl__Accepted *message, uint8_t *out)
size_t ra8__mdl__chunk__get_packed_size(const Ra8__Mdl__Chunk *message)
@ RA8__MDL__STATE__STATE_DOWNLOADING
@ RA8__MDL__STATE__STATE_COMPLETE
size_t ra8__mdl__accepted__get_packed_size(const Ra8__Mdl__Accepted *message)
Ra8__Mdl__CancelRequest * ra8__mdl__cancel_request__unpack(ProtobufCAllocator *allocator, size_t len, const uint8_t *data)
Ra8__Mdl__StartRequest * ra8__mdl__start_request__unpack(ProtobufCAllocator *allocator, size_t len, const uint8_t *data)
#define RA8__MDL__ACCEPTED__INIT
ProtobufCBinaryData sha256
ProtobufCBinaryData data
Caller-bounded bytes and metadata returned by one backend pull.
uint16_t got
Valid body byte count.
bool complete
Whether this is terminal.
uint8_t digest[k_ra8_mdl_sha256_bytes]
Terminal SHA-256 digest.
uint64_t end_offset
Offset after returned data.
uint64_t total
Declared complete size.
ra8_mdl_http_response_t response
Terminal HTTP metadata.
uint8_t bytes[k_ra8_mdl_chunk_data_max]
Returned body bytes.
uint8_t bytes[k_ra8_mdl_decode_arena_bytes]
Fixed protobuf decode storage.
size_t used
Bytes already allocated.
HTTP status and selected response headers proven by the C6 backend.
char last_modified[k_ra8_mdl_http_date_max]
Last-Modified or empty.
int32_t status
Final HTTP status.
char retry_after[k_ra8_mdl_retry_after_max]
Retry-After or empty.
char content_type[k_ra8_mdl_content_type_max]
Content-Type or empty.
char etag[k_ra8_mdl_etag_max]
ETag or empty.
Complete typed HTTPS request accepted by protocol version 3.
ra8_err_t(* begin)(void *ctx, const ra8_mdl_request_t *request)
Validate and begin one bounded typed HTTPS request.
ra8_err_t(* read)(void *ctx, uint8_t *out, uint16_t cap, uint16_t *got, uint64_t *total_bytes, bool *complete, uint8_t sha256[k_ra8_mdl_sha256_bytes], ra8_mdl_http_response_t *response)
Pull bytes or terminal metadata.
void * ctx
Backend callback context.
ra8_err_t(* cancel)(void *ctx)
Cancel and release the active backend job.
uint32_t next_sequence
Sequence required from the next pull.
uint32_t active_job_id
Correlation id of the current job.
bool active
Whether Next or Cancel is currently valid.
uint64_t next_offset
Byte offset required from the next pull.
ra8_mdl_service_backend_t backend
Injected typed-artifact source mechanism.
ra8_mdl_decode_arena_t arena
Decode storage for the running dispatch.
uint32_t next_job_id
Monotonic id candidate for the next Start.