Skip to content

[cpp-RCP v3.0.0] Full ground-up rewrite ported from c-RCP #129

Closed

Description

@SoundMatt

Why

A 2026-08-21 parity audit against c-RCP (the project's reference implementation) found cpp-RCP behind on nearly every axis — no MC/DC, no requirement-atomicity audit, no fixed-capacity architecture, a replay-detection mechanism implemented but never wired into dispatch, a 26-commit tag/main drift — tracked in #122-127 (master #128).

Then a bigger problem surfaced: cpp-RCP was never rebaselined to TC18 v0.5.1_RC5 at all. README.md/ROADMAP.md cite v0.5.1_RC, the exact pre-RC5 baseline c-RCP itself started from before spending a real multi-issue program (c-RCP #96, #339, #341, #434) reconciling to RC5. Confirmed concretely, not just by citation: c-RCP's RC5 rebaseline found spi_nr_cs narrowed to a 4-bit (count-1) field plus a new deassert_cs_pause bit (RC5 ticket NXP_100) — cpp-RCP's spi.hpp has neither. That's a real wire-format defect, not a stale comment.

Given the scale of what's wrong at once — spec version, requirement catalog, safety evidence, and architecture — the decision is a full ground-up rewrite rather than another patch effort: clear cpp-RCP's implementation and rebuild it from scratch, using c-RCP's current (RC5-conformant, atomicity-audited, ASIL-D-hardened) implementation as the content source of truth, re-expressed in idiomatic modern C++.

This is cpp-RCP's own v3.0.0 — its ROADMAP.md already names "Phase 16, v3.0.0: TC18 RCP — General Availability" as the destination. This rewrite fulfills that directly.

Where the work happens

Branch rewrite/v3-from-c-rcp, not main — so main stays green/deployable for however many sessions this takes, and existing tags remain a rollback point. Each module is rebuilt in its own PR against this branch (delete-old + add-new in the same PR, so the branch stays buildable and CI-green throughout rather than broken for the whole multi-session effort). Final cutover merges this branch into main and tags v3.0.0.

Full plan: see the session's plan file (module-by-module mapping, phase sequencing, verification discipline) — summarized in the checklist below.

Supersedes

Closing, with a pointer here: #122 (release integrity), #123 (CI reliability), #124 (requirement schema/atomicity), #125 (ASIL-D SEOOC), #126 (allocation architecture), #127 (CAN XL fragmentation decision), #128 (parity master tracker). All of that work is subsumed by porting c-RCP's already-hardened equivalents directly, rather than incrementally patching the code being replaced.

Not superseded, still real, independent bugs: #58 (cs-bit polarity — will simply not exist in the ported code, verify during the port), #23 (go-RCP tooling-package parity — orthogonal, revisit post-rewrite).

Phases (see plan file for full detail)

  • Phase 0 — branch setup, issue cleanup (this tracker), ROADMAP.md v3 section
  • Phase 1 — core wire/protocol: acf, avtp, request(+sequencer+scheduler folded), fragment (new), respqueue (new), loan
  • Phase 2 — safety layer: e2e, lifecycle, watchdog, allocation seam (DI-based, fixed-capacity, fault-injection tests)
  • Phase 3 — per-endpoint modules (batched): can, lin, iseled, adc, pwm, gpio, spi, uart, mdio, wakeup
  • Phase 4 — server/dispatch: regmap, mock (wire in classifier/Table-30/E2E-guard/fragmentation), server/endpoint.hpp, discovery, adapt
  • Phase 5 — transport: udp, l2, shmem, admin; re-verify tls/capi
  • Phase 6 — requirement catalog: re-derive .fusa-reqs.json from c-RCP's 1282 entries
  • Phase 7 — formal verification + MC/DC + release-pipeline hardening
  • Phase 8 — remaining modules + re-integrate dyndata/redundancy/sim
  • Phase 9 — cutover: full verification, merge to main, tag v3.0.0, update RELAY's docs/RCP-ARCHITECTURE.md

Scale, honestly

Multi-session — comparable to building c-RCP's current maturity from scratch, since it includes standing up infrastructure c-RCP itself took many dedicated efforts to build (fragmentation, respqueue, MC/DC, fixed-capacity architecture, formal-verification CI) and more than tripling cpp-RCP's test suite (730 → ~2350+ test cases). Progress tracked here, one comment per phase batch, same as c-RCP's own tracker-issue pattern.

Activity

  1. SoundMatt commented on Aug 21, 2026

    @SoundMatt
    OwnerAuthor

    Phase 1, batch 1 complete: acf/avtp ported from c-RCP (#130, merged 1edc870). Found and fixed a real regression along the way — ACF_GBB's message_timestamp had been spliced into the wrong position since v2.22.0, meaning every ACF_GBB frame this library emitted was unreadable by a conformant peer. Full CI green (all 21 checks incl. cpfusa check/lint/cyber/trace/verify/qualify/vuln/report, cross-platform build matrix, ASan/UBSan, clang-tidy, coverage). Also had to widen ci.yml/dco.yml's branch triggers to include this branch — they were main-only, so PRs against here got zero CI signal until now.

    Starting batch 2: request(+sequencer+scheduler) and fragment+respqueue+loan, as two parallel PRs since they don't depend on each other at the C level.

  2. SoundMatt commented on Aug 21, 2026

    @SoundMatt
    OwnerAuthor

    Phase 1, batch 2 complete: fragment/respqueue (NEW) + loan (fixed-capacity conversion) ported from c-RCP (#131, merged 79261c4). fragment.hpp closes cpp-RCP's largest functional gap (multi-AVTPDU CAN XL fragmentation, previously an explicit no-go at ROADMAP.md milestone 52) with a genuinely stronger design than c-RCP's own — fully fixed-capacity, zero heap allocation, vs. c-RCP's realloc()-grown buffer. loan.hpp's buffer pool was found unbounded (a real gap an earlier parity audit flagged) and converted to match c-RCP's fixed bound exactly. Full CI green (21 checks), independently re-verified from a fresh build (221/221 targets, 55/55 ctest suites).

    request/sequencer/scheduler (the remaining Phase 1 module) is still in progress — hit a mid-task API error partway through, resumed from the partial state (production code was already complete, test port was what remained).

  3. SoundMatt commented on Aug 21, 2026

    @SoundMatt
    OwnerAuthor

    Phase 1 complete. All three batches merged:

    Final state on rewrite/v3-from-c-rcp, independently re-verified from a fresh full-tree build: 221/221 build targets, 55/55 ctest suites, 100% pass. Every wire-protocol-core module is now RC5-conformant, fixed-capacity where c-RCP itself is (and stronger in fragment/loan, which have zero heap allocation vs. c-RCP's own C version), and test-covered well beyond where it started (acf 24→63, avtp 15→34, request 12→135, fragment/respqueue 0→30/27 new, loan 6→9).

    Starting Phase 2: safety layer (e2e, lifecycle, watchdog, allocation seam).

  4. SoundMatt commented on Aug 21, 2026

    @SoundMatt
    OwnerAuthor

    Phase 2, batch 1 complete: watchdog (content audit, fixed-capacity conversion added) + alloc.hpp (NEW — allocation fault-injection seam, deliberately DI-based rather than a global hook table) ported/built from c-RCP (#133, merged 2d976af). Full CI green (21 checks), independently re-verified from a fresh build (223/223 targets, 56/56 ctest suites).

    e2e/lifecycle (the larger, safety-critical piece of Phase 2 — including the correction to HARA.md's overstated H-004 replay-detection mitigation claim) is still in progress.

  5. 51 remaining items

  6. SoundMatt commented on Aug 22, 2026

    @SoundMatt
    OwnerAuthor

    Phase 6 complete: requirement catalog re-derivation (13/13 batches merged)

    Phase 6 — re-deriving cpp-RCP's .fusa-reqs.json from c-RCP's current RC5-conformant catalog — is done. All 13 batches merged into rewrite/v3-from-c-rcp, each independently re-verified before merge per this rewrite's standing discipline (semantic id-keyed diff, sec-tested count check, direct spot-checks against source, fresh from-scratch rebuild+test, and — from batch 9 onward — a whole-tree tag-coverage re-derivation against the real pinned cpfusa binary itself, not an approximation of its rules).

    Final numbers

    • .fusa-reqs.json: 412 → 1133 entries (+721 across the whole phase)
    • .fusa-reqs-pending.json: 3 → 174 entries (every genuine gap or id-collision found along the way, honestly documented with gap text rather than force-fit into the main catalog)
    • Tag-coverage gate: 1133/1133 traced and tested, 0 gaps — confirmed via the real pinned cpfusa v0.18.0 binary against the fully merged branch, not a proxy check.
    • sec-tested (cybersecurity-relevant) count: held at exactly 72 throughout the whole phase — the enumerated 72-id subset (REQ-TLS-*/REQ-AUTH-*/REQ-E2E-*/REQ-RL-*/REQ-DISC-*/REQ-RMAP-* + 8 Table-30-evt-classification ids) never grew or shrank; no batch added a new cybersecurity-marked entry.
    • Full test suite: 58/58 ctest suites passing (100%) on the merged branch, 0 build errors/warnings.

    Batch list

    # Scope PR
    1 ACF/AVTP (+ CI sec-tested gate structural fix) #155
    2 FRAG (respqueue/loan already correct) #156
    3 REQUEST #157
    4 ALLOC (no file changes — closed via tracker only) —
    5 WATCHDOG #158
    6 E2E/LIFECYCLE #159
    7 CANEP/LINEP/ISELED #160
    8 I2C/ADC/GPIO #161
    9 MDIO/PWM #162
    10 SPI/UART/WAKEUP #163
    11 REQ-REGMAP-→REQ-RMAP- rename + RMAP/SRV #164
    12 DISC/RELAY #165
    13 MOCK/PWRMODE (+ deferred REQ-WDG-010/REQ-E2E-033/039/041/047 pickup) #166

    Recurring patterns worth recording for future reference

    • Orphaned-citation resolution was the core mechanic, not re-derivation from scratch. Nearly every batch found cpp-RCP's code/tests already citing far more REQ-* ids (via //fusa:req///fusa:test/Catch2 bracket tags) than existed in the catalog — an earlier, untracked porting pass had already ported the behavior and left provenance comments; the batch's job was walking those citations into honest catalog entries, not re-deriving requirements independently.
    • Id-collision is a distinct, recurring gap category (first seen batch 6, present in nearly every batch since): cpp-RCP's own pre-rewrite low-numbered ids per prefix were assigned sequentially before this rewrite's "reuse c-RCP's exact id" convention took hold, and now collide with unrelated, real c-RCP requirements sharing the same numeral. Every instance was verified by direct content comparison (never assumed from id presence alone) and filed to .fusa-reqs-pending.json with both the stale and real content documented, for a future dedicated renumbering-cleanup batch — 174 pending entries now carry this history, a real backlog item, not phase debt to write off.
    • The batch-8 CI incident became a durable process change: cpfusa trace's real gate requires testedRequirements == totalRequirements exactly, and — discovered again the hard way in batch 11 — it only recognizes literal // fusa:test REQ-ID manifest comments, not Catch2 [REQ-ID] bracket tags. Every batch from 9 onward mandated grepping the whole tree for both tag forms before adding an id, and building/running the real pinned cpfusa binary directly rather than an approximating script. Batch 11's fixup commit is the concrete example of what happens when that discipline slips.
    • Architecture-divergence documentation, not forced categorization: several batches (PWM-001, ACF-037, FRAG-016, ACF/AVTP-016/027, RELAY-007/011/013/015, several PWRMODE ids) found cpp-RCP's design genuinely differing from c-RCP's while achieving the same safety property differently — these were documented in-file/in-commit-message as deliberate divergences, not stuffed into the collision or gap buckets.
    • REQ-PWRMODE- (batch 13) was the one prefix with zero pre-existing citations* — genuinely new porting work rather than catalog-only derivation, requiring direct comparison against c-RCP's power.c+powerstate.c (folded into cpp's single powerstate.hpp per an earlier deliberate design choice). It surfaced one real safety-relevant finding: REQ-PWRMODE-026, mode committing before the sleep/standby response is confirmed transmitted — filed to pending, not force-fixed, since the right fix touches ordering guarantees this batch's scope didn't cover.

    Next

    Proceeding to Phase 7 (formal verification + MC/DC + release-pipeline hardening) per the original rewrite plan's sequencing.

  7. SoundMatt commented on Aug 22, 2026

    @SoundMatt
    OwnerAuthor

    Phase 7 complete: formal verification + MC/DC + release-pipeline hardening (3/3 batches merged)

    Phase 7 — the remaining safety-evidence infrastructure from the original rewrite plan — is done. All 3 batches merged into rewrite/v3-from-c-rcp, each independently re-verified before merge (reproducing every measurement/claim directly rather than trusting the porting agent's own report — TLC re-run on all 5 specs, the full MC/DC instrumented pipeline reproduced end-to-end, the version-sources-agree/cpfusa cyber/analyze claims all reproduced with matching output).

    Batch list

    # Scope PR
    1 Port LifecycleStateMachine.tla/E2ESafePoint.tla from c-RCP, add TLC CI automation for all 5 specs (the pre-existing 3 had never been run in CI at all) #167
    2 Real MC/DC (condition/decision) coverage via LLVM, ratchet-gated at a freshly-measured 60% floor (~7pt margin) — cpp-RCP had zero MC/DC infrastructure before this #168
    3 version-sources-agree CI gate, real cpfusa analyze/cpfusa cyber gating (both were previously softened — `

    What each batch actually found (not just what it built)

    • Batch 1: cpp-RCP's 3 pre-existing TLA+ specs (WatchdogSafeState, RxSequenceGuard, CrcSafeStateLatch) had never been wired into CI at all — a real, silent gap closed alongside the 2 new spec ports. Both new ports are faithful direct ports (confirmed field-for-field against cpp-RCP's real lifecycle.hpp/e2e.hpp), with one known abstraction gap (already present in c-RCP's own spec, not introduced here) documented rather than silently carried forward.
    • Batch 2: cpfusa coverage --mcdc-file is broken in the same way c-RCP's own cfusa equivalent is (its parser expects JSON keys real llvm-cov export output never produces — confirmed directly against both the tool's source and real export output). The CI job reads llvm-cov's own totals directly instead, matching c-RCP's precedent. Floor set from a real local measurement (67.17%), not copied from c-RCP's own unrelated 93%.
    • Batch 3: .fusa.json had no real project.version field at all — cpfusa had been silently defaulting every generated artifact to "0.1.0" for this repo's whole history, fixed as part of building the version-sources-agree job. cpfusa cyber --strict's job name never matched its run: line (plain cpfusa cyber, no flag) — investigated rather than blindly adding the flag: cpp-RCP surfaces 838 real WARNING findings under --strict (mostly the expected byte-packing idiom of a wire-protocol library), correctly left un-added rather than mass-remediated or blanket-waived in one batch, with the job renamed to match its honest behavior. cpfusa analyze's || true was masking a real job-configuration bug, not a code defect: Catch2's own vendored FetchContent source was inside the scanned tree (cppcheck was misreporting Catch2's own test macros as first-party errors), and compile_commands.json was never actually being found by clang-tidy — both fixed, now a real 0-error hard gate. .fusa-dispositions.json ended up legitimately empty (verified against both tools' real current output, not fabricated to look populated).

    Final state

    • 5/5 TLA+ specs pass TLC, all wired into CI.
    • Real MC/DC evidence: 67%+ condition/decision coverage, ratchet-gated (not a 100% claim — an honest, growing floor).
    • version-sources-agree, cpfusa analyze, and (honestly-named) cpfusa cyber are all real CI gates now, no softened escape hatches.
    • .fusa-dispositions.json and CHANGELOG.md now exist, both honestly reflecting the codebase's real current state rather than being stood up as empty formalities.
    • Release pipeline now includes an IEC 62443 gap report and a real shipped-artifact version check, alongside the pre-existing ISO 26262/IEC 61508/DO-178C reports.

    Next

    Proceeding to Phase 8 (remaining modules: record, observe, config, cli, tsn, powerstate, faultinject, ratelimit, mdns, authz, live bridges, relay/relay.hpp; re-integrate dyndata/redundancy/sim) per the original rewrite plan's sequencing — scoping now, since several of these already appear as passing CI jobs from earlier untracked porting passes (mirroring the Phase 6 pattern where most work turned out to be catalog/wiring, not net-new implementation).

  8. SoundMatt commented on Aug 22, 2026

    @SoundMatt
    OwnerAuthor

    Phase 8 complete: remaining modules (2/2 batches merged)

    Phase 8 — the original rewrite plan's final content phase — is done. Both batches merged into rewrite/v3-from-c-rcp, each independently re-verified before merge.

    Batch list

    # Scope PR
    A record, observe, config, cli + re-verify dyndata/redundancy/sim (no c-RCP equivalent) #171
    B tsn, powerstate, faultinject, ratelimit, mdns, authz, relay/relay.hpp, restbridge/grpcbridge #170

    What each batch found

    Same pattern as Phases 6-7: every module already existed with substantial implementation and passing tests from an earlier untracked pass, so both batches were content-parity audits against c-RCP, not rewrites.

    • Batch A found and fixed a real latent bug in config.hpp: the manifest parser only routed an object to endpoint-entry parsing if both stream_key and byte_bus_id keys were present in an initial scan — an entry with only one was silently dropped instead of rejected (the same bug class c-RCP's own config.c independently found and fixed). Fixed by routing on either key, with the entry parser itself now the one place validating both are genuinely present, throwing a real parse error if not. Also ported genuine capability gaps into observe.hpp (a standalone observe::record() free function, per-endpoint attribution on spans/counters) and cli.hpp (advertising 5 real capabilities — time_sync/enhanced_cancel/trigger/chained/compound_bundles — that were implemented but never surfaced in capabilities_json(), plus a missing tsn transport entry). dyndata/redundancy/sim confirmed clean against the now-much-more-mature Phase 5-7 core, no changes needed.
    • Batch B found only one gap worth documenting: authz.hpp's policy-entry keying is coarser than c-RCP's (can't distinguish read vs write within one request kind) — a pre-existing v2.11.0 design choice, already justified at length in the header, correctly left as a documented divergence rather than force-changed (it would break the already-tested PolicyEntry/permit() contract). Everything else (tsn, powerstate, faultinject, ratelimit, mdns, relay.hpp, both live bridges) confirmed already capability-equivalent to c-RCP, several with their own already-documented architectural divergences (e.g. tsn.hpp's primitive apply_priority() vs c-RCP's full transport-wrapper). Notably faultinject.hpp's pick_rule() was found to already fix a real use-after-free/data-race c-RCP's own mechanism doesn't guard against — arguably ahead of the reference implementation there.

    Both batches correctly declined to re-litigate closed Phase 6 catalog decisions (in particular powerstate.hpp's 8 already-filed REQ-PWRMODE-* architectural gaps) and flagged (without fixing) a handful of catalog-only staleness items for a future targeted pass: REQ-RED-001/002/003's titles still say the retired RedundantController class name though text/code/tests correctly say RedundantRequestFn; several prefixes (REQ-REC-*, REQ-OBS-*, REQ-CFG-*, REQ-CLI-*) have ids that tag different content across languages or have no cpp-RCP counterpart at all, by design.

    Full test suite: 58/58 passing on the merged branch, 0 build errors/warnings.

    Next

    This closes out every phase of the original rewrite plan except final cutover: full-repo verification, merge rewrite/v3-from-c-rcp into main, tag v3.0.0, update RELAY's docs/RCP-ARCHITECTURE.md. Given the scale and finality of that step (replacing main's content and cutting a major version tag), I'm pausing here for an explicit go-ahead before proceeding, rather than treating it as another routine batch-merge.

  9. added a commit that references this issue on Aug 22, 2026
  10. SoundMatt commented on Aug 22, 2026

    @SoundMatt
    OwnerAuthor

    v3.0.0 shipped 🎉 — the full ground-up rewrite is complete

    Tag v3.0.0 is pushed, main is updated, and release.yml ran for real for the first time — regenerated safety artifacts (SBOM, provenance, dFMEA, safety case, all four gap reports including the newly-added IEC 62443 one, compliance report) merged via #173, all correctly declaring version 3.0.0 throughout (confirming the .fusa.json version-default bug found and fixed in Phase 7 batch 3 is genuinely resolved — the pre-fix artifacts had been silently defaulting to "0.1.0").

    Final cutover steps

    1. PR v3.0.0: full ground-up rewrite, ported from c-RCP #172 — merged the full rewrite/v3-from-c-rcp branch into main (89 files, ~54k insertions), after independent full-repo verification: fresh build (0 errors/warnings), 58/58 tests, cpfusa check/trace clean (1133/1133 traced+tested, 0 gaps), all 5 TLA+ specs verify, and the branch's own CI fully green including the MC/DC gate.
    2. Version bump — CMakeLists.txt/version.hpp/.fusa.json still said 2.26.0 after the merge; bumped all three to 3.0.0 together (verified they agree, matching the version-sources-agree gate's own check) and promoted CHANGELOG.md's [Unreleased] to a real [3.0.0] entry.
    3. Tag v3.0.0 — pushed once main's CI confirmed green on the version-bump commit.
    4. release.yml's first-ever real run — succeeded, regenerated every safety artifact for the real 3.0.0 state, opened chore: regenerate safety artifacts for v3.0.0 #173 automatically, merged after spot-checking the version fields.
    5. RELAY sync (RELAY#190) — docs/RCP-ARCHITECTURE.md previously tracked three cpp-RCP conformance gaps as open (Table 30 Row-2 evt[2:0] wiring across all 8 endpoint types, the §13.5.1 compound-wait comparison primitive, and specific request.hpp field-level gaps). All three verified directly against the current codebase and corrected in place — genuinely closed by this rewrite, not assumed from its existence. A smaller, different set of real gaps remains and is cited honestly from cpp-RCP's own current .fusa-reqs-pending.json.

    What this rewrite covered, start to finish

    Per the original plan (docs/wiggly-shimmying-wombat.md), phases 1-5 landed in earlier sessions (core wire/protocol, safety layer, per-endpoint modules, server/dispatch, transport, plus admin/shmem hardening). This session covered:

    Every batch this session was independently re-verified before merge — reproducing measurements directly rather than trusting a porting agent's own report, spot-checking specific claims against real source, fresh from-scratch rebuilds, and (from batch 9 onward) whole-tree tag-coverage re-derivation against the real pinned cpfusa binary.

    cpp-RCP is now, per its own ROADMAP.md Phase 17 goal, the OPEN Alliance TC18 Remote Control Protocol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions