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
Go to the documentation of this file.
1
27#include "safecomm/saf/saf.h"
28
29#include <string.h>
30
31/* Message Type Identifiers, Subset-037-2 Table 7 */
32#define MTI_AU1 0x1U
33#define MTI_AU2 0x2U
34#define MTI_AU3 0x3U
35#define MTI_DT 0x5U
36#define MTI_DI 0x8U
37#define MTI_AR 0x9U
38
39#define SAF_SEQ_WINDOW 8U
40
41/* -- ETCS ID (24-bit) and header-octet (ETY|MTI|DF) packing, Subset-037-2 §6.2.5.1/§6.2.5.2 -- */
42
43static void pack_id24(uint32_t id, uint8_t buf[3])
44{
45 buf[0] = (uint8_t)((id >> 16U) & 0xFFU);
46 buf[1] = (uint8_t)((id >> 8U) & 0xFFU);
47 buf[2] = (uint8_t)(id & 0xFFU);
48}
49
50static uint32_t unpack_id24(const uint8_t buf[3])
51{
52 return ((uint32_t)buf[0] << 16U) | ((uint32_t)buf[1] << 8U) | (uint32_t)buf[2];
53}
54
55/* header = ETY(bits8-6) | MTI(bits5-2) | DF(bit1), Table 8 et al. */
56static uint8_t pack_header(uint8_t ety, uint8_t mti, uint8_t df)
57{
58 return (uint8_t)(((ety & 0x07U) << 5U) | ((mti & 0x0FU) << 1U) | (df & 0x01U));
59}
60
61static uint8_t header_mti(uint8_t header)
62{
63 return (uint8_t)((header >> 1U) & 0x0FU);
64}
65
66static uint8_t header_df(uint8_t header)
67{
68 return (uint8_t)(header & 0x01U);
69}
70
71static void pack_u32_be(uint32_t val, uint8_t buf[4])
72{
73 buf[0] = (uint8_t)((val >> 24U) & 0xFFU);
74 buf[1] = (uint8_t)((val >> 16U) & 0xFFU);
75 buf[2] = (uint8_t)((val >> 8U) & 0xFFU);
76 buf[3] = (uint8_t)(val & 0xFFU);
77}
78
79static uint32_t unpack_u32_be(const uint8_t buf[4])
80{
81 return ((uint32_t)buf[0] << 24U) | ((uint32_t)buf[1] << 16U) | ((uint32_t)buf[2] << 8U) | (uint32_t)buf[3];
82}
83
84static void generate_random_bytes(uint8_t *out, size_t len, uint32_t seed_hint)
85{
86 static uint32_t lcg_state = 0x12345678U;
87 if (seed_hint != 0U)
88 {
89 lcg_state ^= seed_hint;
90 }
91 for (size_t i = 0U; i < len; i++)
92 {
93 lcg_state = (lcg_state * 1664525U) + 1013904223U;
94 out[i] = (uint8_t)((lcg_state >> 16U) & 0xFFU);
95 if (out[i] == 0U)
96 {
97 out[i] = (uint8_t)(i + 1U);
98 }
99 }
100}
101
107static safecomm_status_t mac_over_da_body(const safecomm_mac_ctx_t *mac, uint32_t da_id,
108 const uint8_t *body, size_t body_len,
109 uint8_t out_mac[SAFECOMM_MAC_LEN])
110{
111 /* length(2) + DA(3) + the largest body: a Sa-DATA header(1) + sequence(4) + SAFECOMM_SAF_MAX_PDU. */
112 uint8_t buf[5U + 1U + 4U + SAFECOMM_SAF_MAX_PDU];
113 size_t l = 3U + body_len;
114 if ((body_len + 5U) > sizeof(buf))
115 {
117 }
118 buf[0] = (uint8_t)((l >> 8U) & 0xFFU);
119 buf[1] = (uint8_t)(l & 0xFFU);
120 pack_id24(da_id, &buf[2]);
121 (void)memcpy(&buf[5], body, body_len);
122 return safecomm_mac_compute(mac, buf, 5U + body_len, out_mac);
123}
124
126 const safecomm_saf_config_t *cfg,
127 const safecomm_transport_t *transport)
128{
129 if ((conn == NULL) || (cfg == NULL) || (transport == NULL)
130 || (transport->send == NULL) || (transport->recv == NULL))
131 {
133 }
134 (void)memset(conn, 0, sizeof(*conn));
135 conn->cfg = *cfg;
136 conn->transport = *transport;
137 conn->state = SAFECOMM_SAF_STATE_IDLE;
138 return SAFECOMM_STATUS_OK;
139}
140
141/* REQ-SC-SAF-022: connect budget on the caller-supplied clock (0 disables); wrap-around safe */
142static bool connect_expired(const safecomm_saf_conn_t *conn, uint32_t start_ms)
143{
144 return (conn->cfg.t_connect_ms != 0U) && ((uint32_t)(conn->now_ms - start_ms) > conn->cfg.t_connect_ms);
145}
146
147/* Responder, AU1 -> AU2 (Subset-037-2 Fig.6, us = A/called). Checks AU1, takes the caller's RB, derives KSMAC and
148 * builds AU2 into out_au2 (SAFECOMM_SAF_AU2_LEN bytes). Ends in WAIT_AU3, or IDLE on any failure. */
149static safecomm_status_t responder_process_au1(safecomm_saf_conn_t *conn, const uint8_t *au1, size_t au1_len,
150 const uint8_t rb[SAFECOMM_MAC_RAND_LEN], uint8_t *out_au2)
151{
153
154 conn->state = SAFECOMM_SAF_STATE_IDLE;
155 conn->supervision_armed = false;
156 if ((au1_len != SAFECOMM_SAF_AU1_LEN) || (header_mti(au1[0]) != MTI_AU1) || (header_df(au1[0]) != 0U))
157 {
159 }
160 if (unpack_id24(&au1[1]) != conn->cfg.etcs_id_peer)
161 {
163 }
164 (void)memcpy(conn->ra, &au1[5], SAFECOMM_MAC_RAND_LEN);
165 (void)memcpy(conn->rb, rb, SAFECOMM_MAC_RAND_LEN);
166
167 st = safecomm_mac_derive_ksmac(conn->cfg.kmac, conn->ra, conn->rb, &conn->mac);
168 if (st != SAFECOMM_STATUS_OK)
169 {
170 return st;
171 }
172
173 out_au2[0] = pack_header(conn->cfg.ety, MTI_AU2, 1U);
174 pack_id24(conn->cfg.etcs_id_self, &out_au2[1]);
175 out_au2[4] = 0x01U;
176 (void)memcpy(&out_au2[5], conn->rb, SAFECOMM_MAC_RAND_LEN);
177 {
178 uint8_t input[10 + SAFECOMM_MAC_RAND_LEN + SAFECOMM_MAC_RAND_LEN + 3];
179 size_t body_len = 5U;
180 size_t l = 3U + body_len;
181 input[0] = (uint8_t)((l >> 8U) & 0xFFU);
182 input[1] = (uint8_t)(l & 0xFFU);
183 pack_id24(conn->cfg.etcs_id_peer, &input[2]); /* DA = peer (receiver of AU2) */
184 (void)memcpy(&input[5], &out_au2[0], body_len);
185 (void)memcpy(&input[10], conn->rb, SAFECOMM_MAC_RAND_LEN);
186 (void)memcpy(&input[18], conn->ra, SAFECOMM_MAC_RAND_LEN);
187 pack_id24(conn->cfg.etcs_id_peer, &input[26]);
188 st = safecomm_mac_compute(&conn->mac, input, sizeof(input), &out_au2[13]);
189 }
190 if (st != SAFECOMM_STATUS_OK)
191 {
192 return st;
193 }
194 conn->state = SAFECOMM_SAF_STATE_WAIT_AU3;
195 return SAFECOMM_STATUS_OK;
196}
197
198/* Responder, AU3 -> AR. Verifies AU3's MAC and builds AR into out_ar (SAFECOMM_SAF_AR_LEN bytes). Ends in DATA with
199 * both sequence counters at 1, or IDLE on any failure. The caller checks the connect budget first. */
200static safecomm_status_t responder_process_au3(safecomm_saf_conn_t *conn, const uint8_t *au3, size_t au3_len,
201 uint8_t *out_ar)
202{
205 uint8_t expected_mac[SAFECOMM_MAC_LEN];
206 size_t bl = 1U;
207 size_t rl = 3U + bl;
208
209 if ((au3_len != SAFECOMM_SAF_AU3_LEN) || (header_mti(au3[0]) != MTI_AU3) || (header_df(au3[0]) != 0U))
210 {
211 conn->state = SAFECOMM_SAF_STATE_IDLE;
213 }
214 rmi[0] = (uint8_t)((rl >> 8U) & 0xFFU);
215 rmi[1] = (uint8_t)(rl & 0xFFU);
216 pack_id24(conn->cfg.etcs_id_self, &rmi[2]); /* DA = us (receiver of AU3) */
217 rmi[5] = au3[0];
218 (void)memcpy(&rmi[6], conn->ra, SAFECOMM_MAC_RAND_LEN);
219 (void)memcpy(&rmi[14], conn->rb, SAFECOMM_MAC_RAND_LEN);
220 st = safecomm_mac_compute(&conn->mac, rmi, sizeof(rmi), expected_mac);
221 if ((st != SAFECOMM_STATUS_OK) || (memcmp(expected_mac, &au3[1], SAFECOMM_MAC_LEN) != 0))
222 {
223 conn->state = SAFECOMM_SAF_STATE_IDLE;
225 }
226
227 /* AR: header | MAC via the generic Procedure 1 over just the header (§6.2.3.2.1.11). */
228 out_ar[0] = pack_header(0U, MTI_AR, 1U);
229 st = mac_over_da_body(&conn->mac, conn->cfg.etcs_id_peer, &out_ar[0], 1U, &out_ar[1]);
230 if (st != SAFECOMM_STATUS_OK)
231 {
232 conn->state = SAFECOMM_SAF_STATE_IDLE;
233 return st;
234 }
235 conn->seq_tx = 1U;
236 conn->seq_rx = 1U;
237 conn->state = SAFECOMM_SAF_STATE_DATA;
238 return SAFECOMM_STATUS_OK;
239}
240
242{
243 if (conn == NULL)
244 {
246 }
247 if (conn->state != SAFECOMM_SAF_STATE_IDLE)
248 {
250 }
251 const uint32_t start_ms = conn->now_ms;
252
253 if (conn->cfg.role == SAFECOMM_SAF_ROLE_INITIATOR)
254 {
255 /* ------------------------------------------------------------- */
256 /* INITIATOR (AU1 -> wait AU2 -> AU3 -> DATA), Subset-037-2 Fig.6 with */
257 /* B = us (calling/initiator), A = peer (called/responder). */
258 /* ------------------------------------------------------------- */
259 conn->state = SAFECOMM_SAF_STATE_WAIT_AU2;
260 conn->supervision_armed = false;
261
262 uint8_t au1[13];
263 au1[0] = pack_header(conn->cfg.ety, MTI_AU1, 0U);
264 pack_id24(conn->cfg.etcs_id_self, &au1[1]);
265 au1[4] = 0x01U; /* SaF: single DES with modified MAC algorithm 3 */
266 generate_random_bytes(conn->ra, SAFECOMM_MAC_RAND_LEN, conn->cfg.etcs_id_self);
267 (void)memcpy(&au1[5], conn->ra, SAFECOMM_MAC_RAND_LEN);
268
269 safecomm_status_t st = conn->transport.send(conn->transport.user, au1, sizeof(au1));
270 if (st != SAFECOMM_STATUS_OK)
271 {
272 conn->state = SAFECOMM_SAF_STATE_IDLE;
273 return st;
274 }
275
276 uint8_t au2[21];
277 size_t au2_len = 0U;
278 st = conn->transport.recv(conn->transport.user, au2, sizeof(au2), &au2_len);
279 if (st != SAFECOMM_STATUS_OK)
280 {
281 conn->state = SAFECOMM_SAF_STATE_IDLE;
282 return st;
283 }
284 if (connect_expired(conn, start_ms))
285 {
286 conn->state = SAFECOMM_SAF_STATE_IDLE;
288 }
289 if ((au2_len != sizeof(au2)) || (header_mti(au2[0]) != MTI_AU2) || (header_df(au2[0]) != 1U))
290 {
291 conn->state = SAFECOMM_SAF_STATE_IDLE;
293 }
294 uint32_t peer_id = unpack_id24(&au2[1]);
295 if (peer_id != conn->cfg.etcs_id_peer)
296 {
297 conn->state = SAFECOMM_SAF_STATE_IDLE;
299 }
300 (void)memcpy(conn->rb, &au2[5], SAFECOMM_MAC_RAND_LEN);
301
302 st = safecomm_mac_derive_ksmac(conn->cfg.kmac, conn->ra, conn->rb, &conn->mac);
303 if (st != SAFECOMM_STATUS_OK)
304 {
305 conn->state = SAFECOMM_SAF_STATE_IDLE;
306 return st;
307 }
308
309 /* AU2 MAC = CBC-MAC(KS, Text3 | RA | RB | DA | pad); Text3 = l|DA|header|SA|SaF,
310 * DA = us (the receiver of AU2). "RA"/"RB" here follow Fig.6's own labels:
311 * RA = responder's random (rb), RB = initiator's random (ra). */
312 uint8_t input[10 + SAFECOMM_MAC_RAND_LEN + SAFECOMM_MAC_RAND_LEN + 3];
313 size_t body_len = 5U; /* header + SA(3) + SaF */
314 size_t l = 3U + body_len;
315 input[0] = (uint8_t)((l >> 8U) & 0xFFU);
316 input[1] = (uint8_t)(l & 0xFFU);
317 pack_id24(conn->cfg.etcs_id_self, &input[2]); /* DA = us */
318 (void)memcpy(&input[5], &au2[0], body_len);
319 (void)memcpy(&input[10], conn->rb, SAFECOMM_MAC_RAND_LEN);
320 (void)memcpy(&input[18], conn->ra, SAFECOMM_MAC_RAND_LEN);
321 pack_id24(conn->cfg.etcs_id_self, &input[26]);
322
323 uint8_t expected_mac[SAFECOMM_MAC_LEN];
324 st = safecomm_mac_compute(&conn->mac, input, sizeof(input), expected_mac);
325 if ((st != SAFECOMM_STATUS_OK) || (memcmp(expected_mac, &au2[13], SAFECOMM_MAC_LEN) != 0))
326 {
327 conn->state = SAFECOMM_SAF_STATE_IDLE;
329 }
330
331 /* AU3 = header | MAC(Text5 | RB | RA); Text5 = l|DA|header, DA = peer (receiver of AU3). */
332 uint8_t au3[9];
333 au3[0] = pack_header(0U, MTI_AU3, 0U);
334 {
336 size_t bl = 1U;
337 size_t rl = 3U + bl;
338 rmi[0] = (uint8_t)((rl >> 8U) & 0xFFU);
339 rmi[1] = (uint8_t)(rl & 0xFFU);
340 pack_id24(conn->cfg.etcs_id_peer, &rmi[2]); /* DA = peer */
341 rmi[5] = au3[0];
342 (void)memcpy(&rmi[6], conn->ra, SAFECOMM_MAC_RAND_LEN);
343 (void)memcpy(&rmi[14], conn->rb, SAFECOMM_MAC_RAND_LEN);
344 st = safecomm_mac_compute(&conn->mac, rmi, sizeof(rmi), &au3[1]);
345 }
346 if (st != SAFECOMM_STATUS_OK)
347 {
348 conn->state = SAFECOMM_SAF_STATE_IDLE;
349 return st;
350 }
351
352 st = conn->transport.send(conn->transport.user, au3, sizeof(au3));
353 if (st != SAFECOMM_STATUS_OK)
354 {
355 conn->state = SAFECOMM_SAF_STATE_IDLE;
356 return st;
357 }
358
359 /* Wait for the AR confirmation (§6.2.4.2.12-14: "not required by the peer entity authentication
360 * procedure ... added to provide an OSI-like confirmed service") before declaring DATA. */
361 uint8_t ar[9];
362 size_t ar_len = 0U;
363 st = conn->transport.recv(conn->transport.user, ar, sizeof(ar), &ar_len);
364 if (st != SAFECOMM_STATUS_OK)
365 {
366 conn->state = SAFECOMM_SAF_STATE_IDLE;
367 return st;
368 }
369 if (connect_expired(conn, start_ms))
370 {
371 conn->state = SAFECOMM_SAF_STATE_IDLE;
373 }
374 if ((ar_len != sizeof(ar)) || (header_mti(ar[0]) != MTI_AR) || (header_df(ar[0]) != 1U))
375 {
376 conn->state = SAFECOMM_SAF_STATE_IDLE;
378 }
379 uint8_t ar_expected[SAFECOMM_MAC_LEN];
380 st = mac_over_da_body(&conn->mac, conn->cfg.etcs_id_self, &ar[0], 1U, ar_expected);
381 if ((st != SAFECOMM_STATUS_OK) || (memcmp(ar_expected, &ar[1], SAFECOMM_MAC_LEN) != 0))
382 {
383 conn->state = SAFECOMM_SAF_STATE_IDLE;
385 }
386
387 conn->seq_tx = 1U;
388 conn->seq_rx = 1U;
389 conn->state = SAFECOMM_SAF_STATE_DATA;
390 return SAFECOMM_STATUS_OK;
391 }
392 else
393 {
394 /* RESPONDER (wait AU1 -> AU2 -> wait AU3 -> AR -> DATA), same steps as the frame-driven API below. The blocking
395 * form draws RB from this module's own generator (test and tool use only - see generate_random_bytes()); a vital
396 * responder uses safecomm_saf_responder_on_au1() with an RB from a secure source. */
397 uint8_t au1[SAFECOMM_SAF_AU1_LEN];
398 uint8_t au2[SAFECOMM_SAF_AU2_LEN];
399 uint8_t au3[SAFECOMM_SAF_AU3_LEN];
400 uint8_t ar[SAFECOMM_SAF_AR_LEN];
401 uint8_t rb[SAFECOMM_MAC_RAND_LEN];
402 size_t au1_len = 0U;
403 size_t au3_len = 0U;
404 safecomm_status_t st = conn->transport.recv(conn->transport.user, au1, sizeof(au1), &au1_len);
405 if (st != SAFECOMM_STATUS_OK)
406 {
407 conn->state = SAFECOMM_SAF_STATE_IDLE;
408 return st;
409 }
410 if (connect_expired(conn, start_ms))
411 {
412 conn->state = SAFECOMM_SAF_STATE_IDLE;
414 }
415 generate_random_bytes(rb, SAFECOMM_MAC_RAND_LEN, conn->cfg.etcs_id_self);
416 st = responder_process_au1(conn, au1, au1_len, rb, au2);
417 if (st != SAFECOMM_STATUS_OK)
418 {
419 return st;
420 }
421 conn->connect_start_ms = start_ms;
422 st = conn->transport.send(conn->transport.user, au2, sizeof(au2));
423 if (st != SAFECOMM_STATUS_OK)
424 {
425 conn->state = SAFECOMM_SAF_STATE_IDLE;
426 return st;
427 }
428 st = conn->transport.recv(conn->transport.user, au3, sizeof(au3), &au3_len);
429 if (st != SAFECOMM_STATUS_OK)
430 {
431 conn->state = SAFECOMM_SAF_STATE_IDLE;
432 return st;
433 }
434 if (connect_expired(conn, start_ms))
435 {
436 conn->state = SAFECOMM_SAF_STATE_IDLE;
438 }
439 st = responder_process_au3(conn, au3, au3_len, ar);
440 if (st != SAFECOMM_STATUS_OK)
441 {
442 return st;
443 }
444 st = conn->transport.send(conn->transport.user, ar, sizeof(ar));
445 if (st != SAFECOMM_STATUS_OK)
446 {
447 conn->state = SAFECOMM_SAF_STATE_IDLE;
448 return st;
449 }
450 return SAFECOMM_STATUS_OK;
451 }
452}
453
454safecomm_saf_frame_kind_t safecomm_saf_frame_kind(const uint8_t *frame, size_t frame_len)
455{
456 safecomm_saf_frame_kind_t kind = SAFECOMM_SAF_FRAME_UNKNOWN;
457
458 if ((frame != NULL) && (frame_len > 0U))
459 {
460 switch (header_mti(frame[0]))
461 {
462 case MTI_AU1: kind = SAFECOMM_SAF_FRAME_AU1; break;
463 case MTI_AU2: kind = SAFECOMM_SAF_FRAME_AU2; break;
464 case MTI_AU3: kind = SAFECOMM_SAF_FRAME_AU3; break;
465 case MTI_DT: kind = SAFECOMM_SAF_FRAME_DATA; break;
466 case MTI_DI: kind = SAFECOMM_SAF_FRAME_DISCONNECT; break;
467 case MTI_AR: kind = SAFECOMM_SAF_FRAME_AR; break;
468 default: kind = SAFECOMM_SAF_FRAME_UNKNOWN; break;
469 }
470 }
471 return kind;
472}
473
474safecomm_status_t safecomm_saf_au1_fields(const uint8_t *au1, size_t au1_len, uint32_t *out_peer_id,
475 uint8_t out_ra[SAFECOMM_MAC_RAND_LEN])
476{
477 if ((au1 == NULL) || (out_peer_id == NULL) || (out_ra == NULL))
478 {
480 }
481 if ((au1_len != SAFECOMM_SAF_AU1_LEN) || (header_mti(au1[0]) != MTI_AU1) || (header_df(au1[0]) != 0U))
482 {
484 }
485 *out_peer_id = unpack_id24(&au1[1]);
486 (void)memcpy(out_ra, &au1[5], SAFECOMM_MAC_RAND_LEN);
487 return SAFECOMM_STATUS_OK;
488}
489
491 const uint8_t rb[SAFECOMM_MAC_RAND_LEN],
492 uint8_t *out_au2, size_t out_cap, size_t *out_len)
493{
495
496 if ((conn == NULL) || (au1 == NULL) || (rb == NULL) || (out_au2 == NULL) || (out_len == NULL))
497 {
499 }
500 if (conn->cfg.role != SAFECOMM_SAF_ROLE_RESPONDER)
501 {
503 }
504 if (out_cap < SAFECOMM_SAF_AU2_LEN)
505 {
507 }
508 /* A new AU1 is legal in any state: it is how a peer that restarted opens a fresh connection. Like Sa-DISCONNECT it
509 * is unauthenticated, so a forged one can only end the current connection (availability), never inject data. */
510 st = responder_process_au1(conn, au1, au1_len, rb, out_au2);
511 if (st == SAFECOMM_STATUS_OK)
512 {
513 conn->connect_start_ms = conn->now_ms;
514 *out_len = SAFECOMM_SAF_AU2_LEN;
515 }
516 return st;
517}
518
520 uint8_t *out_ar, size_t out_cap, size_t *out_len)
521{
523
524 if ((conn == NULL) || (au3 == NULL) || (out_ar == NULL) || (out_len == NULL))
525 {
527 }
528 if ((conn->cfg.role != SAFECOMM_SAF_ROLE_RESPONDER) || (conn->state != SAFECOMM_SAF_STATE_WAIT_AU3))
529 {
531 }
532 if (out_cap < SAFECOMM_SAF_AR_LEN)
533 {
535 }
536 if (connect_expired(conn, conn->connect_start_ms))
537 {
538 conn->state = SAFECOMM_SAF_STATE_IDLE;
540 }
541 st = responder_process_au3(conn, au3, au3_len, out_ar);
542 if (st == SAFECOMM_STATUS_OK)
543 {
544 *out_len = SAFECOMM_SAF_AR_LEN;
545 }
546 return st;
547}
548
550{
551 if (conn == NULL)
552 {
554 }
555 if (conn->state != SAFECOMM_SAF_STATE_DATA)
556 {
558 }
559 conn->seq_tx++;
560 return SAFECOMM_STATUS_OK;
561}
562
564 size_t *out_len)
565{
566 if ((out == NULL) || (out_len == NULL))
567 {
569 }
570 if (out_cap < SAFECOMM_SAF_DI_LEN)
571 {
573 }
574 out[0] = pack_header(0U, MTI_DI, (uint8_t)((role == SAFECOMM_SAF_ROLE_INITIATOR) ? 0U : 1U));
575 out[1] = 0x00U; /* reason: normal release */
576 out[2] = 0x00U; /* sub-reason */
577 *out_len = SAFECOMM_SAF_DI_LEN;
578 return SAFECOMM_STATUS_OK;
579}
580
582 const uint8_t *pdu, size_t pdu_len,
583 uint8_t *out_frame, size_t out_frame_cap,
584 size_t *out_frame_len)
585{
586 if ((conn == NULL) || (out_frame == NULL) || (out_frame_len == NULL) ||
587 ((pdu == NULL) && (pdu_len > 0U)) || (pdu_len > SAFECOMM_SAF_MAX_PDU))
588 {
590 }
591 if (conn->state != SAFECOMM_SAF_STATE_DATA)
592 {
594 }
595
596 /* [header(1)][seq(4, our sequence extension, see file header)][pdu(n)][MAC(8)] */
597 size_t body_len = 1U + 4U + pdu_len;
598 size_t total_len = body_len + SAFECOMM_MAC_LEN;
599 if (out_frame_cap < total_len)
600 {
602 }
603
604 uint8_t df = (uint8_t)((conn->cfg.role == SAFECOMM_SAF_ROLE_INITIATOR) ? 0U : 1U);
605 out_frame[0] = pack_header(0U, MTI_DT, df);
606 pack_u32_be(conn->seq_tx, &out_frame[1]);
607 if (pdu_len > 0U)
608 {
609 (void)memcpy(&out_frame[5], pdu, pdu_len);
610 }
611
612 safecomm_status_t st = mac_over_da_body(&conn->mac, conn->cfg.etcs_id_peer,
613 out_frame, body_len, &out_frame[body_len]);
614 if (st != SAFECOMM_STATUS_OK)
615 {
616 return st;
617 }
618
619 *out_frame_len = total_len;
620 return SAFECOMM_STATUS_OK;
621}
622
624 const uint8_t *frame, size_t frame_len)
625{
626 if ((conn == NULL) || (frame == NULL) || (frame_len == 0U))
627 {
629 }
630 if (conn->state != SAFECOMM_SAF_STATE_DATA)
631 {
633 }
634
635 safecomm_status_t st = conn->transport.send(conn->transport.user, frame, frame_len);
636 if (st == SAFECOMM_STATUS_OK)
637 {
638 conn->seq_tx++;
639 }
640 return st;
641}
642
644 const uint8_t *pdu, size_t pdu_len)
645{
647 size_t frame_len = 0U;
648
649 safecomm_status_t st = safecomm_saf_prepare_send(conn, pdu, pdu_len, frame_buf, sizeof(frame_buf), &frame_len);
650 if (st != SAFECOMM_STATUS_OK)
651 {
652 return st;
653 }
654 return safecomm_saf_commit_send(conn, frame_buf, frame_len);
655}
656
658 uint8_t *out, size_t out_cap, size_t *out_len)
659{
660 if ((conn == NULL) || (out == NULL) || (out_len == NULL))
661 {
663 }
664 if (conn->state != SAFECOMM_SAF_STATE_DATA)
665 {
667 }
668
670 size_t rx_len = 0U;
671
672 safecomm_status_t st = conn->transport.recv(conn->transport.user, rx_buf, sizeof(rx_buf), &rx_len);
673 if (st != SAFECOMM_STATUS_OK)
674 {
675 return st;
676 }
677 return safecomm_saf_accept_frame(conn, rx_buf, rx_len, out, out_cap, out_len);
678}
679
680safecomm_status_t safecomm_saf_accept_frame(safecomm_saf_conn_t *conn, const uint8_t *rx_buf, size_t rx_len,
681 uint8_t *out, size_t out_cap, size_t *out_len)
682{
684
685 if ((conn == NULL) || (rx_buf == NULL) || (out == NULL) || (out_len == NULL))
686 {
688 }
689 if (conn->state != SAFECOMM_SAF_STATE_DATA)
690 {
692 }
693
694 if (rx_len < 1U)
695 {
696 conn->state = SAFECOMM_SAF_STATE_IDLE;
698 }
699
700 /* Sa-DISCONNECT carries no MAC (Subset-037-2 §6.2.3.3.3.2: release-phase authentication
701 * is not required — it can only affect availability, and the connection can be released
702 * independently at the transport layer regardless). Any correctly-shaped DI is honoured. */
703 if (header_mti(rx_buf[0]) == MTI_DI)
704 {
705 if (rx_len != 3U)
706 {
707 return SAFECOMM_STATUS_TRANSPORT; /* malformed, not a real disconnect: connection unchanged */
708 }
709 conn->state = SAFECOMM_SAF_STATE_IDLE;
711 }
712
713 uint8_t expected_df = (uint8_t)((conn->cfg.role == SAFECOMM_SAF_ROLE_INITIATOR) ? 1U : 0U);
714 if ((header_mti(rx_buf[0]) != MTI_DT) || (rx_len < (1U + 4U + SAFECOMM_MAC_LEN)))
715 {
716 conn->state = SAFECOMM_SAF_STATE_IDLE;
718 }
719 if (header_df(rx_buf[0]) != expected_df)
720 {
721 conn->state = SAFECOMM_SAF_STATE_IDLE;
723 }
724
725 size_t body_len = rx_len - SAFECOMM_MAC_LEN;
726 uint8_t expected_mac[SAFECOMM_MAC_LEN];
727 st = mac_over_da_body(&conn->mac, conn->cfg.etcs_id_self, rx_buf, body_len, expected_mac);
728 if ((st != SAFECOMM_STATUS_OK) || (memcmp(expected_mac, &rx_buf[body_len], SAFECOMM_MAC_LEN) != 0))
729 {
730 conn->state = SAFECOMM_SAF_STATE_IDLE;
732 }
733
734 uint32_t rx_seq = unpack_u32_be(&rx_buf[1]);
735 /* Sequence window check: rx_seq must be seq_rx or up to SAF_SEQ_WINDOW ahead. The unsigned difference rejects a repeated
736 * or older number (it wraps to a huge value) and cannot overflow near the top of the counter. */
737 if ((uint32_t)(rx_seq - conn->seq_rx) > (uint32_t)SAF_SEQ_WINDOW)
738 {
739 conn->state = SAFECOMM_SAF_STATE_IDLE;
741 }
742
743 conn->seq_rx = rx_seq + 1U;
744 conn->last_rx_ms = conn->now_ms; /* a valid frame refreshes supervision */
745 conn->supervision_armed = true;
746
747 size_t payload_len = body_len - 5U;
748 if (payload_len > out_cap)
749 {
751 }
752 if (payload_len > 0U)
753 {
754 (void)memcpy(out, &rx_buf[5], payload_len);
755 }
756 *out_len = payload_len;
757
758 return SAFECOMM_STATUS_OK;
759}
760
761void safecomm_saf_set_time(safecomm_saf_conn_t *conn, uint32_t now_ms)
762{
763 if (conn != NULL)
764 {
765 conn->now_ms = now_ms;
766 }
767}
768
770{
771 if (conn == NULL)
772 {
774 }
775 if (conn->state != SAFECOMM_SAF_STATE_DATA)
776 {
777 conn->supervision_armed = false;
779 }
780 if (conn->cfg.t_supervision_ms == 0U)
781 {
782 return SAFECOMM_STATUS_OK;
783 }
784 if (!conn->supervision_armed)
785 {
786 conn->last_rx_ms = conn->now_ms;
787 conn->supervision_armed = true;
788 return SAFECOMM_STATUS_OK;
789 }
790 if ((uint32_t)(conn->now_ms - conn->last_rx_ms) > conn->cfg.t_supervision_ms)
791 {
792 conn->state = SAFECOMM_SAF_STATE_IDLE;
793 conn->supervision_armed = false;
795 }
796 return SAFECOMM_STATUS_OK;
797}
798
799safecomm_status_t safecomm_saf_disconnect(safecomm_saf_conn_t *conn)
800{
801 if (conn == NULL)
802 {
804 }
805
806 if (conn->state != SAFECOMM_SAF_STATE_IDLE)
807 {
808 uint8_t disconn[SAFECOMM_SAF_DI_LEN];
809 size_t disconn_len = 0U;
810
811 (void)safecomm_saf_disconnect_frame(conn->cfg.role, disconn, sizeof(disconn), &disconn_len);
812 (void)conn->transport.send(conn->transport.user, disconn, disconn_len);
813 }
814
815 conn->state = SAFECOMM_SAF_STATE_IDLE;
816 return SAFECOMM_STATUS_OK;
817}
818
820{
821 return (conn != NULL) ? conn->state : SAFECOMM_SAF_STATE_IDLE;
822}
823
826{
827 if ((conn == NULL) || (out_state == NULL))
828 {
830 }
831
832 (void)memset(out_state, 0, sizeof(*out_state));
833 out_state->state = conn->state;
834 out_state->seq_tx = conn->seq_tx;
835 out_state->seq_rx = conn->seq_rx;
836 out_state->seq_rx_last_accepted = (conn->seq_rx > 0U) ? (conn->seq_rx - 1U) : 0U;
837 (void)memcpy(out_state->ksmac, conn->mac.ksmac, SAFECOMM_MAC_KEY_LEN);
838 (void)memcpy(out_state->ra, conn->ra, SAFECOMM_MAC_RAND_LEN);
839 (void)memcpy(out_state->rb, conn->rb, SAFECOMM_MAC_RAND_LEN);
840 out_state->etcs_id_self = conn->cfg.etcs_id_self;
841 out_state->etcs_id_peer = conn->cfg.etcs_id_peer;
842
843 return SAFECOMM_STATUS_OK;
844}
845
847 const safecomm_session_vital_state_t *in_state)
848{
849 if ((conn == NULL) || (in_state == NULL))
850 {
852 }
853
854 conn->state = in_state->state;
855 conn->seq_tx = in_state->seq_tx;
856 conn->seq_rx = in_state->seq_rx;
858 (void)memcpy(conn->mac.ksmac, in_state->ksmac, SAFECOMM_MAC_KEY_LEN);
859 (void)memcpy(conn->ra, in_state->ra, SAFECOMM_MAC_RAND_LEN);
860 (void)memcpy(conn->rb, in_state->rb, SAFECOMM_MAC_RAND_LEN);
861 conn->cfg.etcs_id_self = in_state->etcs_id_self;
862 conn->cfg.etcs_id_peer = in_state->etcs_id_peer;
863
864 return SAFECOMM_STATUS_OK;
865}
#define SAFECOMM_MAC_LEN
Definition mac.h:36
#define SAFECOMM_MAC_RAND_LEN
Definition mac.h:38
#define SAFECOMM_MAC_KEY_LEN
Definition mac.h:34
safecomm_status_t safecomm_mac_derive_ksmac(const uint8_t kmac[SAFECOMM_MAC_KEY_LEN], const uint8_t ra[SAFECOMM_MAC_RAND_LEN], const uint8_t rb[SAFECOMM_MAC_RAND_LEN], safecomm_mac_ctx_t *out_ctx)
Derive the session key KSMAC from KMAC and the AU random values.
Definition mac.c:267
@ SAFECOMM_MAC_ALG_3DES_SS037
Definition mac.h:43
safecomm_status_t safecomm_mac_compute(const safecomm_mac_ctx_t *ctx, const uint8_t *msg, size_t msg_len, uint8_t out_mac[SAFECOMM_MAC_LEN])
Compute the 8-byte EuroRadio MAC over msg.
Definition mac.c:300
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")....
Definition saf.c:107
Safety Functional (SaF) sub-layer — shared by Subset-037 and Subset-098 (ERTMS/ETCS Subset-037 §7).
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.
Definition saf.c:623
void safecomm_saf_set_time(safecomm_saf_conn_t *conn, uint32_t now_ms)
Orderly Sa-DISCONNECT, then return the object to IDLE.
Definition saf.c:761
safecomm_saf_frame_kind_t
Frame kind by its header MTI, for a caller that routes received frames itself.
Definition saf.h:48
safecomm_status_t safecomm_saf_connect(safecomm_saf_conn_t *conn)
Run the AU1/AU2/AU3 handshake to completion (blocking).
Definition saf.c:241
#define SAFECOMM_SAF_AU1_LEN
Authentication-phase frame lengths (Subset-037-2 §6.2.5), bytes.
Definition saf.h:39
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.
Definition saf.c:519
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.
Definition saf.c:490
safecomm_saf_state_t safecomm_saf_state(const safecomm_saf_conn_t *conn)
Current state (for logging / tests). Returns IDLE for NULL.
Definition saf.c:819
safecomm_saf_state_t
SaF state-machine state (observable, for logging / tests).
Definition saf.h:67
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.
Definition saf.c:643
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).
Definition saf.c:657
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(),...
Definition saf.c:680
#define SAFECOMM_SAF_DI_LEN
Sa-DISCONNECT frame length (header + reason + sub-reason).
Definition saf.h:240
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....
Definition saf.c:769
#define SAFECOMM_SAF_MAX_PDU
Largest SaF user-data PDU this build accepts (bytes).
Definition saf.h:36
#define SAFECOMM_SAF_DATA_OVERHEAD
Sa-DATA framing overhead around the user PDU: header(1) + sequence(4) + MAC(8).
Definition saf.h:44
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 s...
Definition saf.c:563
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).
Definition saf.c:581
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)....
Definition saf.c:474
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).
Definition saf.c:454
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 se...
Definition saf.c:549
safecomm_saf_role_t
SaF connection role.
Definition saf.h:60
@ SAFECOMM_SAF_ROLE_RESPONDER
Definition saf.h:62
@ SAFECOMM_SAF_ROLE_INITIATOR
Definition saf.h:61
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.
Definition saf.c:846
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.
Definition saf.c:824
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).
Definition saf.c:125
safecomm_status_t
Result of a Safe Communication Framework operation.
Definition status.h:17
@ SAFECOMM_STATUS_AUTH_FAILED
Definition status.h:24
@ SAFECOMM_STATUS_STATE
Definition status.h:21
@ SAFECOMM_STATUS_INVALID_PARAM
Definition status.h:19
@ SAFECOMM_STATUS_MAC_MISMATCH
Definition status.h:25
@ SAFECOMM_STATUS_TIMEOUT
Definition status.h:22
@ SAFECOMM_STATUS_SEQ_ERROR
Definition status.h:26
@ SAFECOMM_STATUS_BUFFER_TOO_SMALL
Definition status.h:28
@ SAFECOMM_STATUS_PEER_DISCONNECT
Definition status.h:27
@ SAFECOMM_STATUS_OK
Definition status.h:18
@ SAFECOMM_STATUS_TRANSPORT
Definition status.h:23
Immutable session-key material for one SaF connection.
Definition mac.h:48
uint8_t ksmac[SAFECOMM_MAC_KEY_LEN]
Definition mac.h:49
safecomm_mac_alg_t alg
Definition mac.h:50
Static configuration for one SaF connection.
Definition saf.h:77
uint32_t etcs_id_peer
Definition saf.h:81
uint8_t kmac[SAFECOMM_MAC_KEY_LEN]
Definition saf.h:79
uint32_t t_connect_ms
Definition saf.h:83
uint32_t etcs_id_self
Definition saf.h:80
uint32_t t_supervision_ms
Definition saf.h:84
SaF connection object. Caller-allocated, zero-initialised, opaque in practice — fields are not part o...
Definition saf.h:92
uint32_t last_rx_ms
Definition saf.h:102
uint32_t connect_start_ms
Definition saf.h:104
uint32_t now_ms
Definition saf.h:101
Vital session state for 2oo2 hot redundancy and active/warm-standby failover.
Definition saf.h:288
A consumer-supplied bearer the SaF layer runs over.
Definition transport.h:56
safecomm_transport_send_fn send
Definition transport.h:57
safecomm_transport_recv_fn recv
Definition transport.h:58