21#define _POSIX_C_SOURCE 200809L
33#include <sys/select.h>
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"
50#define AB_GP_TAKEOVER_CHALLENGE_MS 120000U
55#define AB_GP_STARTUP_TS_REQUEST_PERIOD_MS 100U
94 uint32_t written = 0U;
107 (void)rte_state_transfer_registry_init(registry);
108 (void)rte_state_transfer_register_field_ex(
109 registry,
"sessions", state->
sessions,
117static rte_status_t
encode_state(
void *user, uint8_t *out,
size_t size,
bool live)
121 rte_state_transfer_registry_t registry;
123 rte_mem_set(&state, 0,
sizeof(state));
127 rte_mem_copy(&state.
db, &ctx->
db,
sizeof(state.
db));
130 return rte_state_transfer_encode(®istry, out, size, NULL);
134static bool receive_state(
void *user,
const uint8_t *in,
size_t size,
bool valid)
138 rte_state_transfer_registry_t registry;
140 rte_mem_set(&decoded, 0,
sizeof(decoded));
142 if (rte_state_transfer_decode(®istry, in, size) != RTE_STATUS_OK)
165 if (strcasecmp(env,
"restart") == 0)
169 if (strcasecmp(env,
"ask") == 0)
182 struct timeval timeout;
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",
189 "ASK_USER: awaiting operator restart/continue decision", NULL);
192 FD_SET(STDIN_FILENO, &read_fds);
196 ready = select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);
199 char line[8] = { 0 };
200 ssize_t n = read(STDIN_FILENO, line,
sizeof(line) - 1U);
204 if ((line[0] ==
'r') || (line[0] ==
'R'))
208 "TRANSFER",
"ASK_USER: operator chose RESTART", NULL);
213 "TRANSFER",
"ASK_USER: operator chose CONTINUE", NULL);
217 RTE_EXAMPLE_LOG(
"[%s] ASK_USER: no answer within %ums (EOF/no TTY/timeout) - defaulting to CONTINUE\n",
220 "ASK_USER: no answer in time - defaulting to CONTINUE", NULL);
229 uint32_t applied_cycle;
231 if (!change->snapshot_valid)
233 RTE_EXAMPLE_LOG(
"[%s] state transfer: no valid counterpart snapshot cached yet - starting cold (cycle 1, no sessions, no routes)\n",
236 "no valid counterpart snapshot cached - starting cold", NULL);
239 rte_mem_set(&ctx->
db, 0,
sizeof(ctx->
db));
244 applied_cycle = change->snapshot_cycle;
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);
261 change->snapshot_cycle);
269 (void)snprintf(extra,
sizeof(extra),
"cycle=%u policy=%d", applied_cycle, (
int)policy);
271 "applying transferred snapshot", extra);
274 ctx->
cycle = (applied_cycle > 0U) ? applied_cycle : 1U;
291 if (change->kind == RTE_SITE_ROLE_CHANGE_PROMOTED)
298 if (change->apply_state)
304static void trace(
void *user,
const char *line)
324 out->sibling_online =
false;
325 out->sibling_ready =
false;
326 out->sibling_reachable =
false;
342 if ((path == NULL) || (path[0] ==
'\0'))
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;
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;
355 cfg->channel_id = (uint8_t)(ctx->
is_role_a ? 0U : 1U);
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;
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;
405 char sibling_text[24];
409 if (rte_timer_now(&own_now) != RTE_STATUS_OK)
412 RTE_EXAMPLE_LOG(
"[%s] site startup timestamp: clock unavailable - no exchange with the sibling\n",
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);
422 rte_duration_ms_t elapsed;
423 rte_duration_ms_t remaining;
424 rte_duration_ms_t slice_ms;
427 if (slices_left == 0U)
433 if (rte_timer_now(&now_ms) != RTE_STATUS_OK)
440 elapsed = (rte_duration_ms_t)(now_ms - own_now);
449 last_request_ms = now_ms;
450 request_pending =
false;
457 status = rte_channel_service_read(&ctx->
com.
peer_channel, in_bytes,
sizeof(in_bytes), slice_ms);
458 if (status == RTE_STATUS_OK)
464 &sibling_is_reply, &sibling_ts);
479 else if (status != RTE_STATUS_TIMEOUT)
496 if (!sibling_is_reply)
500 (void)snprintf(sibling_text,
sizeof(sibling_text),
"%llu", (
unsigned long long)sibling_ts);
501 (void)snprintf(detail,
sizeof(detail),
"%s",
507 (void)snprintf(sibling_text,
sizeof(sibling_text),
"%s",
"none");
508 (void)snprintf(detail,
sizeof(detail),
"sibling not reached within %u ms",
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",
517 rte_log_write_event(RTE_LOG_LEVEL_INFO, site, 0U, ctx->
role_tag, sibling_tag,
"NEGOTIATION",
518 "startup timestamp agreed", extra);
520 else if (clock_failed)
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);
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);
534 bool is_reply =
false;
535 rte_timestamp_ms_t ts_ms = 0U;
556 rte_site_role_config_t cfg;
564 west ?
"EAST" :
"WEST");
565 rte_mem_set(&cfg, 0,
sizeof(cfg));
566 cfg.own_id = (uint32_t)ctx->
site;
570 cfg.peer_host = west ? NULL
574 cfg.link_message_size =
sizeof(rte_vital_message_t);
576 cfg.peer_lost_timeout_ms = (rte_duration_ms_t)(
582 cfg.connect_retry_ms = 100U;
583 cfg.connect_timeout_ms = 500U;
585 cfg.standby_sync = (ctx->
redundancy.standby_mode != RTE_STANDBY_MODE_COLD);
593 cfg.sibling_tag = ctx->
is_role_a ?
"B" :
"A";
603 cfg.sibling_reach_cycles = 0U;
606 if (status != RTE_STATUS_OK)
610 status = rte_site_role_init(&ctx->
site_role, &cfg);
611 if (status != RTE_STATUS_OK)
615 status = rte_site_role_start(&ctx->
site_role);
616 if (status != RTE_STATUS_OK)
622 return RTE_STATUS_OK;
627 rte_site_role_local_t local;
634 rte_site_role_execute(&ctx->
site_role, cycle, &local);
647 rte_site_role_local_t local;
650 rte_site_role_sync_standby(&ctx->
site_role, cycle, &local);
655 uint8_t token[RTE_SITE_ROLE_TOKEN_LEN];
663 (void)rte_site_role_on_takeover_token(&ctx->
site_role, ctx->
cyc_cycle, token,
sizeof(token));
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.
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.
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)
const char * site_config_host(const char *env_var_name)
Resolves a CONNECT-role link's target host for this process.
Central application context owned by composition root (main.c).
rte_site_role_t site_role
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
uint32_t join_window_cycles_left
rte_timestamp_ms_t startup_timestamp_ms
uint8_t site_role_rx[RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE]
rte_redundancy_config_t redundancy
uint32_t last_relay_rx_cycle
bool have_sibling_startup_timestamp
struct ab_gp_channel_context_t::@030156273065004347244003027116254344306230337252 neg_peer_extra
uint8_t deferred_peer_count
char site_role_peer_tag[16]
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 neg_ack_timeout_ms
uint32_t reconnect_attempt_timeout_ms
ab_gp_train_session_t sessions[RTE_EXAMPLE_MAX_TRAINS]