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
MISRA C:2012 Compliance Report — RTE RBC 2oo2 (RBC_GP)

Guideline: MISRA C:2012 (with Amendment 1). Target: EN 50128 SIL 4 oriented for the vital A/B roles (src/application/AB/); role C (src/application/C/) is non-vital (ADR-001 section 4) and included below for completeness but not to that target. This is a cppcheck MISRA-addon screening pass, not a full compliance verdict: cppcheck's --addon=misra needs a --rule-texts=<file> pointing at the licensed MISRA C:2012 document to print each rule's actual wording; none is available here (same limitation Platform_Protocol_SafeComm's own report already records), so every finding below is a rule number and a location cppcheck flagged, with this report's own plain-language gloss of the rule's general topic (not the official text) for orientation. A real compliance verdict — confirming each flagged location is a genuine violation and not a false positive, and checking for anything the addon itself misses — needs that licensed text or a commercial tool, tracked as its own, still-open item in the root TODO.dox. Update 1 below covers one specific finding in full detail regardless (the vital A/B code's direct use of <stdio.h>/<stdlib.h>, docs/architecture/OVERVIEW.dox rule 3's "known deviation" and safety_quality/REVIEW_2026-09-21.dox finding A2); Update 2 is the broader screening pass.

Update log

Update 1 — 2026-09-22 — stdio/stdlib deviation recorded (TODO.dox, ARCHITECTURE.dox rule 3 / REVIEW finding A2)

Audited every <stdio.h>/<stdlib.h> include and call site under src/application/AB/ (10 files include <stdio.h>, 6 include <stdlib.h>, matching the review's "about 17 includes"; see the Deviations table below for the exact list). Confirmed no dynamic memory anywhere in this tree (malloc/calloc/realloc/free — zero real calls, only comments stating the existing "no dynamic memory" rule) and no exit/abort/system calls — only getenv from <stdlib.h>.

Investigated replacing the one call site that looked like a clean, self-contained fit for an existing RTE service — ab_gp_channel.c's resolve_site_startup_timestamp() (a fopen/fscanf/fprintf/fclose round trip against a fixed /tmp/rte_site_<site>_ts.dat path, Platform_RTE's rte_nvm service being the obvious candidate) — and stopped short of making that change. rte_nvm's POSIX backend places one file per region under RTE_POSIX_NVM_DIR (env var) or the process's current working directory, not a fixed absolute path; whether that preserves this function's actual intent (a startup timestamp read and written by both A and B of the same site, so their independent West↔East negotiation links present a consistent reference — see startup_timestamp_ms in ab_gp_channel_negotiate.c) depends on whether A and B share a working directory / RTE_POSIX_NVM_DIR in every real deployment, including the Docker Compose stack where A and B run in separate containers with their own filesystem namespaces (and so, already, likely do not share /tmp either — meaning today's real-world behaviour may already be closer to "per-process" than the file path's naming suggests). Swapping the storage backend for vital startup/negotiation timing logic without being certain which of those two semantics is actually intended, and actually relied upon, is not a change to make on inference — flagged here for a maintainer to confirm the intent first, not attempted this session.

No code changed by this update. This is the formal deviation record the TODO item's own "replace ... or record a formal MISRA deviation" alternative asks for, covering everything not already tracked as its own follow-up.

2026-10-06 (G1 step 5a, ADR-040):** the resolve_site_startup_timestamp() deviation above is gone. The intent question it raised is answered: the timestamp is read and written by both A and B of a site so that their independent inter-site tie-breaks present one value (REQ-DUAL-NEGOTIATOR-003 / REQ-SITEROLE-014), and a shared file cannot do that across separate hosts or containers. A and B now agree the value over their existing A/B peer link (PEER_MSG_KIND_STARTUP_TS, exchange_startup_timestamp() in ab_gp_channel_negotiate.c, the rule in the pure ab_gp_startup_timestamp.{h,c}); the function, its fopen/fscanf/fprintf/fclose calls and the /tmp/rte_site_<site>_ts.dat file are deleted. ab_gp_channel.c keeps <stdio.h> for snprintf. The 21.6 row of the Deviations table for that function is marked resolved; see Update 10 for the screening numbers.

Update 2 — 2026-09-22 — cppcheck --addon=misra screening pass (all of src/, AB + C)

Ran cppcheck --addon=misra --enable=warning,style,performance,portability against this project's own 60 source files (build/compile_commands.json filtered to just application/RBC_GP/src/). Filtered twice: once to select these 60 translation units out of the full project (which also pulls in Platform_RTE's and this project's own test sources — an earlier, discarded run against the unfiltered project came out roughly 3× inflated), and a second time on each finding's own reported file path, to drop findings cppcheck attributes to a header this project #includes but does not own (Platform_RTE's headers, mainly) — those belong to that header's own owning project's report, not this one, and counting them here would double-count them if that project ever runs the same screening. No --rule-texts= available (see this report's own top-of-file caveat) — every count below is cppcheck's rule-number classification only, not a confirmed, individually-reviewed violation.

1241 MISRA-addon findings**, dominated by a handful of rules:

Rule Count General topic (this report's own gloss, not official text)
15.5 467 Multiple return statements in one function (this workspace's own convention is "single exit *preferred*", not absolute — CLAUDE.md)
21.6 163 <stdio.h> use — already fully covered, see Update 1 above
17.7 90 A function call's return value is not used/checked
12.1 79 Operator precedence relies on implicit rules rather than explicit parentheses
8.7 61 A function/object with external linkage could be static
8.5 52 An external declaration repeated in more than one file (typically a header re-declaring an extern)
10.8 36 An implicit/composite-expression cast to a wider essential type
17.3 35 A function called without a visible prototype in scope
17.8 31 A function parameter is reassigned inside the function body
11.5 28 A cast from void * to an object pointer type
2.5 23 An unused macro definition
... 176 27 further rules, single/low-digit counts each — full breakdown: misra_screening/rbc_gp_misra_rule_counts.txt

33 free-tier findings** (misra_screening/rbc_gp_cppcheck_free_findings.txt; warning/style/performance/portability — these need no licensed text, every one is a real cppcheck finding, not a rule-number-only flag): mostly constParameterPointer/constVariablePointer (a pointer parameter/variable that could be const) and variableScope (a variable's scope could be narrower) — cosmetic. Two recurring, genuinely worth a look: gateway_c_train_handler.c:176,206 — if ((status != RTE_STATUS_OK) && ...) immediately after a while ((status = rte_channel_service_read_ex(...)) == RTE_STATUS_OK) loop, where the first half of that condition is always true (the loop cannot exit any other way) — harmless (the second half, status != RTE_STATUS_TIMEOUT, still does real work), but redundant; the same pattern repeats in RBC_GA's gateway_c_il_handler.c and gateway_c_ctc_handler.c (see that project's own report) — a shared idiom across all three gateway handlers, not specific to this one. Pre-existing, not introduced by this session's own read_ex change to this file — a real, low-priority cleanup candidate, not a bug (both role C, non-vital).

No code changed by this update.

Update 3 — 2026-10-03 — UTIL relay link open gate (ISS-020)

New vital-side module src/application/AB/GP/com/ab_gp_util_link_gate.{h,c} and its use in ab_gp_channel.c (send_util_gated(), ab_gp_channel_send_relay(), ab_gp_channel_poll_relays()). Screened with cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99, include paths from build/compile_commands.json, findings filtered to the screened file:

  • ab_gp_util_link_gate.c: 5 findings, all 8.7 (advisory; the functions have external linkage because they are called from ab_gp_channel.c, a single-file run cannot see that). No free-tier finding. Every function has a single exit, fixed-width/bool types only, no dynamic memory, every pointer parameter NULL-checked, no library call.
  • ab_gp_channel.c, against the same run on the HEAD version: one more 15.5 finding (45 -> 46), the return send_util_gated(...) added to ab_gp_channel_send_relay(), which already returns from each of its branches; kept in that function's existing shape instead of restructuring vital code. No other rule count changed.
  • Role C (non-vital): the optional idle_hook call added to gateway_c_common.c's wait loop (a NULL-checked function-pointer call); the status service's idle drain is C++ (gateway_c_status_handler.cpp, not in MISRA C scope).

Update 4 — 2026-10-04 — Gateway JSON socket buffer keys (G4 item 6e-2a)

Role C (non-vital): src/application/C/gateway_c_config.{h,c} read optional per-channel rcvbuf_bytes/sndbuf_bytes through a new checked parse, extract_uint32_checked() (no strtoul(): digits only, overflow test before each multiply-add, single exit, uint32_t arithmetic only), and copy them into the rte_netlink_config_t. Screened with cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99 against the same run on the HEAD version of the file: two more 15.5 findings (32 -> 34), the two return branches added to parse_channel_prop(), which already dispatches every key with an early return; kept in that shape. No other rule count changed.

Update 5 — 2026-10-04 — Train Gateway: RTE_STATUS_NOT_CONNECTED handled as OK (G4 item 6f-2)

Role C (non-vital): src/application/C/gateway_c_train_handler.c - send_to_relay() takes its OK branch also for RTE_STATUS_NOT_CONNECTED (Platform_RTE REQ-CHANSVC-001; the POSIX channel service now returns it where it returned OK before), and send_to_sim() adds it to the silent set beside OK and TIMEOUT. One more || / && operand per condition, both fully parenthesised; no new branch, call or variable. Screened with cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99, include paths and defines from build/compile_commands.json, against the same run on the HEAD version of the file, findings in this file only:

File Rule / id Before After Note
gateway_c_train_handler.c 10.4, 10.8, 13.4, 15.5, 15.7, 17.7, 21.6, 8.7 1, 1, 2, 5, 1, 8, 9, 4 unchanged pre-existing; lines after the change move by +3 (relay) and +4 (sim); no finding on the changed conditions (lines 78, 109)
gateway_c_train_handler.c free-tier constParameterPointer, knownConditionTrueFalse, variableScope 3, 2, 1 unchanged pre-existing (knownConditionTrueFalse is the Update 2 read-loop idiom, now lines 200 and 230)

Totals (rule findings): 31 before, 31 after; the finding sequence is identical apart from line numbers. The new tests (tests/gateway/test_gateway_c_train_handler_send.c, tests/posix_osadapter/test_rte_posix_osadapter_channel_service_listen.c) are test code, not screened.

Update 6 — 2026-10-04 — dead cycle_resync_requested removed (G4 item 6 step 6g-6a)

Vital (A/B): src/application/AB/GP/com/ab_gp_channel.c loses the if (ctx->cycle_resync_requested) block of ab_gp_channel_wait_cycle_tick() (cycle reset to 1, lookback marker cleared, one RTE_EXAMPLE_LOG and one rte_log_write_event()), and ab_gp_channel_types.h loses the field. Nothing has set the flag since RBC_GP 13e56c6, and the context is zero-initialised (ab_gp_channel_open_link()), so the branch was never taken: no behaviour change. Screened with cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99, include paths and defines from build/compile_commands.json, against the same run on the HEAD version of the file, findings in this file only:

File Rule / id Before After Note
ab_gp_channel.c 17.7 21 20 the removed RTE_EXAMPLE_LOG (printf return value discarded)
ab_gp_channel.c 21.6 37 36 the same removed RTE_EXAMPLE_LOG (standard I/O)
ab_gp_channel.c 10.4, 11.3, 11.5, 12.1, 15.5, 15.7, 21.8, 22.8, 22.9, 8.7 1, 1, 5, 3, 46, 5, 7, 1, 1, 21 unchanged pre-existing; lines after the removed block move by -14
ab_gp_channel.c free-tier constParameterPointer, constVariablePointer, variableScope 1, 2, 3 unchanged pre-existing

Totals (all findings): 155 before, 153 after; the finding sequence is otherwise identical apart from line numbers.

Update 7 — 2026-10-04 — unused DB train accessors removed (G4 item 6 step 6g-7a)

Vital (A/B): src/application/AB/GP/db/ab_gp_db.{h,c} lose ab_gp_db_train_get_nid_engine(), ab_gp_db_train_set_connected() and ab_gp_db_train_set_nid_engine(), and ab_gp_db.c loses its rte/utils/cast/rte_cast.h include (the removed setter held the file's only rte_cast_* call). No product, test or simulator called any of the three, so no behaviour changes; ab_gp_db_train_t and the A/B DB snapshot layout are untouched. Screened with cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99, include paths and defines from build/compile_commands.json, against the same run on the HEAD version of the file, findings in this file only (none are reported in ab_gp_db.h):

| File | Rule / id | Before | After | Note | |—|—|—|—|—| | ab_gp_db.c | 12.1 | 21 | 14 | the unparenthesised || range-guard operands of the three removed functions (3, 2, 2) | | ab_gp_db.c | 15.5 | 38 | 35 | the early return false; of each removed function | | ab_gp_db.c | 8.7 | 20 | 17 | the three removed external definitions (no other translation unit referenced them) | | ab_gp_db.c | 10.8, 11.8 | 3, 2 | unchanged | pre-existing | | ab_gp_db.c | free-tier variableScope | 1 | unchanged | pre-existing |

Totals (all findings): 85 before, 72 after; the finding sequence is otherwise identical apart from line numbers.

Update 8 — 2026-10-04 — A/B cycle tick count under an RTE mutex (ISS-051)

Vital (A/B): new module src/application/AB/GP/com/ab_gp_cycle_ticks.{h,c} (tick count guarded by rte_mutex, rte_mutex_lock_timed() with a 10 ms bound); ab_gp_channel_types.h changes the cycle_ticks field from volatile uint32_t to ab_gp_cycle_ticks_t; ab_gp_channel.c uses the module in cycle_tick_callback(), ab_gp_channel_init(), ab_gp_channel_cycle_timer_setup(), ab_gp_channel_wait_cycle_tick() and ab_gp_channel_shutdown(). Checked by tool: cppcheck 2.21 --addon=misra --enable=warning,style,performance,portability --std=c99 --template='{file}:{line}:{column}:{id}', the -I and -D flags of the ab_gp_channel.c entry of build/compile_commands.json; ab_gp_channel.c against the same run on the HEAD version of the file (a git archive HEAD copy of src/ outside the tree). Counting: findings located in the named file only (findings in included headers are not counted), one per source line and rule, the way Updates 3, 6 and 7 count. With the column in the template cppcheck reports 12.1 once per operand, 6 positions on 3 lines (125; 1208 three times; 1255 twice, the same on HEAD at other line numbers); per line that is 3, and it is the only rule where the two ways of counting differ (per position the totals would be 156 and 159). Not checked by a second tool; no manual rule-by-rule review beyond the points listed for the new module.

File Rule / id Before After Note
ab_gp_channel.c 15.5 (advisory) 46 49 the early return status; added to ab_gp_channel_init(), ab_gp_channel_cycle_timer_setup() and ab_gp_channel_wait_cycle_tick(); each function keeps its existing multi-return shape instead of being restructured
ab_gp_channel.c 10.4, 11.3, 11.5, 12.1, 15.7, 17.7, 21.6, 21.8, 22.8, 22.9, 8.7 1, 1, 5, 3, 5, 20, 36, 7, 1, 1, 21 unchanged pre-existing
ab_gp_channel.c free-tier constParameterPointer, constVariablePointer, variableScope 1, 2, 3 unchanged pre-existing

Totals (findings in ab_gp_channel.c, per line and rule): 153 before, 156 after. The discarded results are explicit (void) casts with the reason in a comment: ab_gp_cycle_ticks_post() in the timer callback (a failed post drops the tick, no log from the timer thread) and ab_gp_cycle_ticks_teardown() in ab_gp_channel_shutdown() (not set up is a valid state there). The setup failure is reported with rte_log_write_event() only, so no new 17.7 / 21.6 (printf) finding. tests/gateway/test_ab_gp_cycle_ticks.c is test code (uses pthread directly), not screened.

Update 9 — 2026-10-04 — stall detector on the 64-bit monotonic time (ISS-052)

Vital (A/B): ab_gp_channel_types.h changes last_cycle_wall_ms from uint32_t to rte_timestamp_ms_t; the stall rule moves from ab_gp_channel_wait_cycle_tick() (ab_gp_channel.c) to the new pure function ab_gp_stall_lost_contact() in ab_gp_standby_policy.{h,c}; ab_gp_channel.c calls it, stores the time without the narrowing (uint32_t) cast and prints the stall time as llu with an unsigned long long cast. Checked by tool: cppcheck 2.21, the same command, flag source (build/compile_commands.json entry of each file) and HEAD comparison (git archive HEAD copy of src/ outside the tree) as Update 8, run on ab_gp_channel.c and ab_gp_standby_policy.c. Counting: findings located in the named file, one per source line and rule (as Updates 3, 6, 7, 8); header findings are listed separately. Not checked by a second tool; no manual rule-by-rule review beyond the points listed.

File Rule / id Before After Note
ab_gp_channel.c all rules and free-tier ids of Update 8 156 156 every per-rule count unchanged (15.5: 49, 17.7: 20, 21.6: 36, ...)
ab_gp_standby_policy.c 8.7 (advisory) 1 2 the new function has external linkage because ab_gp_channel.c calls it, which a single-file run cannot see (same as ab_gp_session_adopt_allowed())
ab_gp_standby_policy.h in the ab_gp_channel.c run 2.5 (advisory) 2 3 AB_GP_STALL_LOST_CONTACT_MS is no longer used in that translation unit
ab_gp_standby_policy.h in the ab_gp_standby_policy.c run 2.5 (advisory) 3 2 the macro is now used there
ab_gp_channel_types.h in the ab_gp_channel.c run 2.5 (advisory) 6 6 unchanged

No Mandatory or Required finding is added. ab_gp_stall_lost_contact(): single exit, the out pointer is NULL-checked before the write, fixed-width types only, no library call, no dynamic memory; the threshold macro is cast to rte_timestamp_ms_t before the comparison, so both operands have the same essential type. The subtraction now_ms - last_ms is unsigned 64-bit and wraps by definition when now_ms < last_ms; that case is kept as it was (true) and is pinned by a test, because it only arises from the ignored rte_timer_now() status (ISS-036).

Update 10 — 2026-10-06 — site startup timestamp agreed over the A/B peer link (G1 step 5a, ADR-040)

Vital (A/B start-up path feeding the inter-site tie-break): resolve_site_startup_timestamp() and its stream I/O are deleted from ab_gp_channel.c; the new pure module ab_gp_startup_timestamp.{h,c} holds the agreement rule and the PEER_MSG_KIND_STARTUP_TS payload codec; ab_gp_channel_negotiate.c gains exchange_startup_timestamp() (bounded by AB_GP_STARTUP_TS_WAIT_MS, run before rte_site_role_init()) and the public ab_gp_channel_negotiate_on_startup_ts_frame(), called from pre_execute.c and ab_gp_channel_checkpoint.c for a request that arrives later. Checked by tool: cppcheck 2.21, --addon=misra --enable=warning,style,performance,portability --std=c99, template {file}:{line}:{column}:{id}, the -I/-D/-isystem flags of each file's build/compile_commands.json entry, run on ab_gp_channel.c, ab_gp_channel_negotiate.c, ab_gp_channel_checkpoint.c, ab_gp_startup_timestamp.c and pre_execute.c; baseline is a git archive master copy of src/ in a scratch directory outside the tree, same flags. Counting: findings located in the named file, one per source line and rule (as Updates 3, 6-9); header findings listed separately. Not checked by a second tool; no manual rule-by-rule review beyond the points listed.

File Rule / id Before After Note
ab_gp_channel.c 21.6 36 28 the seven stream calls and the snprintf of the deleted function
ab_gp_channel.c 11.3 1 0 the (unsigned long long *)&file_ts cast of the deleted fscanf
ab_gp_channel.c 15.5 49 48 the deleted function's early return
ab_gp_channel.c every other rule and free-tier id unchanged unchanged 10.4: 1, 11.5: 5, 12.1: 3, 15.7: 5, 17.7: 20, 21.8: 7, 22.8: 1, 22.9: 1, 8.7: 21
ab_gp_channel_types.h in the ab_gp_channel.c run 2.5 (advisory) 6 7 PEER_MSG_KIND_STARTUP_TS is not used in that translation unit
ab_gp_channel_negotiate.c 21.6 11 18 two RTE_EXAMPLE_LOG lines and five snprintf of the new exchange (the existing snprintf / RTE_EXAMPLE_LOG deviation rows)
ab_gp_channel_negotiate.c 17.7 9 11 the printf inside the two new RTE_EXAMPLE_LOG lines (every existing line of that macro is counted the same way)
ab_gp_channel_negotiate.c 15.5 13 15 one early return each in exchange_startup_timestamp() (clock unavailable) and ab_gp_channel_negotiate_on_startup_ts_frame() (NULL context or malformed payload)
ab_gp_channel_negotiate.c 8.7 (advisory) 7 8 the new public handler, called from two other files
ab_gp_channel_negotiate.c every other rule and free-tier id unchanged unchanged 10.8: 2, 11.5: 7, 17.3: 6, 2.3: 2, 2.5: 1, 21.1: 1, 21.8: 2
ab_gp_channel.h in the ab_gp_channel_negotiate.c run 17.3 0 1 header now included by this file; the same declaration (ab_gp_channel_shutdown) cppcheck flags in every other includer
ab_gp_channel_types.h, rbc_wire_types.h in the ab_gp_channel_negotiate.c run 2.3, 2.4, 2.5 (advisory) 2, 9, 6 0, 8, 4 fewer unused types/macros now that ab_gp_channel.h is included
ab_gp_startup_timestamp.h in the ab_gp_channel_negotiate.c run 2.5 (advisory) - 2 AB_GP_STARTUP_TS_FRESH_MS, AB_GP_STARTUP_TS_PAYLOAD_SIZE are used in the module, not in this translation unit
ab_gp_startup_timestamp.c (new) 8.7 (advisory) - 3 the three functions have external linkage because ab_gp_channel_negotiate.c calls them (same as ab_gp_standby_policy.c)
ab_gp_startup_timestamp.c (new) everything else - 0 a first draft had two 10.7 findings on 8U * i as a shift operand and one variableScope; the shift count is now a named const uint32_t and the loop index declared in the block
ab_gp_channel_checkpoint.c all 16 16 every per-rule count unchanged (11.5: 2, 12.1: 3, 15.5: 8, 15.7: 1, 8.7: 2)
pre_execute.c all 35 35 every per-rule count unchanged (10.8: 2, 11.5: 4, 15.5: 4, 15.7: 3, 17.7: 8, 21.6: 11, 8.7: 1)

No Mandatory or Required finding is added beyond the existing snprintf / RTE_EXAMPLE_LOG 21.6 deviation rows and the 15.5 early returns (workspace convention: single exit preferred, not absolute). ab_gp_startup_timestamp.c: fixed-width types only, no library call, no dynamic memory, every pointer parameter NULL-checked, the 64-bit value assembled byte by byte with a named shift count. exchange_startup_timestamp() runs before the watchdog exists and is bounded by construction: a slice counter (AB_GP_STARTUP_TS_MAX_SLICES, AB_GP_STARTUP_TS_WAIT_MS / AB_GP_STARTUP_TS_READ_SLICE_MS + 1) ends the loop whatever rte_timer_now() reports, a failed rte_timer_now() inside the loop ends it at once (ERROR event "startup timestamp: clock failed during exchange, own used"; the status of the first read before the loop was already checked), and a read that returns neither OK nor TIMEOUT sleeps the slice so the loop cannot spin. Worst case about AB_GP_STARTUP_TS_WAIT_MS plus one RTE_EXAMPLE_LINK_TIMEOUT_MS (300 ms) send/accept window of the lazily opened link plus one slice. One break-free while (!done) loop (a first draft had a 15.4 finding on two breaks). Review fix round 1 (2026-10-06): the in-loop (void)rte_timer_now() is replaced by the checked call and the slice counter above; ab_gp_channel_negotiate_on_startup_ts_frame() answers only with a non-zero value. Re-screened the same way (cppcheck 2.21, same flags, same master baseline): ab_gp_channel_negotiate.c per file and rule identical to the table above (a draft (uint32_t) cast of the AB_GP_STARTUP_TS_MAX_SLICES macro gave one 10.8 finding and was dropped, the macro is already unsigned int); ab_gp_startup_timestamp.h in the ab_gp_startup_timestamp.c run 2.5 (advisory) 1 -> 3, the two new slice macros are used in ab_gp_channel_negotiate.c and the test, not in the module. The test checks the slice-bound arithmetic (AB_GP_STARTUP_TS_MAX_SLICES * AB_GP_STARTUP_TS_READ_SLICE_MS >= AB_GP_STARTUP_TS_WAIT_MS, 151 slices); the loop itself is not pure (peer link, clock), so its bound is shown by reading: slices_left is decremented once per iteration before any read and the loop ends at 0.

Update 11 — 2026-10-06 — the sibling's site state handed to rte_site_role through the sibling source (G1 step 5b-2, ADR-040 step 5b)

Vital (A/B role agreement and the readiness facts of the site-role decision): the new pure module ab_gp_sibling_state.{h,c} keeps the sibling's raw PEER_MSG_KIND_SITE_STATE frame (bytes, known, fresh) and implements the rte_site_role_sibling_source_fn contract (ab_gp_sibling_state_take()); ab_gp_channel_negotiate.c configures cfg.sibling_source / cfg.sibling_reach_cycles = 0 (the RTE default of 3 cycles, the literal local_facts() used) and hands the three sibling_* local facts over as false; ab_gp_channel_crosscompare_check_site_state() loses its cycle parameter, the local rte_site_role_sibling_t and the rte_site_role_check_sibling() call and only reacts to rte_site_role_sibling_verdict(); pre_execute.c, ab_gp_channel_checkpoint.c and ab_gp_site_status.c follow the context field change (have_peer_site_state / peer_site_state_bytes / peer_site_state_rx_cycle -> one ab_gp_sibling_state_t peer_site_state). Checked by tool: cppcheck 2.21, --addon=misra --enable=warning,style,performance,portability --std=c99, template {file}:{line}:{column}:{id}, the -I/-D/-isystem flags of each file's build/compile_commands.json entry (the new module uses its own entry; the Platform_RTE headers come from the 5b-1 branch of Platform_RTE, todo/g1-5b-sibling-seam, which the superbuild of this step compiles against), run on ab_gp_sibling_state.c, ab_gp_channel_negotiate.c, ab_gp_channel_crosscompare.c, ab_gp_channel_checkpoint.c, ab_gp_site_status.c and pre_execute.c (re-run on pre_execute.c and ab_gp_channel_negotiate.c after review fix round 1, same numbers); baseline is a git archive copy of src/ at the step's base commit 49d8c6e (the 5a branch todo/g1-5a-startup-ts-peer-exchange, in the gate at the time; not master, which lacks 5a) in a scratch directory outside the tree, same flags. Counting: findings located in the named file, one per source line and rule (as Updates 3, 6-10); header findings listed separately. Not checked by a second tool; no manual rule-by-rule review beyond the points listed.

File Rule / id Before After Note
ab_gp_sibling_state.c (new) 8.7 (advisory) - 3 the three functions have external linkage because ab_gp_channel_negotiate.c, pre_execute.c and ab_gp_channel_checkpoint.c call them (same as ab_gp_startup_timestamp.c)
ab_gp_sibling_state.c (new) everything else - 0
headers in the ab_gp_sibling_state.c run 2.3, 2.4, 2.5 (advisory), 20.7, 20.10 - 2.5: 111, 2.3: 4, 2.4: 4, 20.7: 1, 20.10: 1 unused macros/types of common_config.h, rte_site_role.h, rte_site_role_policy.h, rte_safestate.h, rte_dual_frames.h, rte_types.h in this small translation unit, and the two pre-existing Platform_RTE macro findings (rte_types.h 20.7, rte_safestate.h 20.10) every GP run reports; none in this product's new code
ab_gp_channel_negotiate.c 11.5 7 8 the (ab_gp_channel_context_t *)user cast of the new sibling_source() callback - the same void *user cast as encode_state(), receive_state() and on_role_change() (the existing callback-context deviation)
ab_gp_channel_negotiate.c every other rule and free-tier id unchanged unchanged 10.8: 2, 15.5: 15, 17.3: 6, 17.7: 11, 2.3: 2, 2.5: 1, 21.1: 1, 21.6: 18, 21.8: 2, 8.7: 8, constParameterCallback: 1
ab_gp_channel_crosscompare.c all 38 38 every per-rule count unchanged (11.5: 1, 15.5: 7, 17.7: 11, 21.6: 13, 21.8: 1, 22.8: 1, 22.9: 1, 8.7: 3)
common_config.h in the ab_gp_channel_crosscompare.c run 2.5 (advisory) 70 71 RTE_EXAMPLE_SINGLE_MODE_FLAG_SINGLE is no longer used in that translation unit (the decode moved to ab_gp_sibling_state.c)
ab_gp_channel_checkpoint.c all 16 16 every per-rule count unchanged (11.5: 2, 12.1: 3, 15.5: 8, 15.7: 1, 8.7: 2)
ab_gp_site_status.c all 5 5 every per-rule count unchanged (15.7: 1, 21.6: 1, 8.7: 3)
pre_execute.c all 35 35 every per-rule count unchanged (10.8: 2, 11.5: 4, 15.5: 4, 15.7: 3, 17.7: 8, 21.6: 11, 8.7: 1, variableScope: 2)

No Mandatory or Required finding is added beyond the one 11.5 void * callback-context cast that every rte_site_role callback in this file already carries. ab_gp_sibling_state.c: fixed-width types only, no library call, no dynamic memory, every pointer parameter NULL-checked, the frame copied byte by byte under the fixed wire size, single exit per function. Behaviour: the A/B agreement rule, its 3-strike count and the NEGOTIATION_MISMATCH event now run inside rte_site_role_execute() (Platform_RTE REQ-SITEROLE-016, screened in that product's own report); GP's reaction to the FAULT verdict (react_to_fault(RTE_EXAMPLE_REASON_AB_SITE_STATE_MISMATCH), REBOOT) is unchanged and still runs from ab_gp_channel_read_channels() after the same cycle's ab_gp_channel_negotiate_execute(). The one intended functional difference: a site-state frame read by poll_for_checkpoint_frame() now counts for sibling reachability (the old peer_site_state_rx_cycle was never stamped on that path; rte_site_role stamps the cycle of the execute that takes the frame). Review fix round 1 (2026-10-06): the per-cycle peer poll (deferred frames and ab_gp_channel_poll_peer()) moved ahead of ab_gp_channel_negotiate_execute() in ab_gp_channel_pre_execute(), so the service takes the frame received in cycle N for the decision and the agreement check of cycle N (the first draft polled after the step and compared role(N) with the frame of N-1: one extra strike of check lag, a REBOOT on the usual one-cycle A/B promotion skew, and a 4-cycle reachability window). Side effect: the sibling online / ready inputs of the role decision are one cycle fresher than before (the old local_facts() read the previous cycle's frame); deterministic, same order on A and B. The other peer frames handled by that poll (checkpoint request/reply caching and reply send, SAF_RB, KM114_DIGEST, STARTUP_TS) are independent of the site-role step and of the relay poll (the SAF module accepts AU1 and the peer RB in either order; the KM114 digest is compared in whichever order; STARTUP_TS answers from the fixed value); the apply_state_transfer() comment now states that the service resets its link stamps but not sibling_rx_cycle (an upward CONTINUE renumbering may count the pre-renumbering frame as reachable for one cycle; downward is covered by the service's cycle >= sibling_rx_cycle guard). pre_execute.c and ab_gp_channel_negotiate.c re-screened the same way: per file and rule identical to the table above (the moved block is the same code). Test: tests/gateway/test_ab_gp_sibling_state.c (six cases); three out-of-tree mutants of the module (fresh not cleared on take, bytes 1 and 2 swapped in the decode, a short payload accepted) each fail the test. The verdict reaction itself has no unit test, as before this step (it needs a live pair and the REBOOT safe state); it is covered by reading the single call site.

Deviations

Rule Construct Where (file : line) Justification Compensating measures
21.6 (no <stdio.h> stream I/O) fopen/fscanf/fprintf/fclose Resolved 2026-10-06 (G1 step 5a): resolve_site_startup_timestamp() deleted from ab_gp_channel.c, see the dated note under Update 1 and Update 10 Was: reads/writes one small, fixed-format timestamp file for A/B site-startup coordination. A and B now agree the value over the A/B peer link (ab_gp_startup_timestamp.{h,c}, exchange_startup_timestamp()); no file. None needed any more.
21.6 fopen/fgets ab_gp_config.c:119,125 (.conf key=value loader) Reads the role/site startup configuration file (etc/config/<role>_<site>.conf) at process start. Platform_RTE has no line-oriented text-config-reading OSAdapter service today (rte_nvm is a fixed-offset binary region API, not a text-file line iterator) — building one is a real, separate platform feature, not attempted this session. Fixed-size line buffer, fgets bounds its own read to that buffer, fopen result NULL-checked, loop has a definite exit (EOF or read error), no recursion.
21.6 snprintf ab_gp_channel.c, ab_gp_channel_crosscompare.c, ab_gp_channel_negotiate.c, ab_gp_config.c, ab_gp_status.c, ab_gp_train.c, app_main_common.c, common_config.h (≈25 call sites total) Formats small, fixed-shape strings: channel/role names, log "extra info" fields, config file paths, status payloads. snprintf does not perform stream I/O (it writes to a caller-supplied buffer, never to a FILE*) — a materially different risk profile from the stream functions above, though it is declared in the same banned header and so is listed under the same rule. Platform_RTE's rte_string module (rte_string_copy/_concat/_from_u32/_append_u32 etc.) is a real, MISRA-clean alternative for building these strings, but a mechanical per-call-site rewrite of ≈25 format strings into that API was judged too large to do safely as a side effect of writing this report — a real follow-up, not a reason to leave this row blank. Every call site supplies sizeof(destination) as the bound (snprintf's own truncation semantics: never writes past the buffer, always NUL-terminates); return value is intentionally discarded ((void)snprintf(...)) only where a truncated log/status string is an acceptable degradation, never for a value later parsed back.
21.6 printf common_config.h:25-26 (RTE_EXAMPLE_LOG macro) Direct-to-console fallback logging macro used throughout AB/, predating (or running alongside) rte_log's own event API used elsewhere in the same files. Compiled out entirely under RTE_EXAMPLE_LOG_QUIET (see the macro's own definition); every call site is a fixed, developer-written format string, never one built from external input.
21.6 fprintf(stdout/stderr, ...) main.c:199,203,211,216,229, app_main_common.c:97 Startup diagnostics (usage/version banner, argument errors) printed before rte_log is necessarily initialized, and one explicit bootstrap-failure case (app_main_common.c:97, own comment: "nothing else to reach anywhere, so this stays a raw `fprintf()`"). Fixed argv-derived format strings only (argv[0]/argv[1]/argv[2], never unbounded); every one of these is a process-exit path (usage error or unrecoverable bootstrap failure), not steady-state vital logic.
21.8 (no <stdlib.h> getenv/exit/abort/system) getenv ab_gp_channel.c:68,83,96,127,142,155,446,481, ab_gp_channel_crosscompare.c:79, ab_gp_channel_negotiate.c:312, app_main_common.c:116, site_config.c:122 Reads deployment/test overrides at startup or (in two cases, explicitly cached rather than re-read every cycle — see ab_gp_channel_crosscompare.c:68's own comment) once: host address overrides, the config file path, the runtime log level, a fault-injection test hook, the transfer-policy override. Platform_RTE has no environment-variable-reading OSAdapter service today — a real, boundedly-scoped platform feature that does not exist yet, not attempted this session. Every getenv result is NULL-checked before use, falls back to a compiled-in default, and is only ever consumed as a bounded string (copied into a fixed buffer via snprintf or compared against known literal values) — never used to size an allocation or as a format string itself. exit/abort/system are not used anywhere in this tree (confirmed by search, not assumed).

Rules to watch as this report grows

Rule Where it will bite Plan
21.3 (no dynamic memory) Not currently used anywhere in AB/ (confirmed) Keep it that way; flag immediately if a future change introduces malloc/free.
17.1 (no <stdarg.h>) Not currently used anywhere in AB/ (confirmed — snprintf/printf are called, not defined, by this code) No action needed unless a new variadic function is authored here.
Full per-rule sweep Everything else in AB/, C/, db/, train/ Tracked separately in root TODO.dox ("MISRA compliance reports for RBC_GP, RBC_GA, Platform_OS_POSIX") — needs the cppcheck MISRA addon rule text or a licensed tool, neither available this session.