RTE RBC 2oo2 Reference Application 0.1.0 build 0006685
SIL 4 2oo2 Dual-Site Fault-Tolerant ERTMS Radio Block Centre Reference Architecture
Loading...
Searching...
No Matches
ab_gp_saf_train.c
Go to the documentation of this file.
1
5#include "ab_gp_saf_train.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include "rte/oal/random/rte_random.h"
12#include "safecomm/euroradio/euroradio.h"
14
16#define AB_GP_SAF_ETY_RBC 1U
17
18typedef struct
19{
20 bool in_use;
21 uint32_t peer_etcs_id;
22 uint8_t kmac[SAFECOMM_MAC_KEY_LEN];
23} saf_key_t;
24
26
27static safecomm_status_t no_send(void *user, const uint8_t *buf, size_t len)
28{
29 (void)user;
30 (void)buf;
31 (void)len;
32 return SAFECOMM_STATUS_TRANSPORT;
33}
34
35static safecomm_status_t no_recv(void *user, uint8_t *buf, size_t cap, size_t *out_len)
36{
37 (void)user;
38 (void)buf;
39 (void)cap;
40 (void)out_len;
41 return SAFECOMM_STATUS_TRANSPORT;
42}
43
44static const saf_key_t *key_lookup(uint32_t peer_etcs_id)
45{
46 size_t i;
47
48 for (i = 0U; i < (size_t)AB_GP_SAF_KEYS_MAX; i++)
49 {
50 if (s_keys[i].in_use && (s_keys[i].peer_etcs_id == peer_etcs_id))
51 {
52 return &s_keys[i];
53 }
54 }
55 return NULL;
56}
57
58/* REQ-RBC-005: the index into slots[] by train_id for the frame, peer-RB and wrap paths; an out-of-range train_id
59 * returns NULL to the caller before it indexes slots[]. On the wire paths the caller turns NULL into a rejection
60 * that pre_execute.c logs ("frame rejected"); ab_gp_saf_train_wrap() returns false; its train_id was already
61 * range-checked by ab_gp_proc_dispatch_train_inbound(). */
63{
64 if ((s == NULL) || !s->enabled || (train_id < 1U) || (train_id > (uint8_t)RTE_EXAMPLE_MAX_TRAINS))
65 {
66 return NULL;
67 }
68 return &s->slots[train_id - 1U];
69}
70
71static void reject(ab_gp_saf_result_t *out, safecomm_status_t status)
72{
74 out->status = status;
75}
76
77/* Sets up a fresh responder connection for the train named in AU1 and answers it with the given RB. */
78static void respond_au1(ab_gp_saf_train_t *s, ab_gp_saf_train_slot_t *slot, const uint8_t *au1, const uint8_t *rb,
79 uint32_t now_ms, ab_gp_saf_result_t *out)
80{
81 uint32_t peer_id = 0U;
82 uint8_t ra[SAFECOMM_MAC_RAND_LEN];
83 safecomm_er_config_t er;
84 safecomm_saf_config_t cfg;
85 const safecomm_transport_t none = { no_send, no_recv, NULL, NULL };
86 const saf_key_t *key;
87 safecomm_status_t st;
88
89 st = safecomm_saf_au1_fields(au1, SAFECOMM_SAF_AU1_LEN, &peer_id, ra);
90 if (st != SAFECOMM_STATUS_OK)
91 {
92 reject(out, st);
93 return;
94 }
95 key = key_lookup(peer_id);
96 if (key == NULL)
97 {
98 reject(out, SAFECOMM_STATUS_AUTH_FAILED); /* no KMAC for this train: it cannot be authenticated */
99 return;
100 }
101 (void)memset(&er, 0, sizeof(er));
102 er.side = SAFECOMM_ER_SIDE_RBC;
103 (void)memcpy(er.kmac, key->kmac, sizeof(er.kmac));
104 er.etcs_id_self = s->rbc_etcs_id;
105 er.etcs_id_peer = peer_id;
106 (void)safecomm_er_make_saf_config(&er, &cfg);
107 cfg.ety = AB_GP_SAF_ETY_RBC;
108 (void)safecomm_saf_init(&slot->conn, &cfg, &none);
109 (void)memset(&cfg, 0, sizeof(cfg)); /* do not leave a KMAC copy on the stack */
110 safecomm_saf_set_time(&slot->conn, now_ms);
111 st = safecomm_saf_responder_on_au1(&slot->conn, au1, SAFECOMM_SAF_AU1_LEN, rb, out->frame, sizeof(out->frame),
112 &out->frame_len);
113 if (st != SAFECOMM_STATUS_OK)
114 {
115 reject(out, st);
116 return;
117 }
119 out->status = SAFECOMM_STATUS_OK;
120}
121
123{
124 (void)memset(s_keys, 0, sizeof(s_keys));
125}
126
127bool ab_gp_saf_keys_install(uint32_t peer_etcs_id, const uint8_t kmac[SAFECOMM_MAC_KEY_LEN])
128{
129 size_t i;
130 saf_key_t *slot = NULL;
131
132 if (kmac == NULL)
133 {
134 return false;
135 }
136 for (i = 0U; (i < (size_t)AB_GP_SAF_KEYS_MAX) && (slot == NULL); i++)
137 {
138 if (s_keys[i].in_use && (s_keys[i].peer_etcs_id == peer_etcs_id))
139 {
140 slot = &s_keys[i];
141 }
142 }
143 for (i = 0U; (i < (size_t)AB_GP_SAF_KEYS_MAX) && (slot == NULL); i++)
144 {
145 if (!s_keys[i].in_use)
146 {
147 slot = &s_keys[i];
148 }
149 }
150 if (slot == NULL)
151 {
152 return false;
153 }
154 slot->in_use = true;
155 slot->peer_etcs_id = peer_etcs_id;
156 (void)memcpy(slot->kmac, kmac, sizeof(slot->kmac));
157 return true;
158}
159
160static int hex_nibble(char c)
161{
162 if ((c >= '0') && (c <= '9'))
163 {
164 return c - '0';
165 }
166 if ((c >= 'a') && (c <= 'f'))
167 {
168 return (c - 'a') + 10;
169 }
170 if ((c >= 'A') && (c <= 'F'))
171 {
172 return (c - 'A') + 10;
173 }
174 return -1;
175}
176
177/* Parses one non-comment line "<id> <48 hex>"; false on any deviation. */
178static bool parse_key_line(char *line, uint32_t *out_id, uint8_t out_kmac[SAFECOMM_MAC_KEY_LEN])
179{
180 char *id_str = strtok(line, " \t\r\n");
181 char *hex = strtok(NULL, " \t\r\n");
182 char *extra = strtok(NULL, " \t\r\n");
183 char *end = NULL;
184 unsigned long id;
185 size_t i;
186
187 if ((id_str == NULL) || (hex == NULL) || (extra != NULL) || (strlen(hex) != (2U * SAFECOMM_MAC_KEY_LEN)))
188 {
189 return false;
190 }
191 id = strtoul(id_str, &end, 0);
192 if ((end == id_str) || (*end != '\0') || (id > 0xFFFFFFUL))
193 {
194 return false;
195 }
196 for (i = 0U; i < SAFECOMM_MAC_KEY_LEN; i++)
197 {
198 int hi = hex_nibble(hex[2U * i]);
199 int lo = hex_nibble(hex[(2U * i) + 1U]);
200
201 if ((hi < 0) || (lo < 0))
202 {
203 return false;
204 }
205 out_kmac[i] = (uint8_t)((hi << 4) | lo);
206 }
207 *out_id = (uint32_t)id;
208 return true;
209}
210
211bool ab_gp_saf_keys_load_file(const char *path, uint32_t *out_count)
212{
213 FILE *fp;
214 char line[160];
215 bool ok = true;
216
217 if ((path == NULL) || (out_count == NULL))
218 {
219 return false;
220 }
221 *out_count = 0U;
222 fp = fopen(path, "r");
223 if (fp == NULL)
224 {
225 return false;
226 }
227 while (ok && (fgets(line, (int)sizeof(line), fp) != NULL))
228 {
229 char *hash = strchr(line, '#');
230 char *p = line;
231 uint32_t id = 0U;
232 uint8_t kmac[SAFECOMM_MAC_KEY_LEN];
233
234 if (hash != NULL)
235 {
236 *hash = '\0';
237 }
238 while ((*p == ' ') || (*p == '\t') || (*p == '\r') || (*p == '\n'))
239 {
240 p++;
241 }
242 if (*p == '\0')
243 {
244 continue;
245 }
246 ok = parse_key_line(p, &id, kmac) && ab_gp_saf_keys_install(id, kmac);
247 (void)memset(kmac, 0, sizeof(kmac));
248 if (ok)
249 {
250 (*out_count)++;
251 }
252 }
253 (void)memset(line, 0, sizeof(line));
254 (void)fclose(fp);
255 return ok;
256}
257
258void ab_gp_saf_train_init(ab_gp_saf_train_t *s, bool enabled, bool is_role_a, uint32_t rbc_etcs_id)
259{
260 if (s == NULL)
261 {
262 return;
263 }
264 (void)memset(s, 0, sizeof(*s));
265 s->enabled = enabled;
266 s->is_role_a = is_role_a;
267 s->rbc_etcs_id = rbc_etcs_id;
268}
269
270void ab_gp_saf_train_on_frame(ab_gp_saf_train_t *s, uint8_t train_id, const uint8_t *frame, size_t frame_len,
271 uint32_t now_ms, ab_gp_saf_result_t *out)
272{
274 safecomm_status_t st;
275
276 if (out == NULL)
277 {
278 return;
279 }
280 (void)memset(out, 0, sizeof(*out));
281 slot = slot_of(s, train_id);
282 if ((slot == NULL) || (frame == NULL) || (frame_len == 0U))
283 {
284 reject(out, SAFECOMM_STATUS_INVALID_PARAM);
285 return;
286 }
287 safecomm_saf_set_time(&slot->conn, now_ms);
288
289 switch (safecomm_saf_frame_kind(frame, frame_len))
290 {
291 case SAFECOMM_SAF_FRAME_AU1:
292 {
293 uint32_t peer_id = 0U;
294 uint8_t ra[SAFECOMM_MAC_RAND_LEN];
295
296 st = safecomm_saf_au1_fields(frame, frame_len, &peer_id, ra);
297 if (st != SAFECOMM_STATUS_OK)
298 {
299 reject(out, st);
300 return;
301 }
302 if (s->is_role_a)
303 {
304 uint8_t rb[SAFECOMM_MAC_RAND_LEN];
305
306 if (rte_random_fill(rb, sizeof(rb)) != RTE_STATUS_OK)
307 {
308 reject(out, SAFECOMM_STATUS_NOT_INITIALISED); /* no secure random source: never answer */
309 return;
310 }
311 respond_au1(s, slot, frame, rb, now_ms, out);
312 if (out->action == AB_GP_SAF_ACTION_REPLY)
313 {
314 out->share_rb = true;
315 out->rb_share[0] = train_id;
316 (void)memcpy(&out->rb_share[1], ra, SAFECOMM_MAC_RAND_LEN);
317 (void)memcpy(&out->rb_share[1U + SAFECOMM_MAC_RAND_LEN], rb, SAFECOMM_MAC_RAND_LEN);
318 }
319 (void)memset(rb, 0, sizeof(rb));
320 return;
321 }
322 if (slot->rb_parked && (memcmp(slot->rb_ra, ra, sizeof(ra)) == 0))
323 {
324 slot->rb_parked = false;
325 respond_au1(s, slot, frame, slot->rb, now_ms, out);
326 return;
327 }
328 (void)memcpy(slot->au1, frame, SAFECOMM_SAF_AU1_LEN);
329 slot->au1_parked = true;
331 return;
332 }
333 case SAFECOMM_SAF_FRAME_AU3:
334 st = safecomm_saf_responder_on_au3(&slot->conn, frame, frame_len, out->frame, sizeof(out->frame),
335 &out->frame_len);
336 if (st != SAFECOMM_STATUS_OK)
337 {
338 reject(out, st);
339 return;
340 }
342 return;
343 case SAFECOMM_SAF_FRAME_DATA:
344 case SAFECOMM_SAF_FRAME_DISCONNECT:
345 st = safecomm_saf_accept_frame(&slot->conn, frame, frame_len, out->app, sizeof(out->app), &out->app_len);
346 if (st == SAFECOMM_STATUS_OK)
347 {
349 }
350 else if (st == SAFECOMM_STATUS_PEER_DISCONNECT)
351 {
353 }
354 else if ((st == SAFECOMM_STATUS_STATE) && (safecomm_saf_state(&slot->conn) == SAFECOMM_SAF_STATE_IDLE) &&
355 (safecomm_saf_frame_kind(frame, frame_len) == SAFECOMM_SAF_FRAME_DATA) &&
356 (safecomm_saf_disconnect_frame(SAFECOMM_SAF_ROLE_RESPONDER, out->frame, sizeof(out->frame),
357 &out->frame_len) == SAFECOMM_STATUS_OK))
358 {
359 /* Sa-DATA for a connection this channel does not hold (typically: this process restarted under a
360 * running train). Release it so the train reconnects now rather than after its supervision time. The
361 * data itself is still dropped. Not while a handshake is in progress: a stale frame must not abort
362 * the new connection. */
364 }
365 else
366 {
367 reject(out, st);
368 }
369 out->status = st;
370 return;
371 default:
372 reject(out, SAFECOMM_STATUS_STATE);
373 return;
374 }
375}
376
377void ab_gp_saf_train_on_peer_rb(ab_gp_saf_train_t *s, const uint8_t *payload, size_t size, uint32_t now_ms,
379{
381 const uint8_t *ra;
382 const uint8_t *rb;
383
384 if (out == NULL)
385 {
386 return;
387 }
388 (void)memset(out, 0, sizeof(*out));
389 if ((s == NULL) || s->is_role_a || (payload == NULL) || (size < (size_t)AB_GP_SAF_PEER_RB_SIZE))
390 {
391 reject(out, SAFECOMM_STATUS_INVALID_PARAM);
392 return;
393 }
394 slot = slot_of(s, payload[0]);
395 if (slot == NULL)
396 {
397 reject(out, SAFECOMM_STATUS_INVALID_PARAM);
398 return;
399 }
400 ra = &payload[1];
401 rb = &payload[1U + SAFECOMM_MAC_RAND_LEN];
402 if (slot->au1_parked && (memcmp(&slot->au1[5], ra, SAFECOMM_MAC_RAND_LEN) == 0))
403 {
404 slot->au1_parked = false;
405 respond_au1(s, slot, slot->au1, rb, now_ms, out);
406 return;
407 }
408 (void)memcpy(slot->rb_ra, ra, SAFECOMM_MAC_RAND_LEN);
409 (void)memcpy(slot->rb, rb, SAFECOMM_MAC_RAND_LEN);
410 slot->rb_parked = true;
412}
413
414bool ab_gp_saf_train_wrap(ab_gp_saf_train_t *s, uint8_t train_id, const uint8_t *pdu, size_t pdu_len,
415 uint8_t *out_frame, size_t out_cap, size_t *out_len)
416{
417 ab_gp_saf_train_slot_t *slot = slot_of(s, train_id);
418
419 if ((slot == NULL) || (pdu == NULL) || (pdu_len > (size_t)AB_GP_SAF_PDU_MAX) || (out_frame == NULL) ||
420 (out_len == NULL))
421 {
422 return false;
423 }
424 if (safecomm_saf_prepare_send(&slot->conn, pdu, pdu_len, out_frame, out_cap, out_len) != SAFECOMM_STATUS_OK)
425 {
426 return false;
427 }
428 return safecomm_saf_frame_sent(&slot->conn) == SAFECOMM_STATUS_OK;
429}
430
431uint32_t ab_gp_saf_train_supervise(ab_gp_saf_train_t *s, uint32_t now_ms)
432{
433 uint32_t lost = 0U;
434 uint8_t i;
435
436 if ((s == NULL) || !s->enabled)
437 {
438 return 0U;
439 }
440 for (i = 0U; i < (uint8_t)RTE_EXAMPLE_MAX_TRAINS; i++)
441 {
442 safecomm_saf_conn_t *conn = &s->slots[i].conn;
443
444 if (safecomm_saf_state(conn) == SAFECOMM_SAF_STATE_DATA)
445 {
446 safecomm_saf_set_time(conn, now_ms);
447 if (safecomm_saf_supervise(conn) == SAFECOMM_STATUS_TIMEOUT)
448 {
449 lost |= (uint32_t)1U << i;
450 }
451 }
452 }
453 return lost;
454}
455
456bool ab_gp_saf_train_is_connected(const ab_gp_saf_train_t *s, uint8_t train_id)
457{
458 /* REQ-RBC-005: an out-of-range train_id returns false to the caller before it indexes slots[]. */
459 if ((s == NULL) || !s->enabled || (train_id < 1U) || (train_id > (uint8_t)RTE_EXAMPLE_MAX_TRAINS))
460 {
461 return false;
462 }
463 return safecomm_saf_state(&s->slots[train_id - 1U].conn) == SAFECOMM_SAF_STATE_DATA;
464}
465
466bool ab_gp_saf_envelope_encode(uint8_t train_id, const uint8_t *frame, size_t frame_len,
467 uint8_t out[RBC_TRAIN_ENVELOPE_WIRE_SIZE])
468{
469 if ((frame == NULL) || (out == NULL) || (frame_len == 0U) || (frame_len > (size_t)AB_GP_SAF_FRAME_MAX))
470 {
471 return false;
472 }
473 (void)memset(out, 0, (size_t)RBC_TRAIN_ENVELOPE_WIRE_SIZE);
474 out[0] = (uint8_t)RBC_MSG_SAF_ENVELOPE;
475 out[1] = train_id;
476 out[2] = (uint8_t)((frame_len >> 8U) & 0xFFU);
477 out[3] = (uint8_t)(frame_len & 0xFFU);
478 (void)memcpy(&out[AB_GP_SAF_ENVELOPE_HEADER], frame, frame_len);
479 return true;
480}
481
482bool ab_gp_saf_envelope_decode(const uint8_t env[RBC_TRAIN_ENVELOPE_WIRE_SIZE], uint8_t *out_train_id,
483 const uint8_t **out_frame, size_t *out_len)
484{
485 size_t len;
486
487 if ((env == NULL) || (out_train_id == NULL) || (out_frame == NULL) || (out_len == NULL) ||
488 (env[0] != (uint8_t)RBC_MSG_SAF_ENVELOPE))
489 {
490 return false;
491 }
492 len = ((size_t)env[2] << 8U) | (size_t)env[3];
493 if ((len == 0U) || (len > (size_t)AB_GP_SAF_FRAME_MAX) || (env[1] < 1U) ||
494 (env[1] > (uint8_t)RTE_EXAMPLE_MAX_TRAINS))
495 {
496 return false;
497 }
498 *out_train_id = env[1];
499 *out_frame = &env[AB_GP_SAF_ENVELOPE_HEADER];
500 *out_len = len;
501 return true;
502}
static const saf_key_t * key_lookup(uint32_t peer_etcs_id)
bool ab_gp_saf_envelope_decode(const uint8_t env[RBC_TRAIN_ENVELOPE_WIRE_SIZE], uint8_t *out_train_id, const uint8_t **out_frame, size_t *out_len)
Splits a received RBC_MSG_SAF_ENVELOPE relay frame.
void ab_gp_saf_keys_clear(void)
Empties the KMAC table (process-global, shared by every slot).
static saf_key_t s_keys[AB_GP_SAF_KEYS_MAX]
void ab_gp_saf_train_on_peer_rb(ab_gp_saf_train_t *s, const uint8_t *payload, size_t size, uint32_t now_ms, ab_gp_saf_result_t *out)
B only: processes A's PEER_MSG_KIND_SAF_RB payload; answers the parked AU1 when its RA matches.
static int hex_nibble(char c)
static bool parse_key_line(char *line, uint32_t *out_id, uint8_t out_kmac[SAFECOMM_MAC_KEY_LEN])
static void respond_au1(ab_gp_saf_train_t *s, ab_gp_saf_train_slot_t *slot, const uint8_t *au1, const uint8_t *rb, uint32_t now_ms, ab_gp_saf_result_t *out)
bool ab_gp_saf_envelope_encode(uint8_t train_id, const uint8_t *frame, size_t frame_len, uint8_t out[RBC_TRAIN_ENVELOPE_WIRE_SIZE])
Builds an RBC_MSG_SAF_ENVELOPE relay frame (RBC_TRAIN_ENVELOPE_WIRE_SIZE bytes).
bool ab_gp_saf_train_is_connected(const ab_gp_saf_train_t *s, uint8_t train_id)
True if train slot train_id has a connection in DATA.
bool ab_gp_saf_train_wrap(ab_gp_saf_train_t *s, uint8_t train_id, const uint8_t *pdu, size_t pdu_len, uint8_t *out_frame, size_t out_cap, size_t *out_len)
Protects one outbound application PDU for train slot train_id (only in DATA).
static void reject(ab_gp_saf_result_t *out, safecomm_status_t status)
static safecomm_status_t no_recv(void *user, uint8_t *buf, size_t cap, size_t *out_len)
uint32_t ab_gp_saf_train_supervise(ab_gp_saf_train_t *s, uint32_t now_ms)
Idle-link supervision of every connection in DATA (REQ-SC-004).
void ab_gp_saf_train_on_frame(ab_gp_saf_train_t *s, uint8_t train_id, const uint8_t *frame, size_t frame_len, uint32_t now_ms, ab_gp_saf_result_t *out)
Processes one SaF frame received from train slot train_id (1..RTE_EXAMPLE_MAX_TRAINS).
#define AB_GP_SAF_ETY_RBC
bool ab_gp_saf_keys_load_file(const char *path, uint32_t *out_count)
Loads KMACs from a text file: one "<etcs_id> <48 hex chars>" per line (decimal or 0x identity),...
static safecomm_status_t no_send(void *user, const uint8_t *buf, size_t len)
bool ab_gp_saf_keys_install(uint32_t peer_etcs_id, const uint8_t kmac[SAFECOMM_MAC_KEY_LEN])
Installs (or replaces) the KMAC for one train ETCS identity.
static ab_gp_saf_train_slot_t * slot_of(ab_gp_saf_train_t *s, uint8_t train_id)
void ab_gp_saf_train_init(ab_gp_saf_train_t *s, bool enabled, bool is_role_a, uint32_t rbc_etcs_id)
Resets s. enabled false makes every other call a no-op / rejection.
Train link safe communication (Subset-037 SaF) terminated inside A and B - REQ-SC-001,...
#define AB_GP_SAF_FRAME_MAX
Largest SaF frame one Train relay envelope carries.
#define AB_GP_SAF_ENVELOPE_HEADER
Envelope prefix: kind, train_id, 2-byte frame length.
#define AB_GP_SAF_KEYS_MAX
Capacity of the KMAC table (one entry per train ETCS identity).
#define AB_GP_SAF_PDU_MAX
Largest application PDU (ERTMS codec bytes) one protected frame carries.
@ AB_GP_SAF_ACTION_REJECTED
@ AB_GP_SAF_ACTION_APP
@ AB_GP_SAF_ACTION_REPLY
@ AB_GP_SAF_ACTION_DISCONNECTED
@ AB_GP_SAF_ACTION_NONE
#define AB_GP_SAF_PEER_RB_SIZE
PEER_MSG_KIND_SAF_RB payload: train_id(1) + RA(8) + RB(8).
#define RBC_TRAIN_ENVELOPE_WIRE_SIZE
#define RTE_EXAMPLE_MAX_TRAINS
The RBC Train/IL/CTC scenario's shared wire envelope (ADR-029) - one fixed-size struct/frame reused f...
@ RBC_MSG_SAF_ENVELOPE
Output of ab_gp_saf_train_on_frame() / _on_peer_rb().
safecomm_status_t status
uint8_t app[AB_GP_SAF_PDU_MAX]
uint8_t frame[SAFECOMM_SAF_AU2_LEN]
uint8_t rb_share[AB_GP_SAF_PEER_RB_SIZE]
ab_gp_saf_action_t action
Per-train-slot state.
uint8_t rb_ra[SAFECOMM_MAC_RAND_LEN]
uint8_t au1[SAFECOMM_SAF_AU1_LEN]
safecomm_saf_conn_t conn
uint8_t rb[SAFECOMM_MAC_RAND_LEN]
One channel's train-link SaF termination.
ab_gp_saf_train_slot_t slots[RTE_EXAMPLE_MAX_TRAINS]
uint32_t peer_etcs_id
uint8_t kmac[SAFECOMM_MAC_KEY_LEN]