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
common_config.h
Go to the documentation of this file.
1#ifndef RTE_EXAMPLE_COMMON_CONFIG_H
2#define RTE_EXAMPLE_COMMON_CONFIG_H
3
4#include <stdio.h>
5
6#include "rte/utils/safestate/rte_safestate.h"
7#include "rte/oal/timer/rte_timer.h"
8#include "rte/oal/log/rte_log.h" /* RTE_LOG_WRITE_EVENT[_F] below */
9
20#define RTE_EXAMPLE_LOG(...) \
21 do \
22 { \
23 rte_timestamp_ms_t rte_example_log_ts_ = 0U; \
24 (void)rte_timer_now(&rte_example_log_ts_); \
25 printf("[%10llu] ", (unsigned long long)rte_example_log_ts_); \
26 printf(__VA_ARGS__); \
27 (void)fflush(stdout); \
28 } while (0)
29
47#define RTE_LOG_WRITE_EVENT(level, site, cycle, source, dest, type, info, extra) \
48 rte_log_write_event((level), (site), (cycle), (source), (dest), (type), (info), (extra))
49
50#define RTE_LOG_WRITE_EVENT_F(level, site, cycle, source, dest, type, info_fmt, ...) \
51 do \
52 { \
53 char rte_lwe_info_[RTE_LOG_EVENT_LINE_MAX_LEN]; \
54 (void)snprintf(rte_lwe_info_, sizeof(rte_lwe_info_), (info_fmt), __VA_ARGS__); \
55 rte_log_write_event((level), (site), (cycle), (source), (dest), (type), \
56 rte_lwe_info_, NULL); \
57 } while (0)
58
59/* Shared by every role (A, B, C) so port numbers and cycle counts never
60 * drift apart between the processes that must agree on them - in a real
61 * multi-machine deployment these would come from a deployment config
62 * file, not compile-time constants; hardcoded here because this is a
63 * single-host demo (see README's honest limitations). */
64
65/* Per-site port bases. Both West and East run their own full A/B/C group
66 * (hot standby: see README/site_config.h) on the SAME host in this demo,
67 * so each site needs its own set of ports to avoid colliding with the
68 * other site's listeners - in a real multi-machine deployment each site
69 * would run on its own host and could reuse the same port numbers
70 * (pointing rte_netlink's CONNECT side at a real remote host is the
71 * intended path from this single-host demo to that, same as noted for
72 * the single-site A/B/C group before West/East existed). See
73 * site_config.h for the per-site port lookup helpers built from these. */
74/* Gap between the two bases must clear BOTH: (a) the full per-site port
75 * range below (currently 16 ports: offsets 0..C_FOR_CTC_PORT_OFFSET, one
76 * block per {peer, kind, instance} relay + sim-facing link - grows with
77 * RTE_EXAMPLE_MAX_TRAINS), and (b) the two fixed, site-INDEPENDENT
78 * negotiation ports below (15021/15022 - deliberately not derived from
79 * either base, see their own doc on why). Only matters for this project's
80 * local/smoke-test topology, where every site runs as a plain process on
81 * one shared host (127.0.0.1) - two sites' ranges only need to stay apart
82 * there; in the real docker-compose topology each site is a separate
83 * host/network-namespace, so identical port *numbers* across sites never
84 * collide regardless of this gap. Found the hard way this session: the
85 * per-instance relay channel expansion grew the per-site range from ~7 to
86 * 16 ports without widening this gap to match, so WEST's own range
87 * (15001-15016) silently overran EAST's old base (15011) - and EAST's own
88 * range (15011-15026) in turn swallowed the negotiation ports (15021/22)
89 * too. Two UDP sockets in two different processes both bound (SO_REUSEADDR)
90 * to the same port on localhost is not a bind() failure - it's silent,
91 * kernel-dependent datagram misdelivery, which surfaced as an
92 * indistinguishable-from-deadlock hang (one side's HELLO handshake waiting
93 * forever on a socket the OS was routing to a different process' socket
94 * instead) only when WEST+EAST ran together, not in either site alone.
95 * Generous headroom (100, not just "enough for today") is deliberate so
96 * the next MAX_TRAINS/relay-kind increase doesn't reintroduce this exact
97 * bug the same way. */
98#define RTE_EXAMPLE_WEST_PORT_BASE 15001U
99#define RTE_EXAMPLE_EAST_PORT_BASE 15101U
100
101/* Fixed bound on concurrently-tracked train sessions per site (RBC
102 * Train/IL/CTC scenario, ADR-029) - a compile-time array size everywhere a
103 * session table exists (monitor_c_types.h, ab_gp_channel_types.h), never a
104 * dynamic list (CLAUDE.md: no malloc/free/realloc). Exactly matches the
105 * number of live Train/IL container pairs this docker-compose topology
106 * runs (train-west+il-west, train-east+il-east) - see docker-compose.yml. */
107#define RTE_EXAMPLE_MAX_TRAINS 2U
108
109/* Fixed bound for AB/db's own RUNTIME route table (ab_gp_db.h,
110 * ab_gp_db_route_t routes[RTE_EXAMPLE_MAX_ROUTES]) - same
111 * "compile-time array size, never a dynamic list" convention as
112 * RTE_EXAMPLE_MAX_TRAINS above. This is NOT the static site
113 * TOPOLOGY's own size (safeAPIRBC2oo2GA's generated ab_siteGA.c's ab_site_ga_track_layout[] is
114 * a plain extern array, sized by its own initializer, uncapped by any
115 * constant here) - it only needs to hold routes CURRENTLY granted/
116 * tracked at once, bounded by RTE_EXAMPLE_MAX_TRAINS *
117 * _MAX_ROUTES_PER_TRAIN (2*4=8 today) regardless of how large the static
118 * layout itself is. RTE_EXAMPLE_MAX_ROUTES_PER_TRAIN bounds how many
119 * of those a single train can have allocated to it at once (an MA
120 * spanning more than one route section). */
121#define RTE_EXAMPLE_MAX_ROUTES 8U
122/* Guarded, not a plain #define: ga_interface.h (GP's own exported facade
123 * for safeAPIRBC2oo2GA, now a separate shared-library project) ALSO
124 * defines this same constant, guarded the identical way - both this file
125 * and a GA-facing translation unit that includes ga_interface.h first
126 * need to agree, whichever is included first wins, no redefinition
127 * warning either way. Keep both copies' VALUE in sync if this ever
128 * changes. */
129#ifndef RTE_EXAMPLE_MAX_ROUTES_PER_TRAIN
130#define RTE_EXAMPLE_MAX_ROUTES_PER_TRAIN 4U
131#endif
132
133/* RTE_EXAMPLE_MAX_BALISES_PER_ROUTE/_MAX_SEGMENTS_PER_ROUTE/
134 * _MAX_SWITCHES_PER_ROUTE used to live here, but were moved into
135 * safeAPIRBC2oo2SA's own ab_site.h when that module was extracted into
136 * its own project - nothing outside ab_site.h ever referenced them
137 * (confirmed at extraction time), genuinely that module's own concern. */
138
139#define RTE_EXAMPLE_AB_PORT_OFFSET 0U /* A listens, B connects: peer/checkpoint link */
140#define RTE_EXAMPLE_AB_CROSSCOMPARE_PORT_OFFSET 16U /* A listens, B connects: dedicated cross-compare link */
141/* C listens, A/B connect: dedicated relay links, one per {peer, kind,
142 * instance} (this session's own addition - see site_config.h's own
143 * rte_example_relay_kind_t doc). Train/IL each need
144 * RTE_EXAMPLE_MAX_TRAINS ports per peer (site_config_c_port_for_a()/
145 * _for_b() add an instance index on top, 0..RTE_EXAMPLE_MAX_TRAINS-1,
146 * same "base + index" shape as C_FOR_TRAIN_PORT_OFFSET/C_FOR_IL_PORT_OFFSET
147 * below) - CTC stays a single port per peer (only ever index 0), since
148 * there is only ever one CTC instance. Kept as two separate contiguous
149 * blocks (A's own Train+IL+CTC ports, then B's own) rather than
150 * interleaved, so each side's own ports stay easy to eyeball in a port
151 * scan. */
152#define RTE_EXAMPLE_C_FOR_A_TRAIN_PORT_OFFSET 1U
153#define RTE_EXAMPLE_C_FOR_A_IL_PORT_OFFSET \
154 (RTE_EXAMPLE_C_FOR_A_TRAIN_PORT_OFFSET + 1U)
155#define RTE_EXAMPLE_C_FOR_A_CTC_PORT_OFFSET (RTE_EXAMPLE_C_FOR_A_IL_PORT_OFFSET + 1U)
156/* ADR-038: A's general util side channel to C (rbc_util_frame_t). */
157#define RTE_EXAMPLE_C_FOR_A_UTIL_PORT_OFFSET (RTE_EXAMPLE_C_FOR_A_CTC_PORT_OFFSET + 1U)
158#define RTE_EXAMPLE_C_FOR_B_TRAIN_PORT_OFFSET (RTE_EXAMPLE_C_FOR_A_UTIL_PORT_OFFSET + 1U)
159#define RTE_EXAMPLE_C_FOR_B_IL_PORT_OFFSET \
160 (RTE_EXAMPLE_C_FOR_B_TRAIN_PORT_OFFSET + 1U)
161#define RTE_EXAMPLE_C_FOR_B_CTC_PORT_OFFSET (RTE_EXAMPLE_C_FOR_B_IL_PORT_OFFSET + 1U)
162#define RTE_EXAMPLE_C_FOR_B_UTIL_PORT_OFFSET (RTE_EXAMPLE_C_FOR_B_CTC_PORT_OFFSET + 1U)
163/* C listens, one Train sim instance connects: P0/M136 down^H^H up, M24/M15/M3
164 * down - rte_netlink's RTE_NETLINK_ROLE_LISTEN accepts exactly one peer
165 * per bound port (rte_netlink.h), so RTE_EXAMPLE_MAX_TRAINS concurrent
166 * trains each need their OWN port - this is a BASE offset, not a single
167 * port; site_config_c_port_for_train(site, train_index) adds train_index
168 * (0..RTE_EXAMPLE_MAX_TRAINS-1) on top. Same for IL below. Right after
169 * the A/B relay ports above. */
170#define RTE_EXAMPLE_C_FOR_TRAIN_PORT_OFFSET (RTE_EXAMPLE_C_FOR_B_UTIL_PORT_OFFSET + 1U)
171/* One port per IL instance (base + il_index), same reasoning as Train
172 * above - IL instance N sets routes for train instance N's session (see
173 * site_config_c_port_for_il()). Reserves offsets
174 * [C_FOR_TRAIN_PORT_OFFSET .. +RTE_EXAMPLE_MAX_TRAINS-1] for Train, so
175 * this starts right after that block. */
176#define RTE_EXAMPLE_C_FOR_IL_PORT_OFFSET (RTE_EXAMPLE_C_FOR_TRAIN_PORT_OFFSET + 1U)
177/* C listens, the single CTC sim connects: one-way status push
178 * notifications only (train connected/MA granted/MA extended) - see
179 * rbc_wire_types.h. Right after IL's own block. */
180#define RTE_EXAMPLE_C_FOR_CTC_PORT_OFFSET (RTE_EXAMPLE_C_FOR_IL_PORT_OFFSET + 1U)
181/* ADR-038: the C-side status/broker service LISTENs one port per local
182 * gateway service (train/il/ctc) so each can CONNECT and publish its own
183 * status frames - "one link per client", same posture as every other
184 * role-C link. Right after the sim-facing block. */
185#define RTE_EXAMPLE_C_FOR_BROKER_TRAIN_PORT_OFFSET (RTE_EXAMPLE_C_FOR_CTC_PORT_OFFSET + 1U)
186#define RTE_EXAMPLE_C_FOR_BROKER_IL_PORT_OFFSET (RTE_EXAMPLE_C_FOR_BROKER_TRAIN_PORT_OFFSET + 1U)
187#define RTE_EXAMPLE_C_FOR_BROKER_CTC_PORT_OFFSET (RTE_EXAMPLE_C_FOR_BROKER_IL_PORT_OFFSET + 1U)
188
189/* Inter-site negotiation links (replaces the old standalone SITE<->SITE
190 * link - see ab_gp_channel.c's own "negotiate" step, folded directly into
191 * A and B): A-WEST<->A-EAST and B-WEST<->B-EAST are two SEPARATE links,
192 * one owned by each channel, so site-level negotiation is never a single
193 * point of failure the way one shared SITE process/link used to be. Each
194 * is a single fixed port (not a per-site offset): WEST always LISTENs,
195 * EAST always CONNECTs (fixed technical role, unrelated to which site
196 * ends up ONLINE - same convention the old SITE link used), and exactly
197 * one such link exists per channel regardless of site. */
198#define RTE_EXAMPLE_A_NEGOTIATION_PORT 15021U
199#define RTE_EXAMPLE_B_NEGOTIATION_PORT 15022U
200
201#define RTE_EXAMPLE_LOCALHOST "127.0.0.1"
202
203/* ---------------------------------------------------------------------
204 * Container-friendly host resolution
205 * ---------------------------------------------------------------------
206 * Every CONNECT-role endpoint in this example used to hardcode
207 * RTE_EXAMPLE_LOCALHOST - fine when every role ran as a plain OS
208 * process on one host, not once A, B, and C become genuinely separate
209 * containers/machines. Each env var below overrides ONE specific
210 * CONNECT-role link's target host; unset (the default for
211 * etc/run_all.sh/.claude/skills/run-safeAPIRBC2oo2/smoke.sh, both still
212 * fully localhost-based) falls back to RTE_EXAMPLE_LOCALHOST, so the
213 * existing local/manual launch path needs zero changes. Docker Compose
214 * sets these per-service to the peer container's own service-DNS name
215 * (see docker-compose.yml). Read via site_config_host() below. */
216#define RTE_EXAMPLE_C_HOST_ENV "RTE_EXAMPLE_C_HOST" /* A/B (any site) -> this site's C */
217#define RTE_EXAMPLE_A_HOST_ENV "RTE_EXAMPLE_A_HOST" /* B (any site) -> this site's A (peer link) */
218#define RTE_EXAMPLE_REMOTE_A_HOST_ENV "RTE_EXAMPLE_REMOTE_A_HOST" /* A-EAST -> A-WEST (negotiation) */
219#define RTE_EXAMPLE_REMOTE_B_HOST_ENV "RTE_EXAMPLE_REMOTE_B_HOST" /* B-EAST -> B-WEST (negotiation) */
220
221/* ADR-029 addendum #2 (attempted, REVERTED): live GitHub Actions CI
222 * testing (shared 2-vCPU runner, 11 containers) found the inter-site
223 * negotiation link repeatedly faulting ("no ack within
224 * RTE_EXAMPLE_NEGOTIATION_WATCHDOG_TIMEOUT_MS") well into steady-state
225 * operation, alongside the separate AB_CHECKPOINT_MAX_DELAY_MS misses
226 * that prompted a matching RTE_EXAMPLE_AB_CYCLE_PERIOD_MS widening
227 * attempt (see that constant's own doc) - both pointed at the same
228 * underlying cause (severe CPU/I/O contention on a real CI runner, well
229 * beyond what a local developer machine exhibits). Raising this
230 * constant 3000ms -> 5000ms alongside that attempt was tried, but found
231 * live to REGRESS local stability (this project's own previously rock-
232 * solid 11/11 local Robot suite dropped to 6/11 on the SAME machine,
233 * SAME code, with only these timing constants changed - confirmed by
234 * reverting just the constants and re-running, 11/11 again) - kept at
235 * 3000ms. The CI-side instability this was meant to fix remains open;
236 * see RTE_EXAMPLE_AB_CYCLE_PERIOD_MS's own doc for the working
237 * theory on why widening made things worse instead of better, and for
238 * why the fix belongs at the CI-environment layer, not here. */
239#define RTE_EXAMPLE_LINK_TIMEOUT_MS 300U
240#define RTE_EXAMPLE_NEG_ACK_TIMEOUT_MS 50U
241
242/* ISS-020: open gate of the UTIL relay link (ab-relay-util, ab_gp_util_link_gate.h). The link carries only non-safety
243 * traffic (status publish, JRU/diagnostic log forwarding, ADR-001 section 4), its open runs on the cycle thread and
244 * blocks until the status service answers the handshake or the timeout runs out. While the status service is absent,
245 * the cycle thread may spend at most RTE_EXAMPLE_UTIL_OPEN_ATTEMPT_TIMEOUT_MS on one attempt per
246 * RTE_EXAMPLE_UTIL_REOPEN_BACKOFF_MS (0.4 % of the cycle budget), instead of RTE_EXAMPLE_LINK_TIMEOUT_MS per INFO log
247 * event and the channel's 1000 ms connect timeout per cycle (measured 1298 ms per cycle on the read path).
248 * - Back-off 5000 ms = RTE_EXAMPLE_STATUS_PERIOD_MS: a status service that comes up later is reached at most one
249 * status period later than without the gate; nothing is queued for it before that (best-effort, as before).
250 * - Attempt 20 ms: well under the 50 ms the inter-site link's per-cycle reconnect attempt may block the cyclic
251 * executive (AB_GP_NEG_RECONNECT_ATTEMPT_MS) and under RTE_EXAMPLE_NEG_ACK_TIMEOUT_MS, so the negotiation link
252 * serviced between cycles is not starved; a loopback or LAN peer that services its socket answers HELLO within a
253 * few ms. Applies to attempts before the first success only; once open, sends use RTE_EXAMPLE_LINK_TIMEOUT_MS as
254 * before (a send on an open UDP link does not block). */
255#define RTE_EXAMPLE_UTIL_REOPEN_BACKOFF_MS 5000U
256#define RTE_EXAMPLE_UTIL_OPEN_ATTEMPT_TIMEOUT_MS 20U
257
258/* Subset-026 T_NVCONTACT: maximum silence from a train with an active
259 * ERTMS session (ab_gp_proc_session_check_timeout(), called once per cycle
260 * from ab_gp_ertms_engine_step() with the real elapsed_ms) before the
261 * session is torn down. Found live disabled: engine_step() passed a
262 * literal 0U here instead of a real threshold, and check_timeout()'s own
263 * `timeout_limit_ms > 0U` guard (a deliberate "0 = supervision off" opt-out
264 * for that function's other, non-cyclic callers - see its own header doc)
265 * silently no-oped every single call, so T_NVCONTACT supervision never
266 * actually fired for any train, ever. This default is a placeholder, not
267 * a value taken from a real National Values message: this codebase does
268 * not yet store a per-train Packet 3 (National Values) T_NVCONTACT once
269 * received (ab_gp_proc_dispatch.c's own Msg 24 encode path has has_national_values,
270 * but nothing decodes one back in and stores it per train) - wire that up
271 * and use it here in place of this constant when that lands. */
272#define RTE_EXAMPLE_ERTMS_CONTACT_TIMEOUT_MS 20000U
273
274/* ADR-038 status indication: every unit publishes its status (and logs
275 * its own self line) at startup and every _PERIOD_MS; the C-side
276 * aggregator renders a source "down" once its last frame is older than
277 * _STALE_MS (3 missed periods). Non-safety - a monitoring path. */
278#define RTE_EXAMPLE_STATUS_PERIOD_MS 5000U
279#define RTE_EXAMPLE_STATUS_STALE_MS 15000U
280/* ADR-029 section 2.6 addendum: raised 5000ms -> 60000ms after a severe
281 * crash-loop found live in fault_injection/ testing once
282 * ab_gp_channel_safestate_handler (ab_gp_channel.c) started correctly
283 * re-routing a checkpoint-connectivity timeout through this channel's own
284 * REBOOT recovery path instead of a silent permanent halt (see that
285 * handler's own doc). main.c's own startup sequence - ab_gp_channel_open_link()'s
286 * peer link, ab_gp_channel_negotiate_setup()'s negotiation link, and
287 * ab_gp_channel_relay_tasks_setup()'s C relay links - all share this ONE
288 * bounded "give up and exit the process" window (main.c calls
289 * rte_appmanager_run() exactly once and relies entirely on
290 * docker-compose's `restart: unless-stopped` for any retry - there is no
291 * in-process retry loop around init itself, unlike every ONGOING
292 * reconnect in this codebase, which retries forever). During a real
293 * network-partition fault-injection test (tests/robot/fault_injection/),
294 * the partition can legitimately still be in effect for
295 * RTE_EXAMPLE_EXTENDED_PARTITION_SECONDS-class durations (tens of
296 * seconds) - a rebooted process whose own re-init hits the SAME still-
297 * ongoing partition within the old 5s window would immediately fail
298 * init and exit, docker would immediately respawn a brand-new container
299 * that ALSO immediately re-hits the same still-ongoing partition, and so
300 * on for the whole partition duration - observed live as 30+ container
301 * restarts within a few minutes (RestartCount), the exact crash-loop
302 * MAX_ACCEPTABLE_RESTARTS_PER_FAULT (tests/robot) exists to catch. Raised
303 * comfortably past the worst-case fault-injection partition length so a
304 * reboot triggered mid-partition gets a REAL chance to reconnect once the
305 * partition actually lifts, rather than repeatedly racing (and losing)
306 * against a fault that has not yet resolved - this is a demo/first-pass
307 * choice (a real product would give init its own retry-forever loop
308 * instead of a single bounded window backed by a container restart), not
309 * a claim that 60s is itself some principled ceiling. */
310#define RTE_EXAMPLE_CONNECT_TIMEOUT_MS 10000U
311
312/* Bounded wait per attempt for a *background* link reconnect loop (peer or
313 * C link dying mid-run and being re-established without giving up - see
314 * ab_gp_channel.c/monitor_c.c). Deliberately shorter than
315 * RTE_EXAMPLE_CONNECT_TIMEOUT_MS (used for the one-time initial
316 * open, where giving up quickly would be wrong): a background reconnect
317 * loop retries forever anyway, so each individual attempt should be short
318 * enough that the loop also re-checks its own stop-requested flag
319 * promptly during shutdown. */
320#define RTE_EXAMPLE_RECONNECT_ATTEMPT_TIMEOUT_MS 1000U
321
322/* ADR-027 Phase 2: consecutive RTE_STATUS_TIMEOUT results (each already
323 * bounded to RTE_EXAMPLE_LINK_TIMEOUT_MS) a raw, unframed netlink
324 * link's own rx task tolerates before treating the link as dead and
325 * closing it for reconnect - the peer link (ab_gp_channel_io.c) and C's two
326 * links (monitor_c_io.c). Under UDP there is no transport-level "peer
327 * disconnected" signal for these links (unlike the negotiation link,
328 * which has rte_dual_channel's own ACK-timeout-based liveness) - a
329 * receive() call that only ever returns TIMEOUT, forever, is the sole
330 * signal available that the peer's process has restarted, and this many
331 * consecutive occurrences (~RTE_EXAMPLE_LINK_TIMEOUT_MS * this value)
332 * is treated as "gone", not "quiet this cycle".
333 *
334 * ADR-029: raised from 2 to 4 (6s -> 12s) - same root cause as
335 * RTE_EXAMPLE_AB_CYCLE_SKEW_TOLERANCE/_AB_DUAL_TRANSFER_TIMEOUT_MS's
336 * own doc: the C<->A/B link (this same constant also governs
337 * monitor_c_io.c's rx_a/rx_b tasks) now only carries traffic when there
338 * is real train/IL activity to relay, which arrives roughly once per
339 * sims/train_sim.py's own RTE_EXAMPLE_SIM_PERIOD_SECONDS (2000ms
340 * default) rather than every ~600ms cycle - and specifically, A/B's own
341 * REPLY traffic back to C is additionally gated on cross-compare having
342 * AGREED at least once, which can itself take several train-report
343 * cycles to first settle after a fresh start/reconnect. The old 6s
344 * window was found live to spuriously fire during that ordinary startup
345 * ramp-up, closing/reopening a perfectly healthy link mid-flight - and
346 * because this link carries one-time, non-repeating events (ROUTE_ADD,
347 * M146), unlike M136's own self-healing-next-report tolerance, a
348 * datagram lost to that exact reconnect window is gone for good and
349 * causes a genuine, spurious cross-compare DISAGREE (found live: an
350 * IL's very first commanded ADD_ROUTE landed on A mid-reconnect and
351 * never arrived, while B received it fine, diverging the two channels'
352 * otherwise-identical session tables). Kept comfortably under
353 * RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS (20s) - this stays the fast,
354 * lightweight "just reopen the socket" path; that stays the slow,
355 * drastic escalation. */
356#define RTE_EXAMPLE_LINK_STALE_TIMEOUT_COUNT 4U
357
358/* ADR-027 Phase 2: bounded grace period after the peer link reconnects
359 * before the built-in checkpoint is re-armed regardless of whether any
360 * data has arrived yet - see checkpoint_pending_reenable's own doc
361 * (ab_gp_channel_types.h) for why a pure "wait for incoming data" gate can
362 * deadlock (checkpoint's own send is what the peer needs to receive to
363 * clear ITS OWN pending flag) and why this fallback exists.
364 *
365 * ADR-029: raised 600ms -> 15000ms after a real, reproducible permanent
366 * hang found in full 11-container Docker testing - live gdb attach to a
367 * stuck A/B process (main thread pinned at 100% CPU, wchan=0, spinning in
368 * rte_safestate_enter() via rte_channel_checkpoint()) showed the built-in
369 * checkpoint entering an UNRECOVERABLE SAFE-state halt (by design,
370 * REQ-CHECKPOINT-003 - this is not itself a bug) shortly after a peer-link
371 * stale/reconnect event. Root cause: this socket reconnecting proves only
372 * that the LOCAL send/receive path is usable again, not that the PEER's
373 * own reconnect (bounded by the independent
374 * RTE_EXAMPLE_LINK_STALE_TIMEOUT_COUNT * RTE_EXAMPLE_LINK_TIMEOUT_MS
375 * ~= 12s window) has also completed - under real Docker load the two
376 * sides' reconnects are not synchronized, so the old 600ms was routinely
377 * far shorter than the peer's own recovery time, force-re-arming the
378 * checkpoint (and its intentionally tight 450ms
379 * RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS fault budget - left
380 * unchanged, that tightness is correct and load-bearing for catching a
381 * genuinely dead peer within one cycle) against a link that could not yet
382 * possibly answer. Raised to comfortably exceed that ~12s worst case
383 * rather than "one handshake+round-trip on a healthy link" - the
384 * receive()-triggered strong signal (ab_gp_channel_io_check_checkpoint_reenable()
385 * called with got_data=true) still re-arms immediately the moment real
386 * data proves the peer is actually back, so this deadline is only ever
387 * reached in the deadlock-of-last-resort case the comment above already
388 * describes, not on the common path. */
389#define RTE_EXAMPLE_CHECKPOINT_REENABLE_GRACE_MS 15000U
390
391/* Channel-down -> REBOOT threshold for a site's OWN internal redundant
392 * channels (the A<->B same-site peer link, and each of A/B's own links
393 * to/from C) - deliberately NOT applied to the inter-site negotiation
394 * link (A-WEST<->A-EAST etc.), which needs different handling since it
395 * connects two DIFFERENT sites: a link outage looks the same from both
396 * ends, so rebooting on negotiation-link-down alone would reboot both
397 * sites together, achieving the opposite of failover (see ADR-027's own
398 * note on this and ab_gp_channel_negotiate.c's standby_hot handling
399 * instead). A same-site channel losing contact with its OWN redundant
400 * pair, in contrast, is unambiguous local damage - the same reasoning
401 * that already justifies REBOOT on a genuine cross-compare DISAGREE
402 * (ab_gp_channel_crosscompare.c). Deliberately much longer than
403 * RTE_EXAMPLE_LINK_STALE_TIMEOUT_COUNT's own reconnect-trigger
404 * threshold (~2 * RTE_EXAMPLE_LINK_TIMEOUT_MS = 6s): a channel that
405 * is merely reconnecting should get real time to recover on its own
406 * first, and this is deliberately generous enough to tolerate the known,
407 * still-open ADR-027 Phase 2 reconnect-livelock scenario turning into a
408 * bounded reboot rather than a rare successful self-heal, without firing
409 * on routine, short-lived reconnects.
410 *
411 * ADR-029 addendum #2 (attempted, REVERTED): a matching 20000ms ->
412 * 60000ms raise was tried alongside RTE_EXAMPLE_LINK_TIMEOUT_MS's own
413 * attempted 3000ms -> 5000ms raise, to keep this constant's "deliberately
414 * much longer than" relationship to RTE_EXAMPLE_LINK_STALE_TIMEOUT_COUNT/
415 * RTE_EXAMPLE_AB_DUAL_TRANSFER_TIMEOUT_MS intact rather than letting
416 * it silently invert. Reverted along with that whole addendum #2 attempt
417 * (see RTE_EXAMPLE_LINK_TIMEOUT_MS's own doc - found to regress local
418 * stability). Kept at 20000ms. */
419#define RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS 20000U
420
421/* Consecutive missed-ACK cycles the inter-site negotiation link tolerates
422 * before tearing itself down for reconnect (ab_gp_channel_negotiate.c). Under
423 * UDP a single dropped datagram is a routine, expected event even on a
424 * healthy link (no transport-level retransmission) - rebuilding the link
425 * on every miss would reset rte_dual_negotiator_t's tie-break state
426 * machine far more often than a real fault warrants. Kept small: this link
427 * genuinely needs to notice a dead/restarted peer promptly (ADR-027). */
428#define RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD 3U
429
430/* A/B cyclic executive: rte_appmanager_run() loops forever
431 * (max_iterations = 0U), one execute() call = one simulated cycle,
432 * paced by a real periodic rte_timer rather than free-running as fast as
433 * the link allows - see ab_gp_channel.c's file header for the full phase
434 * breakdown (read channels / check timers / check tasks / process data /
435 * cross-compare / prepare data / dual transfer / send to C). Watchdog
436 * timeout is a generous multiple of the cycle period: two independently
437 * free-running timers (A's and B's, not synchronized to each
438 * other) will legitimately skew by more than one cycle under jitter
439 * without that being a real fault.
440 *
441 * ADR-029 addendum: raised 500ms -> 800ms after live 11-container Docker
442 * testing kept reproducing RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS
443 * misses (REQ-COMMON-SAFESTATE-002 halt/reboot, no retry by design) during
444 * otherwise-healthy steady-state operation - not right after a reconnect,
445 * where RTE_EXAMPLE_AB_CHECKPOINT_RELAXED_MAX_DELAY_MS already covers
446 * it. Host-level scheduling jitter from 11 containers sharing one Docker
447 * host (confirmed NOT raw per-container CPU starvation - `docker stats`
448 * stayed under 1.2% CPU throughout) has nowhere else to be absorbed: the
449 * checkpoint's own steady-state budget is deliberately tight (see that
450 * constant's own doc) and is the one window active during normal
451 * operation with no wider fallback. Every timing constant derived FROM
452 * this one (WATCHDOG_TIMEOUT_MS, DUAL_TRANSFER_TIMEOUT_MS, the
453 * negotiation watchdog) scales up proportionally as a result, which is
454 * the intended effect, not a side effect - this is the one base unit the
455 * whole AB timing hierarchy multiplies from, so widening it here widens
456 * jitter tolerance everywhere at once rather than requiring each derived
457 * constant to be re-tuned independently. Slower overall AB cadence (more
458 * latency on position-report/MA-grant forwarding to C) is the accepted
459 * trade-off; confirmed against safeAPITestEnv's own Robot suite that no
460 * test timeout is tightly coupled to the old 500ms value (rbc_scenario's
461 * own PROPAGATION_TIMEOUT is 40s, ~50x this period even after the
462 * raise).
463 *
464 * ADR-029 addendum #2 (attempted, REVERTED): live GitHub Actions CI
465 * testing (standard 2-vCPU hosted runner, not a local developer machine)
466 * found A/WEST still repeatedly hitting BOTH
467 * RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS misses AND separate
468 * inter-site negotiation watchdog timeouts every 15-20s of real
469 * wall-clock time, well into otherwise-healthy steady-state operation,
470 * not just at startup - confirmed via this run's own attached container
471 * logs (ContainerLogAttachListener.py), not inference. Raising this
472 * 800ms -> 2000ms (and RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS/
473 * RTE_EXAMPLE_LINK_TIMEOUT_MS/RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS
474 * alongside it) was tried, but found live to REGRESS this project's own
475 * previously rock-solid local Robot suite (11/11 -> 6/11 on the SAME
476 * machine, SAME code, only these constants changed - confirmed by
477 * reverting just the constants and re-running, 11/11 again). Reverted;
478 * kept at 800ms.
479 *
480 * Working theory for why widening made things WORSE rather than better:
481 * this constant is the ONE base unit checkpoint's own steady-state
482 * budget, the AppManager watchdog, dual-transfer-lost detection, and the
483 * negotiation watchdog all derive from - widening it doesn't just buy
484 * more jitter tolerance, it also lengthens how long EVERY OTHER phase of
485 * one AB cycle (read channels/check timers/check tasks/process data/
486 * cross-compare/prepare data/dual transfer/send to C, all sharing
487 * ctx->peer_send_mutex with the checkpoint's own request/reply) is
488 * ALLOWED to run before the next cycle tick fires - on a genuinely
489 * busier system this plausibly gives contention on that shared mutex
490 * more opportunity to compound cycle-over-cycle instead of draining,
491 * rather than simply absorbing one-off jitter the way the FIRST addendum
492 * (500ms -> 800ms, kept - see below) did on a less contended local
493 * machine. Not fully root-caused live (would need attaching to a
494 * genuinely CI-equivalent contended environment to confirm) - the
495 * CI-side instability this addendum was meant to fix remains OPEN;
496 * fixing it belongs at the CI-environment layer (a less contended
497 * runner, or a retry around the whole Robot suite step) rather than by
498 * further widening a constant already shown to regress local stability
499 * when pushed further in this direction. */
500#define RTE_EXAMPLE_AB_CYCLE_PERIOD_MS 500U
501#define RTE_EXAMPLE_AB_WATCHDOG_TIMEOUT_MS 600U
502
503/* ADR-019 addendum (safeAPIFreamwork): bound on the built-in AppManager
504 * checkpoint rendezvous (rte_appmanager_checkpoint_config_t::max_delay_ms,
505 * forwarded to rte_checkpoint_config_t::max_delay_ms), run once at the
506 * start of every A<->B cycle before pre_execute()/execute()/post_execute()
507 * - see ab_gp_channel.c's file header. Sized well under
508 * RTE_EXAMPLE_AB_CYCLE_PERIOD_MS so a slow-but-still-alive peer costs
509 * cycle latency, not most of the cycle itself, while still being
510 * comfortably longer than one round-trip on a healthy localhost link.
511 * ADR-027: raised from 150ms (a value tuned for TCP's near-instant
512 * connection establishment) once the netlink transport moved to UDP - a
513 * freshly-opened or just-reconnected link now converges through a
514 * retry-based HELLO handshake (rte_posix_osadapter_netlink.c) rather than
515 * a single accept()/connect(), so the very first rendezvous after a link
516 * (re)establishes can take longer even though steady-state round-trip
517 * latency is unchanged (sub-millisecond on a healthy localhost link).
518 * Further raised 300ms -> 450ms after real 6-container Docker testing
519 * (not just loopback) of the ADR-027 Phase 2 staleness-reconnect trigger:
520 * a live peer-restart-and-reconnect has more jitter than a clean startup
521 * - the reconnecting side's own checkpoint is re-enabled the instant its
522 * link reopens, racing the first real round trip on a link that just
523 * came back, on top of real bridge-network latency loopback testing does
524 * not exercise. Still bounded (well under a full cycle, at 90% of
525 * RTE_EXAMPLE_AB_CYCLE_PERIOD_MS) - a genuinely dead peer still halts
526 * within one cycle, this only gives a *just-reconnected* one more room
527 * before that mandatory reaction fires.
528 *
529 * ADR-029 addendum: raised 450ms -> 700ms alongside
530 * RTE_EXAMPLE_AB_CYCLE_PERIOD_MS's own 500ms -> 800ms raise (see that
531 * constant's own doc for the full root-cause writeup: live Docker testing
532 * found this steady-state budget - the one checkpoint window with no
533 * wider "just reconnected" fallback - being missed under ordinary
534 * 11-container host jitter, not just after a reconnect). Kept
535 * proportionate (87.5% of the new 800ms cycle, still comfortably under a
536 * full cycle so a genuinely dead peer is still caught within one cycle),
537 * but the absolute slack this buys is real: 250ms of headroom instead of
538 * the old 50ms (450 of 500).
539 *
540 * ADR-029 addendum #2 (attempted, REVERTED): a matching 700ms -> 1800ms
541 * raise was tried alongside RTE_EXAMPLE_AB_CYCLE_PERIOD_MS's own
542 * attempted 800ms -> 2000ms raise - see that constant's own doc for the
543 * live CI evidence AND why the whole attempt was reverted (regressed
544 * local stability, 11/11 -> 6/11 on the same machine/code). Kept at
545 * 700ms. */
546#define RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS 400U
547
548/* ADR-029 section 2.6 addendum: widened checkpoint budget used ONLY for a
549 * bounded window right after the peer link reconnects
550 * (ab_gp_channel_types.h's checkpoint_relaxed_until_ms) - NOT a replacement
551 * for RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS's own steady-state 450ms,
552 * which stays tight and load-bearing for catching a genuinely dead peer
553 * within one cycle. Found live, with rte_checkpoint.c's own new
554 * diagnostic message (registered via a RTE_SAFESTATE_LEVEL_SAFE handler
555 * that previously did not exist anywhere in either project, so this
556 * REQ-COMMON-SAFESTATE-002 halt was completely silent until now): a real
557 * `docker restart` of the peer container - not just the milder link-blip
558 * RTE_EXAMPLE_CHECKPOINT_REENABLE_GRACE_MS already handles - produces
559 * exactly last_send=RTE_STATUS_OK / last_recv=RTE_STATUS_TIMEOUT, i.e.
560 * the reconnected peer is reachable and the request delivered, it simply
561 * did not reply within 450ms under the genuine scheduling jitter a full
562 * container restart causes on this host. Set generously (well over the
563 * measured single-attempt miss) since this window only relaxes latency
564 * tolerance for a peer already proven reachable, not the "is this peer
565 * even there" question RTE_EXAMPLE_CHECKPOINT_REENABLE_GRACE_MS/
566 * checkpoint_pending_reenable's own strong/weak signals already gate on
567 * separately. */
568#define RTE_EXAMPLE_AB_CHECKPOINT_RELAXED_MAX_DELAY_MS 10000U
569
570/* How long RTE_EXAMPLE_AB_CHECKPOINT_RELAXED_MAX_DELAY_MS stays in
571 * effect after the peer link's checkpoint is re-armed (see
572 * checkpoint_relaxed_until_ms's own doc) before reverting to the normal
573 * tight budget. Deliberately similar magnitude to
574 * RTE_EXAMPLE_CHECKPOINT_REENABLE_GRACE_MS (15s) - both exist to
575 * absorb the same real-world recovery churn window, just at different
576 * layers (whether the checkpoint runs at all vs. how long it is willing
577 * to wait once it does). */
578#define RTE_EXAMPLE_CHECKPOINT_RELAX_DURATION_MS 15000U
579
580/* Jitter-tolerant "lookback" for the built-in AppManager checkpoint
581 * rendezvous (ab_gp_channel_checkpoint.c). The rendezvous is a synchronous
582 * A->B->A round trip run as AppManager stage 4 (last), but the peer can
583 * only answer from its OWN next pre_execute(), so a fresh round trip needs
584 * >= one full RTE_EXAMPLE_AB_CYCLE_PERIOD_MS - more than
585 * RTE_EXAMPLE_AB_CHECKPOINT_MAX_DELAY_MS's steady-state budget allows.
586 * It therefore only ever completed in-window when host scheduling jitter
587 * was low enough to keep the two cycle phases aligned (true on a quiet
588 * dev box, false on a loaded CI runner / VM - see
589 * docs/TIMING_AND_DATA_LOSS_ANALYSIS.md finding 10, and the long revert
590 * history on RTE_EXAMPLE_AB_CYCLE_PERIOD_MS for why simply widening
591 * the budget was rejected).
592 *
593 * With lookback, this cycle's rendezvous confirms if the peer exchanged
594 * ANY checkpoint frame (its own request, or a reply echoing ours) with us
595 * within the last N cycles - a local check against a cycle counter, immune
596 * to sub-cycle phase. A genuinely silent/partitioned peer still fails the
597 * rendezvous after N+1 checkpoint-quiet cycles (~1.5s at N=2), so the
598 * bounded reaction REQ-CHECKPOINT-003 requires is preserved, just measured
599 * in cycles rather than a sub-cycle millisecond window. Same-program-path
600 * divergence (as opposed to liveness) is caught independently and every
601 * cycle by the cross-comparator (ab_gp_channel_crosscompare.c), not by
602 * this rendezvous. */
603#define RTE_EXAMPLE_AB_CHECKPOINT_LOOKBACK_CYCLES 2U
604
605/* "Message and acknowledgment" liveness for the A<->B peer link: every
606 * cycle each side already sends its own sample AND receives the other's
607 * (ab_gp_channel.c's "dual transfer" phase) - that existing exchange already
608 * constitutes an implicit request/ack round-trip, so no separate ack-only
609 * message type is introduced. This is the timeout on THAT round-trip
610 * being usable (decoded + CRC-verified) at least once per window - a
611 * dedicated RTE_WATCHDOG_ACTION_FAILOVER watchdog (see ab_gp_channel.c) is
612 * kicked only when a usable peer sample actually arrives; going this long
613 * without one means the redundant peer is no longer answering, and this
614 * site's own channel drops from dual (2oo2) to single-channel mode rather
615 * than continuing to assume a partner that is not there. A shorter window
616 * than RTE_EXAMPLE_AB_WATCHDOG_TIMEOUT_MS (which only proves the link
617 * itself is still up, not that USABLE data is flowing - see
618 * docs/TIMING_AND_DATA_LOSS_ANALYSIS.md finding 6) so the degradation is
619 * noticed promptly rather than only after the coarser link watchdog would
620 * also have fired.
621 *
622 * ADR-029: raised the multiplier from 6 to 20 for the same reason
623 * RTE_EXAMPLE_AB_CYCLE_SKEW_TOLERANCE was raised (see that constant's
624 * own doc) - a "usable peer sample" is now only produced once per real
625 * train/IL activity (roughly once per sims/train_sim.py's own
626 * RTE_EXAMPLE_SIM_PERIOD_SECONDS, 2000ms default), not every AB
627 * cycle. The old 6x/3000ms window was already tight against that once
628 * any jitter/staggered-train-arrival was added on top - found live as a
629 * genuine, non-obvious REBOOT/single-mode flapping loop (both channels
630 * see each other as intermittently "gone", entering SINGLE mode, then
631 * catching the resulting NEGOTIATION MISMATCH against their own peer's
632 * differently-timed flap - RTE_EXAMPLE_REASON_AB_SITE_STATE_MISMATCH).
633 * This watchdog's own job is still "is my redundant peer's data usably
634 * flowing at all", not "did it flow this exact cycle" - a generous
635 * window here costs a slower single-mode reaction to a GENUINELY dead
636 * peer, not correctness. */
637#define RTE_EXAMPLE_AB_DUAL_TRANSFER_TIMEOUT_MS (RTE_EXAMPLE_AB_CYCLE_PERIOD_MS * 20U)
638
639/* C-side counterpart of the above: how many consecutive C report cycles
640 * (RTE_EXAMPLE_C_REPORT_PERIOD_MS each) A's forwarded answer may stay
641 * byte-for-byte unchanged before monitor_c.c treats A as having gone quiet
642 * and falls back to consuming B's forwarded answer instead (see
643 * monitor_c.c's rx_b_task_entry()/monitor_c_execute() - B is normally
644 * received and discarded, since only A forwards in dual mode; this is what
645 * lets B's own SINGLE-mode solo forwarding, see ab_gp_channel.c's
646 * on_dual_transfer_lost(), actually reach C when A - not B - is the
647 * channel that lost its peer). Sized off the same
648 * RTE_EXAMPLE_AB_DUAL_TRANSFER_TIMEOUT_MS window, just expressed in
649 * C's own 600ms cycles rather than A/B's 500ms ones (5 cycles @ 600ms =
650 * 3000ms, matching the 6 cycles @ 500ms A/B itself uses) - both sides
651 * notice a lost A at roughly the same wall-clock delay. */
652#define RTE_EXAMPLE_C_A_STALE_CYCLE_LIMIT \
653 (RTE_EXAMPLE_AB_DUAL_TRANSFER_TIMEOUT_MS / RTE_EXAMPLE_C_REPORT_PERIOD_MS)
654
655/* C's own M136 report cadence - deliberately NOT the same as A/B's
656 * RTE_EXAMPLE_AB_CYCLE_PERIOD_MS: C is a third, independently
657 * free-running cyclic executive (see monitor_c.c), and this project's
658 * cross-compare is already designed to tolerate A, B, and C all running
659 * on their own unsynchronized clocks (see
660 * RTE_EXAMPLE_AB_CYCLE_SKEW_TOLERANCE above) - a deliberately
661 * different period here is what actually exercises that, rather than the
662 * three processes coincidentally staying in lockstep because they all
663 * share one constant. */
664#define RTE_EXAMPLE_C_REPORT_PERIOD_MS 600U
665
666/* A (LISTEN, starts its cycle timer as soon as its own init() finishes)
667 * and B (CONNECT, whose init() additionally retries the peer connect in
668 * open_connect_with_retry() before it can start its own cycle timer)
669 * start their independent 500ms cycle timers at two different wall-clock
670 * moments - not just "occasional jitter" but a roughly FIXED phase skew
671 * for the rest of that run (same nominal period afterward, so it does not
672 * self-correct). Requiring the peer's stamped cycle number to match
673 * one's own EXACTLY would then permanently fail cross-compare on
674 * whichever side ends up on the "losing" side of that fixed skew - this
675 * tolerance lets cross-compare still proceed when the two sides are
676 * within a few cycles of each other (still rejecting a genuinely stale/
677 * hung peer as "no comparable data this cycle", just with a wider,
678 * skew-tolerant window instead of an exact match).
679 *
680 * ADR-029: raised from 3 to 20 once real Train sims (not C self-
681 * generating M136 every ~600ms) started exercising this - an
682 * AB_SAMPLE is now only sent on a cycle that actually had fresh train/IL
683 * data (ab_gp_channel_send_peer_sample_if_online()'s have_channel_data gate), which
684 * happens roughly once per RTE_EXAMPLE_SIM_PERIOD_SECONDS (2000ms
685 * default = ~4 AB cycles at 500ms each), not every cycle - so the
686 * embedded cycle number in the SENDER's last AB_SAMPLE can legitimately
687 * be several cycles behind the RECEIVER's own current count even on a
688 * perfectly healthy link, on top of the original startup-skew this
689 * tolerance already existed for. 3 rejected essentially every real
690 * cross-compare attempt once trains reported on their own realistic
691 * cadence rather than C's old near-every-cycle heartbeat - found live.
692 * This is purely "is this specific sample still worth comparing", not a
693 * liveness check (a genuinely dead peer is caught by
694 * ctx->dual_transfer_watchdog/RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS
695 * instead - both scoped to actual silence, not sample age) - a generous
696 * value here costs nothing but a slightly wider "still comparable"
697 * window. */
698#define RTE_EXAMPLE_AB_CYCLE_SKEW_TOLERANCE 20U
699
700/* ADR-029: bounds how many consecutive AB cycles
701 * ab_gp_channel_crosscompare_execute() will skip (not disagree) a single
702 * train whose session does not yet match its peer's, before treating it
703 * as a genuine divergence instead of an ordinary asynchronous-arrival
704 * timing race - see that function's own doc. 10 cycles (~5s at
705 * RTE_EXAMPLE_AB_CYCLE_PERIOD_MS) is comfortably longer than the
706 * relay latency a healthy link needs to bring both channels' session
707 * tables back into agreement, while still well under
708 * RTE_EXAMPLE_AB_DUAL_TRANSFER_TIMEOUT_MS/RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS's
709 * own much longer windows - a real, permanent divergence (e.g. a lost
710 * ROUTE_ADD relay datagram - see monitor_c.c's relay_inbound_to_ab() own
711 * doc) is still detected and reacted to promptly, not silently ignored
712 * forever. */
713#define RTE_EXAMPLE_XCOMPARE_SYNC_SKIP_LIMIT 10U
714
715/* Inter-site negotiation: how often each of A's and B's own negotiate
716 * step (ab_gp_channel.c, folded from the old standalone SITE role - see
717 * that file's header) drives its rte_dual_negotiator_execute() call and
718 * exchanges the extra payload below with its own counterpart on the
719 * other site. Reuses the same cadence as the A/B cycle itself
720 * (RTE_EXAMPLE_AB_CYCLE_PERIOD_MS) - no separate timer, since
721 * negotiation is now just one more step inside that same cycle, not a
722 * standalone process with its own heartbeat rate.
723 *
724 * Watchdog timeout: MUST stay longer than the worst case time
725 * RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD's own hysteresis (
726 * ab_gp_channel_negotiate.c) needs to detect a dead link, close it, and get
727 * a fresh reconnect's first send through - otherwise this watchdog (which
728 * reboots the ONLINE side outright on expiry, see
729 * RTE_EXAMPLE_REASON_NEGOTIATION_LINK_LOST) fires and reboots on the
730 * very first missed ACK, before the hysteresis it's layered on top of
731 * ever gets a chance to run - found live (ADR-027): a single dropped UDP
732 * datagram's own ack_timeout_ms wait (RTE_EXAMPLE_LINK_TIMEOUT_MS)
733 * alone already exceeded the previous fixed 2500ms value here, defeating
734 * the hysteresis entirely. Derived, not a bare constant, specifically so
735 * the two can never drift back out of sync silently: worst case is
736 * RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD full ACK-wait timeouts before
737 * the hysteresis closes the link, plus one reconnect attempt, plus one
738 * more cycle for that reconnect's first send to land and kick this
739 * watchdog. */
740#define RTE_EXAMPLE_NEGOTIATION_WATCHDOG_TIMEOUT_MS \
741 ((RTE_EXAMPLE_NEG_SEND_MISS_THRESHOLD * RTE_EXAMPLE_NEG_ACK_TIMEOUT_MS) + \
742 RTE_EXAMPLE_RECONNECT_ATTEMPT_TIMEOUT_MS + RTE_EXAMPLE_AB_CYCLE_PERIOD_MS)
743
744/* This site's own live ONLINE/STANDBY/FAULTED role, negotiated by A and
745 * B themselves (ab_gp_channel.c's own "negotiate" step - see that file's
746 * header) and carried directly as in-memory state (ctx->is_online/
747 * ctx->own_faulted) and inter-site wire messages - no shared filesystem
748 * assumption between containers (each of A, B, and C is a genuinely
749 * separate container/machine - see README's "6 containers" section).
750 *
751 * Three role states, not two: FAULTED is distinct from STANDBY so the
752 * live-failover protocol cannot flap. When ab_gp_channel.c's cross-compare
753 * detects a genuine M24/M15 disagreement, it sets ctx->own_faulted and
754 * enters RTE_SAFESTATE_LEVEL_REBOOT - it never just falls back to
755 * STANDBY for this, specifically so the peer site's own negotiate step
756 * can tell "peer just faulted" apart from "peer has always been
757 * standby." The promotion rule is: a STANDBY channel promotes itself to
758 * ONLINE once its own inter-site link reports the peer FAULTED - a
759 * FAULTED channel itself never self-promotes, which is what breaks the
760 * would-be flapping loop of both sides re-negotiating back and forth. */
761#define RTE_EXAMPLE_SITE_ROLE_ONLINE 1U
762#define RTE_EXAMPLE_SITE_ROLE_STANDBY 0U
763#define RTE_EXAMPLE_SITE_ROLE_FAULTED 2U
769#define RTE_EXAMPLE_SITE_ROLE_VALID_MARKER 0xA5U
770#define RTE_EXAMPLE_SINGLE_MODE_FLAG_DUAL 0U
771#define RTE_EXAMPLE_SINGLE_MODE_FLAG_SINGLE 1U
772
773/* ---------------------------------------------------------------------
774 * Online->standby state transfer (ab_gp_channel.c's "negotiate" step)
775 * ---------------------------------------------------------------------
776 * Carried directly in the inter-site negotiation payload below, with no
777 * NVM round-trip: the channel that is ONLINE already has ctx->cyc_cycle
778 * and its own live ctx->sessions[] (ADR-029 - a whole train-session
779 * table) in memory every cycle and includes them directly in what it
780 * sends to its own
781 * inter-site counterpart. On a STANDBY->ONLINE promotion, the configured
782 * transfer policy (RESTART/CONTINUE/ASK_USER) is applied to ctx->cycle
783 * from the most recently cached counterpart snapshot, while ctx->sessions[]
784 * itself is applied unconditionally (REQ-RBC-006) - see
785 * ab_gp_channel_negotiate.c's own apply_state_transfer(). */
786
787/* Inter-site negotiation payload (rte_dual_channel_send()/_receive(),
788 * separate from the negotiator's own STATE-frame traffic - see
789 * ab_gp_channel.c's own doc): relays this channel's own current FAULTED/
790 * single-mode status (the negotiator's own rte_dual_state_t has no
791 * FAULTED value, see ab_gp_channel.c's own design note) together with the
792 * state-transfer snapshot, in one combined per-cycle send instead of
793 * several.
794 *
795 * ADR-029: the state-transfer snapshot carries the whole train-session
796 * table (RTE_EXAMPLE_MAX_TRAINS entries) - a promoted STANDBY site
797 * must already know every connected train's granted MA length/sequence
798 * (see ab_gp_channel_negotiate.c's encode_extra_payload()/
799 * decode_extra_payload() and monitor_c.c's file header for why C is a
800 * pure relay with no self-generated M136). `cycle` stays a standalone scalar field
801 * (THIS CHANNEL's own next-AB-cycle-to-run counter, unrelated to any
802 * train's own M136 cycle number - see ab_gp_channel_types.h's own
803 * neg_peer_extra doc). Per-session encoding: in_use(1) + train_id(1) +
804 * cycle(4) + d_lrbg(4) + granted_length(4) + ma_seq(4) + ma_acked(1) = 19
805 * bytes. Well under rte_vital_message_t's 248-byte payload capacity
806 * (rte_checksum.h) that this ultimately rides inside via
807 * rte_dual_channel_send() - no framework-side size ceiling anywhere
808 * near this. */
809#define RTE_EXAMPLE_DB_ROUTE_WIRE_SIZE 12U
810#define RTE_EXAMPLE_DB_TRAIN_WIRE_SIZE 16U
811#define RTE_EXAMPLE_DB_WIRE_SIZE \
812 ((RTE_EXAMPLE_MAX_ROUTES * RTE_EXAMPLE_DB_ROUTE_WIRE_SIZE) + \
813 (RTE_EXAMPLE_MAX_TRAINS * RTE_EXAMPLE_DB_TRAIN_WIRE_SIZE))
814
815#define RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SESSION_SIZE 19U /* one ab_gp_train_session_t, encoded */
816#define RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SIZE \
817 (2U + 4U + (RTE_EXAMPLE_MAX_TRAINS * RTE_EXAMPLE_SITE_EXTRA_PAYLOAD_SESSION_SIZE) + \
818 RTE_EXAMPLE_DB_WIRE_SIZE + 1U) /* faulted(1) + single_mode(1) + cycle(4) + sessions[] + db + valid(1) */
819
820
821/* Local A<->B cross-check payload (PEER_MSG_KIND_SITE_STATE - see
822 * ab_gp_channel_types.h): each side's own independently-negotiated
823 * {is_online, single_mode}, exchanged over the SAME existing peer link
824 * every cycle so A and B can confirm their two independent inter-site
825 * negotiation links (above) agree - a mismatch is itself a fault (see
826 * ab_gp_channel_crosscompare.c). */
827#define RTE_EXAMPLE_AB_SITE_STATE_WIRE_SIZE 3U /* is_online(1) + single_mode(1) + ready_for_online(1) */
828
838
842#define RTE_EXAMPLE_TRANSFER_POLICY_ENV "RTE_EXAMPLE_TRANSFER_POLICY"
843
849#define RTE_EXAMPLE_TRANSFER_ASK_USER_TIMEOUT_MS 2000U
850
851/* CRC-64 polynomial used for the A<->B peer link's integrity check
852 * (ab_gp_channel.c) - ERTMS per this codebase's own naming (see
853 * safeAPIFreamwork/src/checksum/rte_checksum.c's file-level note: there
854 * is no publicly standardized "CRC-64/ERTMS"; this is this project's own
855 * convention, matching its railway/CENELEC domain). */
856#define RTE_EXAMPLE_CRC64_POLYNOMIAL RTE_CRC64_ERTMS
857
858/* Application-specific safe-state reason code: the A<->B peer link's
859 * received CRC-64 does not match the data actually received (see
860 * RTE_SAFESTATE_REASON_APPLICATION_BASE's doc in rte_safestate.h -
861 * values below it are reserved for the framework itself). This means
862 * either transmission corruption or a peer that computed a differently-
863 * sized/shaped message than expected; either way it is not the ordinary
864 * "peer computed a different message type" disagreement ab_gp_channel.c also
865 * detects and reports (DISAGREE) - a broken CRC means the peer's message
866 * cannot be trusted at all, which ab_gp_channel.c treats as a fault requiring
867 * RTE_SAFESTATE_LEVEL_SAFE, not a loggable disagreement. */
868#define RTE_EXAMPLE_REASON_AB_CRC_MISMATCH \
869 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 1U))
870
871/* Application-specific safe-state reason code: A and B independently
872 * decided a different M24/M15 message type for the same M136 position
873 * report cycle (both messages individually verified fine over CRC-64 - see
874 * RTE_EXAMPLE_REASON_AB_CRC_MISMATCH above for the distinct "cannot be
875 * trusted at all" case). A CRC-clean *disagreement in meaning* is judged
876 * severe: per the RBC simulation this project models, a channel that
877 * computed the wrong movement-authority decision cannot be allowed to
878 * keep influencing the live output, so this escalates all the way to
879 * RTE_SAFESTATE_LEVEL_REBOOT (see ab_gp_channel.c and
880 * ab_gp_channel_negotiate.c's failover protocol), not just SAFE. */
881#define RTE_EXAMPLE_REASON_AB_MTYPE_DISAGREE \
882 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 2U))
883
884/* Application-specific safe-state reason code: this channel's own
885 * inter-site negotiation link watchdog (ab_gp_channel_negotiate.c) fired -
886 * the message-and-acknowledgment round-trip with this channel's own
887 * counterpart on the other site (send this cycle's extra payload, receive
888 * theirs) has not completed successfully for
889 * RTE_EXAMPLE_NEGOTIATION_WATCHDOG_TIMEOUT_MS - AND this channel
890 * currently considers itself ONLINE (see
891 * ab_gp_channel_negotiate.c's own on_negotiation_link_lost() - a STANDBY
892 * channel losing this same link does NOT reboot, since that is the
893 * ordinary, expected shape of its ONLINE counterpart's own planned
894 * fault->reboot cycle, not a fault of its own). For the ONLINE side,
895 * losing this link is judged serious: it is the only channel through
896 * which the two sites' A (or both B) agree on who is ONLINE, so an ONLINE
897 * channel that can no longer prove it is healthy (or relay a future
898 * fault) risks an undetected split-brain (both sides independently
899 * believing they should be ONLINE) rather than just reduced redundancy.
900 * This escalates all the way to RTE_SAFESTATE_LEVEL_REBOOT - setting
901 * ctx->own_faulted first, same idiom as
902 * RTE_EXAMPLE_REASON_AB_MTYPE_DISAGREE, so the peer's own existing
903 * promotion rule can react to it exactly as it already does for an A/B-
904 * detected fault, and this channel self-recovers via the same already-
905 * verified mock-reboot mechanism rather than halting forever (a bare
906 * RTE_SAFESTATE_LEVEL_SAFE here would have no REBOOT handler to recover
907 * it - see main.c). Honest limitation: once lost, this link itself does
908 * not auto-reconnect - only a subsequent reboot of this process (its
909 * own fault, or this same watchdog) reopens it. */
910#define RTE_EXAMPLE_REASON_NEGOTIATION_LINK_LOST \
911 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 3U))
912
913/* Application-specific safe-state reason code: A's and B's own
914 * independently-negotiated {is_online, single_mode} (each computed via its
915 * own separate inter-site negotiation link - see ab_gp_channel_negotiate.c)
916 * disagree, as cross-checked over the existing local A<->B peer link
917 * (PEER_MSG_KIND_SITE_STATE) - see ab_gp_channel_crosscompare.c's own
918 * negotiation-mismatch reaction. Treated the same as a genuine M24/M15
919 * DISAGREE (RTE_EXAMPLE_REASON_AB_MTYPE_DISAGREE): this is a judgment
920 * call trading some availability for never letting A and B act on a
921 * disagreed site-role - a single degraded inter-site link (not necessarily
922 * a real split-brain) can also trigger this reboot. */
923#define RTE_EXAMPLE_REASON_AB_SITE_STATE_MISMATCH \
924 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 4U))
925
926/* Application-specific safe-state reason code: this channel's own
927 * same-site peer link (A<->B) has been unable to communicate with its
928 * own redundant pair for RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS
929 * continuously (see ab_gp_channel_io.c's peer rx task, and that constant's
930 * own doc for why this is unambiguously local damage, unlike the
931 * inter-site negotiation link - RTE_EXAMPLE_REASON_NEGOTIATION_LINK_LOST
932 * above already covers that separately, with its own ONLINE-only
933 * reasoning). A site whose own A and B cannot reach each other has lost
934 * its own internal cross-compare integrity check entirely, not just one
935 * cycle's worth of data - the same severity judgment as a genuine
936 * DISAGREE (RTE_EXAMPLE_REASON_AB_MTYPE_DISAGREE), just detected via
937 * transport failure instead of a content mismatch. */
938#define RTE_EXAMPLE_REASON_AB_PEER_LINK_DOWN \
939 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 5U))
940
941/* Application-specific safe-state reason code: this channel's own link
942 * to/from C has been unable to communicate for
943 * RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS continuously (see
944 * ab_gp_channel_io.c's M136 rx task). Without C, this channel has no
945 * M136/D_LRBG input at all and cannot produce a movement-authority
946 * decision - the same "cannot keep operating without this channel"
947 * judgment as RTE_EXAMPLE_REASON_AB_PEER_LINK_DOWN above, just for
948 * the other of this channel's two external links. */
949#define RTE_EXAMPLE_REASON_C_LINK_DOWN \
950 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 6U))
951
952/* Application-specific safe-state reason code, C's own side
953 * (monitor_c_io.c): this instance of C has been unable to reach A (or B)
954 * for RTE_EXAMPLE_CHANNEL_DOWN_REBOOT_MS continuously. Note this does
955 * NOT itself drive site failover the way an A/B reboot does (C is not a
956 * party to the inter-site ONLINE/STANDBY negotiation - see
957 * ab_gp_channel_negotiate.c) - this is purely "C's own link is broken badly
958 * enough that a fresh process/socket state is more likely to recover
959 * than continuing to retry the same one", not a failover trigger. */
960#define RTE_EXAMPLE_REASON_C_MONITOR_LINK_DOWN \
961 ((rte_safestate_reason_t)(RTE_SAFESTATE_REASON_APPLICATION_BASE + 7U))
962
963/* ---------------------------------------------------------------------
964 * RBC (Radio Block Centre) train-position-report simulation
965 * ---------------------------------------------------------------------
966 * Each connected Train sim cyclically reports its own position (M136,
967 * D_LRBG) via C to A/B (ADR-029: relayed by C, a pure relay - see
968 * monitor_c.c's own file header for why). Every M136 gets a plain M24
969 * ack, unconditionally, unless this train has some other message due
970 * this same cycle instead (e.g. a pending M3 Movement Authority grant,
971 * sent as an ADDITIONAL output alongside the M24 ack - see
972 * ab_gp_train_build_outputs()). Only A forwards the agreed answer to C
973 * (B's own agreeing answer is redundant once cross-compare confirms it
974 * matches A's - see ab_gp_channel.c).
975 * RTE_EXAMPLE_MSG_M15/the old RTE_EXAMPLE_DLRBG_THRESHOLD
976 * D_LRBG-based M24-vs-M15 decision were RETIRED on direct request - M15
977 * here never represented a real Subset-026 Conditional Emergency Stop
978 * trigger, just this simulated ack arbitrarily picking a different
979 * message kind past a distance threshold, with no operational meaning
980 * behind the switch. The real Subset-026 Msg 15 encoder
981 * (ab_gp_proc_emergency_stop_issue_ces(), ertms_procedures/) is
982 * unaffected - it already has no callers on this flat scheme's own
983 * path. */
984#define RTE_EXAMPLE_MSG_M136 136U
985#define RTE_EXAMPLE_MSG_M24 24U
986
987/* ---------------------------------------------------------------------
988 * RBC Train/IL/CTC scenario (ADR-029) - real Train/IL/CTC TCP clients
989 * ---------------------------------------------------------------------
990 * Extends the M136/M24 simulation above with a real train identity
991 * (P0), a Movement Authority the RBC grants/extends from IL-supplied
992 * routes (M3, acked by the train with M146), and one-way status pushes to
993 * CTC. See rbc_wire_types.h for the shared envelope struct all of this
994 * rides in (one fixed-size struct reused for every kind below, per this
995 * project's MISRA fixed-size-everything convention - no variant-length
996 * encoding anywhere in this codebase). C (monitor_c.c) is a pure relay
997 * for all of these - it does not compute the MA/route decision itself
998 * (that is A/B's job, same as the M24 ack decision already is), it only
999 * translates between the Train/IL/CTC-facing sockets and the existing
1000 * C<->A/B link. */
1001#define RTE_EXAMPLE_MSG_P0 0U /* Train -> C -> AB: handshake, establishes this session's train_id */
1002#define RTE_EXAMPLE_MSG_M3 3U /* AB -> C -> Train: Movement Authority (grant or extend) */
1003#define RTE_EXAMPLE_MSG_M146 146U /* Train -> C -> AB: MA ack (echoes the acked ma_seq) */
1004
1005/* Amount (in meters) each single IL route-add grants/extends this train's
1006 * Movement Authority by - "first simple logic pass" (no real route
1007 * topology/geometry, see ADR-029): every ROUTE_ADD is worth exactly this
1008 * much, and the RBC's own granted_length is just their running sum. */
1009#define RTE_EXAMPLE_MA_ROUTE_LENGTH_M 500U
1010
1011/* Overlap / danger-point margin (meters): the End/Limit of Authority
1012 * (EoA/LOA) the RBC grants sits this far SHORT of the last route's end
1013 * signal (the Supervised Location). Without it the granted MA length was
1014 * exactly the balise->end-signal topology distance, so every MA - and
1015 * the web_runner EoA marker driven by it - ended precisely on a signal,
1016 * which real ETCS never does. Subtracted ONCE, from the final route's
1017 * contribution (ab_gp_proc_ma_compute() / the flat-scheme grant in
1018 * safeAPIRBC2oo2GA's ab_ga_il.c); an MA extension re-targets a new end
1019 * signal but the single subtraction just carries forward. The robot MA
1020 * tests read the same value as ${MA_OVERLAP_M} (generated into
1021 * site_routes.resource). */
1022/* Currently 0: the overlap is skipped for now (decision 2026-09-21); the MA ends on the end signal. Set it back to a
1023 * real margin (was 100 m) when the overlap is wanted again - every consumer reads this one constant. */
1024#define RTE_EXAMPLE_MA_OVERLAP_M 0U
1025
1026/* Wire envelope, all little-endian - see rbc_wire_types.h. REQ-RBC-001
1027 * (revised, safeAPIFreamwork/docs/requirements/SRS.md): a real
1028 * Subset-026-style header, present on every kind -
1029 * kind(1) + train_id(1) + nid_message(1) + reserved(1) + l_message(2)
1030 * + reserved(2) + t_train(4) = 12 bytes
1031 * - followed by CONTENT, whose length genuinely varies by kind (l_message
1032 * says how much of this fixed-size slot is actually meaningful; the rest
1033 * is zero-padding, not sent-as-variable-size-datagrams - see AB/train/
1034 * ab_gp_train.c's own train_wire_encode() header for why: changing the
1035 * actual UDP datagram size touches 20+ already-tested
1036 * rte_netlink_send()/_recv() call sites across ab_gp_channel.c/
1037 * monitor_c*.c, out of scope for this pass):
1038 * cycle(4) + d_lrbg(4) + msg_type(4) + route_len(4) + ma_seq(4) +
1039 * ma_length(4) = 24 bytes for every kind (UNCHANGED layout/order - low
1040 * risk to already-working decode logic), THEN, always at these same
1041 * fixed offsets regardless of kind (no offset aliasing between
1042 * different kinds' own extension fields - see AB/train/ab_gp_train.c's
1043 * own train_wire_encode() header for why not):
1044 * t_train_ack(4) - RBC_MSG_M146 (Acknowledgement) only: real
1045 * Subset-026 message 146 acks by echoing this timestamp, not
1046 * ma_seq. 0/unused for every other kind.
1047 * nid_lrbg(4) + q_dirlrbg(4) + q_dlrbg(4) + l_doubtover(4) +
1048 * l_doubtunder(4) + q_length(4) + v_train(4) + q_dirtrain(4) +
1049 * m_mode(4) + m_level(4) = 40 bytes - RBC_MSG_M136 (Position
1050 * Report) only: the real, flat (no N_ITER/nesting) Subset-026
1051 * Packet 0 fields beyond d_lrbg. 0/unused for every other kind.
1052 * start_signal(4) + end_signal(4) + route_type(4) + route_status(4)
1053 * = 16 bytes, offsets 80-95 - RBC_MSG_ROUTE_ADD/_ROUTE_RELEASE/
1054 * _TRAIN_POSITION_IN_ROUTE only: real route identity (the signal
1055 * pair a route runs between - safeAPIRBC2oo2GA/include/ab_siteGA.h's own
1056 * ab_site_ga_route_layout_t) plus ROUTE_ADD's own supervision-mode
1057 * (route_type) and TRAIN_POSITION_IN_ROUTE's own occupancy
1058 * (route_status). Appended after the M136 block, same additive-
1059 * growth precedent as t_train_ack/the M136 block itself - never a
1060 * repurposed existing field. 0/unused for every other kind.
1061 * l_message says how much of this is actually meaningful for a given
1062 * envelope's own kind - 36 for most kinds, 40 for RBC_MSG_M146, 80 for
1063 * RBC_MSG_M136, 96 (the whole slot) for ROUTE_ADD/ROUTE_RELEASE/
1064 * TRAIN_POSITION_IN_ROUTE - not how many bytes are physically written
1065 * (always all 96). Max total: 12 + 24 + 4 + 40 + 16 = 96 bytes. This
1066 * layout is duplicated (not shared) across AB/train/ab_gp_train.c's,
1067 * AB/GA/il/ab_ga_il.c's, and AB/GA/ctc/ab_ga_ctc.c's own file-static wire codecs -
1068 * each domain owns encoding/decoding the kinds its own relay channel
1069 * carries, with no single shared codec (see ab_gp_train.c's own module doc
1070 * for why). Grown once already this session (80 -> 96, for the IL<->RBC
1071 * route-identity interface) - SimCore's own byte-identical Python mirror
1072 * (simcore/rbc_wire.py) MUST be updated in lockstep whenever this changes;
1073 * see that file's own doc. */
1074#define RBC_ENVELOPE_WIRE_SIZE 96U
1075
1076/* Train relay channel ONLY (see rbc_wire_types.h's RBC_MSG_ERTMS_ENVELOPE
1077 * doc): matches ab_gp_ertms_train_message_codec.h's own RBC_TRAIN_MAX_WIRE_MSG_SIZE
1078 * (512) - RBC_MSG_ERTMS_ENVELOPE's own 2-byte kind+train_id prefix eats
1079 * into that, leaving 510 bytes for the codec's own frame, comfortably
1080 * above every real message this codec actually builds in this scope
1081 * (Msg 3 with every optional packet present is ~180 bytes) and above
1082 * the codec's own real enforced minimums (32 bytes encode, 11 decode -
1083 * its header doc's "buffer_size must be >= RBC_TRAIN_MAX_WIRE_MSG_SIZE"
1084 * is a stale aspiration, not what the code actually checks). Every
1085 * OTHER kind on the Train channel
1086 * (P0/M136/M24_M15/M3/M146/TRAIN_DISCONNECT) keeps using the plain
1087 * RBC_ENVELOPE_WIRE_SIZE shape above, just inside a bigger physically-
1088 * allocated slot - always sent/read at this full fixed size,
1089 * zero-padded past whatever's actually meaningful, same "fixed
1090 * MAXIMUM, a length field says how much is real" philosophy as
1091 * RBC_ENVELOPE_WIRE_SIZE itself (this project's own UDP transport has
1092 * no length-prefix framing of its own to lean on instead - see
1093 * dual_link.py's/rte_posix_osadapter_channel_service.c's own docs).
1094 * SimCore's own byte-identical Python mirror (simcore/rbc_ertms_wire.py)
1095 * MUST match this exactly. */
1096#define RBC_TRAIN_ENVELOPE_WIRE_SIZE 512U
1097
1098/* RTE_EXAMPLE_DLRBG_FAULT_VALUE (was 101U) - RETIRED alongside the
1099 * M24/M15 D_LRBG-threshold decision above: it forced role A to compute
1100 * M15 while B kept computing M24 at D_LRBG=101, demonstrating a genuine
1101 * cross-compare disagreement. With M15 gone, both channels always agree
1102 * on the M24 ack, so this specific fault-injection path can no longer be
1103 * triggered this way. The generic cross-compare mechanism itself
1104 * (ab_gp_channel_crosscompare.c, ctx->own_faulted) is untouched and still
1105 * catches any OTHER genuine A/B divergence - this only removes the one
1106 * D_LRBG-driven demo trigger for it. */
1107
1108/* ---------------------------------------------------------------------
1109 * Mock hardware reboot + live recovery
1110 * ---------------------------------------------------------------------
1111 * A real hardware reset is not instantaneous - main.c's REBOOT safe-state
1112 * handler sleeps this long (with clear "FAULTED, rebooting" logging)
1113 * before actually re-execing, so a demo run shows the fault -> reboot ->
1114 * resume sequence as a visible event instead of an instant, easy-to-miss
1115 * re-exec. */
1116#define RTE_EXAMPLE_REBOOT_DELAY_MS 5000U
1117
1118#endif /* RTE_EXAMPLE_COMMON_CONFIG_H */
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