diff --git a/.fusa-reqs-pending.json b/.fusa-reqs-pending.json index e80dda6..231073b 100644 --- a/.fusa-reqs-pending.json +++ b/.fusa-reqs-pending.json @@ -169,16 +169,6 @@ "tc18": "§11.2.2.6 (\"If the first request in an AVTPDU is a chain request, then there is no predecessor to chain to, thus the entire chain will be ignored. An error response with the error code 'CHAIN_ERROR' to each request will be generated\"), TC18.txt L1796-1798", "gap": "Confirmed absent (2026-08-22): RequestRecord::chained_predecessor is std::optional (request.hpp:1315), and RequestLedger::submit() (request.hpp:1354-1362) never inspects it — a Chained-opcode record with chained_predecessor == std::nullopt is accepted and tracked exactly like any other Pending record, with no special rejection path. RequestErrc (request.hpp:270-285) has no CHAIN_ERROR-equivalent enumerator at all — the closest neighbors (request_not_found, request_canceled, invalid_lifecycle_transition) are semantically unrelated. This is distinct from REQ-CHAIN-009's 'already-aborted chain' case (which cascade_cancel()/propagate_chain_completion() genuinely do handle, unconditionally cancelling every downstream chained_successor once one member aborts — see request.hpp's own file-header delta #4/judgment-call note at lines 207-217): REQ-CHAIN-008 is about a chained member that never had a predecessor relationship recorded in the first place, which nothing in RequestLedger detects or rejects today. A caller assembling chained_predecessor/chained_successors links at submission time (this codebase's own design, not c-RCP's positional has_predecessor bool) could in principle already choose not to submit a first-frame Chained request with a false chained_predecessor link, but RequestLedger itself provides no enforcement or detection of the case, and no test in test_request.cpp exercises it." }, - { - "id": "REQ-TIMED-012", - "title": "A request carried under a TSCF header is postponed until the header's presentation time", - "text": "A caller-facing admission/dispatch entry point shall, given an AVTPDU decoded under a TSCF header (carrying its own avtp_timestamp), defer executing any request in it until that timestamp is reached, using the already-implemented timed_admit()/timed_due() primitives (REQ-TIMED-007/008/011) as the gate.", - "standard": "iso26262", - "level": "ASIL-B", - "asil": "ASIL-B", - "tc18": "§11.2, TC18.txt L1159; §11.2.1, TC18.txt L1204", - "gap": "Confirmed absent (2026-08-22): rcp/request.hpp implements every TSCF-admission PRIMITIVE this requirement depends on (timed_admit()/timed_due()/timed_too_far(), request.hpp:1094-1125) but, by this file's own explicit design note (request.hpp:246-250, 'this header... does not implement a running scheduler thread; wiring select_next_due()'s output into an actual dispatch loop is left to the embedding application'), provides no dispatch/admission LOOP that actually calls them against a real TSCF-decoded avtp_timestamp. Grepped the whole tree (include/, src/) for a dispatch-tscf/admit-gate equivalent to c-RCP's rcp_mock_server_dispatch_tscf()/rcp_server_endpoint_admit(): none exists — rcp/l2.hpp/udp.hpp encode/decode TSCF *framing* (use_tscf bit, avtp_timestamp field) but never route a decoded timestamp into timed_admit()/timed_due(). This is a genuine, confirmed absence of end-to-end behavior (not merely an unwired primitive): nothing in cpp-RCP today defers ANY request's execution until a TSCF header's presentation time arrives. Per this file's own design note this may be intentionally out of request.hpp's charter (a library of primitives, not a server), but the requirement's own text (postponement actually happening) has no home anywhere else in the tree either." - }, { "id": "REQ-TIMED-013", "title": "Standard requests, and timed requests under a TSCF header, are sent as ACF_ABB", @@ -1302,18 +1292,6 @@ "tc18_master_id": "", "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)). The behavior itself is genuinely implemented and tested. The blocker is that this batch's REQ-REGMAP-* -> REQ-RMAP-* rename (ROADMAP.md Phase 17) claimed this id for cpp-RCP's own pre-existing, unrelated content first: this id is already taken in .fusa-reqs.json by that renamed entry. Already taken by: this catalog's own REQ-RMAP-015 (\"EndpointGenericConfig's per-endpoint E2E CRC safe-mode toggles\"). Genuinely implemented+tested: regmap::named_signal_string() -- tests/test_regmap.cpp's \"named_signal_string names are unique across the whole index\" TEST_CASE, dual-tagged [REQ-RMAP-044][REQ-RMAP-015] in this batch." }, - { - "id": "REQ-RMAP-081", - "title": "EP_RESP_ON_ERROR (Table 31 prose, IO-pin gauging error response) is a dangling spec reference", - "text": "NOT IMPLEMENTED, matching c-RCP's own disposition verbatim: TC18's own prose names a configuration parameter (EP_RESP_ON_ERROR) that Table 31 itself never actually defines a bit or field for -- a genuine, confirmed TC18 spec defect, not an addressable local gap. ep_generic_cfg::render()'s own reserved-octet spans (bits [3:1] and [7:6] of octet 1, mask 0xCE) deliberately stay zero for every input rather than inventing a bit position for this dangling reference.", - "standard": "iso26262", - "level": "QM", - "asil": "QM", - "scope": "tc18-gap", - "status": "not-implemented", - "tc18": "§13.2 Table 31 (EP_RESP_ON_ERROR prose reference, no defined bit/field)", - "gap": "Genuine spec-defect gap, matching c-RCP's own disposition verbatim (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)): TC18 names EP_RESP_ON_ERROR in prose without ever defining where it lives on the wire. cpp-RCP additionally has a positive, already-tested defensive guarantee beyond c-RCP's own scope for this id -- ep_generic_cfg::render() never invents a bit for it (tests/test_regmap.cpp's \"ep_generic_cfg::render never sets a bit for the dangling EP_RESP_ON_ERROR reference\" TEST_CASE, tagged [REQ-RMAP-081]) -- but the underlying requirement (a real EP_RESP_ON_ERROR mechanism) remains genuinely unimplementable per the same spec silence c-RCP already confirmed, so this entry is filed as a gap rather than force-fit as implemented." - }, { "id": "REQ-RMAP-068", "title": "Register-write outcome: a read-only register succeeds silently, a write-prohibited register errors", diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 02348fe..f18a883 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -1210,6 +1210,22 @@ "level": "ASIL-B", "asil": "ASIL-B" }, + { + "id": "REQ-SHMEM-009", + "title": "A Channel's state is freed exactly once regardless of shared_ptr release order", + "text": "A shmem::Channel's underlying state shall be freed exactly once, and only once every std::shared_ptr holder has released it, regardless of the order in which holders release -- constructing, copying, and releasing shared_ptr handles in either order shall neither crash nor leak, matching c-RCP's own refcounted rcp_avtp_transport_t contract (shmem.c:151-181).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B" + }, + { + "id": "REQ-SHMEM-010", + "title": "request() plays both the c-RCP send-side and recv-side ErrClosed role", + "text": "Channel::request() shall return ErrClosed once the Channel has been closed, regardless of which of c-RCP's own two separate shmem_side_send()/shmem_side_recv() roles a given call is standing in for -- a single request() call plays both the \"send\" and \"recv\" side of that split API, and each of c-RCP's own analogs independently reports RCP_ERR_CLOSED once its own side is closed (shmem.c:64-67/100-113,109-113).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B" + }, { "id": "REQ-TSN-001", "title": "apply_priority sets SO_PRIORITY per RequestCategory", @@ -4988,6 +5004,17 @@ "status": "implemented", "tc18": "§11.2.2.7 (\"In any case the presentation_time is the earliest point in time, when the request shall be executed.\"), TC18.txt L1952" }, + { + "id": "REQ-TIMED-012", + "title": "A request carried under a TSCF header is postponed until its own presentation time", + "text": "rcp::server::Endpoint::admit()/admit_with_ack() shall, whenever the caller reports tv true (an enclosing TSCF header's own avtp_timestamp), postpone execution of the admitted request -- of any kind, Standard, Cancellation, or a conditional kind with its own start condition -- until avtp::extend_timestamp(avtp_timestamp, gptp_reference_now)'s reconstructed 48-bit instant is reached; select_due()/is_due() shall AND this envelope-level presentation gate with (not substitute for) each kind's own existing execution condition, and shall fail closed -- never report the request due -- whenever gptp_locked is false, regardless of how far in the past the gate's own instant already is.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2, TC18.txt L1159; §11.2.1, TC18.txt L1204" + }, { "id": "REQ-CANCEL-002", "title": "encode_clear_all() encodes a zero-payload clear-all request", @@ -11484,6 +11511,17 @@ "tc18": "§13.2 Table 28/31, TC18.txt L3570-3574", "tc18_master_id": "TC18-13.2-001" }, + { + "id": "REQ-RMAP-081", + "title": "EP_RESP_ON_ERROR (Table 31 prose, IO-pin gauging error response) is a dangling spec reference", + "text": "PARTIAL, matching c-RCP's own disposition: TC18's own prose names a configuration parameter (EP_RESP_ON_ERROR) that Table 31 itself never actually defines a bit or field for -- a genuine, confirmed TC18 spec defect, not an addressable local gap. What IS implemented and tested: ep_generic_cfg::render() deliberately invents no bit for it -- both reserved spans of octet 1 (bits [3:1] and [7:6], mask 0xCE) stay zero for every input, including inputs deliberately chosen to be non-zero/extreme everywhere else. What remains open: a real EP_RESP_ON_ERROR mechanism itself, which TC18's own spec silence makes genuinely unimplementable, not merely unwired.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18-gap", + "status": "partial", + "tc18": "§13.2 Table 31 (EP_RESP_ON_ERROR prose reference, no defined bit/field)" + }, { "id": "REQ-RMAP-082", "title": "named_signal_string() returns \"unknown\" for an out-of-range value", diff --git a/.fusa.json b/.fusa.json index ef3cb5b..ddcbe5b 100644 --- a/.fusa.json +++ b/.fusa.json @@ -1,11 +1,13 @@ { "version": "1", + "standard": "iso26262", + "asil": "ASIL-C", "project": { "name": "cpp-RCP", "version": "3.0.0", "module": "github.com/SoundMatt/cpp-RCP", "standard": "iso26262", - "asil": "ASIL-B" + "asil": "ASIL-C" }, "rules": { "exclude": [], diff --git a/AUDIT_PACK.md b/AUDIT_PACK.md index e7c0a27..2861b4a 100644 --- a/AUDIT_PACK.md +++ b/AUDIT_PACK.md @@ -2,7 +2,7 @@ **Document version**: 2.0.0 **Date**: 2026-07-28 -**Standards**: ISO 26262 (ASIL-B), IEC 61508 (SIL-2), ISO 21434, IEC 62443 SL-2 +**Standards**: ISO 26262 (ASIL-C — corrected from ASIL-B, see `HARA.md` H-001), IEC 61508 (SIL-2), ISO 21434, IEC 62443 SL-2 This revision (ROADMAP.md milestone 62, "Certification Refresh", v2.18.0) supersedes the pre-replacement audit pack authored at milestone 43. All @@ -28,9 +28,19 @@ across Phase 13-16 (v2.0.0-v2.17.0). ## 2. ASIL-D Gap Analysis (ISO 26262 §7) -cpp-RCP targets **ASIL-B** for the RC Client/RC Server communication -subsystem. The following table records deliberate derogations from -ASIL-D: +cpp-RCP targets **ASIL-C** for the RC Client/RC Server communication +subsystem (corrected from a previously-stated ASIL-B; see `HARA.md`'s +H-001 rationale — S3/E4/C2 maps to ASIL-C, not ASIL-B, and no +decomposition is claimed for it). The table and decomposition paragraph +below predate that correction and still frame this subsystem's +derogation from ASIL-D as an ASIL-B(D) = ASIL-A + ASIL-B decomposition — +per `HARA.md`'s own H-001 rationale, no such decomposition is actually +claimed (ASIL-C is met directly). **This section has not yet been +re-derived for an ASIL-C target and is flagged as a follow-up, not +resolved by this pass** — re-deriving which ASIL-D requirements still +warrant derogation, and on what rationale, at an ASIL-C (rather than +ASIL-B) target is a safety-case judgment call outside the scope of a +documentation-consistency fix. | ASIL-D Requirement | Derogation Rationale | ASIL-B Coverage | |--------------------|----------------------|-----------------| @@ -39,9 +49,11 @@ ASIL-D: | MISRA C++:2023 compliance | MISRA advisory rules selectively suppressed with justification | clang-tidy clean on safety-critical rules | | 100% MC/DC structural coverage | 80% branch coverage enforced in CI | 80% branch + path coverage reported by `cpfusa coverage` | -ASIL decomposition: the RC Client/RC Server link is decomposed as -ASIL-B(D) = ASIL-A + ASIL-B per ISO 26262-9 §5 (independent channel -decomposition) — see `HARA.md`'s per-hazard decomposition rationale. +ASIL decomposition (pre-dates the ASIL-C correction above — see this +section's own flagged-follow-up note): the RC Client/RC Server link was +previously described as decomposed as ASIL-B(D) = ASIL-A + ASIL-B per ISO +26262-9 §5 (independent channel decomposition). `HARA.md`'s current +per-hazard rationale for H-001 claims no such decomposition. --- diff --git a/FORMAL_VERIFICATION.md b/FORMAL_VERIFICATION.md index 15e6759..d6d11e0 100644 --- a/FORMAL_VERIFICATION.md +++ b/FORMAL_VERIFICATION.md @@ -59,20 +59,29 @@ comparison against the high-water mark. **STATUS CORRECTED 2026-08-21 (cpp-RCP issue #129 / RELAY Phase 17 Phase 2 pass) — two distinct corrections, both documentation-only:** -1. **Wiring gap, not algorithm gap.** This section previously implied - `RxSequenceGuard` — and by extension the SP1/SP2 properties verified - here — functions as a real, active mitigation for H-004. It does not: - `RxSequenceGuard` is **never instantiated anywhere outside its own unit - test** — not in `mock::Server`'s dispatch, and not in any transport - `Server`. SP1/SP2 are correctly verified properties of the - `RxSequenceGuard` *primitive itself*, but a formally-verified primitive - that nothing in this codebase calls provides no actual protection - against H-004 today. See `HARA.md`'s own corrected H-004 section for - the full account (mirroring c-RCP's own resolution of the identical - ambiguity, issues #601/#606). Wiring `RxSequenceGuard` into - `rcp/mock.hpp`'s dispatch is explicitly out of scope for this pass - (Phase 4/server-dispatch work) — this correction only makes this - section stop overstating what already-verified fact it establishes. +1. **Wiring gap, now closed for this codebase's own reference dispatch.** + This section previously stated that `RxSequenceGuard` was **never + instantiated anywhere outside its own unit test** — not in + `mock::Server`'s dispatch, and not in any transport `Server`. That was + accurate as of the 2026-08-21 pass that added this note, but is + superseded by Phase 4 batch C, "mock.hpp batch C — wire + RxSequenceGuard, StreamFaultTracker, RxWatchdog" (CHANGELOG.md, cpp-RCP + issue #129, PR #148): `rcp/mock.hpp`'s `Server` now holds a + `std::array seq_trackers_` member, and both + `Server::dispatch_e2e()` and `Server::dispatch_frame_e2e()` call it + (via a shared `seq_gate_admits()` helper) on every dispatched + request/frame before CRC unwrap — integration-tested in + `tests/test_mock.cpp` ("dispatch_e2e's sequence gate (REQ-E2E-028/029) + rejects a non-increasing sequence_num"), not merely the standalone + primitive's own `tests/test_e2e.cpp` unit test. See `HARA.md`'s own + corrected H-004 section and `include/rcp/e2e.hpp`'s file header + ("UPDATE (Phase 4/Phase 17 batch C...)") for the full account + (mirroring c-RCP's own earlier resolution of the identical ambiguity, + issues #601/#606). SP1/SP2 as verified here are therefore properties of + a primitive that **is** now wired into this codebase's own reference + dispatch — with the same "an integrator bypassing `mock::Server`'s own + dispatch is on their own" caveat every primitive in this codebase + carries (see `HARA.md`'s H-004 Residual Risks entry). 2. **This spec models the pre-Phase-2-pass algorithm.** The same Phase 2 pass that produced this correction also content-corrected `e2e::RxSequenceGuard`'s actual comparison rule against c-RCP's diff --git a/HARA.md b/HARA.md index c376eb7..f0c660c 100644 --- a/HARA.md +++ b/HARA.md @@ -53,7 +53,7 @@ Phase 13-16 work are appended as H-011/SG-011. | SG-001 | Requests to safety-critical endpoints shall be delivered within the configured watchdog period or a fault shall be signalled. | ASIL-C | `watchdog::Manager`/`StreamWatchdog`, `deadline::Monitor` | | SG-002 | Requests shall only be dispatched to the endpoint they are addressed to (stream_id + byte_bus_id); misaddressed requests shall be rejected. | ASIL-B | `acf::AcfMessageInfo` byte_bus_id decode, RC Server dispatch (e.g. `mock::Server::dispatch`) | | SG-003 | A per-stream watchdog kick shall be recorded for every accepted inbound request, regardless of request kind or safety tag. | ASIL-C | `watchdog::StreamWatchdog::kick_from_request`, `e2e::RxWatchdog` | -| SG-004 | A request stream configured with rx_enforce_seq shall reject any sequence number that is not strictly greater than the last accepted one. | ASIL-B | **Implemented, not wired** — see H-004's corrected note below. `e2e::RxSequenceGuard::evaluate`/`check` correctly implement the check as a standalone primitive, but are not called from `mock::Server`'s dispatch or any transport `Server`; this goal is not actually met until an integrator wires `RxSequenceGuard` into their own dispatch path. | +| SG-004 | A request stream configured with rx_enforce_seq shall reject any sequence number that is not strictly greater than the last accepted one. | ASIL-B | `e2e::RxSequenceGuard::evaluate`/`check`, wired into `mock::Server::dispatch_e2e()`/`dispatch_frame_e2e()` via `seq_gate_admits()` (Phase 4/Phase 17 batch C, cpp-RCP issue #129, PR #148) — see H-004's corrected note below. | | SG-005 | Cancellation and triggered requests shall never be delayed by a standard or compound request queued earlier on the same stream. | ASIL-B | `request::SequencerTable`/`RequestLedger` execution-priority ordering | | SG-006 | Transport authentication (mTLS on the UDP/IP variant, or link-layer authentication on native Ethernet) and per-endpoint access policy shall be enforced on every external RC Server connection. | ASIL-B | `tls::SecureClient`/`SecureServer`, `authz::AccessPolicy`, `discovery::DiscoveryClaim` | | SG-007 | An RC Server that stops responding shall be detected as unreachable within the configured liveness deadline. | ASIL-C | `deadline::Monitor`/`LivenessTracker` | @@ -119,53 +119,67 @@ endpoint through its own safe-state sequence. ### H-004 (ASIL-B): Request replay / out-of-order delivery -**STATUS CORRECTED 2026-08-21 (cpp-RCP issue #129 / RELAY Phase 17 Phase 2 -pass):** this section previously described `e2e::RxSequenceGuard` in -present-tense mitigating language, as if it were an active, wired -mitigation for H-004. It is not. A Phase 2 audit of this codebase found -`RxSequenceGuard` is implemented and unit-tested (`tests/test_e2e.cpp`) -but is **never instantiated anywhere outside its own unit test** — not in -`mock::Server`'s dispatch, and not in any transport `Server`. No caller -anywhere in this tree evaluates a real AVTPDU's `sequence_num` through it, -so linking this library today gives an integrator no actual protection -against H-004. - -c-RCP resolved the identical ambiguity for itself (issues #601/#606): it -found its own equivalent (`rcp_e2e_seq_evaluate()`/`rcp_e2e_seq_tracker_t`) -genuinely **is** wired into its own reference dispatch (`mock.c`'s -`frame_seq_gate_admits()`, called once per AVTPDU frame), and documented -the mitigation as "Mitigated (opt-in)" with an explicit residual-risk list -(opt-in config bits, no cross-restart persistence, the RFC 1982 -`[1,127]` forward-window bound, per-frame not per-message granularity) — -see c-RCP's `HARA.md` H-004 section and `include/rcp/e2e.h`'s file header. -cpp-RCP is not yet at that point. - -`e2e::RxSequenceGuard::evaluate()`/`check()` are a correct, content- -verified primitive as of this same pass (re-derived against c-RCP's -`rcp_e2e_seq_evaluate()`: an RFC 1982 forward-window comparison over the -8-bit AVTPDU `sequence_num` space — accept iff the forward distance from -the last accepted value lies in `[1, 127]` when `rx_enforce_seq` is set, -with a separate `rx_seq_safestate_enable`-gated discontinuity signal for -an increase-by-more-than-one gap; see `e2e.hpp`'s own doc comment for the -full rationale and what this pass corrected from a previous, non-wrapping -`uint32_t` comparison that never consulted `rx_seq_safestate_enable` at -all). But **implemented is not the same as wired**: this header provides -primitives, not a running dispatcher, same as every other header in this -codebase — deciding when to call `evaluate()` against real inbound traffic -is left to the embedding application, and no embedding application in -this tree does so yet. Wiring `RxSequenceGuard` into `rcp/mock.hpp`'s -dispatch (or any transport `Server`) is explicitly **out of scope** for -this pass — it is Phase 4 (server/dispatch) work — so this correction is -documentation-only: it makes this file accurately describe the wiring gap -that already existed, rather than closing it. - -H-004 is therefore **effectively Open** for this hazard's practical -purposes — an integrator gets no protection against replay/out-of-order -delivery by linking this library alone — notwithstanding the primitive's -existence and correctness. This does not change H-004's S2/E3/C2/ASIL-B -classification (the hazard's worst case, "no sequence check evaluated at -all," is exactly what every current caller already experiences); it -corrects SG-004's own "Addressed By" claim below to say so. +**STATUS CORRECTED 2026-08-22 (cpp-RCP issue #129 / RELAY Phase 17 Phase 4 +batch C pass):** the previous correction below (2026-08-21) stated that +`e2e::RxSequenceGuard` was implemented and content-correct but **never +instantiated anywhere outside its own unit test** — accurate at the time, +but superseded by Phase 4 batch C, "mock.hpp batch C — wire +RxSequenceGuard, StreamFaultTracker, RxWatchdog" (CHANGELOG.md, PR #148). +`rcp/mock.hpp`'s `Server` now holds a +`std::array +seq_trackers_` member, and both `Server::dispatch_e2e()` (single-member) +and `Server::dispatch_frame_e2e()` (multi-member, frame-level) call it — +via a shared `seq_gate_admits()` helper — on every dispatched +request/frame, before CRC unwrap, evaluating the real AVTPDU +`sequence_num` against `RxSequenceGuard::evaluate()`. This is real, +wired, integration-tested behavior: `tests/test_mock.cpp` includes +"dispatch_e2e's sequence gate (REQ-E2E-028/029) rejects a non-increasing +sequence_num" as a dispatch-level test, distinct from and in addition to +`RxSequenceGuard`'s own standalone `tests/test_e2e.cpp` unit test. See +`include/rcp/e2e.hpp`'s own file header ("UPDATE (Phase 4/Phase 17 batch +C...)") for the authoritative account of this wiring. + +c-RCP resolved the identical ambiguity for itself earlier (issues +#601/#606): its own equivalent (`rcp_e2e_seq_evaluate()`/ +`rcp_e2e_seq_tracker_t`) is wired into its own reference dispatch +(`mock.c`'s `frame_seq_gate_admits()`, called once per AVTPDU frame), and +documented as "Mitigated (opt-in)" with an explicit residual-risk list +(opt-in config bits, no cross-restart persistence, the RFC 1982 `[1,127]` +forward-window bound, per-frame not per-message granularity) — see +c-RCP's `HARA.md` H-004 section and `include/rcp/e2e.h`'s file header. +cpp-RCP's own `mock::Server` reference dispatch is now at that same +point, for both of its own dispatch paths (single-member and +frame-level). + +`e2e::RxSequenceGuard::evaluate()`/`check()` remain the correct, content- +verified primitive established by the prior pass (an RFC 1982 +forward-window comparison over the 8-bit AVTPDU `sequence_num` space — +accept iff the forward distance from the last accepted value lies in +`[1, 127]` when `rx_enforce_seq` is set, with a separate +`rx_seq_safestate_enable`-gated discontinuity signal for an +increase-by-more-than-one gap; see `e2e.hpp`'s own doc comment for the +full rationale). What has changed is that this primitive is no longer +merely implemented — it is now called against real inbound traffic by +this codebase's own reference dispatch. + +One caveat remains, honestly stated: being wired into THIS codebase's own +`mock::Server` reference implementation does not by itself protect an +integrator who drives a transport `Server` (`rcp/udp.hpp`, `rcp/l2.hpp`) +with their own dispatch logic instead of `mock::Server::dispatch_frame()`/ +`dispatch_frame_e2e()` as the `FrameHandler` — the same "primitives, not +a mandate" caveat every header in this codebase carries. `udp.hpp`'s and +`l2.hpp`'s own header comments recommend wiring `mock::Server`'s +frame-level dispatch as the `FrameHandler` precisely so this and other +frame-level behaviors are not silently bypassed. + +H-004 is therefore **Mitigated (opt-in)**, matching c-RCP's own +disposition, for any integrator using this codebase's `mock::Server` +reference dispatch with `rx_enforce_seq` set on the relevant stream. This +does not change H-004's S2/E3/C2/ASIL-B classification — the hazard's +worst case (no sequence check evaluated at all) is now the state of a +stream with `rx_enforce_seq` clear, a deliberate per-endpoint +configuration choice documented as an accepted residual risk below, not +this library's reference-dispatch default behavior. ### H-006 (ASIL-B): Execution-priority inversion @@ -211,7 +225,7 @@ or best-effort behavior is involved. | Risk | Likelihood | Mitigation | Status | |------|-----------|------------|--------| -| `e2e::RxSequenceGuard` is implemented and content-correct but not wired into `mock::Server`'s dispatch or any transport `Server` (H-004) | Medium | None within this library today — an integrator must call `RxSequenceGuard::evaluate()`/`check()` from their own dispatch loop, per AVTPDU frame, for H-004's SG-004 goal to actually be met; wiring it into `mock.hpp` is tracked as Phase 4 (server/dispatch) work, cpp-RCP issue #129 | **Open — documented gap, not silently overstated (see H-004's corrected note)** | +| An integrator driving a transport `Server` (`udp.hpp`/`l2.hpp`) with their own dispatch logic, instead of wiring `mock::Server::dispatch_frame()`/`dispatch_frame_e2e()` as the `FrameHandler`, bypasses the now-wired `RxSequenceGuard` gate (H-004) | Low | `udp.hpp`/`l2.hpp`'s own header comments document wiring `mock::Server`'s frame-level dispatch as the `FrameHandler` as the correct integration path; `RxSequenceGuard::evaluate()`/`check()` remain directly callable for a fully custom dispatch loop | **Mitigated (opt-in) for `mock::Server`'s own reference dispatch — wired via `seq_gate_admits()`/`dispatch_e2e()`/`dispatch_frame_e2e()`, Phase 4 batch C, cpp-RCP issue #129 (see H-004's corrected note)** | | A stream with `rx_enforce_seq` clear accepts replayed/out-of-order requests by design | Low | Per-endpoint configuration choice; safety-relevant streams are expected to set `rx_enforce_seq` | Accepted | | `tls::SecureClient`/`SecureServer` require an application-supplied OpenSSL/wolfSSL backend; the interface itself performs no cryptography | Low | Native Ethernet deployments should prefer MACsec (802.1AE) at layer 2 instead, per `tls.hpp`'s own header note | Accepted | | `mdns.hpp`'s discovery-adjacent name service is rescoped to the UDP/IP transport variant only | Low | TC18's own wire-level discovery (`discovery.hpp`, v2.2.0) covers native Ethernet deployments without a name-service layer | Accepted | diff --git a/README.md b/README.md index 8b3ad83..5af5f96 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ Errors are returned as `std::error_code` values in the `rcp` category. cpp-RCP targets deployment in automotive safety-critical environments. -- Safety standard: ISO 26262 ASIL-B / IEC 61508 SIL-2 +- Safety standard: ISO 26262 ASIL-C / IEC 61508 SIL-2 (corrected from ASIL-B; see `HARA.md`'s H-001 rationale) - Security standard: IEC 62443 SL-2 - cpp-FuSa static analysis runs in CI on every PR - All requirements are traced to tests in `.fusa-reqs.json` diff --git a/SAFETY_PLAN.md b/SAFETY_PLAN.md index b61d5a0..321d33f 100644 --- a/SAFETY_PLAN.md +++ b/SAFETY_PLAN.md @@ -2,38 +2,51 @@ ## Scope -cpp-RCP is the C++ implementation of the Remote Control Protocol for automotive zonal architecture targeting ISO 26262 ASIL-B / IEC 61508 SIL-2. +cpp-RCP is the C++ implementation of the Remote Control Protocol for automotive zonal architecture targeting ISO 26262 ASIL-C / IEC 61508 SIL-2 (corrected from a previously-stated ASIL-B; see `HARA.md`'s H-001 rationale — S3/E4/C2 maps to ASIL-C, not ASIL-B, and no decomposition is claimed). ## Safety standard | Standard | Target level | |---|---| -| ISO 26262 | ASIL-B | +| ISO 26262 | ASIL-C | | IEC 61508 | SIL-2 | | IEC 62443 | SL-2 | ## Safety goals +Descriptions and ASIL levels below are kept in sync with `.fusa-hara.json` +(the authoritative machine-readable source) and `HARA.md`. The +pre-replacement Zone/Command/Controller terminology this table previously +used ("Commands", "CmdWatchdog", "Dead zone") has been retired along with +that model — see `HARA.md`'s own note on the milestone-62 supersession. + | ID | Description | ASIL | |---|---|---| -| SG-001 | Commands delivered within watchdog period or fault signalled | ASIL-B | -| SG-002 | Misrouted commands rejected | ASIL-B | -| SG-003 | CmdWatchdog always deliverable | ASIL-B | -| SG-004 | Replayed commands detected and rejected | ASIL-B | -| SG-007 | Dead zone detected within configured deadline | ASIL-B | +| SG-001 | Requests to safety-critical endpoints shall be delivered within the configured watchdog period or a fault shall be signalled. | ASIL-C | +| SG-002 | Requests shall only be dispatched to the endpoint they are addressed to (stream_id + byte_bus_id); misaddressed requests shall be rejected. | ASIL-B | +| SG-003 | A per-stream watchdog kick shall be recorded for every accepted inbound request, regardless of request kind or safety tag. | ASIL-C | +| SG-004 | A request stream configured with rx_enforce_seq shall reject any sequence number that is not strictly greater than the last accepted one. | ASIL-B | +| SG-007 | An RC Server that stops responding shall be detected as unreachable within the configured liveness deadline. | ASIL-C | ## Safety mechanisms +The pre-replacement Zone/Command/Controller model (`REQ-CTRL-*`, +`REQ-ERR-011`) this table previously cited was retired at the milestone-62 +Certification Refresh (see `CYBERSECURITY.md`/`TARA-ANALYSIS.md`) and no +longer exists in `.fusa-reqs.json` or `include/`. Rows below are repointed +at the real, currently-existing requirements and mechanisms that replaced +it, verified directly against `.fusa-reqs.json` and the relevant headers. + | Mechanism | Requirement | Description | |---|---|---| -| Zone mismatch detection | REQ-CTRL-025, REQ-ERR-011 | Controller rejects commands addressed to a different zone | -| Payload copy-on-send | REQ-CTRL-026 | Payload is deep-copied before handler invocation | -| Payload copy-on-publish | REQ-CTRL-027 | Published payload is deep-copied before delivery to subscribers | -| Context / deadline propagation | REQ-CTRL-004 | Expired context terminates Send without invoking the handler | -| Watchdog kick | REQ-WDG-002 | Any accepted inbound request resets ("kicks") its stream's watchdog | +| Endpoint addressing validation | REQ-MOCK-010 | `mock::Server::dispatch` rejects a request whose byte_bus_id addresses no known endpoint, returning `invalid_parameter` — the current stream/endpoint addressing model; replaces the retired zone/Controller lookup, which has no direct analog in a byte_bus_id-addressed architecture | +| Watchdog kick | REQ-WDG-002 | `watchdog::StreamWatchdog::kick_from_request` resets that stream's watchdog on every accepted inbound request, regardless of request kind or safety tag | | Sequence guard | REQ-E2E-007 | RxSequenceGuard rejects a non-strictly-increasing sequence number when rx_enforce_seq is set | -| Deadline monitor | REQ-DL-002 | Monitor detects a zone going silent within Config.Deadline | +| Deadline monitor | REQ-DL-002 | `deadline::Monitor` detects an RC Server going silent within the configured liveness deadline (heartbeat or lifecycle-change signal) | | Execution-priority ordering | REQ-SEQ-002 | Cancellation and triggered requests dispatch ahead of standard/compound requests queued earlier on the same stream | +| Payload copy-on-send *(retired)* | — | No direct analog. This was the pre-replacement in-process `Controller`'s defensive deep-copy before invoking a registered handler; the current stream/endpoint/register-map wire-protocol architecture has no equivalent client-side handler-invocation step to protect | +| Payload copy-on-publish *(retired)* | — | No direct analog, for the same reason — the pre-replacement pub/sub `Controller::Publish`/subscriber model is gone. `admin.hpp`'s SSE/event broadcast (REQ-ADMIN-002) is a different, non-safety-relevant surface (in-process observability, not request payload delivery) and is not offered as a substitute | +| Context / deadline propagation *(retired)* | — | No direct analog. The pre-replacement per-call context-cancellation ("expired context terminates Send without invoking the handler") has no current equivalent; the closest current mechanisms are client-side request timeout (REQ-UDP-010) and the watchdog/deadline-monitor rows above, neither of which reproduces "suppress handler invocation on an already-expired call" | ## Verification approach diff --git a/TARA-ANALYSIS.md b/TARA-ANALYSIS.md index f000b1a..00ac463 100644 --- a/TARA-ANALYSIS.md +++ b/TARA-ANALYSIS.md @@ -2,7 +2,7 @@ ## ISO 21434 / IEC 62443 SL-2 **Document version**: 2.0.0 -**ASIL level**: ASIL-B (ISO 26262) / SIL-2 (IEC 61508) +**ASIL level**: ASIL-C (ISO 26262, corrected from ASIL-B — see `HARA.md`'s H-001 rationale) / SIL-2 (IEC 61508) **Date**: 2026-07-28 --- @@ -74,7 +74,7 @@ Ratings: Feasibility (1-5) × Impact (1-5) = Risk Score Residual risks above threshold (score ≥ 10): T-01, T-02, T-04, T-05, T-11. All are covered by implemented mitigations. Residual risk is accepted at -ASIL-B. +ASIL-C (corrected from ASIL-B; see `HARA.md`'s H-001 rationale). --- @@ -100,7 +100,8 @@ ASIL-B. All identified threats have corresponding mitigations aligned with IEC 62443 SL-2 security level requirements. The residual risk profile is -acceptable for ASIL-B / SIL-2 operation. A re-assessment is required when: +acceptable for ASIL-C / SIL-2 operation (corrected from ASIL-B; see +`HARA.md`'s H-001 rationale). A re-assessment is required when: - The threat landscape changes (new attack vectors identified) - A new protocol bridge (`mqttbr.hpp`/`ddsbr.hpp`/`someipbr.hpp`/ `restbridge.hpp`/`grpcbridge.hpp`/`udsbr.hpp`/`doipbr.hpp`) moves past its