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_channel_negotiate.c
Go to the documentation of this file.
1
20/* _POSIX_C_SOURCE 200809L: needed for select() below. */
21#define _POSIX_C_SOURCE 200809L
22
24#include "ab_gp_sibling_state.h"
25#include "ab_gp_site_status.h"
28#include "ab_gp_takeover_key.h"
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <strings.h>
33#include <sys/select.h>
34#include <sys/types.h>
35#include <unistd.h>
36
37#include "rte/oal/log/rte_log.h"
38#include "rte/oal/memory/rte_mem_util.h"
39#include "rte/oal/timer/rte_timer.h"
40#include "rte/redundancy/site_role/rte_site_role.h"
41#include "rte/redundancy/state_transfer/rte_state_transfer.h"
42
44#include "ab_gp_channel.h"
46#include "ab_gp_channel_util.h"
47
48/* Lifetime of a takeover challenge (REQ-SITEROLE-015): long enough for the status line (every 5 s) to reach the
49 * issuer and the token to come back, short enough that an unused token dies within minutes. */
50#define AB_GP_TAKEOVER_CHALLENGE_MS 120000U
51
52/* Startup timestamp exchange (ADR-040 step 5a): a STARTUP_TS request is re-sent this often while the sibling has
53 * not answered (A's listen role has no peer until B connects). The read slice and the slice bound are in
54 * ab_gp_startup_timestamp.h. */
55#define AB_GP_STARTUP_TS_REQUEST_PERIOD_MS 100U
56
57/* REQ-RBC-006/007: the negotiate-payload and AB_SAMPLE per-session encodings must agree byte-for-byte. */
60/* The frame is the service's framing around GP's state region. */
63 (RTE_SITE_ROLE_FRAME_OVERHEAD + ((size_t)RTE_EXAMPLE_MAX_TRAINS * (size_t)RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE) +
64 (size_t)RTE_EXAMPLE_DB_WIRE_SIZE)) &&
65 (RTE_EXAMPLE_SITE_ROLE_VALID_MARKER == RTE_SITE_ROLE_VALID_MARKER) &&
66 (RTE_EXAMPLE_SINGLE_MODE_FLAG_SINGLE == RTE_SITE_ROLE_SINGLE_MODE_SINGLE)) ? 1 : -1];
67
74
75/* ---- state codec (rte_state_transfer registry; wire layout unchanged) ------------------------------------------- */
76
77static rte_status_t encode_sessions_field(const void *data, uint8_t *out, size_t size)
78{
79 (void)size;
81 (const ab_gp_train_session_t *)data);
82 return RTE_STATUS_OK;
83}
84
85static rte_status_t decode_sessions_field(void *data, const uint8_t *in, size_t size)
86{
87 (void)size;
89 return RTE_STATUS_OK;
90}
91
92static rte_status_t encode_db_field(const void *data, uint8_t *out, size_t size)
93{
94 uint32_t written = 0U;
95 return ab_gp_db_encode((const ab_gp_db_t *)data, out, (uint32_t)size, &written);
96}
97
98static rte_status_t decode_db_field(void *data, const uint8_t *in, size_t size)
99{
100 return ab_gp_db_decode((ab_gp_db_t *)data, in, (uint32_t)size);
101}
102
105static void build_state_transfer_registry(rte_state_transfer_registry_t *registry, negotiate_state_t *state)
106{
107 (void)rte_state_transfer_registry_init(registry);
108 (void)rte_state_transfer_register_field_ex(
109 registry, "sessions", state->sessions,
112 (void)rte_state_transfer_register_field_ex(registry, "db", &state->db, (size_t)RTE_EXAMPLE_DB_WIRE_SIZE,
114}
115
117static rte_status_t encode_state(void *user, uint8_t *out, size_t size, bool live)
118{
119 const ab_gp_channel_context_t *ctx = (const ab_gp_channel_context_t *)user;
120 negotiate_state_t state;
121 rte_state_transfer_registry_t registry;
122
123 rte_mem_set(&state, 0, sizeof(state));
124 if (live)
125 {
126 rte_mem_copy(state.sessions, ctx->sessions, sizeof(state.sessions));
127 rte_mem_copy(&state.db, &ctx->db, sizeof(state.db));
128 }
129 build_state_transfer_registry(&registry, &state);
130 return rte_state_transfer_encode(&registry, out, size, NULL);
131}
132
134static bool receive_state(void *user, const uint8_t *in, size_t size, bool valid)
135{
137 negotiate_state_t decoded;
138 rte_state_transfer_registry_t registry;
139
140 rte_mem_set(&decoded, 0, sizeof(decoded));
141 build_state_transfer_registry(&registry, &decoded);
142 if (rte_state_transfer_decode(&registry, in, size) != RTE_STATUS_OK)
143 {
144 return false;
145 }
146 if (valid)
147 {
148 rte_mem_copy(ctx->neg_peer_extra.sessions, decoded.sessions, sizeof(ctx->neg_peer_extra.sessions));
149 rte_mem_copy(&ctx->neg_peer_extra.db, &decoded.db, sizeof(ctx->neg_peer_extra.db));
150 }
151 return true;
152}
153
154/* ---- state transfer on promotion -------------------------------------------------------------------------------- */
155
158{
159 const char *env = getenv(RTE_EXAMPLE_TRANSFER_POLICY_ENV);
160
161 if (env == NULL)
162 {
164 }
165 if (strcasecmp(env, "restart") == 0)
166 {
168 }
169 if (strcasecmp(env, "ask") == 0)
170 {
172 }
174}
175
179static bool ask_user_restart_or_continue(const ab_gp_channel_context_t *ctx, uint32_t transferred_cycle)
180{
181 fd_set read_fds;
182 struct timeval timeout;
183 int ready;
184
185 RTE_EXAMPLE_LOG("[%s] *** ASK_USER: promoted to ONLINE with a transferred cycle=%u - restart (r) or continue (c)? "
186 "[%ums to answer, default continue] ***\n",
187 ctx->role_tag, transferred_cycle, (unsigned int)RTE_EXAMPLE_TRANSFER_ASK_USER_TIMEOUT_MS);
188 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-", "TRANSFER",
189 "ASK_USER: awaiting operator restart/continue decision", NULL);
190
191 FD_ZERO(&read_fds);
192 FD_SET(STDIN_FILENO, &read_fds);
193 timeout.tv_sec = (time_t)(RTE_EXAMPLE_TRANSFER_ASK_USER_TIMEOUT_MS / 1000U);
194 timeout.tv_usec = (suseconds_t)((RTE_EXAMPLE_TRANSFER_ASK_USER_TIMEOUT_MS % 1000U) * 1000U);
195
196 ready = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);
197 if (ready > 0)
198 {
199 char line[8] = { 0 };
200 ssize_t n = read(STDIN_FILENO, line, sizeof(line) - 1U);
201
202 if (n > 0)
203 {
204 if ((line[0] == 'r') || (line[0] == 'R'))
205 {
206 RTE_EXAMPLE_LOG("[%s] ASK_USER: operator chose RESTART\n", ctx->role_tag);
207 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-",
208 "TRANSFER", "ASK_USER: operator chose RESTART", NULL);
209 return true;
210 }
211 RTE_EXAMPLE_LOG("[%s] ASK_USER: operator chose CONTINUE\n", ctx->role_tag);
212 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-",
213 "TRANSFER", "ASK_USER: operator chose CONTINUE", NULL);
214 return false;
215 }
216 }
217 RTE_EXAMPLE_LOG("[%s] ASK_USER: no answer within %ums (EOF/no TTY/timeout) - defaulting to CONTINUE\n",
219 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-", "TRANSFER",
220 "ASK_USER: no answer in time - defaulting to CONTINUE", NULL);
221 return false;
222}
223
226static void apply_state_transfer(ab_gp_channel_context_t *ctx, const rte_site_role_change_t *change)
227{
229 uint32_t applied_cycle;
230
231 if (!change->snapshot_valid)
232 {
233 RTE_EXAMPLE_LOG("[%s] state transfer: no valid counterpart snapshot cached yet - starting cold (cycle 1, no sessions, no routes)\n",
234 ctx->role_tag);
235 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-", "TRANSFER",
236 "no valid counterpart snapshot cached - starting cold", NULL);
237 ctx->cycle = 1U;
238 rte_mem_set(ctx->sessions, 0, sizeof(ctx->sessions));
239 rte_mem_set(&ctx->db, 0, sizeof(ctx->db));
240 return;
241 }
242
243 policy = read_transfer_policy();
244 applied_cycle = change->snapshot_cycle;
245 switch (policy)
246 {
248 RTE_EXAMPLE_LOG("[%s] state transfer: policy=RESTART - renumbering from cycle 0 (transferred cycle was %u)\n",
249 ctx->role_tag, change->snapshot_cycle);
250 applied_cycle = 0U;
251 break;
253 if (ask_user_restart_or_continue(ctx, change->snapshot_cycle))
254 {
255 applied_cycle = 0U;
256 }
257 break;
259 default:
260 RTE_EXAMPLE_LOG("[%s] state transfer: policy=CONTINUE - resuming from cycle %u\n", ctx->role_tag,
261 change->snapshot_cycle);
262 break;
263 }
264
265 RTE_EXAMPLE_LOG("[%s] state transfer: applying {cycle=%u} + %u train session(s) + route db\n", ctx->role_tag,
266 applied_cycle, (unsigned int)RTE_EXAMPLE_MAX_TRAINS);
267 {
268 char extra[48];
269 (void)snprintf(extra, sizeof(extra), "cycle=%u policy=%d", applied_cycle, (int)policy);
270 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site_config_name(ctx->site), 0U, ctx->role_tag, "-", "TRANSFER",
271 "applying transferred snapshot", extra);
272 }
273 /* ctx->cycle is "next cycle to run, 1-based"; a RESTART-policy transfer maps onto the same cold-start 1. */
274 ctx->cycle = (applied_cycle > 0U) ? applied_cycle : 1U;
275 /* Every "last seen at cycle N" stamp refers to the old numbering. The service resets its own link stamps
276 * (peer_last_rx_cycle, standby_rx_cycle, standby_last_try_cycle in report_change()) but NOT the sibling
277 * stamp (sibling_rx_cycle, REQ-SITEROLE-016): a CONTINUE renumbering upwards may count a frame taken before
278 * the renumbering as reachable for one more cycle (the next execute takes a fresh frame and re-stamps it);
279 * downwards the `cycle >= sibling_rx_cycle` guard of the service makes the old frame unreachable at once. */
280 ctx->last_relay_rx_cycle = 0U;
281 rte_mem_copy(ctx->sessions, ctx->neg_peer_extra.sessions, sizeof(ctx->sessions));
282 rte_mem_copy(&ctx->db, &ctx->neg_peer_extra.db, sizeof(ctx->db));
283}
284
286static void on_role_change(void *user, const rte_site_role_change_t *change)
287{
289
290 ctx->is_online = change->online;
291 if (change->kind == RTE_SITE_ROLE_CHANGE_PROMOTED)
292 {
293 /* A/B partner state is judged afresh from here: a STANDBY channel's flag can be stale, and one channel with the
294 * flag set and its sibling without it is a site-state mismatch that reboots the pair right after a takeover. */
295 ctx->single_mode = false;
297 }
298 if (change->apply_state)
299 {
300 apply_state_transfer(ctx, change);
301 }
302}
303
304static void trace(void *user, const char *line)
305{
306 (void)user;
307 RTE_EXAMPLE_LOG("%s", line);
308}
309
310static void delay_ms(void *user, uint32_t ms)
311{
312 (void)user;
313 ab_gp_channel_sleep_ms((unsigned int)ms);
314}
315
316/* ---- local facts ------------------------------------------------------------------------------------------------ */
317
318static void local_facts(const ab_gp_channel_context_t *ctx, rte_site_role_local_t *out)
319{
320 out->faulted = ctx->own_faulted;
321 out->single_mode = ctx->single_mode;
322 out->own_ready = ab_gp_site_status_ready_for_online(ctx);
323 /* Derived by rte_site_role from the sibling source (REQ-SITEROLE-016); unused with a source configured. */
324 out->sibling_online = false;
325 out->sibling_ready = false;
326 out->sibling_reachable = false;
327}
328
330static bool sibling_source(void *user, rte_site_role_sibling_t *out)
331{
332 return ab_gp_sibling_state_take(&((ab_gp_channel_context_t *)user)->peer_site_state, out);
333}
334
335/* ADR-040 step 4b: with RTE_TAKEOVER_KEY_FILE set, only an authenticated token confirms a takeover (REQ-SITEROLE-015);
336 * a set but unusable key file refuses to start, it never falls back to the unauthenticated path. */
337static rte_status_t configure_takeover_key(ab_gp_channel_context_t *ctx, rte_site_role_config_t *cfg)
338{
339 const char *path = getenv(AB_GP_TAKEOVER_KEY_FILE_ENV);
340 const char *site = site_config_name(ctx->site);
341
342 if ((path == NULL) || (path[0] == '\0'))
343 {
344 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site, 0U, ctx->role_tag, "-", "TAKEOVER",
345 "no RTE_TAKEOVER_KEY_FILE - dispatcher confirmation is unauthenticated (interim)", NULL);
346 return RTE_STATUS_OK;
347 }
349 {
350 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site, 0U, ctx->role_tag, "-", "TAKEOVER",
351 "RTE_TAKEOVER_KEY_FILE unreadable or malformed - refusing to start", NULL);
352 return RTE_STATUS_INVALID_PARAM;
353 }
354 cfg->verify_mac = ab_gp_takeover_verify_mac;
355 cfg->channel_id = (uint8_t)(ctx->is_role_a ? 0U : 1U);
356 cfg->challenge_lifetime_cycles = (ctx->config.timers.cycle_period_ms != 0U)
358 : 0U; /* 0: the RTE default */
359 rte_log_write_event(RTE_LOG_LEVEL_INFO, site, 0U, ctx->role_tag, "-", "TAKEOVER",
360 "dispatcher confirmation authenticated (RTE_TAKEOVER_KEY_FILE)", NULL);
361 return RTE_STATUS_OK;
362}
363
364/* ---- site startup timestamp (ADR-040 step 5a) ------------------------------------------------------------------- */
365
368static void send_startup_ts_frame(ab_gp_channel_context_t *ctx, bool is_reply, rte_timestamp_ms_t ts_ms)
369{
370 uint8_t frame[PEER_WIRE_FRAME_SIZE] = { 0 };
371
372 frame[0] = PEER_MSG_KIND_STARTUP_TS;
373 ab_gp_startup_timestamp_encode(&frame[1], is_reply, ts_ms);
374 (void)ab_gp_channel_send_peer((ab_gp_channel_handle_t)ctx, frame, sizeof(frame));
375}
376
391{
392 const char *site = site_config_name(ctx->site);
393 const char *sibling_tag = ctx->is_role_a ? "B" : "A";
394 rte_timestamp_ms_t own_now = 0U;
395 rte_timestamp_ms_t now_ms = 0U;
396 rte_timestamp_ms_t last_request_ms = 0U;
397 rte_timestamp_ms_t sibling_ts = 0U;
398 bool sibling_is_reply = false;
399 bool have_sibling = false;
400 bool request_pending = true;
401 bool clock_failed = false;
402 bool done = false;
403 uint32_t slices_left = AB_GP_STARTUP_TS_MAX_SLICES;
404 uint8_t in_bytes[PEER_WIRE_FRAME_SIZE];
405 char sibling_text[24];
406 char detail[48];
407 char extra[96];
408
409 if (rte_timer_now(&own_now) != RTE_STATUS_OK)
410 {
411 /* No clock: no exchange; startup_timestamp_ms stays 0 and rte_site_role_start() takes its own clock. */
412 RTE_EXAMPLE_LOG("[%s] site startup timestamp: clock unavailable - no exchange with the sibling\n",
413 ctx->role_tag);
414 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site, 0U, ctx->role_tag, sibling_tag, "NEGOTIATION",
415 "startup timestamp: clock unavailable, no exchange", NULL);
416 ctx->startup_timestamp_ms = 0U;
417 return;
418 }
419
420 while (!done)
421 {
422 rte_duration_ms_t elapsed;
423 rte_duration_ms_t remaining;
424 rte_duration_ms_t slice_ms;
425 rte_status_t status;
426
427 if (slices_left == 0U)
428 {
429 done = true; /* hard bound: AB_GP_STARTUP_TS_MAX_SLICES slices read, the own clock is used */
430 continue;
431 }
432 slices_left--;
433 if (rte_timer_now(&now_ms) != RTE_STATUS_OK)
434 {
435 /* The clock failed after the exchange started: treated as the deadline, the own clock is used. */
436 clock_failed = true;
437 done = true;
438 continue;
439 }
440 elapsed = (rte_duration_ms_t)(now_ms - own_now);
441 if (elapsed >= (rte_duration_ms_t)AB_GP_STARTUP_TS_WAIT_MS)
442 {
443 done = true; /* timeout: the own clock is used */
444 continue;
445 }
446 if (request_pending || ((now_ms - last_request_ms) >= (rte_timestamp_ms_t)AB_GP_STARTUP_TS_REQUEST_PERIOD_MS))
447 {
448 send_startup_ts_frame(ctx, false, own_now);
449 last_request_ms = now_ms;
450 request_pending = false;
451 }
452 remaining = (rte_duration_ms_t)AB_GP_STARTUP_TS_WAIT_MS - elapsed;
453 slice_ms = (remaining > (rte_duration_ms_t)AB_GP_STARTUP_TS_READ_SLICE_MS)
454 ? (rte_duration_ms_t)AB_GP_STARTUP_TS_READ_SLICE_MS
455 : remaining;
456
457 status = rte_channel_service_read(&ctx->com.peer_channel, in_bytes, sizeof(in_bytes), slice_ms);
458 if (status == RTE_STATUS_OK)
459 {
460 if (in_bytes[0] == PEER_MSG_KIND_STARTUP_TS)
461 {
462 /* A malformed payload is dropped; the next request asks again. */
463 have_sibling = ab_gp_startup_timestamp_decode(&in_bytes[1], sizeof(in_bytes) - 1U,
464 &sibling_is_reply, &sibling_ts);
465 done = have_sibling;
466 }
467 else if (((in_bytes[0] == PEER_MSG_KIND_SAF_RB) || (in_bytes[0] == PEER_MSG_KIND_KM114_DIGEST)) &&
469 {
470 /* One-off control frames, replayed by pre_execute (see poll_for_checkpoint_frame()). */
471 rte_mem_copy(ctx->deferred_peer_frames[ctx->deferred_peer_count], in_bytes, sizeof(in_bytes));
472 ctx->deferred_peer_count++;
473 }
474 else
475 {
476 /* other kinds are per-cycle data a later cycle re-sends */
477 }
478 }
479 else if (status != RTE_STATUS_TIMEOUT)
480 {
481 /* No peer yet (A listens until B connects) or a read fault: keep the slice pacing, the deadline bounds
482 * the loop either way. */
483 ab_gp_channel_sleep_ms((unsigned int)slice_ms);
484 }
485 else
486 {
487 /* nothing arrived in this slice */
488 }
489 }
490
491 if (have_sibling)
492 {
493 ctx->sibling_startup_timestamp_ms = sibling_ts;
495 ctx->startup_timestamp_ms = ab_gp_startup_timestamp_agree(own_now, sibling_ts, true);
496 if (!sibling_is_reply)
497 {
499 }
500 (void)snprintf(sibling_text, sizeof(sibling_text), "%llu", (unsigned long long)sibling_ts);
501 (void)snprintf(detail, sizeof(detail), "%s",
502 (ctx->startup_timestamp_ms == sibling_ts) ? "adopted sibling" : "own kept");
503 }
504 else
505 {
506 ctx->startup_timestamp_ms = own_now;
507 (void)snprintf(sibling_text, sizeof(sibling_text), "%s", "none");
508 (void)snprintf(detail, sizeof(detail), "sibling not reached within %u ms",
509 (unsigned int)AB_GP_STARTUP_TS_WAIT_MS);
510 }
511 RTE_EXAMPLE_LOG("[%s] site startup timestamp %llu ms (own %llu, sibling %s, %s)\n", ctx->role_tag,
512 (unsigned long long)ctx->startup_timestamp_ms, (unsigned long long)own_now, sibling_text, detail);
513 (void)snprintf(extra, sizeof(extra), "agreed=%llu own=%llu sibling=%s",
514 (unsigned long long)ctx->startup_timestamp_ms, (unsigned long long)own_now, sibling_text);
515 if (have_sibling)
516 {
517 rte_log_write_event(RTE_LOG_LEVEL_INFO, site, 0U, ctx->role_tag, sibling_tag, "NEGOTIATION",
518 "startup timestamp agreed", extra);
519 }
520 else if (clock_failed)
521 {
522 rte_log_write_event(RTE_LOG_LEVEL_ERROR, site, 0U, ctx->role_tag, sibling_tag, "NEGOTIATION",
523 "startup timestamp: clock failed during exchange, own used", extra);
524 }
525 else
526 {
527 rte_log_write_event(RTE_LOG_LEVEL_WARNING, site, 0U, ctx->role_tag, sibling_tag, "NEGOTIATION",
528 "startup timestamp: sibling not reached, own used", extra);
529 }
530}
531
532void ab_gp_channel_negotiate_on_startup_ts_frame(ab_gp_channel_context_t *ctx, const uint8_t *payload, size_t size)
533{
534 bool is_reply = false;
535 rte_timestamp_ms_t ts_ms = 0U;
536
537 if ((ctx == NULL) || !ab_gp_startup_timestamp_decode(payload, size, &is_reply, &ts_ms))
538 {
539 return;
540 }
541 ctx->sibling_startup_timestamp_ms = ts_ms;
543 if (!is_reply && (ctx->startup_timestamp_ms != 0U))
544 {
545 /* The sibling restarted and asks: it gets this channel's agreed value (own if the exchange timed out). A
546 * channel whose clock was unavailable at start has no value (0, rte_site_role took its own clock) and does not
547 * answer; the sibling then uses its own clock after its wait. */
549 }
550}
551
552/* ---- GP entry points -------------------------------------------------------------------------------------------- */
553
555{
556 rte_site_role_config_t cfg;
557 rte_status_t status;
558 const bool west = (ctx->site == RTE_EXAMPLE_SITE_WEST);
559
560 /* ADR-040 step 5a: both channels of the site present one timestamp to their inter-site tie-break. */
562
563 (void)snprintf(ctx->site_role_peer_tag, sizeof(ctx->site_role_peer_tag), "%s/%s", ctx->is_role_a ? "A" : "B",
564 west ? "EAST" : "WEST");
565 rte_mem_set(&cfg, 0, sizeof(cfg));
566 cfg.own_id = (uint32_t)ctx->site;
567 cfg.peer_id = west ? (uint32_t)RTE_EXAMPLE_SITE_EAST : (uint32_t)RTE_EXAMPLE_SITE_WEST;
568 cfg.is_west = west;
569 cfg.listen = west; /* WEST listens, EAST connects - same convention as every other link of this component */
570 cfg.peer_host = west ? NULL
572 /* One fixed port per channel (not per site): A always RTE_EXAMPLE_A_NEGOTIATION_PORT, B always _B_. */
573 cfg.port = ctx->is_role_a ? (uint16_t)RTE_EXAMPLE_A_NEGOTIATION_PORT : (uint16_t)RTE_EXAMPLE_B_NEGOTIATION_PORT;
574 cfg.link_message_size = sizeof(rte_vital_message_t);
575 cfg.ack_timeout_ms = (rte_duration_ms_t)ctx->config.timers.neg_ack_timeout_ms;
576 cfg.peer_lost_timeout_ms = (rte_duration_ms_t)(
579 cfg.reconnect_attempt_ms = (rte_duration_ms_t)AB_GP_NEG_RECONNECT_ATTEMPT_MS;
580 cfg.startup_timeout_ms = (rte_duration_ms_t)RTE_EXAMPLE_CONNECT_TIMEOUT_MS;
581 cfg.link_poll_ms = (rte_duration_ms_t)RTE_EXAMPLE_LINK_TIMEOUT_MS;
582 cfg.connect_retry_ms = 100U;
583 cfg.connect_timeout_ms = 500U;
584 cfg.send_miss_threshold = (uint8_t)RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD;
585 cfg.standby_sync = (ctx->redundancy.standby_mode != RTE_STANDBY_MODE_COLD);
586 cfg.startup_timestamp_ms = ctx->startup_timestamp_ms;
588 cfg.tx_buf = ctx->site_role_tx;
589 cfg.rx_buf = ctx->site_role_rx;
590 cfg.role_tag = ctx->role_tag;
591 cfg.peer_tag = ctx->site_role_peer_tag;
592 cfg.site_name = site_config_name(ctx->site);
593 cfg.sibling_tag = ctx->is_role_a ? "B" : "A";
594 cfg.encode_state = encode_state;
595 cfg.receive_state = receive_state;
596 cfg.on_change = on_role_change;
597 cfg.trace = trace;
598 cfg.delay = delay_ms;
599 cfg.user = ctx;
600 /* ADR-040 step 5b: the service reads the A/B sibling's site state itself and derives online / ready / reachable;
601 * 0 = RTE_SITE_ROLE_SIBLING_REACH_DEFAULT_CYCLES (3), the literal local_facts() used for reachability before. */
602 cfg.sibling_source = sibling_source;
603 cfg.sibling_reach_cycles = 0U;
604
605 status = configure_takeover_key(ctx, &cfg);
606 if (status != RTE_STATUS_OK)
607 {
608 return status;
609 }
610 status = rte_site_role_init(&ctx->site_role, &cfg);
611 if (status != RTE_STATUS_OK)
612 {
613 return status;
614 }
615 status = rte_site_role_start(&ctx->site_role);
616 if (status != RTE_STATUS_OK)
617 {
618 return status;
619 }
620 ctx->is_online = rte_site_role_is_online(&ctx->site_role);
622 return RTE_STATUS_OK;
623}
624
626{
627 rte_site_role_local_t local;
628
629 if (ctx->join_window_cycles_left > 0U)
630 {
632 }
633 local_facts(ctx, &local);
634 rte_site_role_execute(&ctx->site_role, cycle, &local);
635}
636
638{
639 if (ctx != NULL)
640 {
641 rte_site_role_service_link(&ctx->site_role, ctx->cyc_cycle);
642 }
643}
644
646{
647 rte_site_role_local_t local;
648
649 local_facts(ctx, &local);
650 rte_site_role_sync_standby(&ctx->site_role, cycle, &local);
651}
652
654{
655 uint8_t token[RTE_SITE_ROLE_TOKEN_LEN];
656
657 if (ctx == NULL)
658 {
659 return;
660 }
661 if (ab_gp_takeover_token_from_payload(payload, token))
662 {
663 (void)rte_site_role_on_takeover_token(&ctx->site_role, ctx->cyc_cycle, token, sizeof(token));
664 }
665 else
666 {
667 /* Not a token (the interim file trigger sends an empty payload): the unauthenticated path, which
668 * rte_site_role refuses when a key is configured. */
669 rte_site_role_on_takeover_confirm(&ctx->site_role, ctx->cyc_cycle);
670 }
671}
672
674{
675 rte_site_role_flush_faulted(&ctx->site_role, ctx->single_mode);
676}
677
679{
680 rte_site_role_shutdown(&ctx->site_role);
681}
rte_status_t ab_gp_channel_send_peer(ab_gp_channel_handle_t handle, const uint8_t *bytes, size_t size)
Sends a frame over the A<->B peer channel.
struct ab_gp_channel_context_s * ab_gp_channel_handle_t
Opaque handle over this component's own static per-process context.
void ab_gp_channel_negotiate_flush_faulted(ab_gp_channel_context_t *ctx)
Best-effort, synchronous final send of this channel's own {faulted=true, single_mode}...
void ab_gp_channel_negotiate_on_takeover_confirm(ab_gp_channel_context_t *ctx, const char *payload)
Records a dispatcher confirmation that the other site is not ONLINE (from Gateway,...
static rte_status_t decode_db_field(void *data, const uint8_t *in, size_t size)
void ab_gp_channel_negotiate_sync_standby(ab_gp_channel_context_t *ctx, uint32_t cycle)
ONLINE only: send this cycle's post-compute snapshot to STANDBY and wait (bounded by the link's ack t...
static void apply_state_transfer(ab_gp_channel_context_t *ctx, const rte_site_role_change_t *change)
static void send_startup_ts_frame(ab_gp_channel_context_t *ctx, bool is_reply, rte_timestamp_ms_t ts_ms)
static rte_example_transfer_policy_t read_transfer_policy(void)
static bool sibling_source(void *user, rte_site_role_sibling_t *out)
static rte_status_t encode_state(void *user, uint8_t *out, size_t size, bool live)
static void trace(void *user, const char *line)
static rte_status_t configure_takeover_key(ab_gp_channel_context_t *ctx, rte_site_role_config_t *cfg)
void ab_gp_channel_negotiate_service_link(ab_gp_channel_context_t *ctx)
Fast internal-cycle service of the negotiation link (warm/hot standby only).
static void delay_ms(void *user, uint32_t ms)
static void build_state_transfer_registry(rte_state_transfer_registry_t *registry, negotiate_state_t *state)
static rte_status_t decode_sessions_field(void *data, const uint8_t *in, size_t size)
static void local_facts(const ab_gp_channel_context_t *ctx, rte_site_role_local_t *out)
static rte_status_t encode_db_field(const void *data, uint8_t *out, size_t size)
char negotiate_frame_layout_check_t[((RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE==(RTE_SITE_ROLE_FRAME_OVERHEAD+((size_t) RTE_EXAMPLE_MAX_TRAINS *(size_t) RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE)+(size_t) RTE_EXAMPLE_DB_WIRE_SIZE)) &&(RTE_EXAMPLE_SITE_ROLE_VALID_MARKER==RTE_SITE_ROLE_VALID_MARKER) &&(RTE_EXAMPLE_SINGLE_MODE_FLAG_SINGLE==RTE_SITE_ROLE_SINGLE_MODE_SINGLE)) ? 1 :-1]
#define AB_GP_STARTUP_TS_REQUEST_PERIOD_MS
static void exchange_startup_timestamp(ab_gp_channel_context_t *ctx)
static rte_status_t encode_sessions_field(const void *data, uint8_t *out, size_t size)
char negotiate_session_size_check_t[(RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE==RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SESSION_SIZE) ? 1 :-1]
void ab_gp_channel_negotiate_execute(ab_gp_channel_context_t *ctx, uint32_t cycle)
Runs one cycle's worth of inter-site negotiation: drives the negotiator's own STATE-frame exchange (d...
static bool receive_state(void *user, const uint8_t *in, size_t size, bool valid)
static void on_role_change(void *user, const rte_site_role_change_t *change)
#define AB_GP_TAKEOVER_CHALLENGE_MS
void ab_gp_channel_negotiate_on_startup_ts_frame(ab_gp_channel_context_t *ctx, const uint8_t *payload, size_t size)
Handles a PEER_MSG_KIND_STARTUP_TS frame that arrives after ab_gp_channel_negotiate_init() returned (...
rte_status_t ab_gp_channel_negotiate_init(ab_gp_channel_context_t *ctx)
Opens this channel's own inter-site negotiation link (WEST LISTENs, EAST CONNECTs),...
void ab_gp_channel_negotiate_shutdown(ab_gp_channel_context_t *ctx)
static bool ask_user_restart_or_continue(const ab_gp_channel_context_t *ctx, uint32_t transferred_cycle)
This channel's own West<->East inter-site negotiation link - see ab_gp_channel.c's file header for ho...
void ab_gp_channel_payload_decode_collector(ab_gp_channel_payload_collector_id_t id, const uint8_t *in, void *out_items)
Decodes one collector's own items - see ab_gp_channel_payload_encode_collector() (same contract,...
void ab_gp_channel_payload_encode_collector(ab_gp_channel_payload_collector_id_t id, uint8_t *out, const void *items)
Encodes one collector's own items into its own slice of a combined buffer - used both standalone (e....
Generic, data-blind wire framing for the A<->B peer link's AB_SAMPLE frame (CRC-64-protected,...
#define PEER_WIRE_FRAME_SIZE
#define PEER_MSG_KIND_KM114_DIGEST
#define PEER_MSG_KIND_STARTUP_TS
#define AB_GP_DEFERRED_PEER_FRAMES
Capacity of ab_gp_channel_context_t::deferred_peer_frames.
void ab_gp_channel_sleep_ms(unsigned int ms)
#define PEER_MSG_KIND_SAF_RB
Declares ab_gp_channel_sleep_ms() - see ab_gp_channel_types.h's own doc on why this tiny helper gets ...
rte_status_t ab_gp_db_decode(ab_gp_db_t *db, const uint8_t *in_bytes, uint32_t in_size)
Decodes the full route and train database from a deterministic wire format.
Definition ab_gp_db.c:557
rte_status_t ab_gp_db_encode(const ab_gp_db_t *db, uint8_t *out_bytes, uint32_t out_capacity, uint32_t *out_written)
Encodes the full route and train database into a deterministic wire format.
Definition ab_gp_db.c:474
bool ab_gp_sibling_state_take(ab_gp_sibling_state_t *st, rte_site_role_sibling_t *out)
The rte_site_role_sibling_source_fn contract: hands out the frame stored since the previous take.
The A/B sibling's last site-state frame (PEER_MSG_KIND_SITE_STATE), kept raw and handed to Platform_R...
bool ab_gp_site_status_ready_for_online(const ab_gp_channel_context_t *ctx)
Whether this channel confirms it is ready to be ONLINE: the Gateway relay delivered a frame within th...
The site status monitor: collects, in one struct, the facts that decide and explain this channel's si...
GP-specific standby and site-link constants, the session-adoption rule and the stall rule.
#define AB_GP_JOIN_ADOPT_CYCLES
#define AB_GP_NEG_RECONNECT_ATTEMPT_MS
rte_timestamp_ms_t ab_gp_startup_timestamp_agree(rte_timestamp_ms_t own_now_ms, rte_timestamp_ms_t sibling_ts_ms, bool have_sibling)
The site startup timestamp this channel uses given its own clock and the sibling's value.
bool ab_gp_startup_timestamp_decode(const uint8_t *buf, size_t size, bool *is_reply, rte_timestamp_ms_t *ts_ms)
Decodes a STARTUP_TS payload.
void ab_gp_startup_timestamp_encode(uint8_t *buf, bool is_reply, rte_timestamp_ms_t ts_ms)
Encodes a STARTUP_TS payload.
The site startup timestamp A and B agree over the A/B peer link (ADR-040 step 5a): the agreement rule...
#define AB_GP_STARTUP_TS_MAX_SLICES
#define AB_GP_STARTUP_TS_WAIT_MS
#define AB_GP_STARTUP_TS_READ_SLICE_MS
bool ab_gp_takeover_token_from_payload(const char *payload, uint8_t out[RTE_SITE_ROLE_TOKEN_LEN])
Decodes a util payload token=<48 hex> into a token.
bool ab_gp_takeover_verify_mac(void *user, const uint8_t *msg, size_t msg_len, const uint8_t mac[RTE_SITE_ROLE_TOKEN_MAC_LEN])
rte_site_role verify_mac callback: true only when a key is loaded and the Subset-037 MAC over msg mat...
bool ab_gp_takeover_key_load_file(const char *path)
Loads the key file (one key, 48 hex characters) into this process's key slot.
The site's takeover key and the token codec for the dispatcher takeover confirmation (ADR-040 step 4b...
#define AB_GP_TAKEOVER_KEY_FILE_ENV
uint32_t ab_gp_train_payload_collector_id(void)
#define RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE
#define RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD
#define RTE_EXAMPLE_LOG(...)
#define RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE
rte_example_transfer_policy_t
Policy applied to the transferred cycle counter at the moment a standby channel is promoted to ONLINE...
@ RTE_EXAMPLE_TRANSFER_POLICY_ASK_USER
@ RTE_EXAMPLE_TRANSFER_POLICY_CONTINUE
@ RTE_EXAMPLE_TRANSFER_POLICY_RESTART
#define RTE_EXAMPLE_REMOTE_B_HOST_ENV
#define RTE_EXAMPLE_B_NEGOTIATION_PORT
#define RTE_EXAMPLE_DB_WIRE_SIZE
#define RTE_EXAMPLE_TRANSFER_ASK_USER_TIMEOUT_MS
#define RTE_EXAMPLE_A_NEGOTIATION_PORT
#define RTE_EXAMPLE_MAX_TRAINS
#define RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SESSION_SIZE
#define RTE_EXAMPLE_SITE_ROLE_VALID_MARKER
#define RTE_EXAMPLE_REMOTE_A_HOST_ENV
#define RTE_EXAMPLE_LINK_TIMEOUT_MS
#define RTE_EXAMPLE_SINGLE_MODE_FLAG_SINGLE
#define RTE_EXAMPLE_CONNECT_TIMEOUT_MS
#define RTE_EXAMPLE_TRANSFER_POLICY_ENV
const char * site_config_name(rte_example_site_t site)
Definition site_config.c:52
const char * site_config_host(const char *env_var_name)
Resolves a CONNECT-role link's target host for this process.
@ RTE_EXAMPLE_SITE_WEST
Definition site_config.h:21
@ RTE_EXAMPLE_SITE_EAST
Definition site_config.h:22
Central application context owned by composition root (main.c).
uint8_t deferred_peer_frames[AB_GP_DEFERRED_PEER_FRAMES][PEER_WIRE_FRAME_SIZE]
ab_gp_train_session_t sessions[RTE_EXAMPLE_MAX_TRAINS]
rte_timestamp_ms_t sibling_startup_timestamp_ms
rte_timestamp_ms_t startup_timestamp_ms
uint8_t site_role_rx[RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE]
rte_redundancy_config_t redundancy
struct ab_gp_channel_context_t::@030156273065004347244003027116254344306230337252 neg_peer_extra
uint8_t site_role_tx[RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE]
rte_channel_service_t peer_channel
ab_gp_config_timers_t timers
uint32_t reconnect_attempt_timeout_ms
ab_gp_train_session_t sessions[RTE_EXAMPLE_MAX_TRAINS]