|
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
|
Safety Functional (SaF) sub-layer — shared by Subset-037 and Subset-098 (ERTMS/ETCS Subset-037 §7). More...
#include <stdbool.h>#include <stddef.h>#include <stdint.h>#include "safecomm/status.h"#include "safecomm/mac/mac.h"#include "safecomm/transport/transport.h"Go to the source code of this file.
Data Structures | |
| struct | safecomm_saf_config_t |
| Static configuration for one SaF connection. More... | |
| struct | safecomm_saf_conn_t |
| SaF connection object. Caller-allocated, zero-initialised, opaque in practice — fields are not part of the API contract. More... | |
| struct | safecomm_session_vital_state_t |
| Vital session state for 2oo2 hot redundancy and active/warm-standby failover. More... | |
Macros | |
| #define | SAFECOMM_SAF_MAX_PDU 1024U |
| Largest SaF user-data PDU this build accepts (bytes). | |
| #define | SAFECOMM_SAF_AU1_LEN 13U |
| Authentication-phase frame lengths (Subset-037-2 §6.2.5), bytes. | |
| #define | SAFECOMM_SAF_AU2_LEN 21U |
| #define | SAFECOMM_SAF_AU3_LEN 9U |
| #define | SAFECOMM_SAF_AR_LEN 9U |
| #define | SAFECOMM_SAF_DATA_OVERHEAD 13U |
| Sa-DATA framing overhead around the user PDU: header(1) + sequence(4) + MAC(8). | |
| #define | SAFECOMM_SAF_DI_LEN 3U |
| Sa-DISCONNECT frame length (header + reason + sub-reason). | |
Enumerations | |
| enum | safecomm_saf_frame_kind_t { SAFECOMM_SAF_FRAME_UNKNOWN = 0 , SAFECOMM_SAF_FRAME_AU1 , SAFECOMM_SAF_FRAME_AU2 , SAFECOMM_SAF_FRAME_AU3 , SAFECOMM_SAF_FRAME_DATA , SAFECOMM_SAF_FRAME_DISCONNECT , SAFECOMM_SAF_FRAME_AR } |
| Frame kind by its header MTI, for a caller that routes received frames itself. More... | |
| enum | safecomm_saf_role_t { SAFECOMM_SAF_ROLE_INITIATOR = 0 , SAFECOMM_SAF_ROLE_RESPONDER } |
| SaF connection role. More... | |
| enum | safecomm_saf_state_t { SAFECOMM_SAF_STATE_IDLE = 0 , SAFECOMM_SAF_STATE_WAIT_AU2 , SAFECOMM_SAF_STATE_WAIT_AU3 , SAFECOMM_SAF_STATE_DATA , SAFECOMM_SAF_STATE_DISCONNECTING } |
| SaF state-machine state (observable, for logging / tests). More... | |
Functions | |
| 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). | |
| safecomm_status_t | safecomm_saf_connect (safecomm_saf_conn_t *conn) |
| Run the AU1/AU2/AU3 handshake to completion (blocking). | |
| 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_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_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). | |
| 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). | |
| 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. | |
| 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. | |
Safety Functional (SaF) sub-layer — shared by Subset-037 and Subset-098 (ERTMS/ETCS Subset-037 §7).
The SaF sub-layer provides an authenticated, sequence-checked, supervised message channel over an unreliable safecomm_transport_t:
Sa-CONNECT establish + AU1/AU2/AU3 mutual authentication Sa-DATA send / receive a MAC-protected, sequence-numbered PDU Sa-DISCONNECT orderly teardown
State machine (Subset-037 §7.4): IDLE -> WAIT_AU2 -> WAIT_AU3 -> DATA -> DISCONNECTING -> IDLE with any state transitioning to IDLE on a supervision timeout or a transport / authentication failure.
REQ-SC-SAF-001..030 — see docs/requirements/SRS.md.
Definition in file saf.h.
| #define SAFECOMM_SAF_MAX_PDU 1024U |
| #define SAFECOMM_SAF_AU1_LEN 13U |
| #define SAFECOMM_SAF_DATA_OVERHEAD 13U |
| #define SAFECOMM_SAF_DI_LEN 3U |
| enum safecomm_saf_role_t |
| enum safecomm_saf_state_t |
| 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).
| conn | caller-allocated, will be fully overwritten. Non-NULL. |
| cfg | static configuration, copied. Non-NULL. |
| transport | fully-populated transport, copied. Non-NULL, .send/.recv non-NULL. |
| safecomm_status_t safecomm_saf_connect | ( | safecomm_saf_conn_t * | conn | ) |
Run the AU1/AU2/AU3 handshake to completion (blocking).
| conn | an initialised, IDLE connection. Non-NULL. |
| 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.
| conn | a connection in DATA. Non-NULL. |
| pdu | user bytes. Non-NULL when pdu_len > 0. |
| pdu_len | 1..SAFECOMM_SAF_MAX_PDU. |
| 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).
| conn | a connection in DATA. Non-NULL. |
| out | destination for the user bytes. Non-NULL. |
| out_cap | capacity of out. |
| out_len | receives the user-byte count. Non-NULL. |
| safecomm_saf_frame_kind_t safecomm_saf_frame_kind | ( | const uint8_t * | frame, |
| size_t | frame_len ) |
| 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.
| au1 | the received frame. Non-NULL. |
| au1_len | its length; must be SAFECOMM_SAF_AU1_LEN. |
| out_peer_id | receives the 24-bit source ETCS identity. Non-NULL. |
| out_ra | receives SAFECOMM_MAC_RAND_LEN bytes. Non-NULL. |
| 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.
| conn | an initialised RESPONDER connection, in any state (a new AU1 restarts the connection). Non-NULL. |
| au1 | the received AU1. Non-NULL. |
| au1_len | its length. |
| rb | the responder random, SAFECOMM_MAC_RAND_LEN bytes from a secure source. Non-NULL. |
| out_au2 | receives AU2 to transmit. Non-NULL. |
| out_cap | capacity of out_au2, >= SAFECOMM_SAF_AU2_LEN. |
| out_len | receives SAFECOMM_SAF_AU2_LEN. Non-NULL. |
| 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.
| conn | a RESPONDER connection in WAIT_AU3. Non-NULL. |
| au3 | the received AU3. Non-NULL. |
| au3_len | its length. |
| out_ar | receives AR to transmit. Non-NULL. |
| out_cap | capacity of out_ar, >= SAFECOMM_SAF_AR_LEN. |
| out_len | receives SAFECOMM_SAF_AR_LEN. Non-NULL. |
| 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).
| 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).
| role | the sending side's role (sets the direction flag). |
| out | receives SAFECOMM_SAF_DI_LEN bytes. Non-NULL. |
| out_cap | capacity of out. |
| out_len | receives SAFECOMM_SAF_DI_LEN. Non-NULL. |
| void safecomm_saf_set_time | ( | safecomm_saf_conn_t * | conn, |
| uint32_t | now_ms ) |
Orderly Sa-DISCONNECT, then return the object to IDLE.
| conn | any non-IDLE connection. Non-NULL. |
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.
| 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 | ) |
| safecomm_status_t safecomm_saf_export_session_state | ( | const safecomm_saf_conn_t * | conn, |
| safecomm_session_vital_state_t * | out_state ) |
| safecomm_status_t safecomm_saf_import_session_state | ( | safecomm_saf_conn_t * | conn, |
| const safecomm_session_vital_state_t * | in_state ) |
| 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).
| conn | connection in DATA. Non-NULL. |
| pdu | user payload bytes. Non-NULL. |
| pdu_len | payload length. |
| out_frame | receives the fully formatted on-the-wire frame. Non-NULL. |
| out_frame_cap | capacity of out_frame. |
| out_frame_len | receives total length of the frame. Non-NULL. |
| 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.
| conn | connection in DATA. Non-NULL. |
| frame | frame from safecomm_saf_prepare_send(). Non-NULL. |
| frame_len | frame length. |