|
RTE RBC 2oo2 Reference Application 0.1.0 build 0006685
SIL 4 2oo2 Dual-Site Fault-Tolerant ERTMS Radio Block Centre Reference Architecture
|
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.
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.
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.
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:
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.
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.
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.
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.
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.
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).
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.
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.
| 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). |
| 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. |