Safe Communication Framework 0.1.0 build feea4f9
ERTMS/ETCS safe communication (Subset-037 EuroRadio, Subset-098 RBC/RBC), CENELEC EN 50128 SIL 4 oriented
Loading...
Searching...
No Matches
saf.c File Reference

Safety Functional (SaF) sub-layer state machine. More...

#include "safecomm/saf/saf.h"
#include <string.h>
Include dependency graph for saf.c:

Go to the source code of this file.

Macros

#define MTI_AU1   0x1U
#define MTI_AU2   0x2U
#define MTI_AU3   0x3U
#define MTI_DT   0x5U
#define MTI_DI   0x8U
#define MTI_AR   0x9U
#define SAF_SEQ_WINDOW   8U

Functions

static void pack_id24 (uint32_t id, uint8_t buf[3])
static uint32_t unpack_id24 (const uint8_t buf[3])
static uint8_t pack_header (uint8_t ety, uint8_t mti, uint8_t df)
static uint8_t header_mti (uint8_t header)
static uint8_t header_df (uint8_t header)
static void pack_u32_be (uint32_t val, uint8_t buf[4])
static uint32_t unpack_u32_be (const uint8_t buf[4])
static void generate_random_bytes (uint8_t *out, size_t len, uint32_t seed_hint)
static safecomm_status_t mac_over_da_body (const safecomm_mac_ctx_t *mac, uint32_t da_id, const uint8_t *body, size_t body_len, uint8_t out_mac[SAFECOMM_MAC_LEN])
 Build [length(2, BE) | DA(3) | body] and MAC it (Subset-037-2 §6.2.3.2.1.1 "Procedure 1"). safecomm_mac_compute() applies the ISO/IEC 9797-1 Method 1 zero-padding itself, so the caller need only supply the unpadded bytes.
safecomm_status_t safecomm_saf_init (safecomm_saf_conn_t *conn, const safecomm_saf_config_t *cfg, const safecomm_transport_t *transport)
 Initialise a SaF connection object (no I/O yet).
static bool connect_expired (const safecomm_saf_conn_t *conn, uint32_t start_ms)
static safecomm_status_t responder_process_au1 (safecomm_saf_conn_t *conn, const uint8_t *au1, size_t au1_len, const uint8_t rb[SAFECOMM_MAC_RAND_LEN], uint8_t *out_au2)
static safecomm_status_t responder_process_au3 (safecomm_saf_conn_t *conn, const uint8_t *au3, size_t au3_len, uint8_t *out_ar)
safecomm_status_t safecomm_saf_connect (safecomm_saf_conn_t *conn)
 Run the AU1/AU2/AU3 handshake to completion (blocking).
safecomm_saf_frame_kind_t safecomm_saf_frame_kind (const uint8_t *frame, size_t frame_len)
 Classify a received frame by its header MTI (no MAC check).
safecomm_status_t safecomm_saf_au1_fields (const uint8_t *au1, size_t au1_len, uint32_t *out_peer_id, uint8_t out_ra[SAFECOMM_MAC_RAND_LEN])
 Read the caller's ETCS identity and initiator random RA out of an AU1 frame (no MAC exists on AU1). A responder that serves many peers uses the identity to pick the peer's KMAC and configure the connection, and RA to pair a shared RB with the handshake it belongs to.
safecomm_status_t safecomm_saf_responder_on_au1 (safecomm_saf_conn_t *conn, const uint8_t *au1, size_t au1_len, const uint8_t rb[SAFECOMM_MAC_RAND_LEN], uint8_t *out_au2, size_t out_cap, size_t *out_len)
 Responder: accept AU1, derive the session key from RA and the caller's rb, and build AU2.
safecomm_status_t safecomm_saf_responder_on_au3 (safecomm_saf_conn_t *conn, const uint8_t *au3, size_t au3_len, uint8_t *out_ar, size_t out_cap, size_t *out_len)
 Responder: verify AU3 and build the AR confirmation; the connection enters DATA.
safecomm_status_t safecomm_saf_frame_sent (safecomm_saf_conn_t *conn)
 Record that the caller transmitted the frame from safecomm_saf_prepare_send() itself: advances the send sequence exactly as safecomm_saf_commit_send() does after a successful transport send.
safecomm_status_t safecomm_saf_disconnect_frame (safecomm_saf_role_t role, uint8_t *out, size_t out_cap, size_t *out_len)
 Build a Sa-DISCONNECT (reason 0, sub-reason 0) for a side of the given role, without any connection state - e.g. a responder releasing a Sa-DATA frame that belongs to no connection it holds (the peer's session is stale, typically after this node restarted), so the peer reconnects at once instead of waiting out its supervision time. Unauthenticated, as every Sa-DISCONNECT (Subset-037-2 §6.2.3.3.3.2).
safecomm_status_t safecomm_saf_prepare_send (safecomm_saf_conn_t *conn, const uint8_t *pdu, size_t pdu_len, uint8_t *out_frame, size_t out_frame_cap, size_t *out_frame_len)
 Prepare and MAC-protect one Sa-DATA frame without transmitting it (for 2oo2 cross-checking).
safecomm_status_t safecomm_saf_commit_send (safecomm_saf_conn_t *conn, const uint8_t *frame, size_t frame_len)
 Transmit an already prepared and cross-compared Sa-DATA frame.
safecomm_status_t safecomm_saf_send (safecomm_saf_conn_t *conn, const uint8_t *pdu, size_t pdu_len)
 Send one user PDU as a MAC-protected, sequence-numbered Sa-DATA.
safecomm_status_t safecomm_saf_recv (safecomm_saf_conn_t *conn, uint8_t *out, size_t out_cap, size_t *out_len)
 Receive and verify one Sa-DATA PDU (blocking).
safecomm_status_t safecomm_saf_accept_frame (safecomm_saf_conn_t *conn, const uint8_t *rx_buf, size_t rx_len, uint8_t *out, size_t out_cap, size_t *out_len)
 Verify one received Sa-DATA or Sa-DISCONNECT frame (the checks of safecomm_saf_recv(), without the transport).
void safecomm_saf_set_time (safecomm_saf_conn_t *conn, uint32_t now_ms)
 Orderly Sa-DISCONNECT, then return the object to IDLE.
safecomm_status_t safecomm_saf_supervise (safecomm_saf_conn_t *conn)
 Idle-link supervision (EN 50159 delay and loss): in DATA, if no valid frame arrived for cfg.t_supervision_ms the connection goes to IDLE and SAFECOMM_STATUS_TIMEOUT is returned. Call it cyclically. The first call arms the timer. t_supervision_ms == 0 disables it.
safecomm_status_t safecomm_saf_disconnect (safecomm_saf_conn_t *conn)
safecomm_saf_state_t safecomm_saf_state (const safecomm_saf_conn_t *conn)
 Current state (for logging / tests). Returns IDLE for NULL.
safecomm_status_t safecomm_saf_export_session_state (const safecomm_saf_conn_t *conn, safecomm_session_vital_state_t *out_state)
 Export vital session state for warm/hot standby synchronization.
safecomm_status_t safecomm_saf_import_session_state (safecomm_saf_conn_t *conn, const safecomm_session_vital_state_t *in_state)
 Import vital session state upon warm-standby takeover or channel resynchronization.

Detailed Description

Safety Functional (SaF) sub-layer state machine.

PDU layouts and the MAC input construction follow Subset-037-2 ("EuroRadio FIS - Safety Layer") v4.0.0 §6.2.5 (frame structure) and §6.2.3.2.1/6.2.3.2.2 (message-origin-authentication / peer-entity-authentication MAC inputs), cross-checked against the worked CBC-MAC example in that document's Annex B.

One deliberate addition beyond the official SL: the official Sa-DATA (DT) PDU carries no sequence number (Subset-037-2 Table 12) — replay/deletion/ re-ordering defence there is the job of the Subset-098 SAI sub-layer, which this library does not implement. In its place this module puts a 4-byte sequence number inside the MAC-covered SaUD area (immediately before the user PDU) so a frame still carries some ordering defence end to end. This is a project-specific extension, not part of Subset-037-2's own PDU, and is not itself conformance-tested; see docs/safety/SUBSET_MAPPING.md.

Implements:

  • AU1/AU2/AU3/AR mutual authentication handshake
  • Sa-DATA framing: MAC-protected, with the sequence extension above
  • 2oo2 split-send (prepare_send / commit_send) for hot redundancy
  • Vital session state checkpoint export/import for warm/hot failover
  • Sa-DISCONNECT teardown (unauthenticated, per Subset-037-2 §6.2.3.3.3.2: "the authentication of the connection release phase is not required")

Definition in file saf.c.

Macro Definition Documentation

◆ MTI_AU1

#define MTI_AU1   0x1U

Definition at line 32 of file saf.c.

◆ MTI_AU2

#define MTI_AU2   0x2U

Definition at line 33 of file saf.c.

◆ MTI_AU3

#define MTI_AU3   0x3U

Definition at line 34 of file saf.c.

◆ MTI_DT

#define MTI_DT   0x5U

Definition at line 35 of file saf.c.

◆ MTI_DI

#define MTI_DI   0x8U

Definition at line 36 of file saf.c.

◆ MTI_AR

#define MTI_AR   0x9U

Definition at line 37 of file saf.c.

◆ SAF_SEQ_WINDOW

#define SAF_SEQ_WINDOW   8U

Definition at line 39 of file saf.c.

Function Documentation

◆ pack_id24()

void pack_id24 ( uint32_t id,
uint8_t buf[3] )
static

Definition at line 43 of file saf.c.

◆ unpack_id24()

uint32_t unpack_id24 ( const uint8_t buf[3])
static

Definition at line 50 of file saf.c.

◆ pack_header()

uint8_t pack_header ( uint8_t ety,
uint8_t mti,
uint8_t df )
static

Definition at line 56 of file saf.c.

◆ header_mti()

uint8_t header_mti ( uint8_t header)
static

Definition at line 61 of file saf.c.

◆ header_df()

uint8_t header_df ( uint8_t header)
static

Definition at line 66 of file saf.c.

◆ pack_u32_be()

void pack_u32_be ( uint32_t val,
uint8_t buf[4] )
static

Definition at line 71 of file saf.c.

◆ unpack_u32_be()

uint32_t unpack_u32_be ( const uint8_t buf[4])
static

Definition at line 79 of file saf.c.

◆ generate_random_bytes()

void generate_random_bytes ( uint8_t * out,
size_t len,
uint32_t seed_hint )
static

Definition at line 84 of file saf.c.

◆ mac_over_da_body()

safecomm_status_t mac_over_da_body ( const safecomm_mac_ctx_t * mac,
uint32_t da_id,
const uint8_t * body,
size_t body_len,
uint8_t out_mac[SAFECOMM_MAC_LEN] )
static

Build [length(2, BE) | DA(3) | body] and MAC it (Subset-037-2 §6.2.3.2.1.1 "Procedure 1"). safecomm_mac_compute() applies the ISO/IEC 9797-1 Method 1 zero-padding itself, so the caller need only supply the unpadded bytes.

Definition at line 107 of file saf.c.

◆ safecomm_saf_init()

safecomm_status_t safecomm_saf_init ( safecomm_saf_conn_t * conn,
const safecomm_saf_config_t * cfg,
const safecomm_transport_t * transport )

Initialise a SaF connection object (no I/O yet).

Parameters
conncaller-allocated, will be fully overwritten. Non-NULL.
cfgstatic configuration, copied. Non-NULL.
transportfully-populated transport, copied. Non-NULL, .send/.recv non-NULL.
Returns
SAFECOMM_STATUS_OK / _INVALID_PARAM / _NOT_IMPLEMENTED.
Note
REQ-SC-SAF-002.

Definition at line 125 of file saf.c.

◆ connect_expired()

bool connect_expired ( const safecomm_saf_conn_t * conn,
uint32_t start_ms )
static

Definition at line 142 of file saf.c.

◆ responder_process_au1()

safecomm_status_t responder_process_au1 ( safecomm_saf_conn_t * conn,
const uint8_t * au1,
size_t au1_len,
const uint8_t rb[SAFECOMM_MAC_RAND_LEN],
uint8_t * out_au2 )
static

Definition at line 149 of file saf.c.

◆ responder_process_au3()

safecomm_status_t responder_process_au3 ( safecomm_saf_conn_t * conn,
const uint8_t * au3,
size_t au3_len,
uint8_t * out_ar )
static

Definition at line 200 of file saf.c.

◆ safecomm_saf_connect()

safecomm_status_t safecomm_saf_connect ( safecomm_saf_conn_t * conn)

Run the AU1/AU2/AU3 handshake to completion (blocking).

Parameters
connan initialised, IDLE connection. Non-NULL.
Returns
SAFECOMM_STATUS_OK (now in DATA), _AUTH_FAILED, _TIMEOUT, _TRANSPORT, _STATE, or _NOT_IMPLEMENTED.
Note
REQ-SC-SAF-010. When cfg.t_connect_ms is non-zero the whole handshake must finish within it on the clock supplied through safecomm_saf_set_time() (the transport must keep that clock current while it blocks); otherwise the result is _TIMEOUT and the connection returns to IDLE (REQ-SC-SAF-022).

Definition at line 241 of file saf.c.

◆ safecomm_saf_frame_kind()

safecomm_saf_frame_kind_t safecomm_saf_frame_kind ( const uint8_t * frame,
size_t frame_len )

Classify a received frame by its header MTI (no MAC check).

Returns
the frame kind; SAFECOMM_SAF_FRAME_UNKNOWN for NULL, empty or an unknown MTI.
Note
REQ-SC-SAF-040.

Definition at line 454 of file saf.c.

◆ safecomm_saf_au1_fields()

safecomm_status_t safecomm_saf_au1_fields ( const uint8_t * au1,
size_t au1_len,
uint32_t * out_peer_id,
uint8_t out_ra[SAFECOMM_MAC_RAND_LEN] )

Read the caller's ETCS identity and initiator random RA out of an AU1 frame (no MAC exists on AU1). A responder that serves many peers uses the identity to pick the peer's KMAC and configure the connection, and RA to pair a shared RB with the handshake it belongs to.

Parameters
au1the received frame. Non-NULL.
au1_lenits length; must be SAFECOMM_SAF_AU1_LEN.
out_peer_idreceives the 24-bit source ETCS identity. Non-NULL.
out_rareceives SAFECOMM_MAC_RAND_LEN bytes. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _INVALID_PARAM / _AUTH_FAILED (not a well-formed AU1).
Note
REQ-SC-SAF-041.

Definition at line 474 of file saf.c.

◆ safecomm_saf_responder_on_au1()

safecomm_status_t safecomm_saf_responder_on_au1 ( safecomm_saf_conn_t * conn,
const uint8_t * au1,
size_t au1_len,
const uint8_t rb[SAFECOMM_MAC_RAND_LEN],
uint8_t * out_au2,
size_t out_cap,
size_t * out_len )

Responder: accept AU1, derive the session key from RA and the caller's rb, and build AU2.

Parameters
connan initialised RESPONDER connection, in any state (a new AU1 restarts the connection). Non-NULL.
au1the received AU1. Non-NULL.
au1_lenits length.
rbthe responder random, SAFECOMM_MAC_RAND_LEN bytes from a secure source. Non-NULL.
out_au2receives AU2 to transmit. Non-NULL.
out_capcapacity of out_au2, >= SAFECOMM_SAF_AU2_LEN.
out_lenreceives SAFECOMM_SAF_AU2_LEN. Non-NULL.
Returns
SAFECOMM_STATUS_OK (now WAIT_AU3; the connect budget starts at the current now_ms), _AUTH_FAILED (malformed AU1 or unexpected peer identity; now IDLE), _STATE (not a responder), _BUFFER_TOO_SMALL, _INVALID_PARAM.
Note
REQ-SC-SAF-042.

Definition at line 490 of file saf.c.

◆ safecomm_saf_responder_on_au3()

safecomm_status_t safecomm_saf_responder_on_au3 ( safecomm_saf_conn_t * conn,
const uint8_t * au3,
size_t au3_len,
uint8_t * out_ar,
size_t out_cap,
size_t * out_len )

Responder: verify AU3 and build the AR confirmation; the connection enters DATA.

Parameters
conna RESPONDER connection in WAIT_AU3. Non-NULL.
au3the received AU3. Non-NULL.
au3_lenits length.
out_arreceives AR to transmit. Non-NULL.
out_capcapacity of out_ar, >= SAFECOMM_SAF_AR_LEN.
out_lenreceives SAFECOMM_SAF_AR_LEN. Non-NULL.
Returns
SAFECOMM_STATUS_OK (now DATA), _AUTH_FAILED (bad AU3 or MAC; now IDLE), _TIMEOUT (cfg.t_connect_ms elapsed since AU1 on the safecomm_saf_set_time() clock; now IDLE), _STATE, _BUFFER_TOO_SMALL, _INVALID_PARAM.
Note
REQ-SC-SAF-043.

Definition at line 519 of file saf.c.

◆ safecomm_saf_frame_sent()

safecomm_status_t safecomm_saf_frame_sent ( safecomm_saf_conn_t * conn)

Record that the caller transmitted the frame from safecomm_saf_prepare_send() itself: advances the send sequence exactly as safecomm_saf_commit_send() does after a successful transport send.

Returns
SAFECOMM_STATUS_OK / _STATE (not in DATA) / _INVALID_PARAM.
Note
REQ-SC-SAF-045.

Definition at line 549 of file saf.c.

◆ safecomm_saf_disconnect_frame()

safecomm_status_t safecomm_saf_disconnect_frame ( safecomm_saf_role_t role,
uint8_t * out,
size_t out_cap,
size_t * out_len )

Build a Sa-DISCONNECT (reason 0, sub-reason 0) for a side of the given role, without any connection state - e.g. a responder releasing a Sa-DATA frame that belongs to no connection it holds (the peer's session is stale, typically after this node restarted), so the peer reconnects at once instead of waiting out its supervision time. Unauthenticated, as every Sa-DISCONNECT (Subset-037-2 §6.2.3.3.3.2).

Parameters
rolethe sending side's role (sets the direction flag).
outreceives SAFECOMM_SAF_DI_LEN bytes. Non-NULL.
out_capcapacity of out.
out_lenreceives SAFECOMM_SAF_DI_LEN. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _BUFFER_TOO_SMALL / _INVALID_PARAM.
Note
REQ-SC-SAF-046.

Definition at line 563 of file saf.c.

◆ safecomm_saf_prepare_send()

safecomm_status_t safecomm_saf_prepare_send ( safecomm_saf_conn_t * conn,
const uint8_t * pdu,
size_t pdu_len,
uint8_t * out_frame,
size_t out_frame_cap,
size_t * out_frame_len )

Prepare and MAC-protect one Sa-DATA frame without transmitting it (for 2oo2 cross-checking).

Parameters
connconnection in DATA. Non-NULL.
pduuser payload bytes. Non-NULL.
pdu_lenpayload length.
out_framereceives the fully formatted on-the-wire frame. Non-NULL.
out_frame_capcapacity of out_frame.
out_frame_lenreceives total length of the frame. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _STATE / _BUFFER_TOO_SMALL / _INVALID_PARAM.

Definition at line 581 of file saf.c.

◆ safecomm_saf_commit_send()

safecomm_status_t safecomm_saf_commit_send ( safecomm_saf_conn_t * conn,
const uint8_t * frame,
size_t frame_len )

Transmit an already prepared and cross-compared Sa-DATA frame.

Parameters
connconnection in DATA. Non-NULL.
frameframe from safecomm_saf_prepare_send(). Non-NULL.
frame_lenframe length.
Returns
SAFECOMM_STATUS_OK / _TRANSPORT / _STATE / _INVALID_PARAM.

Definition at line 623 of file saf.c.

◆ safecomm_saf_send()

safecomm_status_t safecomm_saf_send ( safecomm_saf_conn_t * conn,
const uint8_t * pdu,
size_t pdu_len )

Send one user PDU as a MAC-protected, sequence-numbered Sa-DATA.

Parameters
conna connection in DATA. Non-NULL.
pduuser bytes. Non-NULL when pdu_len > 0.
pdu_len1..SAFECOMM_SAF_MAX_PDU.
Returns
SAFECOMM_STATUS_OK / _STATE / _INVALID_PARAM / _TRANSPORT / _BUFFER_TOO_SMALL / _NOT_IMPLEMENTED.
Note
REQ-SC-SAF-015.

Definition at line 643 of file saf.c.

◆ safecomm_saf_recv()

safecomm_status_t safecomm_saf_recv ( safecomm_saf_conn_t * conn,
uint8_t * out,
size_t out_cap,
size_t * out_len )

Receive and verify one Sa-DATA PDU (blocking).

Parameters
conna connection in DATA. Non-NULL.
outdestination for the user bytes. Non-NULL.
out_capcapacity of out.
out_lenreceives the user-byte count. Non-NULL.
Returns
SAFECOMM_STATUS_OK, _MAC_MISMATCH, _SEQ_ERROR, _TIMEOUT, _PEER_DISCONNECT, _BUFFER_TOO_SMALL, _STATE, or _NOT_IMPLEMENTED.
Note
REQ-SC-SAF-018. A MAC or sequence failure drops the connection to IDLE. Any correctly-shaped Sa-DISCONNECT (right length and message type) is acted on (_PEER_DISCONNECT, IDLE) with no further check: Subset-037-2 §6.2.3.3.3.2 does not require release-phase authentication, since a forged one can only end the connection early (an availability effect), never inject or alter safety data (REQ-SC-SAF-023).

Definition at line 657 of file saf.c.

◆ safecomm_saf_accept_frame()

safecomm_status_t safecomm_saf_accept_frame ( safecomm_saf_conn_t * conn,
const uint8_t * frame,
size_t frame_len,
uint8_t * out,
size_t out_cap,
size_t * out_len )

Verify one received Sa-DATA or Sa-DISCONNECT frame (the checks of safecomm_saf_recv(), without the transport).

Returns
as safecomm_saf_recv().
Note
REQ-SC-SAF-044.

Definition at line 680 of file saf.c.

◆ safecomm_saf_set_time()

void safecomm_saf_set_time ( safecomm_saf_conn_t * conn,
uint32_t now_ms )

Orderly Sa-DISCONNECT, then return the object to IDLE.

Parameters
connany non-IDLE connection. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _NOT_IMPLEMENTED. Always ends IDLE.
Note
REQ-SC-SAF-020.

Set the current time for supervision. The library reads no clock (it must not call the OS); the caller passes a monotonic millisecond counter (wrap-around safe) before recv / supervise.

Note
REQ-SC-SAF-020.

Definition at line 761 of file saf.c.

◆ safecomm_saf_supervise()

safecomm_status_t safecomm_saf_supervise ( safecomm_saf_conn_t * conn)

Idle-link supervision (EN 50159 delay and loss): in DATA, if no valid frame arrived for cfg.t_supervision_ms the connection goes to IDLE and SAFECOMM_STATUS_TIMEOUT is returned. Call it cyclically. The first call arms the timer. t_supervision_ms == 0 disables it.

Returns
OK while supervised and alive; TIMEOUT when the link is declared lost; STATE if not in DATA; INVALID_PARAM.
Note
REQ-SC-SAF-021.

Definition at line 769 of file saf.c.

◆ safecomm_saf_disconnect()

safecomm_status_t safecomm_saf_disconnect ( safecomm_saf_conn_t * conn)

Definition at line 799 of file saf.c.

◆ safecomm_saf_state()

safecomm_saf_state_t safecomm_saf_state ( const safecomm_saf_conn_t * conn)

Current state (for logging / tests). Returns IDLE for NULL.

Definition at line 819 of file saf.c.

◆ safecomm_saf_export_session_state()

safecomm_status_t safecomm_saf_export_session_state ( const safecomm_saf_conn_t * conn,
safecomm_session_vital_state_t * out_state )

Export vital session state for warm/hot standby synchronization.

Parameters
connactive connection. Non-NULL.
out_statedestination buffer. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _INVALID_PARAM.

Definition at line 824 of file saf.c.

◆ safecomm_saf_import_session_state()

safecomm_status_t safecomm_saf_import_session_state ( safecomm_saf_conn_t * conn,
const safecomm_session_vital_state_t * in_state )

Import vital session state upon warm-standby takeover or channel resynchronization.

Parameters
conninitialised connection object. Non-NULL.
in_statecheckpointed state. Non-NULL.
Returns
SAFECOMM_STATUS_OK / _INVALID_PARAM.

Definition at line 846 of file saf.c.