|
RTE RBC 2oo2 Reference Application 0.1.0 build 0006685
SIL 4 2oo2 Dual-Site Fault-Tolerant ERTMS Radio Block Centre Reference Architecture
|
A-vs-B cross-compare decision, routed through rte_cross_comparator (ADR-025) - see ab_gp_channel_crosscompare.h. More...
#include "ab_gp_channel_crosscompare.h"#include "ab_gp_standby_policy.h"#include <stdio.h>#include <stdlib.h>#include "rte/oal/log/rte_log.h"#include "rte/oal/memory/rte_mem_util.h"#include "rte/utils/safestate/rte_safestate.h"#include "../../common/common_config.h"#include "ab_gp_channel.h"#include "ab_gp_channel_negotiate.h"#include "ab_gp_channel_payload.h"Go to the source code of this file.
Macros | |
| #define | XCOMPARE_PAYLOAD_SIZE ((size_t)RTE_EXAMPLE_MAX_TRAINS * RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE) |
| #define | RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE_ENV "RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE" |
Functions | |
| static uint32_t | xcompare_fault_cycle (void) |
| static void | encode_local_payload (const ab_gp_channel_context_t *ctx, uint8_t out[XCOMPARE_PAYLOAD_SIZE]) |
| static void | encode_peer_payload (const ab_gp_channel_context_t *ctx, uint8_t out[XCOMPARE_PAYLOAD_SIZE]) |
| static void | fault_cleanup (ab_gp_channel_context_t *ctx, rte_safestate_reason_t reason) |
| static void | react_to_fault (ab_gp_channel_context_t *ctx, rte_safestate_reason_t reason) |
| static int | first_disagreeing_train_index (const ab_gp_channel_context_t *ctx) |
| static void | xcompare_on_disagreement (void *context, rte_voting_result_t result) |
| rte_status_t | ab_gp_channel_crosscompare_init (ab_gp_channel_context_t *ctx) |
| Creates this channel's rte_cross_comparator_t. Called once from ab_gp_channel_crosscompare_setup() (AB/GP/com/ab_gp_channel.c). RCA/OCORA Phase 2b: no backing rte_channel_t "sources" to register any more - ab_gp_channel_crosscompare_execute() calls rte_cross_comparator_execute_buffers() with local/peer bytes directly (see ab_gp_channel_crosscompare.c's own header). | |
| static bool | per_train_readiness_gate (ab_gp_channel_context_t *ctx, uint32_t cycle) |
| ADR-029 per-train readiness gate: skip cross-compare this cycle for any train whose local/peer session snapshot hasn't caught up with each other yet. | |
| static void | execute_cross_comparator (ab_gp_channel_context_t *ctx, uint32_t cycle) |
| Route the actual AGREED/DISAGREED decision through the real rte_cross_comparator_t (ADR-025). | |
| rte_status_t | ab_gp_channel_crosscompare_execute (ab_gp_channel_context_t *ctx) |
| Runs one cycle's cross-compare decision - see this file's own header. This is ab_gp_channel.c's ab_gp_channel_crosscompare_run() (decision 2 of 2 behind main/execute.c's own ab_gp_channel_execute() orchestrator)'s entire body; ab_gp_channel_crosscompare_run() itself is a thin wrapper that just calls this. | |
| void | ab_gp_channel_crosscompare_check_site_state (ab_gp_channel_context_t *ctx) |
| Reacts to the A/B role agreement verdict: this channel's own independently-negotiated {online, single_mode} against whatever the local A<->B peer most recently sent via PEER_MSG_KIND_SITE_STATE (ctx->peer_site_state, read by Platform_RTE's rte_site_role through the sibling source). The rule, its 3-strike count, the NEGOTIATION_MISMATCH event and the cached sibling facts live in rte_site_role (REQ-SITEROLE-014/016, ADR-040 step 5b), which runs the check inside rte_site_role_execute(); this function only reads rte_site_role_sibling_verdict(). A confirmed mismatch reacts exactly like a genuine M24/M15 DISAGREE (FAULTED, REBOOT - see RTE_EXAMPLE_REASON_AB_SITE_STATE_MISMATCH's own doc) and never returns in that case (REQ-COMMON-SAFESTATE-002). Called from ab_gp_channel_read_channels(), after this cycle's own negotiate step (ab_gp_channel_negotiate_execute()) has run, so the verdict is that execute's. | |
A-vs-B cross-compare decision, routed through rte_cross_comparator (ADR-025) - see ab_gp_channel_crosscompare.h.
Definition in file ab_gp_channel_crosscompare.c.
| #define XCOMPARE_PAYLOAD_SIZE ((size_t)RTE_EXAMPLE_MAX_TRAINS * RTE_EXAMPLE_TRAIN_SESSION_WIRE_SIZE) |
RCA/OCORA Phase 2b (see docs/rca/ at the workspace root and TODO.md): local/peer payload preparation used to be two rte_channel_t "pure in-memory adapter" recv() callbacks (xcompare_send_noop()/ xcompare_recv_local()/xcompare_recv_peer(), each requiring its own rte_channel_init() + rte_cross_comparator_register_channel() call in ab_gp_channel_crosscompare_init() below) purely to satisfy rte_cross_comparator_execute()'s channel-based interface - the data they handed back always already arrived (and was CRC-verified/decoded) earlier this cycle via ab_gp_channel_io.c; send() was never actually invoked. Now two plain functions, called directly from ab_gp_channel_crosscompare_execute() and handed straight to rte_cross_comparator_execute_buffers() - no channel-adapter boilerplate left at all.
Cross-compare votes on the whole train-session table (ctx->sessions[]/ctx->cyc_peer_sessions[]), not one scalar M24/M15 decision - see ab_gp_train_session_t's own doc (ab_gp_channel_types.h) for which fields this covers (everything except ma_pending_send, a pure local scratch flag with no cross-compare meaning).
rte_cross_comparator_execute() compares with a raw memcmp() (see rte_cross_comparator.c) - comparing ab_gp_train_session_t structs directly would risk comparing uninitialized/compiler-dependent padding bytes alongside the real fields (this struct mixes bool/uint8_t/uint32_t members, so the compiler is free to insert padding). The WIRE-ENCODED form (ab_gp_channel_payload_encode_collector(), no padding by construction) sidesteps this entirely, so that is what both sources below hand back instead of the raw struct.
Definition at line 49 of file ab_gp_channel_crosscompare.c.
| #define RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE_ENV "RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE" |
Test-only fault-injection hook, role A only: RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE_ENV ("RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE" - unset by default, so production/normal runs are completely unaffected) names a cycle number at or after which role A's own xcompare_recv_local() flips one bit of its own already-encoded cross-compare payload before handing it to the comparator, producing a genuine CRC-clean DISAGREE through the real rte_cross_comparator_t / xcompare_on_disagreement() / react_to_fault() path
Definition at line 67 of file ab_gp_channel_crosscompare.c.
|
static |
Parses RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE_ENV once (cached - getenv() every cycle would be wasteful and this value cannot change mid-run). Unset/unparsable -> UINT32_MAX (never reached by any real cyc_cycle), i.e. the hook is a no-op unless explicitly configured.
Definition at line 73 of file ab_gp_channel_crosscompare.c.
|
static |
"Local decision": this cycle's own already-computed session table (ctx->sessions[]), wire-encoded - see this file's own doc above.
Definition at line 99 of file ab_gp_channel_crosscompare.c.
|
static |
"Peer decision": this cycle's already-CRC-verified, already-decoded peer session table (ctx->cyc_peer_sessions), re-encoded the same way - see this file's own doc above.
Definition at line 114 of file ab_gp_channel_crosscompare.c.
|
static |
Pre-transition cleanup shared by both a genuine DISAGREE (xcompare_on_disagreement()) and a negotiation mismatch (ab_gp_channel_crosscompare_check_site_state()) - see this file's own header. Does NOT itself enter a safe-state level; see react_to_fault() (below, for check_site_state()'s own direct use) and xcompare_on_disagreement() (which relies on the cross-comparator itself entering REBOOT right after this returns - see this file's own RCA/OCORA PI-API compatibility note above ab_gp_channel_crosscompare_init()).
Definition at line 127 of file ab_gp_channel_crosscompare.c.
|
static |
Direct REBOOT reaction for ab_gp_channel_crosscompare_check_site_state()'s own negotiation-mismatch path (unrelated to the cross-comparator - that path calls RTE_SAFESTATE itself). Never returns (REQ-COMMON-SAFESTATE-002); if the underlying reboot OSAdapter somehow failed, falls through to the same defensive halt RTE_SAFESTATE_LEVEL_SAFE would.
Definition at line 158 of file ab_gp_channel_crosscompare.c.
|
static |
rte_cross_comparator_config_t::on_disagreement: per the RCA/OCORA PI-API compatibility change (see rte_cross_comparator.h's own note, and docs/rca/RCA-OCORA-SCP-Mapping.md at the workspace root), the Platform (rte_cross_comparator) now unconditionally enters config->safestate_level right after this callback returns - see ab_gp_channel_crosscompare_init() below, which configures RTE_SAFESTATE_LEVEL_REBOOT. This callback therefore does ONLY the application's own pre-transition cleanup (fault_cleanup()); it must NOT itself call RTE_SAFESTATE - that would race the Platform's own guaranteed call immediately after.
Definition at line 181 of file ab_gp_channel_crosscompare.c.
|
static |
Definition at line 199 of file ab_gp_channel_crosscompare.c.
| rte_status_t ab_gp_channel_crosscompare_init | ( | ab_gp_channel_context_t * | ctx | ) |
Creates this channel's rte_cross_comparator_t. Called once from ab_gp_channel_crosscompare_setup() (AB/GP/com/ab_gp_channel.c). RCA/OCORA Phase 2b: no backing rte_channel_t "sources" to register any more - ab_gp_channel_crosscompare_execute() calls rte_cross_comparator_execute_buffers() with local/peer bytes directly (see ab_gp_channel_crosscompare.c's own header).
| ctx | Context to initialize the comparator into (ctx->xcompare_cmp). Must not be NULL. |
Definition at line 237 of file ab_gp_channel_crosscompare.c.
|
static |
ADR-029 per-train readiness gate: skip cross-compare this cycle for any train whose local/peer session snapshot hasn't caught up with each other yet.
C broadcasts each train/IL envelope to A and B identically, but the two channels drain their OWN independent queues against their OWN independent 500ms cycle boundaries - it is entirely normal (not a fault) for one channel to have processed a train's freshest M136/ROUTE_ADD a cycle earlier than the other, purely from timer-phase/network-jitter luck, exactly the same class of skew RTE_EXAMPLE_AB_CYCLE_SKEW_TOLERANCE already exists to tolerate at the AB-envelope level. Comparing session tables byte-for-byte while they are still mid-catch-up would report a spurious DISAGREE (both sides genuinely agree on the TRAIN's own state, just not-yet-synchronized on exactly which update each has most recently seen) - found live for both the cyclic .cycle/.d_lrbg fields (M136) and the discrete .granted_length/.ma_seq fields (ROUTE_ADD).
Skips (not disagrees) for any train either side currently considers in_use, until both sides report an identical wire-encoded snapshot for it - BUT only up to RTE_EXAMPLE_XCOMPARE_SYNC_SKIP_LIMIT consecutive skips per train (REQ-RBC-008): unlike the cyclic M136 fields (which naturally keep updating, so a mismatch this cycle just means "compare the NEXT one instead"), a discrete ROUTE_ADD/M146 event has no "next one" to fall back on if the single relay datagram carrying it was lost to ONE side only (see monitor_c.c's relay_inbound_to_ab() own doc on this known limitation) - that is a genuine, permanent divergence, not a timing race, and skipping it forever would silently defeat cross-compare's whole purpose for that train. Past the limit, falls through so the real comparator will then correctly DISAGREE - the same safety posture this project already takes for every other detected divergence.
Definition at line 291 of file ab_gp_channel_crosscompare.c.
|
static |
Route the actual AGREED/DISAGREED decision through the real rte_cross_comparator_t (ADR-025).
On DISAGREED, xcompare_on_disagreement() reacts (persist FAULTED, shut down, REBOOT) and never returns - so nothing after that call runs for that outcome; on AGREED, sets ctx->cyc_agree. RCA/OCORA Phase 2b: local/peer bytes are prepared directly (encode_local_payload()/encode_peer_payload(), this file's own doc above) instead of via two rte_channel_t "source" callbacks - no channel-health concept left to log either (the old XCOMPARE_HEALTH diagnostic is gone with it - there is no I/O here to be unhealthy).
Definition at line 396 of file ab_gp_channel_crosscompare.c.
| rte_status_t ab_gp_channel_crosscompare_execute | ( | ab_gp_channel_context_t * | ctx | ) |
Runs one cycle's cross-compare decision - see this file's own header. This is ab_gp_channel.c's ab_gp_channel_crosscompare_run() (decision 2 of 2 behind main/execute.c's own ab_gp_channel_execute() orchestrator)'s entire body; ab_gp_channel_crosscompare_run() itself is a thin wrapper that just calls this.
| ctx | Initialized context, with this cycle's ctx->cyc_* fields already populated by ab_gp_channel_read_channels()/ ab_gp_channel_read_and_stash_peer_sample(). |
Definition at line 420 of file ab_gp_channel_crosscompare.c.
| void ab_gp_channel_crosscompare_check_site_state | ( | ab_gp_channel_context_t * | ctx | ) |
Reacts to the A/B role agreement verdict: this channel's own independently-negotiated {online, single_mode} against whatever the local A<->B peer most recently sent via PEER_MSG_KIND_SITE_STATE (ctx->peer_site_state, read by Platform_RTE's rte_site_role through the sibling source). The rule, its 3-strike count, the NEGOTIATION_MISMATCH event and the cached sibling facts live in rte_site_role (REQ-SITEROLE-014/016, ADR-040 step 5b), which runs the check inside rte_site_role_execute(); this function only reads rte_site_role_sibling_verdict(). A confirmed mismatch reacts exactly like a genuine M24/M15 DISAGREE (FAULTED, REBOOT - see RTE_EXAMPLE_REASON_AB_SITE_STATE_MISMATCH's own doc) and never returns in that case (REQ-COMMON-SAFESTATE-002). Called from ab_gp_channel_read_channels(), after this cycle's own negotiate step (ab_gp_channel_negotiate_execute()) has run, so the verdict is that execute's.
| ctx | Initialized context. Must not be NULL. |
Definition at line 501 of file ab_gp_channel_crosscompare.c.