SafTrainLink - Subset-037 SaF protection of a train sim's ERTMS traffic (the train = SaF initiator).
Python side of RBC_GP's train-link SaF termination (``ab_gp_saf_train.c``; root
``docs/architecture/SAFE_COMM_INTEGRATION.dox`` plan steps 2/3). Wraps either transport a train sim uses
(``DualHomedLink`` or ``SafeCommLink``) and keeps their ``ensure_connected()`` / ``send_all()`` / ``poll_recv()``
surface, so ``train_sim`` only swaps the object:
* An outgoing ``RBC_MSG_ERTMS_ENVELOPE`` (kind 22: kind, train_id, codec bytes, zero padding) becomes one Sa-DATA
frame of that train's SaF connection, carried in an ``RBC_MSG_SAF_ENVELOPE`` (kind 28: kind, train_id, 2-byte
big-endian frame length, frame, zero padding). The PDU is exactly the codec bytes (``l_message`` long).
* The first ERTMS send for a train starts its handshake (AU1); PDUs queue until AR arrives, then go out in order.
* An incoming kind-28 frame is verified; a Sa-DATA PDU is handed to the caller as an ordinary kind-22 frame, so the
catalog decoder and everything above it are unchanged.
* Anything else (the flat P0/M136/TRAIN_CONNECT scheme) passes through untouched - the RBC side protects only the
ERTMS envelope too.
* A handshake past ``t_connect_ms`` or a DATA connection silent past ``t_supervision_ms`` is dropped; the next ERTMS
send starts a new handshake (this is also how a site failover is recovered: the newly ONLINE site has no session).
* A Sa-DISCONNECT from the RBC (it answers a Sa-DATA frame for a connection it no longer holds - e.g. its processes
restarted) drops the session at once: the PDUs sent in the last ``resend_window_ms`` were swallowed by the dead
session, so they are queued again and a new handshake starts immediately (a real train's application would redo
whatever the lost messages carried, e.g. Start of Mission).
Keys: ``keys`` maps a train's ETCS identity (its nid_engine) to its 24-byte KMAC - the same test-only key file the
RBC reads through ``RTE_SAFECOMM_TRAIN_KEYS_FILE``.