Skeleton. Each requirement is traced from a subset clause (docs/safety/SUBSET_MAPPING.dox) and referenced by a REQ-... tag in the public headers. Expand as modules are implemented; a public-header change updates the matching row here in the same commit.
SIL boundary (ADR-002)
| Module | Target | SIL | Rationale |
| SFM | safecomm::safety | SIL 4 (vital) | produces/verifies the MAC that is the safety property; runs in A and B |
| CFM | safecomm::cfm | vital context, deterministic interface upward | bearer I/O timing must not reach the 2oo2 cross-compared path — quarantined behind a discrete whole-PDU interface |
| ALE | safecomm::ale | non-vital | holds no KSMAC; can drop/misroute (SFM times out safe-side) but cannot inject an authenticated message |
| config | safecomm::config | non-vital | syntax parse only; every value range-checked by its vital consumer at init, bad value → refuse to start (safe) |
Core
| ID | Requirement |
| REQ-SC-CORE-001 | Every public function that can fail returns safecomm_status_t; SAFECOMM_STATUS_OK is 0. |
| REQ-SC-CORE-002 | The library exposes its version as macros (SAFECOMM_VERSION_*) and safecomm_version(). |
| REQ-SC-CORE-003 | No dynamic memory allocation after initialisation; no variadic functions in library code (MISRA 17.1). |
MAC (Subset-037 §7.3)
| ID | Requirement |
| REQ-SC-MAC-001 | KMAC and KSMAC are 24 bytes (3 x 8-byte DES keys). |
| REQ-SC-MAC-003 | safecomm_mac_derive_ksmac() derives KSMAC from KMAC, RA and RB per Subset-037-2 §6.2.3.2.3.13-16 (RA/RB split into 32-bit left/right halves; KS3 uses the key order reversed). |
| REQ-SC-MAC-005 | safecomm_mac_compute() produces the 8-byte CBC-MAC over the given bytes per Subset-037-2 §6.2.2 ("single DES with modified MAC algorithm 3": every block but the last under K1 only, the last block E(K3,D(K2,E(K1,·))); ISO/IEC 9797-1 Padding Method 1 zero-padding, no extra block when already 64-bit aligned). |
| REQ-SC-MAC-006 | safecomm_mac_verify() compares in constant time and never short-circuits on the first differing byte. |
| REQ-SC-MAC-010 | The CBC-MAC reproduces the official Subset-037-2 Annex B worked example byte-for-byte (tests/test_mac_kat.c). The KSMAC derivation has no official worked example to check against; it is verified only for internal consistency. |
Transport
Safety Functional sub-layer (Subset-037 §7.2 / §7.4 / §7.5 / §7.6)
| ID | Requirement |
| REQ-SC-SAF-002 | safecomm_saf_init() fully overwrites the connection object and validates the transport callbacks. |
| REQ-SC-SAF-010 | safecomm_saf_connect() runs AU1/AU2/AU3 per role and, on success, leaves the connection in DATA with a derived KSMAC. |
| REQ-SC-SAF-015 | safecomm_saf_send() frames the PDU as a Subset-037-2 §6.2.5.3 DT SaPDU (packed header, MAC), with a project-specific 4-byte sequence number inside the MAC-covered user-data area (Subset-037-2's own DT carries no sequence field — see docs/safety/SUBSET_MAPPING.dox). |
| REQ-SC-SAF-018 | safecomm_saf_recv() rejects a PDU whose MAC does not verify (MAC_MISMATCH), whose sequence is outside the accepted window (SEQ_ERROR), or whose direction flag is wrong (AUTH_FAILED, the reflection-attack defence of §6.2.5.1.7), and drops the connection to IDLE. |
| REQ-SC-SAF-020 | safecomm_saf_set_time() supplies the current time; the library reads no clock (it must not call the OS). |
| REQ-SC-SAF-021 | safecomm_saf_supervise() declares the link lost (TIMEOUT, connection to IDLE) when no valid frame arrived for t_supervision_ms; the first call arms the timer, 0 disables it, the comparison is wrap-around safe. |
| REQ-SC-SAF-022 | safecomm_saf_connect() fails with TIMEOUT (connection to IDLE) when the handshake takes longer than t_connect_ms on the caller-supplied clock; 0 disables it, the comparison is wrap-around safe. The transport must keep the clock current while it blocks. |
| REQ-SC-SAF-023 | safecomm_saf_recv() acts on any well-formed Sa-DISCONNECT (right length and message type; PEER_DISCONNECT, connection to IDLE) with no MAC or identity check, per Subset-037-2 §6.2.3.3.3.2 ("authentication of the connection release phase is not required" — a forged one can only end the connection early, never inject or alter safety data). A malformed one returns TRANSPORT and leaves the connection unchanged. |
| REQ-SC-SAF-020 | safecomm_saf_disconnect() always leaves the object in IDLE, best-effort emitting Sa-DISCONNECT first. |
| REQ-SC-SAF-025 | A supervision timer expiry in any state transitions the connection to IDLE and returns SAFECOMM_STATUS_TIMEOUT. |
| REQ-SC-SAF-026 | A Sa-DATA frame carries up to SAFECOMM_SAF_MAX_PDU user bytes in both directions (the MAC input buffer is sized from it; before 2026-09-28 a fixed 64-byte buffer capped the user data at 54 bytes). |
Frame-driven responder (2026-09-28)
For a caller that owns its I/O and cannot block (a cyclic executive, one relay link shared by many connections). None of these functions touches conn->transport; they share their checks with the blocking calls above.
| ID | Requirement |
| REQ-SC-SAF-040 | safecomm_saf_frame_kind() classifies a received frame by its header MTI (AU1/AU2/AU3/DATA/DISCONNECT/AR, else UNKNOWN) without any MAC check. |
| REQ-SC-SAF-041 | safecomm_saf_au1_fields() returns the source ETCS identity and the initiator random RA of a well-formed AU1 (SAFECOMM_SAF_AU1_LEN bytes, MTI AU1, DF 0), else AUTH_FAILED; AU1 carries no MAC, so both are unauthenticated until AU3 verifies. |
| REQ-SC-SAF-042 | safecomm_saf_responder_on_au1() accepts an AU1 from the configured peer in any state (a fresh AU1 restarts the connection; unauthenticated, so a forged one only affects availability, as for Sa-DISCONNECT), uses the caller's RB, derives KSMAC and builds AU2; it ends in WAIT_AU3 and starts the connect budget at the current now_ms, or in IDLE with AUTH_FAILED on a malformed AU1 or unexpected peer identity. The caller must supply RB from a cryptographically secure source, and in a 2oo2 system the same RB to both channels. |
| REQ-SC-SAF-043 | safecomm_saf_responder_on_au3() in WAIT_AU3 returns TIMEOUT (to IDLE) once more than t_connect_ms has passed since AU1, AUTH_FAILED (to IDLE) on a bad AU3 or MAC, else builds AR and enters DATA with both sequence counters at 1. |
| REQ-SC-SAF-044 | safecomm_saf_accept_frame() applies exactly safecomm_saf_recv()'s checks (Sa-DISCONNECT handling, header, MAC, sequence window, supervision refresh) to a frame the caller already received; safecomm_saf_recv() is implemented on top of it. |
| REQ-SC-SAF-045 | safecomm_saf_frame_sent() in DATA advances the send sequence exactly as a successful safecomm_saf_commit_send() does, for a frame from safecomm_saf_prepare_send() that the caller transmitted itself. |
| REQ-SC-SAF-046 | safecomm_saf_disconnect_frame() builds a Sa-DISCONNECT (reason 0, sub-reason 0, direction flag from the given role) with no connection state, for a responder releasing a Sa-DATA frame that belongs to no connection it holds; safecomm_saf_disconnect() sends the same frame. |
The blocking responder in safecomm_saf_connect() still draws RB from this module's internal linear congruential generator: predictable, and process-global (two processes diverge after either restarts). It is for tests and tools only; a vital responder uses safecomm_saf_responder_on_au1() with a secure RB (RBC_GP draws it through Platform_RTE's rte_random_fill()).
Subset-037 profile
| ID | Requirement |
| REQ-SC-ER-003 | safecomm_er_make_saf_config() maps EVC->initiator / RBC->responder and fills the Subset-037 T_CONNECT and supervision timeouts. |
Subset-098 profile
| ID | Requirement |
| REQ-SC-RR-003 | safecomm_rr_make_saf_config() maps handing-over->initiator / accepting->responder and fills the Subset-098 timeouts. |
Configuration (file-based)
| ID | Requirement |
| REQ-SC-CFG-001 | Every runtime parameter of the stack is read from a text file; none is hard-coded or required on the command line. |
| REQ-SC-CFG-002 | safecomm_config_load() parses key = value lines, # full-line and inline comments, trims surrounding whitespace, has no sections, and allocates nothing dynamically. |
| REQ-SC-CFG-003 | The loader performs syntax parsing ONLY — no semantic / range validation — so it is non-vital. |
| REQ-SC-CFG-005..007 | Typed accessors _get_str / _get_u32 / _get_bool; absent key or bad conversion returns a distinct error, never a silent default. |
| REQ-SC-CFG-010 | A vital component range-checks every value it reads at init and returns an error (refuse to start) on any out-of-range or missing mandatory value. |
| REQ-SC-CFG-012 | KMAC is never stored in the main config file; keys.kmac_file names a separate file of 48 hex characters. |
CFM (Communication Functional Module)
| ID | Requirement |
| REQ-SC-CFM-003 | safecomm_cfm_from_config() reads and range-checks the cfm.* keys and selects the SS-037 or SS-098 communication-layer profile. |
| REQ-SC-CFM-008 | safecomm_cfm_transport() exposes the CFM upward as a safecomm_transport_t whose send/recv carry whole SaF PDUs; framing, keep-alive and reconnect are hidden below it. |
| REQ-SC-CFM-012 | The CFM's interface to the SFM is discrete and drained at RBC-cycle boundaries; bearer I/O timing never enters the 2oo2 cross-compared path. |
ALE (Application Layer Entity, Gateway)
| ID | Requirement |
| REQ-SC-ALE-003 | safecomm_ale_from_config() reads ale.* and loads ale.peer_map_file into a fixed peer table. |
| REQ-SC-ALE-006 | safecomm_ale_lookup() resolves a peer id (NID_ENGINE / RBC-ID) to its A and B endpoints. |
| REQ-SC-ALE-009 | The ALE relays the raw bearer byte stream to BOTH A and B and never parses, stores or modifies a SaF PDU; it holds no KSMAC. |
| REQ-SC-ALE-010 | A bearer connection with no identifiable peer within ale.accept_timeout_ms is dropped. |
KM (Subset-114, Subset-038)
Moved out to their own standalone products on 2026-09-23: Platform_Protocol_KM114 (Subset-114, KMC-to-entity) and Platform_Protocol_KM038 (Subset-038, KMC-to-KMC interworking) — KM is administrative/non-vital, distinct from this project's own vital safety layer, and this workspace's own pattern is one product per protocol. Each carries its own docs/requirements/SRS.dox. See MISRA_COMPLIANCE_REPORT.dox Update 9 in this project for the removal record, and REQ-SC-MAC-011's own removal note (the 3DES block primitive it named went with them - each new product ported its own copy instead).
All 4 off-line KM message types plus their confirmations are now implemented (as of 2026-09-23).