|
Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
|
Status: Draft Date: 2026-08-02 Applies to: include/safeapi/common/sapi_string.h, additions to include/safeapi/common/sapi_buffer.h
Two related gaps: (1) the framework had no string handling of its own, so any application code built on it would fall back to strcpy/strcat/ sprintf - exactly the unbounded, MISRA-flagged functions this project exists to avoid; (2) sapi_buffer_t (ADR-002) copies raw bytes with no defined multi-byte value byte order, which is a real interoperability gap for a framework meant to exchange data between layers that could run on different hardware (NVM data persisted across a firmware/hardware update, IPC between differently-endian nodes).
A distinct wrapping struct (not a bare typedef sapi_buffer_t sapi_string_t) so the type system stops a raw byte buffer from being passed where string semantics are expected and vice versa - consistent with this project's preference for explicit, checked type boundaries (ADR-003). Internally it reuses sapi_buffer_t's bounds-checked, caller-owned storage; no new allocation strategy is introduced.
buf.length tracks the string's length not counting a NUL terminator. A NUL is only materialized on demand by sapi_string_c_str() (2.3), so a sapi_string_t can hold binary-safe content up to that point and only pays the "must have room for one more byte" cost when a caller actually needs a C string to hand to a legacy/third-party API.
sapi_string_copy() accepts a plain const char *src (for interop with string literals and legacy C APIs) but never calls strlen(src) - strlen has no bound and would defeat the entire point of this module if src turned out not to be NUL-terminated within a safe range. Instead it scans src for a NUL up to dest's capacity and fails with SAPI_STATUS_RESOURCE_EXHAUSTED if none is found in that range. Callers that already know their exact length (e.g. a length-prefixed field from sapi_ipc) should prefer sapi_string_copy_n(), which takes an explicit length and never scans.
Per the "most usable features developers use daily" framing: bounded equivalents of strcpy/strcat/strlen/strcmp, substring/character search, numeric formatting (both directions), and splitting.
strtok keeps hidden internal state, which makes it non-reentrant and unsafe to interleave across tasks - a well-known reason it's often banned outright in safety-critical guidelines. sapi_string_split_next() instead takes an explicit, caller-owned cursor:
*io_cursor starts at 0 (caller-initialized) and is advanced by the function on each call; out_token is a zero-copy sapi_const_buffer_t view into str's own storage (valid only as long as str's storage is). Multiple independent splits of the same or different strings can run concurrently on different tasks because all state is caller-owned.
No "default"/"network order" variant is provided - every call site states _le or _be explicitly. A hidden default is exactly the kind of convention someone eventually gets wrong across a team or across years of a railway signaling system's service life; an explicit suffix costs nothing at the call site and removes the ambiguity entirely. Byte order is handled by explicit shifting/masking (not htons/ntohl-family functions, which assume a fixed network-order convention this ADR deliberately avoids, and are not guaranteed available outside POSIX-like environments this framework's OAL is explicitly meant to abstract away from).
write_* functions append at the buffer's current length (like sapi_buffer_copy_in) and advance it; read_* functions take an explicit offset and do not mutate the buffer, mirroring the existing append-write/random-access-read asymmetry already implicit in sapi_buffer_copy_in/sapi_buffer_copy_out.
Superseded by ADR-007. See below for the original path; the current physical layout is include/safeapi/string/sapi_string.h + src/string/sapi_string.c, target safeapi::string (links safeapi::buffer and safeapi::cast).
include/safeapi/common/sapi_string.h + src/common/sapi_string.c (added to safeapi_common). Endian helpers added directly to sapi_buffer.h/sapi_buffer.c (ADR-002) rather than a new file, since they operate on sapi_buffer_t itself.