diff --git a/.fusa-dispositions.json b/.fusa-dispositions.json new file mode 100644 index 0000000..4e30d9b --- /dev/null +++ b/.fusa-dispositions.json @@ -0,0 +1,3 @@ +{ + "dispositions": [] +} diff --git a/.fusa.json b/.fusa.json index 81838c3..9520c48 100644 --- a/.fusa.json +++ b/.fusa.json @@ -2,6 +2,7 @@ "version": "1", "project": { "name": "cpp-RCP", + "version": "2.26.0", "module": "github.com/SoundMatt/cpp-RCP", "standard": "iso26262", "asil": "ASIL-B" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24fdb4..3be9f6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,39 @@ jobs: name: compile-commands path: build/compile_commands.json + # ── Version sources agree ──────────────────────────────────────────────────── + # Ported from c-RCP's own version-sources-agree job, adapted for cpp-RCP's + # real version.hpp syntax: c-RCP's version.h uses a preprocessor + # `#define RCP_VERSION "X.Y.Z"`, while cpp-RCP's version.hpp (this being a + # C++17 header-only port) instead declares a + # `constexpr std::string_view kVersion = "X.Y.Z";`, so the grep pattern + # below matches that syntax rather than c-RCP's #define form. + version-sources-agree: + name: CMakeLists.txt / version.hpp / .fusa.json versions agree + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Compare the three version sources + run: | + cmake_version=$(grep -m1 -oE '^\s*VERSION [0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt | awk '{print $2}') + header_version=$(grep -m1 -oE 'kVersion[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' include/rcp/version.hpp | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + fusa_version=$(grep -m1 -oE '"version": *"[0-9]+\.[0-9]+\.[0-9]+"' .fusa.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + + echo "CMakeLists.txt project() VERSION : ${cmake_version:-}" + echo "include/rcp/version.hpp kVersion : ${header_version:-}" + echo ".fusa.json \"version\" : ${fusa_version:-}" + + fail=0 + [ -n "$cmake_version" ] || { echo "::error file=CMakeLists.txt::Could not parse project() VERSION"; fail=1; } + [ -n "$header_version" ] || { echo "::error file=include/rcp/version.hpp::Could not parse kVersion"; fail=1; } + [ -n "$fusa_version" ] || { echo "::error file=.fusa.json::Could not parse \"version\""; fail=1; } + [ "$fail" -eq 0 ] || exit 1 + + if [ "$cmake_version" != "$header_version" ] || [ "$cmake_version" != "$fusa_version" ]; then + echo "::error::Version sources disagree -- CMakeLists.txt=$cmake_version, version.hpp=$header_version, .fusa.json=$fusa_version. Bump all three together (see version.hpp's header comment); a mismatch here silently mislabels every SBOM/provenance/compliance-report artifact release.yml generates from .fusa.json's stale value." + exit 1 + fi + # ── TLA+ formal verification (TLC model checking) ──────────────────────────── # Independent of the C++ build (tla/*.tla model the safety-relevant state # machines rcp/lifecycle.hpp and rcp/e2e.hpp implement, not the C++ code @@ -677,12 +710,61 @@ jobs: - name: Build run: cmake --build build --parallel + # Real root cause of the `|| true` this replaces: `cpfusa analyze` + # (both its clang-tidy and cppcheck integrations) recursively scans + # whatever directory tree it is pointed at with no awareness of + # .fusa.json's excludePatterns. Left as `build/`, that tree contains + # Catch2's own vendored source (FetchContent-populated + # build/_deps/catch2-src/), including Catch2's own SelfTest suite — + # cppcheck chokes on that third-party test code's BDD macros + # (SECTION/GIVEN/CHECKED_IF/...) and reports them as first-party + # ERROR findings (e.g. "unknownMacro", intentional ODR violations in + # Catch2's own test fixtures), which is what was actually keeping this + # job red, not a real defect in cpp-RCP's own code. Separately, + # `cpfusa analyze` looks for compile_commands.json directly under the + # directory it scans (the repo root here) — since it was left inside + # build/ with no copy at the root, clang-tidy has never actually run + # in this job at all (silently falling back to its + # "compile_commands.json not found" info finding). + # + # Fixed by copying compile_commands.json to the root (so clang-tidy + # finally runs for real) and then deleting build/ (and the vendored + # sources inside it) before invoking analyze, so the scan only ever + # sees this project's own first-party sources. With both fixed, + # cpp-RCP's own code has 0 ERROR-severity findings, so this can be a + # real, ungated `|| true`-free hard gate. + - name: Isolate compile_commands.json from vendored build tree + run: | + cp build/compile_commands.json . + rm -rf build + - name: Static analysis - run: /tmp/cpfusa/build/cpfusa analyze || true + run: /tmp/cpfusa/build/cpfusa analyze # ── cpp-FuSa: 20 CWE-mapped cybersecurity rules (ISO 21434) ───────────────── + # Job renamed from its previous "cpfusa cyber --strict" name, which never + # matched its `run:` line below (plain `cpfusa cyber`, no --strict). --strict + # is a real flag (`cpfusa cyber --help`: "Exit 1 on warnings too"), but + # cpp-RCP does not cleanly pass under it today: a real run surfaces 838 + # WARNING-severity findings (613 CYBER009 "integer narrowing conversion", + # 189 CYBER017 "hardcoded IP address", plus smaller CYBER004/012/019 + # counts), overwhelmingly the expected, reviewed shape of a wire-protocol + # library (masking/shifting a wider integer down to a byte for + # serialization, and loopback/RFC5737 test addresses in the test suite) — + # not defects, but also not sites that can be responsibly mass-remediated + # or blanket-waived in this batch. Critically, `.fusa-dispositions.json` + # cannot fix this either way: unlike gated_exit_code() (used by + # check/lint/analyze), the `cyber` subcommand's --strict check + # (`cyber_strict && warnings > 0`) never consults dispositions at all, so + # no waiver entry changes its outcome. Turning --strict on today would + # therefore require either fixing several hundred call sites sight-unseen + # or sprinkling `// fusa:suppress CYBERxxx` at each one purely to satisfy + # the flag — the "soften a gate to make CI pass" move this batch is + # explicitly not supposed to make. So the job keeps running plain `cyber` + # (a real gate on ERROR-severity findings, of which cpp-RCP has none) and + # is named to match. cpfusa-cyber: - name: cpfusa cyber --strict + name: cpfusa cyber runs-on: ubuntu-22.04 needs: [build-and-test, cpfusa-build] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa3b80a..70f6a36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,6 +122,16 @@ jobs: --dal DAL-B \ --output do178-gap-report.json || true + # .fusa-iec62443.json (target_sl/component_type/incident_resp_doc) is + # the hand-authored input `cpfusa iec62443` reads; already a required + # artifact per ci.yml's cpfusa-check job. Same non-gating rationale as + # the ISO 26262/IEC 61508/DO-178C gap reports above. + - name: Regenerate IEC 62443 gap report (SL-2, artifact only — non-gating) + run: | + /tmp/cpfusa/build/cpfusa iec62443 \ + --sl SL-2 \ + --output iec62443-gap-report.json || true + - name: Regenerate structural coverage report (DO-178C, artifact only — non-gating) run: | /tmp/cpfusa/build/cpfusa coverage \ @@ -148,6 +158,53 @@ jobs: /tmp/cpfusa/build/cpfusa report --format json --output report.json || true /tmp/cpfusa/build/cpfusa report --format html --output report.html || true + # Release-gate check (mirrors c-RCP's own "Verify shipped artifacts + # declare the version being released" step), adapted to what cpp-FuSa + # v0.18.0's actual output carries for this project. c-RCP's tool + # stamps its generated sbom.json/provenance.json module field as + # "c-RCP@", so its check reads that field directly. cpp-FuSa + # v0.18.0 does not do the equivalent for cpp-RCP: `cpfusa release`'s + # sbom.json/provenance.json/artifact-manifest.json and `cpfusa + # qualify`'s qualify-report.json all hardcode + # "module": "github.com/SoundMatt/cpp-FuSa" regardless of target + # project (verified directly against a local build of the pinned + # v0.18.0 binary) — a real limitation of that pinned tool release, not + # something this repo's own files can fix. report.html is the one + # artifact `cpfusa report` actually generates that renders the target + # project's own name+version ("Project: cpp-RCP v"), + # sourced from .fusa.json's project.version — so it is what this check + # verifies instead, alongside re-confirming .fusa.json itself agrees + # with version.hpp. This re-checks what ci.yml's version-sources-agree + # job already checks on every push, because that job's trigger + # (push to main/rewrite branches) does not fire on a tag push, so nothing + # else guarantees the three sources still agreed at the exact ref this + # release job checked out. + - name: Verify shipped artifacts declare the version being released + run: | + header_version=$(grep -m1 -oE 'kVersion[[:space:]]*=[[:space:]]*"[0-9]+\.[0-9]+\.[0-9]+"' include/rcp/version.hpp | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ -z "$header_version" ]; then + echo "::error file=include/rcp/version.hpp::Could not parse kVersion" + exit 1 + fi + + fail=0 + + fusa_version=$(grep -m1 -oE '"version": *"[0-9]+\.[0-9]+\.[0-9]+"' .fusa.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + if [ "$fusa_version" != "$header_version" ]; then + echo "::error file=.fusa.json::.fusa.json's project.version is '$fusa_version', but include/rcp/version.hpp says '$header_version' -- every artifact cpfusa generates from .fusa.json (report.html included) is stamped from the stale value." + fail=1 + fi + + if [ ! -f report.html ]; then + echo "::error::Expected report.html to have been generated by the previous step" + fail=1 + elif ! grep -qE "cpp-RCP v${header_version}[[:space:]&<]" report.html; then + echo "::error file=report.html::Does not declare 'cpp-RCP v${header_version}' -- expected this release's compliance report to carry the version being released." + fail=1 + fi + + [ "$fail" -eq 0 ] || exit 1 + - name: Tool qualification evidence run: /tmp/cpfusa/build/cpfusa qualify @@ -179,6 +236,7 @@ jobs: iso26262-gap-report.json \ iec61508-gap-report.json \ do178-gap-report.json \ + iec62443-gap-report.json \ coverage-report.json \ sas.json sas.md \ sci.json \ @@ -242,6 +300,7 @@ jobs: iso26262-gap-report.json iec61508-gap-report.json do178-gap-report.json + iec62443-gap-report.json coverage-report.json audit-pack.zip fusa-badge.svg diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9f5185b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,113 @@ +# Changelog + +All notable changes to cpp-RCP are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/). + +This changelog starts from the ground-up rewrite tracked on +`rewrite/v3-from-c-rcp` (cpp-RCP issue #129, ROADMAP.md Phase 17) and does not +attempt to reconstruct the project's full pre-rewrite history — see git log +for that. Entries below are one line per merged PR/batch, in the order +merged. + +## [Unreleased] — v3.0.0 rewrite (`rewrite/v3-from-c-rcp`) + +### Phase 7 — release hardening, formal verification, coverage + +- Release-pipeline hardening: `version-sources-agree` CI job, real `cpfusa + analyze`/`cpfusa cyber` gating, `.fusa-dispositions.json`, IEC 62443 release + gap report, shipped-artifact version verification (batch 3) +- rewrite(phase7): batch 2 — add real MC/DC coverage ratchet gate (LLVM) (#168) +- rewrite(phase7): batch 1 — port LifecycleStateMachine/E2ESafePoint TLA+ + specs, add TLC CI job (#167) + +### Phase 6 — requirement catalog re-derivation (complete) + +- rewrite(phase6): batch 13 — MOCK/PWRMODE catalog re-derivation (Phase 6 + complete) (#166) +- rewrite(phase6): batch 12 — DISC/RELAY catalog re-derivation (#165) +- rewrite(phase6): batch 11 — REQ-REGMAP-\*→REQ-RMAP-\* rename + RMAP/SRV + catalog (#164) +- rewrite(phase6): batch 10 — SPI/UART/WAKEUP catalog re-derivation (#163) +- rewrite(phase6): batch 9 — MDIO/PWM catalog re-derivation (#162) +- rewrite(phase6): batch 8 — I2C/ADC/GPIO catalog re-derivation (#161) +- rewrite(phase6): batch 7 — CANEP/LINEP/ISELED catalog re-derivation (#160) +- rewrite(phase6): batch 6 — E2E/LIFECYCLE catalog re-derivation (#159) +- rewrite(phase6): batch 5 — watchdog: one traceability gap found, no catalog + change (#158) +- rewrite(phase6): batch 3 — conditional-request cluster catalog + re-derivation (#157) +- rewrite(phase6): batch 2 — FRAG/RESPQUEUE-slice/LOAN catalog re-derivation + (#156) +- rewrite(phase6): batch 1 — ACF/AVTP/WIREERR catalog re-derivation (#155) + +### Phase 5 — admin/shmem and transport dispatch wiring + +- rewrite(phase5): admin.hpp — fixed-capacity subscriber/counter bounds, port + deadlock fix (#154) +- rewrite(phase5): shmem.hpp — rebuild Channel around a real bounded + byte-level buffer (#153) +- rewrite(phase5): l2.hpp — add FrameHandler wired to Phase 4 frame-level + dispatch (#152) +- rewrite(phase5): udp.hpp — wire Server::Handler to Phase 4 frame-level + dispatch (#151) + +### Phase 4 — mock dispatch, discovery, register map, server admission + +- rewrite(phase4): mock.hpp batch D2 — AVTPDU frame-level dispatch, closes + out Phase 4 (#150) +- rewrite(phase4): mock.hpp batch D1 — wire fragment.hpp/respqueue.hpp for + E2E fragmented dispatch (#149) +- rewrite(phase4): mock.hpp batch C — wire RxSequenceGuard, + StreamFaultTracker, RxWatchdog (#148) +- rewrite(phase4): mock.hpp batch B — Table 24 response suppression + + regmap/discovery wiring (#147) +- rewrite(phase4): mock.hpp batch A — wire server::Endpoint admission (#146) +- rewrite(phase4): fix adapt.hpp's missing read_size_or_segment_num field, + add test_adapt.cpp (#145) +- rewrite(phase4): port discovery from c-RCP, fix claim-release and + validation gaps (#144) +- rewrite(phase4): port regmap batch B from c-RCP — HW pins, streams, EP-ID + map, optional subsystems (#143) +- rewrite(phase4): port server.c admission/scheduling into new server.hpp + (#141) +- rewrite(phase4): port regmap batch A from c-RCP — general map, EP0, + generic/functional split (#142) + +### Phase 3 — remaining endpoint types + +- rewrite(phase3): port spi and uart from c-RCP, RC5 nr_cs/deassert_cs_pause + fix (#139) +- rewrite(phase3): port pwm and wakeup from c-RCP, fix PWM_OUT Subtract + operand order (#140) +- rewrite(phase3): port mdio from c-RCP, revert an earlier session's own + regression (#138) +- rewrite(phase3): port adc/gpio from c-RCP, fix a critical ADC averaging + regression (#137) +- rewrite(phase3): port can/lin from c-RCP, wire CAN XL fragmentation via + fragment.hpp (#136) +- rewrite(phase3): port iseled/i2c from c-RCP; fix ROADMAP.md's Phase 17 + missing i2c entry (#135) + +### Phase 2 — E2E/lifecycle/watchdog + +- rewrite(phase2): port e2e/lifecycle from c-RCP, correct HARA.md's + overstated H-004 claim (#134) +- rewrite(phase2): port watchdog + build allocation fault-injection seam, + ported from c-RCP (#133) + +### Phase 1 — core wire format foundation + +- rewrite(phase1): port request/sequencer/scheduler from c-RCP's + RC5-conformant reference (#132) +- rewrite(phase1): add fragment/respqueue, convert loan to fixed-capacity, + ported from c-RCP (#131) +- rewrite(phase1): port acf.hpp/avtp.hpp from c-RCP's RC5-conformant + reference (#130) + +### Rewrite kickoff + +- ci: run CI/DCO on the rewrite/v3-from-c-rcp branch too +- docs(roadmap): v3.0.0 becomes a full rewrite ported from c-RCP, not Phase + 16's organic conclusion