RTE RBC 2oo2 Reference Application
0.1.0 build 0006685
SIL 4 2oo2 Dual-Site Fault-Tolerant ERTMS Radio Block Centre Reference Architecture
Toggle main menu visibility
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
832
typedef
enum
833
{
834
RTE_EXAMPLE_TRANSFER_POLICY_RESTART
= 0,
835
RTE_EXAMPLE_TRANSFER_POLICY_CONTINUE
= 1,
836
RTE_EXAMPLE_TRANSFER_POLICY_ASK_USER
= 2
837
}
rte_example_transfer_policy_t
;
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
rte_example_transfer_policy_t
Policy applied to the transferred cycle counter at the moment a standby channel is promoted to ONLINE...
Definition
common_config.h:833
RTE_EXAMPLE_TRANSFER_POLICY_ASK_USER
@ RTE_EXAMPLE_TRANSFER_POLICY_ASK_USER
Definition
common_config.h:836
RTE_EXAMPLE_TRANSFER_POLICY_CONTINUE
@ RTE_EXAMPLE_TRANSFER_POLICY_CONTINUE
Definition
common_config.h:835
RTE_EXAMPLE_TRANSFER_POLICY_RESTART
@ RTE_EXAMPLE_TRANSFER_POLICY_RESTART
Definition
common_config.h:834
src
application
AB
common
common_config.h
Generated by
1.18.0