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
ab_gp_channel_crosscompare.c File Reference

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"
Include dependency graph for ab_gp_channel_crosscompare.c:

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.

Detailed Description

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.

Macro Definition Documentation

◆ XCOMPARE_PAYLOAD_SIZE

#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.

◆ RTE_EXAMPLE_XCOMPARE_FAULT_CYCLE_ENV

#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

  • the same code every real A/B divergence goes through, just fed a deliberately wrong byte instead of a real logic bug. Only role A's own OUTGOING comparison payload is altered here - no session/db/wire state used by any actual safety decision is touched, and B's own comparison of the SAME (correct) session data against A's now-corrupted payload is what genuinely disagrees, exactly like two independently- computed decisions really diverging. Replaces the now-retired M24/M15 D_LRBG threshold trick (see common_config.h's own RTE_EXAMPLE_DLRBG_FAULT_VALUE doc) as .claude/skills/run-safeAPIRBC2oo2/smoke.sh's failover-phase trigger, once M15 itself was removed from the protocol and that trick could no longer fire through any real message path.

Definition at line 67 of file ab_gp_channel_crosscompare.c.

Function Documentation

◆ xcompare_fault_cycle()

uint32_t xcompare_fault_cycle ( void )
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.

◆ encode_local_payload()

void encode_local_payload ( const ab_gp_channel_context_t * ctx,
uint8_t out[XCOMPARE_PAYLOAD_SIZE] )
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.

◆ encode_peer_payload()

void encode_peer_payload ( const ab_gp_channel_context_t * ctx,
uint8_t out[XCOMPARE_PAYLOAD_SIZE] )
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.

◆ fault_cleanup()

void fault_cleanup ( ab_gp_channel_context_t * ctx,
rte_safestate_reason_t reason )
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.

◆ react_to_fault()

void react_to_fault ( ab_gp_channel_context_t * ctx,
rte_safestate_reason_t reason )
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.

◆ first_disagreeing_train_index()

int first_disagreeing_train_index ( const ab_gp_channel_context_t * ctx)
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.

Returns
The index of the first train whose local/peer session bytes differ, for a useful diagnostic log line - ADR-029, replaces the old direct local_msg_type/peer_msg_type values now that the compared payload is the whole session table, not one scalar. -1 if every session's encoded bytes match (should not happen when actually called from a genuine DISAGREE, but this is diagnostic-only, not itself a safety decision).

Definition at line 181 of file ab_gp_channel_crosscompare.c.

◆ xcompare_on_disagreement()

void xcompare_on_disagreement ( void * context,
rte_voting_result_t result )
static

Definition at line 199 of file ab_gp_channel_crosscompare.c.

◆ ab_gp_channel_crosscompare_init()

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).

Parameters
ctxContext to initialize the comparator into (ctx->xcompare_cmp). Must not be NULL.
Returns
RTE_STATUS_OK on success; the underlying rte_cross_comparator_init() status on failure (this is a fixed, always-valid local configuration, so failure here would indicate a genuine programming error, not a runtime condition - see the call site's own handling).

Definition at line 237 of file ab_gp_channel_crosscompare.c.

◆ per_train_readiness_gate()

bool per_train_readiness_gate ( ab_gp_channel_context_t * ctx,
uint32_t cycle )
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.

Returns
true if every train is caught up (cross-compare should proceed this cycle); false if this cycle was skipped for a still-catching-up train (already logged; caller should return RTE_STATUS_OK without comparing).

Definition at line 291 of file ab_gp_channel_crosscompare.c.

◆ execute_cross_comparator()

void execute_cross_comparator ( ab_gp_channel_context_t * ctx,
uint32_t cycle )
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.

◆ ab_gp_channel_crosscompare_execute()

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.

Parameters
ctxInitialized context, with this cycle's ctx->cyc_* fields already populated by ab_gp_channel_read_channels()/ ab_gp_channel_read_and_stash_peer_sample().
Returns
RTE_STATUS_OK for every outcome that returns at all (including "nothing to compare this cycle") - see this file's own header for the one outcome (DISAGREE) that never returns.

Definition at line 420 of file ab_gp_channel_crosscompare.c.

◆ ab_gp_channel_crosscompare_check_site_state()

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.

Parameters
ctxInitialized context. Must not be NULL.

Definition at line 501 of file ab_gp_channel_crosscompare.c.