Safe API Framework 0.1.0 build 384e6ae
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
rte_site_role.c
Go to the documentation of this file.
1
13
14#include <stdio.h>
15
16#include "rte/oal/log/rte_log.h"
21
22#define SITE_ROLE_TRACE_LINE_MAX 256U
23#define SITE_ROLE_SERVICE_DRAIN 4U
24
25/* ---- logging -------------------------------------------------------------------------------------------------- */
26
27static void trace_line(const rte_site_role_t *rs, const char *line)
28{
29 if (rs->cfg.trace != NULL)
30 {
31 rs->cfg.trace(rs->cfg.user, line);
32 }
33}
34
35static void log_event(const rte_site_role_t *rs, rte_log_level_t level, uint32_t cycle, const char *source,
36 const char *destination, const char *type, const char *info, const char *extra)
37{
38 rte_log_write_event(level, rs->cfg.site_name, cycle, source, destination, type, info, extra);
39}
40
41/* ---- frame codec ------------------------------------------------------------------------------------------------ */
42
43static size_t state_size(const rte_site_role_t *rs)
44{
46}
47
48static void encode_frame(rte_site_role_t *rs, bool faulted, bool single_mode, uint32_t cycle, bool live)
49{
50 uint8_t *out = rs->cfg.tx_buf;
51 rte_buffer_t buf;
52
53 (void)rte_buffer_init(&buf, out, rs->cfg.frame_size);
54 out[0] = faulted ? 1U : 0U;
55 out[1] = single_mode ? (uint8_t)RTE_SITE_ROLE_SINGLE_MODE_SINGLE : (uint8_t)RTE_SITE_ROLE_SINGLE_MODE_DUAL;
56 (void)rte_buffer_set_length(&buf, 2U);
57 (void)rte_buffer_write_u32_le(&buf, live ? cycle : 0U);
58 /* Zeroed before encoding, so the region never carries stale bytes. */
59 rte_mem_set(&out[RTE_SITE_ROLE_FRAME_STATE_OFFSET], 0, state_size(rs));
60 (void)rs->cfg.encode_state(rs->cfg.user, &out[RTE_SITE_ROLE_FRAME_STATE_OFFSET], state_size(rs), live);
61 out[rs->cfg.frame_size - 1U] = live ? (uint8_t)RTE_SITE_ROLE_VALID_MARKER : 0U;
62}
63
64/* ---- role changes ----------------------------------------------------------------------------------------------- */
65
66static void report_change(rte_site_role_t *rs, rte_site_role_change_kind_t kind, rte_site_role_reason_t reason,
67 bool apply_state)
68{
70
71 change.kind = kind;
72 change.reason = reason;
73 change.online = rs->online;
74 change.apply_state = apply_state;
75 change.snapshot_valid = rs->peer_snapshot_valid;
76 change.snapshot_cycle = rs->peer_snapshot_cycle;
77 rs->cfg.on_change(rs->cfg.user, &change);
78 if (apply_state && rs->peer_snapshot_valid)
79 {
80 /* The application renumbers its cycle from the snapshot: every "last seen at cycle N" stamp refers to the old
81 * numbering. Left as it is, a stamp from just before the renumbering looked like a fresh one against the new,
82 * lower cycle number: a silent counterpart then "answered" and a dispatcher-confirmed takeover raised a
83 * dual-ONLINE alarm two cycles later (Docker takeover test). */
84 rs->peer_last_rx_cycle = 0U;
85 rs->standby_rx_cycle = 0U;
86 rs->standby_last_try_cycle = 0U;
87 }
88}
89
90static void promote_to_online(rte_site_role_t *rs, const char *message, rte_site_role_reason_t reason)
91{
92 char line[SITE_ROLE_TRACE_LINE_MAX];
93
94 rs->online = true;
95 rs->peer_unresponsive = false;
96 rs->takeover_confirmed = false;
97 (void)snprintf(line, sizeof(line), "[%s] *** TAKEOVER: %s, this channel promotes itself to ONLINE ***\n",
98 rs->cfg.role_tag, message);
99 trace_line(rs, line);
100 log_event(rs, RTE_LOG_LEVEL_WARNING, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "ROLE", message, NULL);
101 report_change(rs, RTE_SITE_ROLE_CHANGE_PROMOTED, reason, true);
102}
103
104static void demote_to_standby(rte_site_role_t *rs, const char *message, rte_site_role_reason_t reason)
105{
106 char line[SITE_ROLE_TRACE_LINE_MAX];
107
108 rs->online = false;
109 rs->lost_peer_while_online = false;
110 (void)snprintf(line, sizeof(line), "[%s] *** %s - settling into STANDBY ***\n", rs->cfg.role_tag, message);
111 trace_line(rs, line);
112 log_event(rs, RTE_LOG_LEVEL_WARNING, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "ROLE", message, NULL);
113 report_change(rs, RTE_SITE_ROLE_CHANGE_DEMOTED, reason, false);
114}
115
118{
119 char line[SITE_ROLE_TRACE_LINE_MAX];
120
121 rs->online = rs->cfg.is_west;
122 (void)snprintf(line, sizeof(line), "[%s] *** RE-NEGOTIATING: both sides FAULTED -> %s ***\n", rs->cfg.role_tag,
123 rs->online ? "ONLINE" : "STANDBY");
124 trace_line(rs, line);
125 log_event(rs, RTE_LOG_LEVEL_WARNING, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "NEGOTIATION",
126 rs->online ? "RE-NEGOTIATED (both FAULTED): ONLINE" : "RE-NEGOTIATED (both FAULTED): STANDBY", NULL);
127 report_change(rs, RTE_SITE_ROLE_CHANGE_RENEGOTIATED, reason, rs->online);
128}
129
130/* ---- link ------------------------------------------------------------------------------------------------------- */
131
132static void on_negotiator_state_change(rte_dual_state_t new_own_state, rte_dual_state_t old_own_state,
133 rte_dual_state_t new_peer_state, rte_dual_state_t old_peer_state,
134 void *user_ctx)
135{
136 const rte_site_role_t *rs = (const rte_site_role_t *)user_ctx;
137 char line[SITE_ROLE_TRACE_LINE_MAX];
138 char extra[96];
139 const char *old_own_str = rte_dual_state_to_string(old_own_state);
140 const char *new_own_str = rte_dual_state_to_string(new_own_state);
141 const char *old_peer_str = rte_dual_state_to_string(old_peer_state);
142 const char *new_peer_str = rte_dual_state_to_string(new_peer_state);
143
144 (void)snprintf(line, sizeof(line), "[%s] negotiator state change: own %s->%s, peer %s->%s (own_ts=%llu, peer_ts=%llu)\n",
145 rs->cfg.role_tag, old_own_str, new_own_str, old_peer_str, new_peer_str,
146 (unsigned long long)rte_dual_negotiator_get_own_startup_timestamp_ms(&rs->negotiator),
147 (unsigned long long)rte_dual_negotiator_get_peer_startup_timestamp_ms(&rs->negotiator));
148 trace_line(rs, line);
149 (void)snprintf(extra, sizeof(extra), "own:%s->%s peer:%s->%s", old_own_str, new_own_str, old_peer_str, new_peer_str);
150 log_event(rs, RTE_LOG_LEVEL_INFO, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "NEGOTIATOR", "state change", extra);
151}
152
153static rte_status_t open_link_once(rte_site_role_t *rs, rte_duration_ms_t timeout_ms)
154{
155 rte_netlink_config_t link_cfg;
156
157 rte_mem_set(&link_cfg, 0, sizeof(link_cfg));
159 link_cfg.host = rs->cfg.listen ? NULL : rs->cfg.peer_host;
160 link_cfg.port = rs->cfg.port;
161 link_cfg.message_size = rs->cfg.link_message_size; /* required by rte_dual_channel_init() */
162 link_cfg.connect_timeout_ms = timeout_ms;
163 return rte_netlink_open(&rs->link_storage, &link_cfg, &rs->link);
164}
165
169{
170 rte_status_t status;
171 uint32_t waited_ms = 0U;
172
173 if (rs->cfg.listen)
174 {
175 return open_link_once(rs, rs->cfg.startup_timeout_ms);
176 }
177 status = RTE_STATUS_TIMEOUT;
178 while (waited_ms < (uint32_t)rs->cfg.startup_timeout_ms)
179 {
180 status = open_link_once(rs, rs->cfg.connect_timeout_ms);
181 if (status == RTE_STATUS_OK)
182 {
183 break;
184 }
185 if (rs->cfg.delay != NULL)
186 {
187 rs->cfg.delay(rs->cfg.user, rs->cfg.connect_retry_ms);
188 }
189 waited_ms += rs->cfg.connect_retry_ms;
190 status = RTE_STATUS_TIMEOUT;
191 }
192 return status;
193}
194
201{
203 rte_dual_negotiator_config_t negotiator_cfg;
204 rte_status_t status;
205
206 rte_mem_set(&dual_cfg, 0, sizeof(dual_cfg));
207 dual_cfg.links[0] = rs->link;
208 dual_cfg.link_count = 1U;
209 dual_cfg.sender_id = rs->cfg.own_id;
210 dual_cfg.expected_peer_id = rs->cfg.peer_id;
211 dual_cfg.ack_timeout_ms = rs->cfg.ack_timeout_ms;
212 status = rte_dual_channel_init(&rs->dual_channel, &dual_cfg);
213 if (status != RTE_STATUS_OK)
214 {
215 return status;
216 }
217 /* The counterpart restarts on its own: its frames start at sequence 0 again while this end keeps expecting the
218 * old number; without a resync both ends rejected each other for good. */
219 (void)rte_dual_channel_set_resync_on_sequence_error(&rs->dual_channel, true);
220 rs->send_miss = 0U;
221
222 rte_mem_set(&negotiator_cfg, 0, sizeof(negotiator_cfg));
223 negotiator_cfg.channel = &rs->dual_channel;
224 negotiator_cfg.own_id = rs->cfg.own_id;
225 negotiator_cfg.peer_id = rs->cfg.peer_id;
226 negotiator_cfg.peer_lost_timeout_ms = rs->cfg.peer_lost_timeout_ms;
227 negotiator_cfg.state_change_callback = on_negotiator_state_change;
228 negotiator_cfg.state_change_callback_ctx = rs;
229 negotiator_cfg.resume_own_startup_timestamp_ms = rs->startup_timestamp_ms;
230 if (rs->online)
231 {
233 }
234 else if (resume_standby)
235 {
237 }
238 else
239 {
240 /* RTE_DUAL_STATE_IDLE (zeroed): the negotiator decides from the startup timestamps */
241 }
242 status = rte_dual_negotiator_init(&rs->negotiator, &negotiator_cfg);
243 if (status != RTE_STATUS_OK)
244 {
245 return status;
246 }
247 if (rs->startup_timestamp_ms == 0U)
248 {
249 rs->startup_timestamp_ms = rte_dual_negotiator_get_own_startup_timestamp_ms(&rs->negotiator);
250 }
251 return RTE_STATUS_OK;
252}
253
254static void close_link(rte_site_role_t *rs)
255{
256 if (rs->link != NULL)
257 {
258 (void)rte_netlink_close(rs->link);
259 rs->link = NULL;
260 }
261}
262
266{
267 rte_status_t status = open_link_once(rs, rs->cfg.reconnect_attempt_ms);
268 char line[SITE_ROLE_TRACE_LINE_MAX];
269
270 if (status != RTE_STATUS_OK)
271 {
272 return status;
273 }
274 status = init_channel_and_negotiator(rs, false);
275 if (status != RTE_STATUS_OK)
276 {
277 close_link(rs);
278 return status;
279 }
280 (void)snprintf(line, sizeof(line), "[%s] inter-site negotiation link (re)established\n", rs->cfg.role_tag);
281 trace_line(rs, line);
282 log_event(rs, RTE_LOG_LEVEL_INFO, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "NEGOTIATION", "link (re)established",
283 NULL);
284 return RTE_STATUS_OK;
285}
286
287/* ---- per cycle -------------------------------------------------------------------------------------------------- */
288
289static bool send_frame(rte_site_role_t *rs, uint32_t cycle)
290{
291 rte_status_t status;
292 char line[SITE_ROLE_TRACE_LINE_MAX];
293
294 encode_frame(rs, rs->local_faulted, rs->local_single_mode, rs->cycle, rs->online);
295 status = rte_dual_channel_send(&rs->dual_channel, rs->cfg.tx_buf, (uint8_t)rs->cfg.frame_size, NULL);
296 if (status != RTE_STATUS_OK)
297 {
298 /* Not fatal: the counterpart may still have a frame queued for us (e.g. its own final FAULTED flush), and
299 * that frame can be exactly the promotion signal this channel is waiting for. */
300 (void)snprintf(line, sizeof(line), "[%s] cycle %u: negotiation extra payload send failed (%s)\n",
301 rs->cfg.role_tag, (unsigned int)cycle, rte_status_to_string(status));
302 trace_line(rs, line);
303 }
304 return status == RTE_STATUS_OK;
305}
306
307static rte_status_t receive_frame(rte_site_role_t *rs, uint32_t cycle, bool quiet)
308{
309 uint8_t received_size = 0U;
310 rte_status_t status;
311 char line[SITE_ROLE_TRACE_LINE_MAX];
312
313 status = rte_dual_channel_receive(&rs->dual_channel, rs->cfg.rx_buf, (uint8_t)rs->cfg.frame_size, 0U,
314 &received_size);
315 if (status == RTE_STATUS_OK)
316 {
317 const uint8_t *in = rs->cfg.rx_buf;
318 rte_buffer_t buf;
319 uint32_t peer_cycle = 0U;
320 const bool valid = (in[rs->cfg.frame_size - 1U] == (uint8_t)RTE_SITE_ROLE_VALID_MARKER);
321 bool decoded = false;
322
323 if (rte_buffer_init(&buf, rs->cfg.rx_buf, rs->cfg.frame_size) == RTE_STATUS_OK)
324 {
325 (void)rte_buffer_set_length(&buf, rs->cfg.frame_size);
326 decoded = (rte_buffer_read_u32_le(&buf, 2U, &peer_cycle) == RTE_STATUS_OK) &&
327 rs->cfg.receive_state(rs->cfg.user, &in[RTE_SITE_ROLE_FRAME_STATE_OFFSET], state_size(rs),
328 valid);
329 }
330 if (decoded)
331 {
332 const bool peer_faulted = (in[0] != 0U);
333
334 rs->peer_faulted = peer_faulted;
335 rs->peer_single_mode = in[1];
336 /* The counterpart is talking again: it may well be ONLINE, so any confirmation is void. */
337 rs->peer_unresponsive = false;
338 rs->takeover_confirmed = false;
339 rs->peer_last_rx_cycle = rs->cycle;
340 rs->peer_online_now = valid && !peer_faulted;
341 if (valid)
342 {
343 rs->peer_snapshot_cycle = peer_cycle;
344 rs->peer_snapshot_valid = true;
345 rs->standby_rx_cycle = rs->cycle;
346 }
347 if (!quiet)
348 {
349 (void)snprintf(line, sizeof(line),
350 "[%s] cycle %u: negotiation received payload from counterpart (valid=%d, peer_cycle=%u, "
351 "faulted=%d, single=%d)\n",
352 rs->cfg.role_tag, (unsigned int)cycle, valid ? 1 : 0, (unsigned int)peer_cycle,
353 peer_faulted ? 1 : 0, (int)in[1]);
354 trace_line(rs, line);
355 }
356 }
357 }
358
359 if ((status != RTE_STATUS_OK) && !quiet)
360 {
361 (void)snprintf(line, sizeof(line),
362 "[%s] cycle %u: negotiation extra payload receive failed (%s) - using last-known counterpart "
363 "status\n",
364 rs->cfg.role_tag, (unsigned int)cycle, rte_status_to_string(status));
365 trace_line(rs, line);
366 log_event(rs, RTE_LOG_LEVEL_DEBUG, cycle, rs->cfg.peer_tag, rs->cfg.role_tag, "NEGOTIATION",
367 "extra payload receive failed - using last-known counterpart status", NULL);
368 }
369 return status;
370}
371
374static void handle_send_miss_and_link_teardown(rte_site_role_t *rs, uint32_t cycle, bool send_ok,
375 rte_status_t receive_status)
376{
377 char line[SITE_ROLE_TRACE_LINE_MAX];
378
379 if (send_ok)
380 {
381 rs->send_miss = 0U;
382 }
383 else if (rs->send_miss < UINT8_MAX)
384 {
385 rs->send_miss++;
386 }
387 else
388 {
389 /* already saturated */
390 }
391
392 if ((rs->send_miss >= rs->cfg.send_miss_threshold) ||
393 ((receive_status != RTE_STATUS_OK) && (receive_status != RTE_STATUS_TIMEOUT)))
394 {
395 (void)snprintf(line, sizeof(line), "[%s] cycle %u: inter-site negotiation link appears dead - closing for reconnect\n",
396 rs->cfg.role_tag, (unsigned int)cycle);
397 trace_line(rs, line);
398 if (rs->online)
399 {
400 /* If the counterpart was confirmed absent by the dispatcher and took over meanwhile, this channel must
401 * yield when it sees that: see rte_site_role_decide(). */
402 rs->lost_peer_while_online = true;
403 }
404 if (!rs->online && !rs->local_faulted && !rs->peer_unresponsive)
405 {
406 /* A silent counterpart never promotes this channel by itself (REQ-SITEROLE-003): it stays STANDBY until
407 * a dispatcher confirmation, so a partition can never leave two ONLINE sites. */
408 rs->peer_unresponsive = true;
409 (void)snprintf(line, sizeof(line),
410 "[%s] cycle %u: counterpart not responding - staying STANDBY, waiting for the dispatcher to "
411 "confirm the other site is not ONLINE\n",
412 rs->cfg.role_tag, (unsigned int)cycle);
413 trace_line(rs, line);
414 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "TAKEOVER",
415 "counterpart not responding - dispatcher confirmation required", NULL);
416 }
417 close_link(rs);
418 rs->send_miss = 0U;
419 }
420}
421
423static void update_standby_warmth(rte_site_role_t *rs, uint32_t cycle)
424{
425 char line[SITE_ROLE_TRACE_LINE_MAX];
426
427 if (rte_site_role_standby_data_stale(rs->cfg.standby_sync, rs->online, rs->peer_snapshot_valid, rs->cycle,
428 rs->standby_rx_cycle))
429 {
430 rs->peer_snapshot_valid = false;
431 (void)snprintf(line, sizeof(line), "[%s] cycle %u: standby snapshot silent for more than %u cycles - STANDBY goes cold\n",
432 rs->cfg.role_tag, (unsigned int)cycle, (unsigned int)RTE_SITE_ROLE_STANDBY_DATA_TIMEOUT_CYCLES);
433 trace_line(rs, line);
434 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "STANDBY", "snapshot stale - cold", NULL);
435 }
436 if (rs->cfg.standby_sync && !rs->online)
437 {
438 const bool warm = rs->peer_snapshot_valid;
439
440 if (warm && !rs->standby_warm)
441 {
442 (void)snprintf(line, sizeof(line), "[%s] cycle %u: STANDBY is warm (receiving ONLINE snapshots)\n",
443 rs->cfg.role_tag, (unsigned int)cycle);
444 trace_line(rs, line);
445 }
446 rs->standby_warm = warm;
447 }
448 else
449 {
450 rs->standby_warm = false;
451 }
452}
453
454/* ---- A/B sibling source (REQ-SITEROLE-016) ------------------------------------------------------------------------ */
455
457static void poll_sibling_source(rte_site_role_t *rs, uint32_t cycle)
458{
459 if (rs->cfg.sibling_source != NULL)
460 {
461 if (rs->cfg.sibling_source(rs->cfg.user, &rs->sibling))
462 {
463 rs->sibling_rx_cycle = cycle;
464 }
465 }
466}
467
471static bool sibling_reachable(const rte_site_role_t *rs)
472{
473 return rs->sibling.known && (rs->cycle >= rs->sibling_rx_cycle) &&
474 ((rs->cycle - rs->sibling_rx_cycle) <= rs->cfg.sibling_reach_cycles);
475}
476
478static void fill_sibling_facts(const rte_site_role_t *rs, const rte_site_role_local_t *local,
480{
481 if (rs->cfg.sibling_source != NULL)
482 {
483 in->sibling_online = rs->sibling.known && rs->sibling.online;
484 in->sibling_ready = rs->sibling.known && rs->sibling.ready;
486 }
487 else
488 {
489 in->sibling_online = local->sibling_online;
490 in->sibling_ready = local->sibling_ready;
492 }
493}
494
497static void check_sibling_after_decision(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
498{
499 if (rs->cfg.sibling_source != NULL)
500 {
501 rs->sibling_verdict = rte_site_role_check_sibling(rs, cycle, local->single_mode, &rs->sibling);
502 }
503}
504
509static void startup_role_from_sibling(rte_site_role_t *rs, bool *resume_standby)
510{
511 char line[SITE_ROLE_TRACE_LINE_MAX];
512
513 *resume_standby = false;
514 if (rs->cfg.sibling_source == NULL)
515 {
516 /* no source: the application supplies the sibling facts (step 3), the start-up is unchanged */
517 }
518 else if (rs->sibling.known)
519 {
520 rs->online = rs->sibling.online;
521 *resume_standby = !rs->sibling.online;
522 (void)snprintf(line, sizeof(line), "[%s] start-up: A/B sibling reports %s, resuming that role (REQ-SITEROLE-017)\n",
523 rs->cfg.role_tag, rs->online ? "ONLINE" : "STANDBY");
524 trace_line(rs, line);
525 log_event(rs, RTE_LOG_LEVEL_INFO, 0U, rs->cfg.role_tag, (rs->cfg.sibling_tag != NULL) ? rs->cfg.sibling_tag : "-",
526 "NEGOTIATION",
527 rs->online ? "start-up role resumed from A/B sibling: ONLINE"
528 : "start-up role resumed from A/B sibling: STANDBY",
529 NULL);
530 }
531 else
532 {
533 (void)snprintf(line, sizeof(line), "[%s] start-up: no A/B sibling state, negotiating from timestamps\n",
534 rs->cfg.role_tag);
535 trace_line(rs, line);
536 }
537}
538
539static void build_decision_input(const rte_site_role_t *rs, const rte_site_role_local_t *local,
541{
542 in->is_online = rs->online;
543 in->own_faulted = rs->local_faulted;
544 in->is_west = rs->cfg.is_west;
545 in->link_up = (rs->link != NULL);
546 in->peer_unresponsive = rs->peer_unresponsive;
547 in->cycle = rs->cycle;
548 in->peer_last_rx_cycle = rs->peer_last_rx_cycle;
549 in->peer_online_now = rs->peer_online_now;
550 in->peer_faulted = rs->peer_faulted;
551 in->peer_snapshot_valid = rs->peer_snapshot_valid;
552 in->takeover_confirmed = rs->takeover_confirmed;
553 in->takeover_confirm_cycle = rs->takeover_confirm_cycle;
554 in->lost_peer_while_online = rs->lost_peer_while_online;
555 in->both_standby_cycles = rs->both_standby_cycles;
556 in->own_ready = local->own_ready;
557 fill_sibling_facts(rs, local, in);
558}
559
560static bool reason_changed(const rte_site_role_t *rs, rte_site_role_reason_t reason)
561{
562 return !rs->last_reason_set || (rs->last_reason != reason);
563}
564
565static void set_reason(rte_site_role_t *rs, rte_site_role_reason_t reason)
566{
567 rs->last_reason = reason;
568 rs->last_reason_set = true;
569}
570
572static void run_site_role_decision(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
573{
576 char message[160];
577 char line[SITE_ROLE_TRACE_LINE_MAX];
578
579 build_decision_input(rs, local, &in);
580 d = rte_site_role_decide(&in);
581 rs->both_standby_cycles = d.both_standby_cycles;
582
583 switch (d.action)
584 {
586 (void)snprintf(message, sizeof(message), "TAKEOVER: %s, promoted to ONLINE", rte_site_role_reason_text(d.reason));
587 promote_to_online(rs, message, d.reason);
588 set_reason(rs, d.reason);
589 break;
591 (void)snprintf(message, sizeof(message), "%s, demoted to STANDBY", rte_site_role_reason_text(d.reason));
592 demote_to_standby(rs, message, d.reason);
593 set_reason(rs, d.reason);
594 break;
596 renegotiate_from_deadlock(rs, d.reason);
597 set_reason(rs, d.reason);
598 break;
600 if (reason_changed(rs, d.reason))
601 {
602 set_reason(rs, d.reason);
603 (void)snprintf(line, sizeof(line),
604 "[%s] *** DUAL ONLINE detected without a lost-contact history - not resolved automatically ***\n",
605 rs->cfg.role_tag);
606 trace_line(rs, line);
607 log_event(rs, RTE_LOG_LEVEL_ERROR, cycle, rs->cfg.role_tag, "-", "ROLE",
608 "DUAL ONLINE detected, not resolved automatically", NULL);
609 }
610 break;
612 if (reason_changed(rs, d.reason))
613 {
614 set_reason(rs, d.reason);
615 (void)snprintf(line, sizeof(line),
616 "[%s] cycle %u: promotion due but held back - waiting for both A/B channels to be ready\n",
617 rs->cfg.role_tag, (unsigned int)cycle);
618 trace_line(rs, line);
619 log_event(rs, RTE_LOG_LEVEL_INFO, cycle, rs->cfg.role_tag, "-", "ROLE",
620 "promotion held back - waiting for both A/B channels to be ready", NULL);
621 }
622 break;
624 default:
625 break;
626 }
627}
628
629static void take_local(rte_site_role_t *rs, const rte_site_role_local_t *local)
630{
631 rs->local_faulted = local->faulted;
632 rs->local_single_mode = local->single_mode;
633}
634
635/* ---- takeover confirmation (REQ-SITEROLE-015) ------------------------------------------------------------------- */
636
637static uint32_t read_u32_le(const uint8_t *in)
638{
639 return (uint32_t)in[0] | ((uint32_t)in[1] << 8U) | ((uint32_t)in[2] << 16U) | ((uint32_t)in[3] << 24U);
640}
641
642static uint32_t challenge_lifetime(const rte_site_role_t *rs)
643{
644 return (rs->cfg.challenge_lifetime_cycles != 0U) ? rs->cfg.challenge_lifetime_cycles
646}
647
648static void draw_challenge(rte_site_role_t *rs, uint32_t cycle)
649{
650 uint8_t bytes[4];
651 char line[SITE_ROLE_TRACE_LINE_MAX];
652
653 rs->challenge_cycle = cycle;
654 if (rte_random_fill(bytes, sizeof(bytes)) != RTE_STATUS_OK)
655 {
656 rs->challenge_valid = false; /* no token can match until a draw succeeds */
657 if (!rs->challenge_fail_reported)
658 {
659 rs->challenge_fail_reported = true;
660 (void)snprintf(line, sizeof(line), "[%s] cycle %u: takeover challenge could not be drawn - tokens refused\n",
661 rs->cfg.role_tag, (unsigned int)cycle);
662 trace_line(rs, line);
663 log_event(rs, RTE_LOG_LEVEL_ERROR, cycle, rs->cfg.role_tag, "-", "TAKEOVER",
664 "challenge could not be drawn - tokens refused", NULL);
665 }
666 return;
667 }
668 rs->challenge = read_u32_le(bytes);
669 rs->challenge_valid = true;
670 rs->challenge_fail_reported = false;
671}
672
673/* A new challenge when none is held, when it has lived its lifetime, or when the cycle number went back (a state
674 * transfer renumbers cycles). */
675static void refresh_challenge(rte_site_role_t *rs, uint32_t cycle)
676{
677 if (rs->cfg.verify_mac == NULL)
678 {
679 return;
680 }
681 if ((!rs->challenge_valid) || (cycle < rs->challenge_cycle) ||
682 ((cycle - rs->challenge_cycle) >= challenge_lifetime(rs)))
683 {
684 draw_challenge(rs, cycle);
685 }
686}
687
688static void record_takeover_confirmation(rte_site_role_t *rs, uint32_t cycle, const char *how)
689{
690 char line[SITE_ROLE_TRACE_LINE_MAX];
691
692 rs->takeover_confirmed = true;
693 rs->takeover_confirm_cycle = cycle;
694 (void)snprintf(line, sizeof(line), "[%s] cycle %u: dispatcher takeover confirmation received (valid for %u cycles)\n",
695 rs->cfg.role_tag, (unsigned int)cycle, (unsigned int)RTE_SITE_ROLE_TAKEOVER_CONFIRM_VALID_CYCLES);
696 trace_line(rs, line);
697 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "TAKEOVER", "dispatcher confirmation received",
698 how);
699}
700
701static void trace_ignored_while_online(const rte_site_role_t *rs)
702{
703 char line[SITE_ROLE_TRACE_LINE_MAX];
704
705 (void)snprintf(line, sizeof(line), "[%s] takeover confirmation ignored - already ONLINE\n", rs->cfg.role_tag);
706 trace_line(rs, line);
707}
708
709static rte_site_role_token_result_t check_token(const rte_site_role_t *rs, const uint8_t *token, size_t token_len)
710{
712
713 if (rs->cfg.verify_mac == NULL)
714 {
716 }
717 else if ((token == NULL) || (token_len != (size_t)RTE_SITE_ROLE_TOKEN_LEN) ||
718 (token[0] != (uint8_t)RTE_SITE_ROLE_TOKEN_VERSION) || (token[1] != (uint8_t)(rs->cfg.own_id & 0xFFU)) ||
719 (token[2] != rs->cfg.channel_id) || (token[3] != 0U) || (read_u32_le(&token[12]) != 0U))
720 {
722 }
723 else if (!rs->challenge_valid)
724 {
726 }
727 else if (!rs->cfg.verify_mac(rs->cfg.user, token, (size_t)RTE_SITE_ROLE_TOKEN_MSG_LEN,
728 &token[RTE_SITE_ROLE_TOKEN_MSG_LEN]))
729 {
731 }
732 else if (read_u32_le(&token[4]) != rs->challenge)
733 {
735 }
736 else
737 {
739 }
740 return result;
741}
742
743static const char *token_result_text(rte_site_role_token_result_t result)
744{
745 const char *text;
746
747 switch (result)
748 {
750 text = "accepted";
751 break;
753 text = "ignored, already ONLINE";
754 break;
756 text = "no takeover key configured";
757 break;
759 text = "no challenge held";
760 break;
762 text = "bad format";
763 break;
765 text = "bad MAC";
766 break;
768 default:
769 text = "stale challenge";
770 break;
771 }
772 return text;
773}
774
775/* ---- public ----------------------------------------------------------------------------------------------------- */
776
778{
779 if ((rs == NULL) || (cfg == NULL) || (cfg->tx_buf == NULL) || (cfg->rx_buf == NULL) ||
780 (cfg->encode_state == NULL) || (cfg->receive_state == NULL) || (cfg->on_change == NULL) ||
781 (cfg->frame_size <= (size_t)RTE_SITE_ROLE_FRAME_OVERHEAD) || (cfg->frame_size > (size_t)UINT8_MAX) ||
782 (cfg->role_tag == NULL) || (cfg->peer_tag == NULL) || (cfg->site_name == NULL) ||
783 ((!cfg->listen) && (cfg->peer_host == NULL)))
784 {
786 }
787 rte_mem_set(rs, 0, sizeof(*rs));
788 rs->cfg = *cfg;
789 rs->link = NULL;
790 rs->startup_timestamp_ms = cfg->startup_timestamp_ms;
791 rs->standby_healthy = true;
792 rs->last_reason = RTE_SITE_ROLE_REASON_NONE;
793 rs->last_reason_set = true; /* matches the application's former zero-initialised int */
794 /* Sibling source (REQ-SITEROLE-016): no frame yet, no verdict yet; 0 selects the default reach window. */
795 rs->sibling.known = false;
796 rs->sibling_rx_cycle = 0U;
798 if (rs->cfg.sibling_reach_cycles == 0U)
799 {
801 }
802 return RTE_STATUS_OK;
803}
804
806{
807 rte_status_t status;
808 char line[SITE_ROLE_TRACE_LINE_MAX];
809 uint32_t waited_ms = 0U;
810 rte_dual_state_t own_state;
811 bool resume_standby = false;
812
813 if (rs == NULL)
814 {
816 }
817 rs->standby_healthy = true;
818 rs->standby_last_try_cycle = 0U;
819 rs->standby_rx_cycle = 0U;
820 rs->standby_warm = false;
821 if (rs->startup_timestamp_ms == 0U)
822 {
823 (void)rte_timer_now(&rs->startup_timestamp_ms);
824 }
825
826 /* REQ-SITEROLE-017: next to a running A/B sibling the site's role is already decided; the startup timestamp is
827 * not touched. */
828 poll_sibling_source(rs, 0U);
829 startup_role_from_sibling(rs, &resume_standby);
830
831 status = open_link_blocking(rs);
832 (void)snprintf(line, sizeof(line), "[%s] inter-site negotiation link -> %s\n", rs->cfg.role_tag,
833 rte_status_to_string(status));
834 trace_line(rs, line);
835 if (status != RTE_STATUS_OK)
836 {
837 log_event(rs, RTE_LOG_LEVEL_ERROR, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "LINK",
838 "inter-site negotiation link failed to establish", NULL);
839 return status;
840 }
841
842 status = init_channel_and_negotiator(rs, resume_standby);
843 if (status != RTE_STATUS_OK)
844 {
845 (void)snprintf(line, sizeof(line), "[%s] negotiation dual_channel/negotiator init failed (%s)\n",
846 rs->cfg.role_tag, rte_status_to_string(status));
847 trace_line(rs, line);
848 log_event(rs, RTE_LOG_LEVEL_ERROR, 0U, rs->cfg.role_tag, "-", "NEGOTIATOR", "dual_channel/negotiator init failed",
849 NULL);
850 close_link(rs);
851 return status;
852 }
853
854 /* Startup negotiation until it settles (older startup timestamp wins, REQ-DUAL-NEGOTIATOR-003). A role resumed
855 * from the A/B sibling is not IDLE: one execute (one beacon out) and the loop ends with that role. */
856 do
857 {
858 status = rte_dual_negotiator_execute(&rs->negotiator, rs->cfg.link_poll_ms);
859 own_state = rte_dual_negotiator_get_own_state(&rs->negotiator);
860 if ((status != RTE_STATUS_OK) || (own_state == RTE_DUAL_STATE_IDLE))
861 {
862 waited_ms += (uint32_t)rs->cfg.link_poll_ms;
863 }
864 } while ((own_state == RTE_DUAL_STATE_IDLE) && (waited_ms < (uint32_t)rs->cfg.startup_timeout_ms));
865
866 if (own_state == RTE_DUAL_STATE_IDLE)
867 {
868 (void)snprintf(line, sizeof(line), "[%s] negotiation did not settle within %ums\n", rs->cfg.role_tag,
869 (unsigned int)rs->cfg.startup_timeout_ms);
870 trace_line(rs, line);
871 log_event(rs, RTE_LOG_LEVEL_ERROR, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "NEGOTIATION",
872 "did not settle within timeout", NULL);
873 close_link(rs);
874 return RTE_STATUS_TIMEOUT;
875 }
876 rs->online = (own_state == RTE_DUAL_STATE_ONLINE);
877
878 (void)snprintf(line, sizeof(line), "[%s] negotiation result -> %s (own_ts=%llu, peer_ts=%llu, own_id=%u, peer_id=%u)\n",
879 rs->cfg.role_tag, rs->online ? "ONLINE" : "STANDBY",
880 (unsigned long long)rte_dual_negotiator_get_own_startup_timestamp_ms(&rs->negotiator),
881 (unsigned long long)rte_dual_negotiator_get_peer_startup_timestamp_ms(&rs->negotiator),
882 (unsigned int)rte_dual_negotiator_get_own_id(&rs->negotiator),
883 (unsigned int)rte_dual_negotiator_get_peer_id(&rs->negotiator));
884 trace_line(rs, line);
885 log_event(rs, RTE_LOG_LEVEL_INFO, 0U, rs->cfg.role_tag, rs->cfg.peer_tag, "NEGOTIATION",
886 rs->online ? "result: ONLINE" : "result: STANDBY", NULL);
887 return RTE_STATUS_OK;
888}
889
890void rte_site_role_execute(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
891{
892 rte_status_t status;
893 rte_status_t receive_status;
894 bool send_ok;
895 char line[SITE_ROLE_TRACE_LINE_MAX];
896
897 if ((rs == NULL) || (local == NULL))
898 {
899 return;
900 }
901 rs->cycle = cycle;
902 take_local(rs, local);
903 poll_sibling_source(rs, cycle);
904 refresh_challenge(rs, cycle);
905
906 if (rs->link == NULL)
907 {
908 if (reconnect(rs) != RTE_STATUS_OK)
909 {
910 run_site_role_decision(rs, cycle, local); /* rules that need no link (dispatcher confirmation) still apply */
911 check_sibling_after_decision(rs, cycle, local);
912 return;
913 }
914 }
915
916 /* The negotiator's own STATE-frame exchange (ADR-020 section 3), non-blocking in cyclic execution. */
917 status = rte_dual_negotiator_execute(&rs->negotiator, 0U);
918 if (status != RTE_STATUS_OK)
919 {
920 (void)snprintf(line, sizeof(line), "[%s] cycle %u: negotiator execute failed (%s)\n", rs->cfg.role_tag,
921 (unsigned int)cycle, rte_status_to_string(status));
922 trace_line(rs, line);
923 }
924
925 send_ok = send_frame(rs, cycle);
926 receive_status = receive_frame(rs, cycle, false);
927 update_standby_warmth(rs, cycle);
928
929 run_site_role_decision(rs, cycle, local);
930 check_sibling_after_decision(rs, cycle, local);
931
932 {
933 const char *own_role = rs->local_faulted ? "FAULTED" : (rs->online ? "ONLINE" : "STANDBY");
934 const char *peer_role = rs->peer_faulted ? "FAULTED" : (rs->peer_snapshot_valid ? "ONLINE" : "STANDBY");
935
936 (void)snprintf(line, sizeof(line),
937 "[%s] cycle %u: negotiation ok (role=%s, counterpart=%s, negotiator diagnostic own=%d peer=%d)\n",
938 rs->cfg.role_tag, (unsigned int)cycle, own_role, peer_role,
939 (int)rte_dual_negotiator_get_own_state(&rs->negotiator),
940 (int)rte_dual_negotiator_get_peer_state(&rs->negotiator));
941 trace_line(rs, line);
942 }
943
944 handle_send_miss_and_link_teardown(rs, cycle, send_ok, receive_status);
945}
946
948{
949 uint32_t i;
950
951 if ((rs == NULL) || (rs->link == NULL))
952 {
953 return;
954 }
955 rs->cycle = cycle;
956 /* rte_dual_channel acknowledges a DATA frame only from inside a receive call and the two sites' cycles are not
957 * phase-locked: polling from the fast tick makes the acknowledgement independent of phase. Bounded drain. */
958 for (i = 0U; i < SITE_ROLE_SERVICE_DRAIN; i++)
959 {
960 if (receive_frame(rs, cycle, true) != RTE_STATUS_OK)
961 {
962 break;
963 }
964 }
965}
966
968{
969 bool link_up;
970 bool ok;
971 char line[SITE_ROLE_TRACE_LINE_MAX];
972
973 if ((rs == NULL) || (local == NULL))
974 {
975 return;
976 }
977 take_local(rs, local);
978 link_up = (rs->link != NULL);
979 if (rs->cfg.standby_sync && rs->online && !link_up)
980 {
981 rs->standby_healthy = false;
982 }
983 if (!rte_site_role_standby_sync_due(rs->cfg.standby_sync, rs->online, link_up, rs->standby_healthy, cycle,
984 rs->standby_last_try_cycle))
985 {
986 return;
987 }
988 ok = send_frame(rs, cycle);
989 if (ok)
990 {
991 if (!rs->standby_healthy)
992 {
993 (void)snprintf(line, sizeof(line), "[%s] cycle %u: STANDBY acknowledges again\n", rs->cfg.role_tag,
994 (unsigned int)cycle);
995 trace_line(rs, line);
996 }
997 rs->standby_healthy = true;
998 }
999 else
1000 {
1001 if (rs->standby_healthy)
1002 {
1003 (void)snprintf(line, sizeof(line),
1004 "[%s] cycle %u: STANDBY did not acknowledge the snapshot - considered not working\n",
1005 rs->cfg.role_tag, (unsigned int)cycle);
1006 trace_line(rs, line);
1007 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "STANDBY",
1008 "no acknowledgement - considered not working", NULL);
1009 }
1010 rs->standby_healthy = false;
1011 rs->standby_last_try_cycle = cycle;
1012 }
1013}
1014
1016{
1017 char line[SITE_ROLE_TRACE_LINE_MAX];
1018
1019 if (rs == NULL)
1020 {
1021 return;
1022 }
1023 if (rs->cfg.verify_mac != NULL)
1024 {
1025 (void)snprintf(line, sizeof(line),
1026 "[%s] cycle %u: unauthenticated takeover confirmation refused - a takeover key is configured\n",
1027 rs->cfg.role_tag, (unsigned int)cycle);
1028 trace_line(rs, line);
1029 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "TAKEOVER",
1030 "unauthenticated confirmation refused", NULL);
1031 return;
1032 }
1033 if (rs->online)
1034 {
1035 trace_ignored_while_online(rs);
1036 return;
1037 }
1038 record_takeover_confirmation(rs, cycle, "unauthenticated");
1039}
1040
1042 size_t token_len)
1043{
1045 char line[SITE_ROLE_TRACE_LINE_MAX];
1046 char extra[64];
1047
1048 if (rs == NULL)
1049 {
1051 }
1052 if ((rs->cfg.verify_mac != NULL) && rs->online)
1053 {
1054 trace_ignored_while_online(rs);
1056 }
1057 result = check_token(rs, token, token_len);
1058 if (result == RTE_SITE_ROLE_TOKEN_ACCEPTED)
1059 {
1060 (void)snprintf(extra, sizeof(extra), "authenticated seq=%u", (unsigned int)read_u32_le(&token[8]));
1061 record_takeover_confirmation(rs, cycle, extra);
1062 draw_challenge(rs, cycle); /* single use */
1063 }
1064 else
1065 {
1066 (void)snprintf(line, sizeof(line), "[%s] cycle %u: takeover confirmation token rejected (%s)\n",
1067 rs->cfg.role_tag, (unsigned int)cycle, token_result_text(result));
1068 trace_line(rs, line);
1069 log_event(rs, RTE_LOG_LEVEL_WARNING, cycle, rs->cfg.role_tag, "-", "TAKEOVER", "confirmation token rejected",
1070 token_result_text(result));
1071 }
1072 return result;
1073}
1074
1076{
1077 if ((rs == NULL) || (out == NULL) || (!rs->challenge_valid))
1078 {
1079 return false;
1080 }
1081 *out = rs->challenge;
1082 return true;
1083}
1084
1086{
1087 if (rs != NULL)
1088 {
1089 rs->lost_peer_while_online = true;
1090 }
1091}
1092
1094{
1095 rte_status_t status;
1096 char line[SITE_ROLE_TRACE_LINE_MAX];
1097
1098 if ((rs == NULL) || (rs->link == NULL))
1099 {
1100 return; /* link already down - nothing to flush */
1101 }
1102 /* This channel is shutting down: faulted, no state-transfer snapshot to offer. */
1103 encode_frame(rs, true, single_mode, 0U, false);
1104 status = rte_dual_channel_send(&rs->dual_channel, rs->cfg.tx_buf, (uint8_t)rs->cfg.frame_size, NULL);
1105 (void)snprintf(line, sizeof(line), "[%s] flushing FAULTED to counterpart before shutdown -> %s\n", rs->cfg.role_tag,
1106 rte_status_to_string(status));
1107 trace_line(rs, line);
1108}
1109
1111 const rte_site_role_sibling_t *sibling)
1112{
1114 char line[SITE_ROLE_TRACE_LINE_MAX];
1115 char extra[80];
1116
1117 if ((rs == NULL) || (sibling == NULL) || !sibling->known)
1118 {
1119 return RTE_SITE_ROLE_SIBLING_UNKNOWN; /* the sibling has not sent its site state yet - normal at start-up */
1120 }
1121 if ((sibling->online == rs->online) && (sibling->single_mode == own_single_mode))
1122 {
1123 rs->sibling_mismatch = 0U;
1125 }
1126
1127 if (rs->sibling_mismatch < UINT8_MAX)
1128 {
1129 rs->sibling_mismatch++;
1130 }
1131 if (rs->sibling_mismatch < (uint8_t)RTE_SITE_ROLE_SIBLING_MISMATCH_LIMIT)
1132 {
1133 (void)snprintf(line, sizeof(line),
1134 "[%s] cycle %u: site state mismatch with local peer (mismatch count %u/%u: own online=%d single=%d "
1135 "vs peer online=%d single=%d)\n",
1136 rs->cfg.role_tag, (unsigned int)cycle, (unsigned int)rs->sibling_mismatch,
1137 (unsigned int)RTE_SITE_ROLE_SIBLING_MISMATCH_LIMIT, rs->online ? 1 : 0, own_single_mode ? 1 : 0,
1138 sibling->online ? 1 : 0, sibling->single_mode ? 1 : 0);
1139 trace_line(rs, line);
1141 }
1142 else
1143 {
1144 (void)snprintf(line, sizeof(line),
1145 "[%s] cycle %u: NEGOTIATION MISMATCH - own (is_online=%d, single=%d) vs local peer's own "
1146 "(is_online=%d, single=%d) - this site's role -> FAULTED, entering REBOOT\n",
1147 rs->cfg.role_tag, (unsigned int)cycle, rs->online ? 1 : 0, own_single_mode ? 1 : 0,
1148 sibling->online ? 1 : 0, sibling->single_mode ? 1 : 0);
1149 trace_line(rs, line);
1150 (void)snprintf(extra, sizeof(extra), "own(online=%d,single=%d) peer(online=%d,single=%d)", rs->online ? 1 : 0,
1151 own_single_mode ? 1 : 0, sibling->online ? 1 : 0, sibling->single_mode ? 1 : 0);
1152 log_event(rs, RTE_LOG_LEVEL_ERROR, cycle, rs->cfg.role_tag,
1153 (rs->cfg.sibling_tag != NULL) ? rs->cfg.sibling_tag : "-", "NEGOTIATION_MISMATCH",
1154 "FAULTED, entering REBOOT", extra);
1156 }
1157 return verdict;
1158}
1159
1164
1166{
1167 if (rs != NULL)
1168 {
1169 close_link(rs);
1170 }
1171}
1172
1174{
1175 return (rs != NULL) && rs->online;
1176}
1177
1179{
1180 if ((rs == NULL) || (out == NULL))
1181 {
1182 return;
1183 }
1184 out->online = rs->online;
1185 out->link_up = (rs->link != NULL);
1186 out->peer_answering = (rs->cycle >= rs->peer_last_rx_cycle) && ((rs->cycle - rs->peer_last_rx_cycle) <= 2U);
1187 out->peer_online_now = rs->peer_online_now;
1188 out->peer_unresponsive = rs->peer_unresponsive;
1189 out->standby_warm = rs->standby_warm;
1190 out->standby_healthy = rs->standby_healthy;
1191 out->last_reason = rs->last_reason;
1192}
1193
1195{
1196 if ((rs == NULL) || (own == NULL) || (peer == NULL))
1197 {
1198 return;
1199 }
1200 *own = rte_dual_negotiator_get_own_state(&rs->negotiator);
1201 *peer = rte_dual_negotiator_get_peer_state(&rs->negotiator);
1202}
rte_status_t rte_buffer_set_length(rte_buffer_t *buf, size_t length)
Marks length bytes of already-written storage as valid.
Definition rte_buffer.c:31
rte_status_t rte_buffer_write_u32_le(rte_buffer_t *buf, uint32_t value)
Appends a little-endian uint32_t at buf's current length.
Definition rte_buffer.c:186
rte_status_t rte_buffer_init(rte_buffer_t *buf, void *storage, size_t capacity)
Binds a buffer view to caller-owned storage. Initial length is 0.
Definition rte_buffer.c:9
rte_status_t rte_buffer_read_u32_le(const rte_buffer_t *buf, size_t offset, uint32_t *out_value)
Reads a little-endian uint32_t at the given offset.
Definition rte_buffer.c:269
rte_status_t rte_dual_negotiator_init(rte_dual_negotiator_t *negotiator, const rte_dual_negotiator_config_t *config)
Initializes a rte_dual_negotiator_t. Captures this instance's own startup timestamp once (rte_timer_n...
rte_status_t rte_dual_channel_send(rte_dual_channel_t *channel, const uint8_t *payload, uint8_t payload_size, uint32_t *out_ack_link_count)
Sends payload as a DATA frame on every configured redundant link - never gated by any negotiated stat...
uint32_t rte_dual_negotiator_get_own_id(const rte_dual_negotiator_t *negotiator)
Returns this instance's own tie-break identifier (rte_dual_negotiator_config_t::own_id at init time)....
rte_status_t rte_dual_channel_init(rte_dual_channel_t *channel, const rte_dual_channel_config_t *config)
Initializes a rte_dual_channel_t: initializes every configured redundant link (rte_dual_msgchannel_in...
uint32_t rte_dual_negotiator_get_peer_id(const rte_dual_negotiator_t *negotiator)
Returns the expected peer's tie-break identifier (rte_dual_negotiator_config_t::peer_id at init time)...
rte_dual_state_t rte_dual_negotiator_get_peer_state(const rte_dual_negotiator_t *negotiator)
Returns this instance's last-known view of the peer's rte_dual_state_t.
rte_status_t rte_dual_negotiator_execute(rte_dual_negotiator_t *negotiator, rte_duration_ms_t receive_timeout_ms)
Drives one round of state negotiation: refreshes own_channel_degraded from the attached channel's cur...
rte_dual_state_t
State of one instance in a dual (two-redundant-instance) relationship, as decided by rte_dual_negotia...
uint64_t rte_dual_negotiator_get_own_startup_timestamp_ms(const rte_dual_negotiator_t *negotiator)
Returns this instance's own startup timestamp (captured at rte_dual_negotiator_init() time,...
rte_status_t rte_dual_channel_receive(rte_dual_channel_t *channel, uint8_t *out_payload, uint8_t max_size, rte_duration_ms_t timeout_ms, uint8_t *out_size)
Returns the most recently staged inbound DATA frame, actively polling the configured links (each auto...
rte_dual_state_t rte_dual_negotiator_get_own_state(const rte_dual_negotiator_t *negotiator)
Returns this instance's own current rte_dual_state_t.
uint64_t rte_dual_negotiator_get_peer_startup_timestamp_ms(const rte_dual_negotiator_t *negotiator)
Returns this instance's last-known view of the peer's own startup timestamp (0/unset until the first ...
const char * rte_dual_state_to_string(rte_dual_state_t state)
Returns a short, static, human-readable string for a rte_dual_state_t. Intended for diagnostics/loggi...
rte_status_t rte_dual_channel_set_resync_on_sequence_error(rte_dual_channel_t *channel, bool enable)
@ RTE_DUAL_STATE_HOTSTANDBY
@ RTE_DUAL_STATE_ONLINE
@ RTE_DUAL_STATE_IDLE
void rte_log_write_event(rte_log_level_t level, const char *site, uint32_t cycle, const char *source, const char *destination, const char *type, const char *info, const char *extra_fields)
Emits one structured inter-channel event/message-trail log line - for a message actually sent/receive...
Definition rte_log.c:188
rte_log_level_t
Log message severity, passed to rte_log_write().
Definition rte_log.h:34
@ RTE_LOG_LEVEL_DEBUG
Definition rte_log.h:35
@ RTE_LOG_LEVEL_INFO
Definition rte_log.h:36
@ RTE_LOG_LEVEL_WARNING
Definition rte_log.h:37
@ RTE_LOG_LEVEL_ERROR
Definition rte_log.h:38
rte_status_t rte_random_fill(uint8_t *out, size_t len)
Fills out with len cryptographically secure random bytes from the registered OSAdapter.
Definition rte_random.c:34
const char * rte_status_to_string(rte_status_t status)
Returns a short, static, human-readable string for a status code. Intended for diagnostics/logging on...
Definition rte_status.c:22
rte_status_t
Common result/status codes.
Definition rte_status.h:27
@ RTE_STATUS_OK
Definition rte_status.h:28
@ RTE_STATUS_INVALID_PARAM
Definition rte_status.h:29
@ RTE_STATUS_TIMEOUT
Definition rte_status.h:32
rte_status_t rte_timer_now(rte_timestamp_ms_t *out_now_ms)
Returns the current monotonic time base used by all timers.
Definition rte_timer.c:139
uint32_t rte_duration_ms_t
Definition rte_types.h:27
Cross-layer data buffer abstraction (ADR-002).
OS Abstraction Layer - Logging/diagnostics service.
Thin, MISRA-visible wrappers over the three raw memory-block primitives (fill/copy/compare) every oth...
static void rte_mem_set(void *dest, int value, size_t count)
Fills count bytes starting at dest with value - the one sanctioned call site for libc memset() in thi...
OS Abstraction Layer - Cryptographically secure random bytes.
static bool sibling_reachable(const rte_site_role_t *rs)
static rte_status_t reconnect(rte_site_role_t *rs)
static void check_sibling_after_decision(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
static void run_site_role_decision(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
static void fill_sibling_facts(const rte_site_role_t *rs, const rte_site_role_local_t *local, rte_site_role_input_t *in)
static void update_standby_warmth(rte_site_role_t *rs, uint32_t cycle)
static void poll_sibling_source(rte_site_role_t *rs, uint32_t cycle)
static rte_status_t open_link_blocking(rte_site_role_t *rs)
static rte_status_t init_channel_and_negotiator(rte_site_role_t *rs, bool resume_standby)
static void renegotiate_from_deadlock(rte_site_role_t *rs, rte_site_role_reason_t reason)
static void startup_role_from_sibling(rte_site_role_t *rs, bool *resume_standby)
static void handle_send_miss_and_link_teardown(rte_site_role_t *rs, uint32_t cycle, bool send_ok, rte_status_t receive_status)
Site role service (ADR-040 step 2): the inter-site link, ONLINE/STANDBY negotiation,...
rte_site_role_sibling_verdict_t rte_site_role_sibling_verdict(const rte_site_role_t *rs)
The A/B agreement verdict of the check run by the last rte_site_role_execute() (REQ-SITEROLE-016).
rte_site_role_sibling_verdict_t rte_site_role_check_sibling(rte_site_role_t *rs, uint32_t cycle, bool own_single_mode, const rte_site_role_sibling_t *sibling)
A/B role agreement (REQ-SITEROLE-014): compares this channel's role and single_mode with its sibling'...
void rte_site_role_execute(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
One cycle: reconnect if needed, exchange frames, update the standby state, decide and apply the role.
#define RTE_SITE_ROLE_SINGLE_MODE_DUAL
void rte_site_role_shutdown(rte_site_role_t *rs)
Closes the link. Idempotent.
void rte_site_role_flush_faulted(rte_site_role_t *rs, bool single_mode)
Best-effort final frame with faulted=1 before the process reboots, so the counterpart can promote.
#define RTE_SITE_ROLE_FRAME_OVERHEAD
rte_status_t rte_site_role_init(rte_site_role_t *rs, const rte_site_role_config_t *cfg)
Validates and stores the configuration. Opens nothing.
#define RTE_SITE_ROLE_CHALLENGE_LIFETIME_DEFAULT_CYCLES
rte_site_role_change_kind_t
@ RTE_SITE_ROLE_CHANGE_DEMOTED
@ RTE_SITE_ROLE_CHANGE_RENEGOTIATED
@ RTE_SITE_ROLE_CHANGE_PROMOTED
#define RTE_SITE_ROLE_VALID_MARKER
rte_status_t rte_site_role_start(rte_site_role_t *rs)
Opens the link (blocking, bounded by startup_timeout_ms) and runs the startup negotiation until it se...
rte_site_role_token_result_t rte_site_role_on_takeover_token(rte_site_role_t *rs, uint32_t cycle, const uint8_t *token, size_t token_len)
Verifies a takeover confirmation token (REQ-SITEROLE-015) and, when it is valid, records the confirma...
void rte_site_role_on_takeover_confirm(rte_site_role_t *rs, uint32_t cycle)
Records an unauthenticated dispatcher confirmation that the other site is not ONLINE (ignored while O...
void rte_site_role_service_link(rte_site_role_t *rs, uint32_t cycle)
Fast tick: drains up to 4 frames so acknowledgements do not depend on the two sites' cycle phase.
void rte_site_role_get_status(const rte_site_role_t *rs, rte_site_role_status_t *out)
Fills a status snapshot for monitors.
#define RTE_SITE_ROLE_SIBLING_MISMATCH_LIMIT
#define RTE_SITE_ROLE_SIBLING_REACH_DEFAULT_CYCLES
rte_site_role_token_result_t
@ RTE_SITE_ROLE_TOKEN_BAD_FORMAT
@ RTE_SITE_ROLE_TOKEN_NOT_CONFIGURED
@ RTE_SITE_ROLE_TOKEN_NO_CHALLENGE
@ RTE_SITE_ROLE_TOKEN_STALE_CHALLENGE
@ RTE_SITE_ROLE_TOKEN_ACCEPTED
@ RTE_SITE_ROLE_TOKEN_IGNORED_ONLINE
@ RTE_SITE_ROLE_TOKEN_BAD_MAC
void rte_site_role_note_lost_contact(rte_site_role_t *rs)
The application stalled while ONLINE long enough for the counterpart to have taken over: yield if it ...
bool rte_site_role_is_online(const rte_site_role_t *rs)
void rte_site_role_get_negotiator_states(const rte_site_role_t *rs, rte_dual_state_t *own, rte_dual_state_t *peer)
The underlying negotiator's own and peer states (diagnostic only; the role is rte_site_role_is_online...
rte_site_role_sibling_verdict_t
@ RTE_SITE_ROLE_SIBLING_PENDING
@ RTE_SITE_ROLE_SIBLING_FAULT
@ RTE_SITE_ROLE_SIBLING_AGREE
@ RTE_SITE_ROLE_SIBLING_UNKNOWN
bool rte_site_role_takeover_challenge(const rte_site_role_t *rs, uint32_t *out)
This channel's current takeover challenge, for the application's status line (the issuer MACs it).
#define RTE_SITE_ROLE_FRAME_STATE_OFFSET
void rte_site_role_sync_standby(rte_site_role_t *rs, uint32_t cycle, const rte_site_role_local_t *local)
ONLINE, warm/hot standby: sends this cycle's snapshot before the output commit and tracks the ack.
bool rte_site_role_standby_data_stale(bool sync_enabled, bool is_online, bool have_snapshot, uint32_t cycle, uint32_t last_rx_cycle)
Whether STANDBY's held snapshot is stale and must be dropped (STANDBY goes cold).
rte_site_role_decision_t rte_site_role_decide(const rte_site_role_input_t *in)
Decides this cycle's role action. Rules are evaluated in this order and the first that applies wins:
rte_site_role_reason_t
const char * rte_site_role_reason_text(rte_site_role_reason_t reason)
@ RTE_SITE_ROLE_ACTION_NONE
@ RTE_SITE_ROLE_ACTION_RENEGOTIATE
@ RTE_SITE_ROLE_ACTION_PROMOTE
@ RTE_SITE_ROLE_ACTION_DEMOTE
@ RTE_SITE_ROLE_ACTION_WAIT
@ RTE_SITE_ROLE_ACTION_ALARM
#define RTE_SITE_ROLE_STANDBY_DATA_TIMEOUT_CYCLES
#define RTE_SITE_ROLE_TAKEOVER_CONFIRM_VALID_CYCLES
bool rte_site_role_standby_sync_due(bool sync_enabled, bool is_online, bool link_up, bool healthy, uint32_t cycle, uint32_t last_try)
Whether ONLINE should attempt the pre-commit standby sync this cycle.
OS Abstraction Layer - Timer service.
Mutable, bounds-tracked view over caller-owned storage.
Definition rte_buffer.h:36
Configuration for rte_dual_channel_init().
rte_duration_ms_t ack_timeout_ms
rte_netlink_handle_t links[RTE_DUAL_CHANNEL_MAX_LINKS]
Configuration for rte_dual_negotiator_init().
rte_dual_negotiator_state_change_callback_t state_change_callback
rte_timestamp_ms_t startup_timestamp_ms
rte_duration_ms_t ack_timeout_ms
rte_duration_ms_t peer_lost_timeout_ms
rte_site_role_trace_fn trace
rte_duration_ms_t startup_timeout_ms
rte_site_role_delay_fn delay
rte_duration_ms_t connect_timeout_ms
rte_duration_ms_t link_poll_ms
rte_duration_ms_t reconnect_attempt_ms
rte_site_role_sibling_verdict_t sibling_verdict
rte_site_role_sibling_t sibling
uint32_t sibling_rx_cycle