diff --git a/.fusa-reqs-pending.json b/.fusa-reqs-pending.json index 3ed0879..262239f 100644 --- a/.fusa-reqs-pending.json +++ b/.fusa-reqs-pending.json @@ -1221,6 +1221,200 @@ "tc18": "§13.7.2.1, TC18.txt L4482-4483 -- CLOSED 2026-08-13 (tc18-gap backlog PR G, issue #336): enforces at write time the invariant REQ-WAKEUP-020's own diagnostic only ever detected after the fact. Static ep_id_map_write_keeps_fixed_ep_id() (src/regmap.c) peeks what a write's own touched row(s) would become -- via the same render-then-patch half of rcp_regmap_ep_id_map_apply_reconfig()'s own idiom, stopped short of committing. Same caller-supplied-target design as REQ-WAKEUP-020's own diagnostic: regmap.c keeps no dependency on ep_wakeup.h and does not hardcode RCP_EP_WAKEUP_EP_TYPE/RCP_EP_WAKEUP_ENDPOINT_NUM -- a real caller supplies ep_wakeup.h's own concrete values. Split 2026-08-18 (issue #533 tracker, [c-RCP-18-tracker] REQ-WAKEUP batch, atomicity audit) from REQ-WAKEUP-020's own prior bundled text, which described this enforcement mechanism alongside the unrelated read-only diagnostic function under one id.", "tc18_master_id": "TC18-13.7.2.1-001", "gap": "Genuine behavioral gap (2026-08-22, cpp-RCP Phase 6 batch 10 (SPI/UART/WAKEUP)), NOT an id-collision. This requirement's real content lives in c-RCP's regmap.c (rcp_regmap_ep0_decode_write_request()'s fixed-ep_id write-time enforcement), not ep_wakeup.c -- it uses the REQ-WAKEUP prefix only because the invariant it enforces is WakeUp's own fixed EP_Nr (TC18 §13.7.2.1). cpp-RCP's include/rcp/regmap.hpp already ports the READ-ONLY diagnostic half of this pair (REQ-WAKEUP-020's own ep_id_map::ep_type_has_fixed_ep_id(), fully implemented and tested -- see that id's own catalog entry) but does NOT port the WRITE-TIME enforcement half: ep_id_map::apply_reconfig() (regmap.hpp, the EP_ID_config table's own reconfig path) applies every in-range write unconditionally -- it never calls ep_type_has_fixed_ep_id() (or an equivalent would-be-result check) before committing, so a conforming write that would leave WakeUp's own EP_Nr row at anything other than the required fixed value 1 is silently accepted rather than denied with RCP_ERROR_INVALID_PARAMETER as c-RCP's own rcp_regmap_ep0_decode_write_request() does. No test exists for this rejection path because the rejection path itself does not exist. Fix would add a caller-supplied (target_ep_type, required_ep_id, ep_types[]) parameter set to ep_id_map::apply_reconfig() (or a wrapper around it) that peeks the would-be-patched result via the same render-then-check idiom the read-only diagnostic already uses, and rejects before committing -- out of this batch's scope (regmap.hpp is not one of this batch's three in-scope modules)." + }, + { + "id": "REQ-RMAP-001", + "title": "is_ep0() identifies EP0 by index", + "text": "is_ep0(ep_index) shall return true iff ep_index == kEp0.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-001 ('Generic vs. functional endpoint config split'). Genuinely implemented+tested: regmap::is_ep0() -- tests/test_regmap.cpp's \"is_ep0 is true only for EP0's own index\" TEST_CASE (tagged [REQ-RMAP-001], sharing that id string with this catalog's own now-renamed REQ-RMAP-001 at test_regmap.cpp's later \"EndpointGenericConfig and EndpointFunctionalConfig are distinct...\" TEST_CASE)." + }, + { + "id": "REQ-RMAP-002", + "title": "EP0 shares its index with the discovery byte_bus_id", + "text": "kEp0 and discovery's kDiscoveryByteBusId shall be the same numeric value, so discovery and the general register map are reached through the same address.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-002 ('EP0 whole-register-map read is unrestricted'). Genuinely implemented+tested: discovery.hpp's own kDiscoveryByteBusId is defined directly in terms of regmap::kEp0 (`constexpr avtp::ByteBusId kDiscoveryByteBusId = static_cast(regmap::kEp0);`, include/rcp/discovery.hpp) -- a compile-time equality, not merely a runtime-tested one; no dedicated REQUIRE-based test exists for this specific fact (discovery.hpp/its own tests are out of this batch's scope, so none was added here)." + }, + { + "id": "REQ-RMAP-003", + "title": "general_init() zero-initializes with no root client granted", + "text": "A default-constructed GeneralMap shall have every field zero and svr_root_client_index == kNoRootClient (no root client granted).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-003 ('EP0 whole-register-map write requires the root client'). Genuinely implemented+tested: GeneralMap's default member initializers -- tests/test_regmap.cpp's \"GeneralMap default-constructs zeroed, with the no-root-client sentinel\" TEST_CASE (tagged [REQ-RMAP-003], sharing that id string with this catalog's own now-renamed REQ-RMAP-003 at test_regmap.cpp's earlier \"Only the root client may write the whole register map through EP0\" TEST_CASE)." + }, + { + "id": "REQ-RMAP-009", + "title": "writer_ctx() grants via_root_client_ep0 for the root client through EP0", + "text": "writer_ctx() shall set via_root_client_ep0 true iff the caller is the currently-claimed root client, addressing EP0, on a unicast frame.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-009 ('Generic vs. functional endpoint config split'). Genuinely implemented+tested: regmap::writer_ctx() -- tests/test_regmap.cpp's \"writer_ctx grants via_root_client_ep0 only via EP0 on the exact root-client stream\" and \"writer_ctx denies via_root_client_ep0 when no root client is configured\" TEST_CASEs (tagged [REQ-RMAP-009])." + }, + { + "id": "REQ-RMAP-010", + "title": "writer_ctx() withholds via_root_client_ep0 while no root client is granted", + "text": "writer_ctx() shall set via_root_client_ep0 false whenever svr_root_client_index == kNoRootClient, regardless of any other argument.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-010 ('EP0 whole-register-map read is unrestricted'). Genuinely implemented+tested: regmap::writer_ctx() -- tests/test_regmap.cpp's \"writer_ctx denies via_root_client_ep0 when no root client is configured\" TEST_CASE (tagged [REQ-RMAP-010])." + }, + { + "id": "REQ-RMAP-011", + "title": "writer_ctx() grants via_owning_stream to an endpoint's owning stream", + "text": "writer_ctx() shall set via_owning_stream true iff the caller's EpClient has has_owning_stream true and owning_stream_index matches the caller's own stream index.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-011 ('Response/ack queue config fields'). Genuinely implemented+tested: regmap::writer_ctx() -- tests/test_regmap.cpp's \"writer_ctx grants via_owning_stream only for the matching stream index\" TEST_CASE, dual-tagged [REQ-RMAP-010][REQ-RMAP-011][REQ-RMAP-012] in this batch." + }, + { + "id": "REQ-RMAP-012", + "title": "writer_ctx() withholds via_owning_stream with no owning stream on record", + "text": "writer_ctx() shall set via_owning_stream false whenever the caller's EpClient has has_owning_stream false, or no EpClient (nullptr) is supplied.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-012 ('Sequencer-state registers persist independent 8-bit values'). Genuinely implemented+tested: regmap::writer_ctx() -- tests/test_regmap.cpp's \"writer_ctx grants via_owning_stream only for the matching stream index\" TEST_CASE (its REQUIRE_FALSE(...) branches for a nullptr EpClient and a has_owning_stream==false EpClient), dual-tagged [REQ-RMAP-010][REQ-RMAP-011][REQ-RMAP-012] in this batch." + }, + { + "id": "REQ-RMAP-013", + "title": "HW pin-property bit assignments are pairwise distinct", + "text": "hw_pin::kPullMask, kStageMask, kDriveMask, and kSchmittTrigger shall be pairwise non-overlapping bitmasks within hw_pin_type's own 8-bit register.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-013 ('INVALID_PARAMETER on out-of-range register-map requests'). Genuinely implemented+tested: hw_pin::k* constants -- tests/test_regmap.cpp's \"hw_pin bit-layout constants are non-overlapping within their own sub-field\" TEST_CASE, dual-tagged [REQ-RMAP-013][REQ-RMAP-042][REQ-RMAP-043] in this batch." + }, + { + "id": "REQ-RMAP-014", + "title": "named_signal_string() never returns NULL for an in-range value", + "text": "named_signal_string() shall return a non-null, non-empty string for every NamedSignal value in 0..Count-1.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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-014 ('The four mandatory register-map error codes are distinct'). Genuinely implemented+tested: regmap::named_signal_string() -- tests/test_regmap.cpp's \"named_signal_string never returns an empty string for a valid signal\" TEST_CASE, dual-tagged [REQ-RMAP-044][REQ-RMAP-014] in this batch." + }, + { + "id": "REQ-RMAP-015", + "title": "named_signal_string() returns a unique name per signal", + "text": "named_signal_string() shall return a distinct string for every distinct NamedSignal value in 0..Count-1.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "", + "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", + "text": "The EP0 address-routed write dispatcher shall silently no-op a write touching a read-only octet within an otherwise-writable table, while reporting an error for a write to a wholly write-prohibited table/state -- distinguishing the two outcomes per-octet, not per-request.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18-gap", + "status": "not-implemented", + "tc18": "§12.9.6 Table 30 (\"Error codes in responses\")", + "tc18_master_id": "TC18-12.9.6-004", + "gap": "Genuine not-implemented gap, matching this file's own explicit scope-exclusion (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)): regmap.hpp's own file-header comment states outright that c-RCP's single ~500-line EP0 address-routed dispatcher (rcp_regmap_ep0_decode_write_request()/_decode_read_request()/_encode_read_response(), a cross-cutting orchestrator combining every sub-table's own wire codec with lifecycle-state/writer authorization and SEQUENCER_config's own ownership-aware access control) is NOT ported in this batch. This id's own behavior is specific to that unported dispatcher; the individual sub-table wire codecs it would orchestrate (GeneralMap, hw_pin_map, ep_generic_cfg, request_stream_cfg, ep_id_map, response-queue config) are each independently implemented and tested under their own ids. Filed as a real gap, not force-fit as implemented -- out of this batch's own scope to build the dispatcher itself (a materially larger, separate feature)." + }, + { + "id": "REQ-RMAP-069", + "title": "Effective register-write payload length is derived from acf_msg_length", + "text": "The EP0 address-routed write dispatcher shall derive the effective write-payload length as (acf_msg_length - 3) * 4 - pad - 2, matching the ACF_ABB wire framing's own quadlet-padding accounting, before applying any per-table write.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18-gap", + "status": "not-implemented", + "tc18": "§11.2.2 (ACF_ABB byte_message_info framing)", + "tc18_master_id": "TC18-11.2.2-001", + "gap": "Genuine not-implemented gap, matching this file's own explicit scope-exclusion (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)): regmap.hpp's own file-header comment states outright that c-RCP's single ~500-line EP0 address-routed dispatcher (rcp_regmap_ep0_decode_write_request()/_decode_read_request()/_encode_read_response(), a cross-cutting orchestrator combining every sub-table's own wire codec with lifecycle-state/writer authorization and SEQUENCER_config's own ownership-aware access control) is NOT ported in this batch. This id's own behavior is specific to that unported dispatcher; the individual sub-table wire codecs it would orchestrate (GeneralMap, hw_pin_map, ep_generic_cfg, request_stream_cfg, ep_id_map, response-queue config) are each independently implemented and tested under their own ids. Filed as a real gap, not force-fit as implemented -- out of this batch's own scope to build the dispatcher itself (a materially larger, separate feature)." + }, + { + "id": "REQ-RMAP-072", + "title": "The EP0 dispatcher consults lifecycle-state/writer/lock authorization for all 4 pointed-to tables before applying any write", + "text": "The EP0 address-routed write dispatcher shall consult lifecycle-state/writer authorization (and, where applicable, an explicit lock) for the HW_config, request-stream-cfg, EP_ID_config, and ep_generic_cfg tables uniformly before applying any incoming write to any of them, rather than authorizing per-table with divergent rules.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18-gap", + "status": "not-implemented", + "tc18": "§12.7 Configuration (cross-table dispatcher authorization)", + "tc18_master_id": "TC18-12.7.5-001", + "gap": "Genuine not-implemented gap, matching this file's own explicit scope-exclusion (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)): regmap.hpp's own file-header comment states outright that c-RCP's single ~500-line EP0 address-routed dispatcher (rcp_regmap_ep0_decode_write_request()/_decode_read_request()/_encode_read_response(), a cross-cutting orchestrator combining every sub-table's own wire codec with lifecycle-state/writer authorization and SEQUENCER_config's own ownership-aware access control) is NOT ported in this batch. This id's own behavior is specific to that unported dispatcher; the individual sub-table wire codecs it would orchestrate (GeneralMap, hw_pin_map, ep_generic_cfg, request_stream_cfg, ep_id_map, response-queue config) are each independently implemented and tested under their own ids. Filed as a real gap, not force-fit as implemented -- out of this batch's own scope to build the dispatcher itself (a materially larger, separate feature)." + }, + { + "id": "REQ-RMAP-080", + "title": "The EP0 dispatcher routes svr_ep_generic_cfg_ptr's own extent to ep_generic_cfg's render()/apply_reconfig() in both directions", + "text": "The EP0 address-routed read/write dispatcher shall route any request whose address falls within svr_ep_generic_cfg_ptr's own [ptr, ptr+capacity) extent to ep_generic_cfg::render()/apply_reconfig(), the 6th and final EP0 dispatcher extent (after Table 20, HW_config, request-stream-cfg, EP_ID_config, and response-queue config).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18-gap", + "status": "not-implemented", + "tc18": "§12.7.5 Table 20 (svr_ep_generic_cfg_ptr) + §13.2", + "tc18_master_id": "TC18-12.7.5-001", + "gap": "Genuine not-implemented gap, matching this file's own explicit scope-exclusion (2026-08-22, cpp-RCP Phase 6 batch 11 (RMAP/SRV)): regmap.hpp's own file-header comment states outright that c-RCP's single ~500-line EP0 address-routed dispatcher (rcp_regmap_ep0_decode_write_request()/_decode_read_request()/_encode_read_response(), a cross-cutting orchestrator combining every sub-table's own wire codec with lifecycle-state/writer authorization and SEQUENCER_config's own ownership-aware access control) is NOT ported in this batch. This id's own behavior is specific to that unported dispatcher; the individual sub-table wire codecs it would orchestrate (GeneralMap, hw_pin_map, ep_generic_cfg, request_stream_cfg, ep_id_map, response-queue config) are each independently implemented and tested under their own ids. Filed as a real gap, not force-fit as implemented -- out of this batch's own scope to build the dispatcher itself (a materially larger, separate feature)." } ] } diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 600b53c..550f8ed 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -2327,7 +2327,7 @@ "asil": "ASIL-B" }, { - "id": "REQ-REGMAP-001", + "id": "REQ-RMAP-001", "title": "Generic vs. functional endpoint config split", "text": "rcp::regmap shall represent an endpoint's server-owned generic config (EndpointGenericConfig: HW pin indices, request/response queue sizes) and its endpoint-type-specific functional config (EndpointFunctionalConfig) as distinct, independently settable types.", "standard": "iso26262", @@ -2338,7 +2338,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-002", + "id": "REQ-RMAP-002", "title": "EP0 whole-register-map read is unrestricted", "text": "Ep0::check_read_access shall return no error for any in-range target, including EP0 itself, regardless of which client is asking, and Ep0::read_whole_map shall return the current RegisterMap contents to any caller.", "standard": "iso26262", @@ -2349,7 +2349,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-003", + "id": "REQ-RMAP-003", "title": "EP0 whole-register-map write requires the root client", "text": "Ep0::write_whole_map shall return RegMapErrc::unauthorized_access and leave the RegisterMap unchanged when called by a client that is not the currently claimed root client (svr_root_client_index), and shall apply the write when called by the root client.", "standard": "iso26262", @@ -2360,7 +2360,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-004", + "id": "REQ-RMAP-004", "title": "Root-client claim is exclusive", "text": "Ep0::claim_root_client shall return RegMapErrc::request_rejected when a client distinct from the currently claimed root client attempts to claim the slot, shall treat the current root client re-claiming its own slot as a no-op success, and shall allow a new claim once Ep0::release_root_client has been called.", "standard": "iso26262", @@ -2371,7 +2371,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-005", + "id": "REQ-RMAP-005", "title": "Per-endpoint write restriction for non-root clients", "text": "Ep0::check_write_access (and the write_generic_config/write_functional_config calls built on it) shall permit a non-root client to write only the FUNCTIONAL config block of an endpoint it owns per Ep0::set_endpoint_owner (TC18 section 13.1 grants a non-ROOT_CLIENT write access to EP_FUNC_config only; section 13.2 places the generic block under the RC Server's ownership), shall return RegMapErrc::unauthorized_access for a non-root client writing the GENERIC config block of any endpoint including one it owns, shall return RegMapErrc::unauthorized_access for a non-root client writing either block of an endpoint it does not own, shall always permit the root client, and shall return RegMapErrc::invalid_parameter for an out-of-range endpoint id.", "standard": "iso26262", @@ -2382,7 +2382,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-006", + "id": "REQ-RMAP-006", "title": "Config-block locks are enforced independently by EP0", "text": "Ep0::write_generic_config shall return RegMapErrc::locked_mem_access once ServerLifecycle::generic_config_locked is true, and Ep0::write_functional_config shall return RegMapErrc::locked_mem_access once ServerLifecycle::functional_config_locked is true, even when called by a client that REQ-REGMAP-005 would otherwise authorize for that block.", "standard": "iso26262", @@ -2393,7 +2393,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-007", + "id": "REQ-RMAP-007", "title": "General bootstrap register-map fields", "text": "RegisterMap shall hold magic, protocol_version_major/minor, vendor_id, device_id, endpoint_count, max_streams, max_queue_depth, svr_implemented_options, and a TablePointer (offset+capacity) for each of the HW pin-map, request-stream, response-stream, EP-ID mapping, and functional-config tables, all independently settable and retrievable.", "standard": "iso26262", @@ -2404,7 +2404,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-008", + "id": "REQ-RMAP-008", "title": "HW pin-mapping config storage", "text": "RegisterMap::hw_pin_map shall store an ordered list of HwPinMapEntry (pin_id, function) values and return them unchanged.", "standard": "iso26262", @@ -2415,7 +2415,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-009", + "id": "REQ-RMAP-009", "title": "RequestStreamConfig carries the full v2.6.0 watchdog/safe-state register set", "text": "regmap::RequestStreamConfig shall carry rx_wd_timeout_interval, rx_wd_enable, rx_wd_safestate_enable, rx_wd_info_enable, rx_enforce_e2e, rx_enforce_seq, rx_seq_safestate_enable, rx_ovrflw_safestate_enable, rx_safety_measure, rx_safestate_sequencer, and rx_safe_sequencer_state as independently settable fields, each defaulting to disabled/false/RxSafetyMeasure::ForceHighImpedance.", "standard": "iso26262", @@ -2426,7 +2426,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-010", + "id": "REQ-RMAP-010", "title": "EP-ID/byte_bus_id mapping preserves client insertion order", "text": "RegisterMap::ep_id_mapping shall preserve EpIdMappingEntry values in the exact order a client inserted them, without the server re-deriving, validating, or re-sorting an ordering of its own.", "standard": "iso26262", @@ -2437,7 +2437,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-011", + "id": "REQ-RMAP-011", "title": "Response/ack queue config fields", "text": "ResponseQueueConfig shall provide independently settable response_queue_size and ack_queue_size fields.", "standard": "iso26262", @@ -2448,7 +2448,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-012", + "id": "REQ-RMAP-012", "title": "Sequencer-state registers persist independent 8-bit values", "text": "RegisterMap::sequencer_states shall store one persistent 8-bit SequencerState value per configured slot, each independently settable and retrievable, ahead of the sequencing behavior introduced in v2.5.0.", "standard": "iso26262", @@ -2459,7 +2459,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-013", + "id": "REQ-RMAP-013", "title": "INVALID_PARAMETER on out-of-range register-map requests", "text": "Ep0::check_read_access, Ep0::check_write_access, and Ep0::set_endpoint_owner shall return RegMapErrc::invalid_parameter for a target endpoint id outside the configured endpoint_count range, or (for set_endpoint_owner) for a request to assign an owner to EP0 itself.", "standard": "iso26262", @@ -2470,7 +2470,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-014", + "id": "REQ-RMAP-014", "title": "The four mandatory register-map error codes are distinct", "text": "rcp::regmap::RegMapErrc shall define UNAUTHORIZED_ACCESS, LOCKED_MEM_ACCESS, REQUEST_REJECTED, and INVALID_PARAMETER as pairwise-distinct error codes in their own error_category, each with a non-empty diagnostic message.", "standard": "iso26262", @@ -2479,7 +2479,7 @@ "severity": "cybersecurity" }, { - "id": "REQ-REGMAP-015", + "id": "REQ-RMAP-015", "title": "EndpointGenericConfig's per-endpoint E2E CRC safe-mode toggles", "text": "regmap::EndpointGenericConfig::ep_req_crc_enable, ep_ack_crc_enable, and ep_response_crc_enable shall each default to false and shall be independently settable without affecting one another.", "standard": "iso26262", @@ -10283,6 +10283,1216 @@ "status": "implemented", "tc18": "§12.7.1 (\"Any payload whose length plus the start address exceeds EP_LEN is to be ignored\") -- split 2026-08-18 (issue #533 tracker, [c-RCP-18-tracker] REQ-WAKEUP batch, atomicity audit) from REQ-WAKEUP-021's own prior bundled text; this id's own general write-validation/orchestration contract had no dedicated id at all before this split, its coverage having been carried only incidentally by REQ-WAKEUP-021/-022's own (now-narrowed) tags on the same function.", "tc18_master_id": "TC18-13.7.2.2-002" + }, + { + "id": "REQ-RMAP-016", + "title": "EndpointGenericConfig zero-initializes generic endpoint config", + "text": "EndpointGenericConfig shall default-construct with ep_used false and every other field 0, matching c-RCP's rcp_regmap_ep_generic_cfg_init(). This default is unaffected by REQ-RMAP-087's own ep_generic_cfg::apply_reconfig() correction for EP0's own row.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3534-3582", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-017", + "title": "EpFunctionalCfg zero-initializes functional endpoint config", + "text": "EpFunctionalCfg shall default-construct with every one of its five flags (ep_enable, ep_clear_req_storage, ep_req_crc_enable, ep_response_ts_enable, ep_suppress_response) false, matching c-RCP's rcp_regmap_ep_functional_cfg_init(). Content-modeling only in this codebase: this project's own Phase 3 already independently ported these same 5 fields inline into each concrete endpoint type's own functional-config struct rather than composing this shared type.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3534-3582", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-023", + "title": "The lifecycle state is exposed as the svr_lifecycle_state register-map entry", + "text": "PARTIAL, matching c-RCP's own disposition: GeneralMap::svr_lifecycle_state carries lifecycle::ServerLifecycle's own state (content modeling only) but has no genuine TC18 §12.7.5 Table 20 address, so GeneralMap render()/encode_read_response() deliberately exclude it (see render()'s own doc comment and the test proving neither it nor svr_root_client_index leaks onto the wire). Its real home, TC18 §13.7.1.2 Table 36's RC-Server-specific EP_FUNC_config block, is not wire-reachable (REQ-RMAP-067) -- a remote client cannot read this server's lifecycle state over the wire, only an in-process caller can.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18-gap", + "status": "partial", + "tc18": "§12.3.1.1/§12.3.1.2, TC18.txt L2167, L2186, L2198", + "tc18_master_id": "TC18-13.7.1.2-001" + }, + { + "id": "REQ-RMAP-024", + "title": "GeneralMap's full Table 20 extent is reachable on the wire, not just a narrow slice", + "text": "GeneralMap::render()/encode_read_response()/decode_read_response() shall serialize/deserialize GeneralMap's full TC18 §12.7.5 Table 20 extent (kGeneralMapLen octets, addresses 0x0000..0x0043 inclusive) over ACF_ABB at each field's own TC18-documented absolute address, with a shorter read_size still returning a valid prefix. svr_lifecycle_state and svr_root_client_index, which have no genuine Table 20 address, are deliberately excluded rather than force-mapped (see REQ-RMAP-023).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7 Configuration, TC18.txt L2452-2453", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-025", + "title": "Every register in GeneralMap's static part is read-only to a remote client", + "text": "GeneralMap::decode_write_request() shall recognize any ACF_ABB WRITE addressed to EP0's general register map and report acf::WireErrorCode::LockedMemAccess unconditionally, for every writer and every lifecycle state, reusing no write-authorization check (there is none to consult -- TC18 §12.7.5 Table 20 marks every register of the RC Server general part access type R).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2520-2521, L2661, L2664-2665", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-030", + "title": "svr_implemented_options is 8-bit with five independent bits, matching Table 20 exactly", + "text": "GeneralMap::svr_implemented_options shall be an 8-bit field with five independent, pairwise-distinct bits (kOptCompoundWait/kOptTrigger/kOptChained/kOptTimeSync/kOptEnhCancel at 0x01/0x02/0x04/0x08/0x10), each independently settable with no sibling-bit requirement, matching TC18 §12.7.5 Table 20's own one-independent-bit-per-optional-feature definition (bits f/g/h reserved).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2554-2560", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-034", + "title": "The request/response stream config pointer and capacity registers are correctly sized and separately addressed", + "text": "GeneralMap shall declare svr_request_stream_cfg_capacity (uint8_t, 0x001C), svr_response_stream_cfg_capacity (uint8_t, 0x001D), svr_request_stream_cfg_ptr (uint16_t, 0x001E), and svr_response_stream_cfg_ptr (uint16_t, 0x0020) as four independently-sized, independently-addressed scalar fields, matching TC18 §12.7.5 Table 20 exactly, and render()/decode_read_response() shall carry all four at their own cited addresses.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2587-2598", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-037", + "title": "svr_ep_bytebus_id_map_ptr/_capacity are correctly sized and separately addressed", + "text": "GeneralMap shall declare svr_ep_bytebus_id_map_ptr (uint16_t, 0x0028) and svr_ep_bytebus_id_map_capacity (uint8_t, 0x002A) as independently-sized scalar fields matching TC18 §12.7.5 Table 20, wire-reachable via render()/decode_read_response() at their own cited addresses.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2604-2609", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-039", + "title": "All five optional-subsystem pointer/capacity register pairs are declared, each as an opaque byte buffer", + "text": "GeneralMap shall declare svr_network_interface_cfg_ptr/_capacity, svr_physical_layer_cfg_ptr/_capacity, svr_time_synch_cfg_ptr/_capacity, svr_security_cfg_ptr/_capacity, and svr_device_specific_cfg_ptr/_capacity as five independent 16-bit ptr/capacity scalar pairs at their own (TC18-inferred, address-column-blank-on-the-primary-source) relative addresses 0x0030..0x0043, matching TC18 §12.7.5 Table 20's continuation page and §12.7.11-.14's own product-specific opaque-content framing (OptionalSubsystemCfg models this as a flat, capacity-bounded byte buffer rather than a structured row type, since TC18 defines no field-level layout for any of the five). apply_reconfig() applies a direct write into that buffer; the read-side wire codec for this section is deliberately out of this batch's own scope (see this file's own \"NOT ported in this batch\" note) -- content-modeling and the write path only.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20 (continued), TC18.txt L2635-2659", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-040", + "title": "HwPinMapEntry models a HW_config table holding per-IO-pin endpoint-signal assignments", + "text": "HwPinMapEntry shall hold, for each used physical IO-pin, its hw_ep_nr/hw_ep_pin_nr endpoint-signal mapping and its hw_pin_type IO-pin properties, matching TC18 §12.7.6's HW_config table content -- replacing this codebase's own earlier pin_id/function placeholder shape (confirmed to have zero consumers before replacement).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.6 Table 21: HW_config, TC18.txt L2698-2717", + "tc18_master_id": "TC18-12.7.6-003" + }, + { + "id": "REQ-RMAP-041", + "title": "hw_pin_map rows are 3 octets per IO pin, at relative addresses 3*N, 3*N+1, 3*N+2", + "text": "hw_pin_map::render()/apply_reconfig() shall place/patch each HwPinMapEntry row at its own 3-octet stride (kRowLen == 3), matching TC18 §12.7.6 Table 21's own per-pin row shape, with every octet of every row R/W* (no read-only sub-field within a row).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.6 Table 21: HW_config, TC18.txt L2698-2717", + "tc18_master_id": "TC18-12.7.6-003" + }, + { + "id": "REQ-RMAP-042", + "title": "hw_pin_type matches Table 22's four-packed-sub-field bit layout", + "text": "hw_pin::k* constants shall model hw_pin_type as four packed, non-overlapping sub-fields -- Pull (bits 1:0), Output stage (bits 3:2), Drive strength (bits 5:4), Schmitt-Trigger (bit 7), bit 6 reserved -- matching TC18 §12.7.6 Table 22 exactly, verified pairwise-distinct against one another.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.6 Table 22: IO-pin properties, TC18.txt L3096-3145", + "tc18_master_id": "TC18-12.7.6-005" + }, + { + "id": "REQ-RMAP-044", + "title": "NamedSignal covers every endpoint type this codebase implements", + "text": "named_signal_string() shall return a non-empty, unique name for every NamedSignal value in 0..Count-1, and \"unknown\" for any value outside that range, covering GPIO/SPI/I2C/UART/LIN/PWM/ADC/DAC/CAN/ISELED/MDIO -- matching TC18 §12.7.6 Table 23's own EP_Signal_Nr enumeration for every endpoint type this codebase implements.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.6 Table 23: Enumeration of signals at endpoints, TC18.txt L2779-2820", + "tc18_master_id": "TC18-12.7.6-006" + }, + { + "id": "REQ-RMAP-045", + "title": "named_signal_ep_signal_nr restarts at 0 for each endpoint type", + "text": "named_signal_ep_signal_nr() shall convert NamedSignal's own flat enum ordinal into TC18 Table 23's per-endpoint-type EP_Signal_Nr wire value, restarting at 0 for every endpoint type (e.g. SpiClk -> 0, SpiCs5 -> 8, I2cScl -> 0) rather than continuing NamedSignal's own flat numbering, matching hw_ep_pin_nr's real wire meaning (Table 21).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.6 Table 23 + Table 21 row hw_ep_pin_nr, TC18.txt L2704, L2764, L2779-2820", + "tc18_master_id": "TC18-12.7.6-006" + }, + { + "id": "REQ-RMAP-047", + "title": "RequestStreamConfig carries a per-stream secure-channel index selecting the MACsec channel", + "text": "RequestStreamConfig::rx_secure_channel_index shall carry the per-request-stream MACsec channel selector, defaulting to 0, matching TC18 §12.7.7 Table 24's own field.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2859-L2860", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-048", + "title": "RequestStreamConfig carries the request stream's acknowledge-stream pointer", + "text": "RequestStreamConfig::rx_ack_stream_index shall carry the response-stream index this request stream's acknowledges are routed through, defaulting to 0, matching TC18 §12.7.7 Table 24's own field -- exercised end-to-end by the Table 24 response-suppression test over mock::Server::dispatch_frame.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2912-L2916", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-049", + "title": "RequestStreamConfig's rx_resp_stream_index defaults to 1, enabling discovery before configuration", + "text": "RequestStreamConfig::rx_resp_stream_index shall default to 1, not 0, so a freshly reset server can answer a discovery request before any configuration has been written, matching TC18 §12.7.7 Table 24's own power-on-default rule -- exercised end-to-end by the Table 24 response-suppression test over mock::Server::dispatch_frame.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2917-L2921", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-050", + "title": "request_stream_cfg watchdog-timeout register conversions round down and bounds-check", + "text": "request_stream_cfg::wd_timeout_ms_to_ticks()/_ticks_to_ms() shall convert between a free-form millisecond value and TC18 §12.7.7 Table 24's own packed 16-bit clock-tick register, rounding down and rejecting/clamping an out-of-range input rather than silently wrapping.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2855-L2857", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-051", + "title": "RequestStreamConfig's W* fields are writable only in HW_UNCONFIGURED/HW_CONFIGURED, read-only in RCP_CONFIGURED", + "text": "request_stream_cfg::apply_reconfig() shall accept a write to any Table 24 W* field while lifecycle::ServerState is HwUnconfigured or HwConfigured, and reject it once RcpConfigured, matching TC18 §12.7.7's own state-gated writability rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7, TC18.txt L2930-L2931", + "tc18_master_id": "TC18-12.7.7-002" + }, + { + "id": "REQ-RMAP-052", + "title": "EpIdMappingEntry rows are (request_stream_index, EP_Nr, byte_bus_id) triples, not (EP_Nr, byte_bus_id) pairs", + "text": "EpIdMappingEntry shall carry request_stream_index alongside its pre-existing ep_id/byte_bus_id fields, and ep_id_map::render()/apply_reconfig() shall place/patch each row at its own TC18-cited stride, matching TC18 §12.7.8 Table 25's own three-column row shape.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8 Table 25, TC18.txt L3330, L3331, L3332", + "tc18_master_id": "TC18-12.7.8-001" + }, + { + "id": "REQ-RMAP-053", + "title": "byte_bus_id is an 11-bit address; EpIdMappingEntry::crc_required models the full 0..2047 range", + "text": "EpIdMappingEntry::crc_required shall carry the per-row CRC-required flag TC18 §12.7.8 Table 25/26 defines alongside byte_bus_id's own full 11-bit (0..2047) address range, defaulting to false.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8 Table 25/26, TC18.txt L3332, L3345", + "tc18_master_id": "TC18-12.7.8-001" + }, + { + "id": "REQ-RMAP-054", + "title": "A request_stream_index of 0 terminates the EP_ID_config table", + "text": "ep_id_map::effective_count() shall stop counting at the first entry whose request_stream_index is 0 (the sentinel), returning that index rather than the table's full capacity when a sentinel is present, and the full capacity unchanged when no sentinel exists -- matching TC18 §12.7.8's own table-termination rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3361", + "tc18_master_id": "TC18-12.7.8-004" + }, + { + "id": "REQ-RMAP-056", + "title": "EP_ID_config ordering is ascending in (request_stream_index, byte_bus_id), not byte_bus_id alone", + "text": "ep_id_map::is_ascending() shall report whether entries[0..count) is ordered ascending in the composite key (request_stream_index, byte_bus_id): request_stream_index must never decrease, and within one unchanged request_stream_index run, byte_bus_id must strictly increase -- a purely diagnostic, read-only helper never invoked for server-side enforcement (TC18's own underlying ordering sentence was deleted by the 0.5.1_RC4 rebaseline, so this function's correct composite-key behavior is preserved but no longer traces to a live TC18 MUST).", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3364-L3367 -- REMOVED in spec revision 0.5.1_RC4, TC18 PDF 0.5.1_RC5 p.68 (sentence shown struck through, 051RC4 tag)", + "tc18_master_id": "TC18-12.7.8-006" + }, + { + "id": "REQ-RMAP-057", + "title": "has_single_client_per_ep diagnoses (without enforcing) the single-RC-Client-per-endpoint recommendation", + "text": "ep_id_map::has_single_client_per_ep() shall be a read-only diagnostic reporting whether the EP_ID_config table associates each endpoint with at most one client, matching TC18 §12.7.8's own recommendation (neither this function nor the server enforces it -- diagnosis only).", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3370", + "tc18_master_id": "TC18-12.7.8-008" + }, + { + "id": "REQ-RMAP-058", + "title": "shared_bus_homogeneous diagnoses whether endpoints sharing a byte_bus_id within one stream share an ep_type", + "text": "ep_id_map::shared_bus_homogeneous() shall be a read-only diagnostic reporting whether every endpoint sharing a byte_bus_id within one request stream is of the same ep_type, matching TC18 §12.7.8's own recommendation (diagnosis only, not enforced).", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3384", + "tc18_master_id": "TC18-12.7.8-011" + }, + { + "id": "REQ-RMAP-060", + "title": "response_queue_stream_id combines a ResponseQueueConfig's stream_uid with the interface's own MAC", + "text": "response_queue_stream_id() shall combine a ResponseQueueConfig row's own stream_uid (the unique_id half of the queue's outgoing stream_id, TC18 §12.7.9 Table 27 relative 0x0000) with a caller-supplied interface MAC to produce the queue's full outgoing stream_id.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.9 Table 27, TC18.txt L3425", + "tc18_master_id": "TC18-12.7.9-007" + }, + { + "id": "REQ-RMAP-066", + "title": "SvrEpCfg carries svr_discovery_timeout, the RC Server's own functional-config discovery-timeout register", + "text": "SvrEpCfg::svr_discovery_timeout shall carry the RC Server's own discovery-response-timeout value (16 bit, R/W*), matching TC18 §13.7.1.2 Table 36 (Table 33 in RC1)'s RC-Server-specific functional-config block. Content-modeling only -- SvrEpCfg is not wired into RegisterMap or Ep0 (matching REQ-RMAP-067's own partial status for the block as a whole).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.7.1.2 Table 36 (Table 33 in RC1), TC18.txt L4392-4393", + "tc18_master_id": "TC18-13.7.1.2-001" + }, + { + "id": "REQ-RMAP-067", + "title": "SvrEpCfg models the RC Server's functional-config block, including svr_enable always-1/read-only", + "text": "PARTIAL, matching c-RCP's own disposition: SvrEpCfg shall model TC18 §13.7.1.2 Table 36 (Table 33 in RC1)'s RC-Server-specific EP_FUNC_config block, including svr_ep_status and the always-1/read-only svr_enable convention every functional-config block shares -- content-modeling only, never wired into RegisterMap/Ep0's own wire dispatch (that wiring is c-RCP's own unported EP0 address-routed dispatcher, out of this batch's scope), and TC18's own Table 36 carries a real, confirmed address collision with §13.7.1.1's prose for the RC Server's own EP_FUNC-common-header fields -- a genuine spec self-contradiction, not a local implementation gap.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18-gap", + "status": "partial", + "tc18": "§13.7.1.2 Table 36 (Table 33 in RC1), TC18.txt L4370-4395", + "tc18_master_id": "TC18-13.7.1.2-001" + }, + { + "id": "REQ-RMAP-070", + "title": "writer_ctx sets via_discovery_stream via caller pass-through", + "text": "writer_ctx() shall plumb its caller-supplied via_discovery_stream argument straight through onto the returned WriterCtx unchanged -- one axis among writer_ctx()'s several independent authorization facts (REQ-RMAP-009/010/086).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.2, TC18.txt L2184", + "tc18_master_id": "TC18-12.3.1.2-004" + }, + { + "id": "REQ-RMAP-071", + "title": "RequestStreamConfig carries rx_ovrflw_safestate_enable", + "text": "RequestStreamConfig::rx_ovrflw_safestate_enable shall carry the per-request-stream overflow-triggers-safe-state toggle TC18 §12.7.7 Table 24 defines, defaulting to false, closing a gap this file's own terminology-drift note had already identified.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L3252-L3256", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-076", + "title": "ep_delay_time_us_to_reg()/_reg_to_us() convert between a free microsecond value and TC18's packed 2-bit register enum", + "text": "ep_generic_cfg::ep_delay_time_us_to_reg() shall accept exactly TC18's own four allowed microsecond values and encode them into the packed 2-bit ep_delay_time register field; ep_delay_time_reg_to_us() shall be its exact inverse, matching TC18 §13.2 Table 28/31.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3555-3559", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-077", + "title": "ep_req_storage_size_words_to_octets()/_octets_to_words() convert exactly between an octet count and TC18's 32-bit-word register unit", + "text": "ep_generic_cfg::ep_req_storage_size_words_to_octets()/_octets_to_words() shall convert exactly (no rounding loss for a valid word-aligned value) between ep_req_storage_size's own octet count and TC18 §13.2 Table 28/31's 32-bit-word register unit.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3562", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-078", + "title": "ep_generic_cfg::render() serializes each field at its own 12-octet-per-endpoint TC18-cited byte offset", + "text": "ep_generic_cfg::render() shall serialize every EndpointGenericConfig field (ep_type, ep_used/ep_delay_time packed byte, ep_req_storage_size, ep_description, ep_tx_buffer_size, ep_rx_buffer_size) into its own byte offset within a fixed 12-octet-per-endpoint stride (kRowLen == 12), matching TC18 §13.2 Table 28/31's own per-endpoint generic-config row shape, and never invents a bit for Table 31's own dangling EP_RESP_ON_ERROR reference (both reserved spans stay 0).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3534-3582", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-079", + "title": "ep_generic_cfg::apply_reconfig() is the write side of the 12-octet-per-endpoint wire codec, per-field range-overlap tracked", + "text": "ep_generic_cfg::apply_reconfig() shall patch only the octets an incoming write's own [relative_start_address, +data_len) range actually overlaps, per field, rather than the whole-row render-patch-reparse idiom every sibling apply_reconfig() in this file uses -- and shall never let a write touch the read-only ep_type octet (a no-op for that byte regardless of the incoming data), matching TC18 §13.2 Table 28/31's own per-field access-type column.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31 + §13.7.1.2, TC18.txt L3932-3980", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-083", + "title": "Watchdog timeout register render() falls back to 0x0000 when the internal ms value overflows the register's 16-bit clock-tick width", + "text": "request_stream_cfg::render() shall render 0x0000 for the watchdog-timeout register when the internal millisecond value, converted to clock ticks, does not fit the register's own 16-bit width, rather than silently truncating to a misleading nonzero value -- the read-side counterpart to REQ-RMAP-050's own write-side bounds check.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2855-L2857", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-084", + "title": "ep_id_map::row_init_default() grants EP0 access as the EP_ID_config table's power-on default row", + "text": "ep_id_map::row_init_default() shall produce a power-on-default EpIdMappingEntry row that grants access to EP0 (the RC Server's own pseudo-endpoint), matching TC18 §12.7.8's own power-on-default rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3361", + "tc18_master_id": "TC18-12.7.8-004" + }, + { + "id": "REQ-RMAP-086", + "title": "writer_ctx explicitly assigns every member of the returned WriterCtx, none left uninitialized", + "text": "writer_ctx() shall explicitly assign every member of the WriterCtx it returns (via_root_client_ep0, via_owning_stream, via_non_unicast_frame, via_discovery_stream, via_valid_stream_association, and any other member added since), leaving none to an implicit/uninitialized default.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.2, TC18.txt L2184", + "tc18_master_id": "TC18-12.3.1.2-004" + }, + { + "id": "REQ-RMAP-087", + "title": "ep_generic_cfg::apply_reconfig() forces EP0's own ep_used bit true regardless of an incoming write", + "text": "ep_generic_cfg::apply_reconfig() shall force row 0's (EP0's own) ep_used bit true after applying any incoming write to that row, regardless of what the write itself specified, matching TC18 §13.2 Table 31's own ep_used row (\"fixed to 1 as EP0 needs to be always implemented\").", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 31 (ep_used row, \"fixed to 1 as EP0 needs to be always implemented\") + §13.7.1.2, TC18.txt L3932-3980", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-055", + "title": "lifecycle::field_writable_w_plus models TC18's W+ (explicitly lockable) access type", + "text": "lifecycle::field_writable_w_plus() shall follow the same lifecycle-state/writer rule as FieldKind::FunctionalWStar when its caller-supplied locked flag is false, and shall unconditionally deny writability whenever locked is true regardless of lifecycle state or writer -- matching TC18's own W+ access type (\"an independent lock the configuring instance may set at any time ... independently of the lifecycle state that governs W and W*\"), the shared primitive EP_ID_config and the Table 27 W+ queue fields both need. field_write_error_w_plus() reports LockedMemAccess for the explicit-lock case and distinguishes it from an ordinary writer-unauthorized/state-locked FunctionalWStar denial when unlocked.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8, TC18.txt L3362", + "tc18_master_id": "TC18-12.7.8-005" + }, + { + "id": "REQ-SRV-001", + "title": "A disabled endpoint queues submitted requests instead of executing them", + "text": "Endpoint::submit() shall, while ep_enable() is false, append an operational (non-configuration-write) request frame to the endpoint's own FIFO queue and return false (queued, not executed) rather than execute it, matching TC18 §12.3.1.3's \"Operational requests will be stored in the EP's queue\" rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-002", + "title": "An enabled endpoint reports immediate execution without touching its queue", + "text": "Endpoint::submit() shall, while ep_enable() is true, leave the queue untouched and return true, meaning the caller must execute the request now.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-003", + "title": "Re-enabling an endpoint drains its queued requests in FIFO order", + "text": "Endpoint::drain_one() shall, once set_enable(true) has been called, dequeue the oldest queued request first, in the exact order submit() enqueued them, until the queue is empty.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-004", + "title": "admit()/admit_with_ack() classify each arriving request by its own request_type and take the standard-request path when none applies", + "text": "Endpoint::admit_with_ack() shall peek frame's own repurposed ACF_GBB opcode byte and route it through the matching rcp::request decode_*_request() path for a recognized conditional/cancellation opcode, and through the ordinary submit() path (AdmitOutcome::ExecuteNow/Queued) for every other frame -- an ordinary ACF_ABB, a GBB with mtv set, an unrecognized opcode byte, or a frame too short to carry one.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.2 / §13.5.1 request_type taxonomy", + "tc18_master_id": "TC18-11.2.2-002" + }, + { + "id": "REQ-SRV-005", + "title": "admit_with_ack() rejects an undecodable or unstorable conditional request", + "text": "Endpoint::admit_with_ack() shall return AdmitOutcome::Rejected, releasing any claimed store slot, when a recognized conditional opcode's own decode_*_request() call reports a decode error, and shall return AdmitOutcome::Rejected with WireErrorCode::ReqStorageOverflow when no free slot exists to claim in the first place.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.2, §12.9.6 Table 30", + "tc18_master_id": "TC18-12.9.6-004" + }, + { + "id": "REQ-SRV-006", + "title": "select_due() gates a compound request on the endpoint being idle", + "text": "Endpoint::select_due()'s auxiliary_condition_met() shall require ctx.endpoint_idle for a Compound request, never reporting it due while the endpoint is busy, and never reporting any request due at all while its own required SequencerTable is absent (ctx.sequencers == nullptr).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5", + "tc18_master_id": "TC18-13.5-001" + }, + { + "id": "REQ-SRV-007", + "title": "select_due() holds safety-tagged requests back until the safe state is reached", + "text": "Endpoint::is_due() shall report false for any stored request whose request_type is a safety (0x8x) variant while ctx.in_safe_state is false, regardless of that request's own kind-specific condition, and only consider it once ctx.in_safe_state becomes true.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.4 safety-tagged request semantics", + "tc18_master_id": "TC18-13.4-001" + }, + { + "id": "REQ-SRV-008", + "title": "select_due() returns the highest-priority due request", + "text": "Endpoint::select_due() shall, among every stored request whose is_due() holds, return the one with the numerically lowest request::priority_rank() (Cancellation > Triggered > Timed > Compound > CompoundWait > Chained > Standard), breaking a tie between equal-rank due requests by earliest arrival (FIFO).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5 priority ordering", + "tc18_master_id": "TC18-13.5-002" + }, + { + "id": "REQ-SRV-009", + "title": "complete() applies SequencerTable::tick() for a compound request", + "text": "Endpoint::complete() shall, for a stored Compound request, call ctx.sequencers->tick(slot.compound, elapsed) to apply its own delay-elapsed/advance-guard/next-state transition.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-001" + }, + { + "id": "REQ-SRV-010", + "title": "complete() decrements a finite non-zero repeat_count and re-arms the request", + "text": "Endpoint::complete() shall, for a Compound or Triggered request whose repeat_count is neither kCompoundRepeatInfinite/kTriggeredRepeatInfinite nor 0, decrement repeat_count by one, clear armed, and leave the request stored for its next repetition.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1 repeat_count", + "tc18_master_id": "TC18-13.5.1-002" + }, + { + "id": "REQ-SRV-011", + "title": "notify_trigger() routes an occurrence to every matching stored request", + "text": "Endpoint::notify_trigger(source_ep, signal_nr) shall record the occurrence, via request::triggered_record_occurrence(), against every stored Triggered request whose own trigger_source_ep/trigger_signal_nr selection matches both arguments, and against no request whose selection matches only one of the two.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.7.1.3 Table 37 trigger delivery", + "tc18_master_id": "TC18-13.7.1.3-001" + }, + { + "id": "REQ-SRV-012", + "title": "chain_predecessor_done() starts a chained request's delay at the predecessor's finalization", + "text": "Endpoint::chain_predecessor_done(index, now) shall, for a stored Chained request at index, set predecessor_done true and armed_at to now, so its chain_exec_delay timer runs from the predecessor's own finalization instant rather than from admission time.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 chained requests", + "tc18_master_id": "TC18-11.2.3-001" + }, + { + "id": "REQ-SRV-013", + "title": "cancel_all() removes every stored conditional request", + "text": "Endpoint::cancel_all() (clear-all, 0x05) shall remove every currently-stored request regardless of kind or safety tag, returning the count removed.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 clear-all (0x05)", + "tc18_master_id": "TC18-11.2.3-002" + }, + { + "id": "REQ-SRV-014", + "title": "watchdog_purge() keeps only the safety sequence", + "text": "Endpoint::watchdog_purge() shall remove every stored request that is not a safety (0x8x) variant, leaving only safety-tagged requests in the store, so that only the safety sequence survives a watchdog overflow.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.4 watchdog-driven safe-state entry", + "tc18_master_id": "TC18-13.4-002" + }, + { + "id": "REQ-SRV-015", + "title": "A disabled endpoint still executes configuration requests immediately", + "text": "Endpoint::submit() shall execute a configuration-write request (evt[2:0] == 111b) immediately even while ep_enable() is false -- for an ACF_ABB request and for every ACF_GBB conditional request kind except CompoundWait, whose evt[2:0] instead selects a comparison mode -- matching TC18 §12.3.1.3's \"they will only execute config requests\" rule; a GBB frame whose request_type cannot be peeked is conservatively queued, not executed.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-016", + "title": "Queuing a request on a disabled endpoint still emits the requested acknowledge", + "text": "Endpoint::submit()/admit_with_ack() shall, when a request is queued or stored rather than executed and its own evt[3] requested an acknowledge, still build and return a genuine Acknowledge response addressed to the request's own byte_bus_id/transaction_num, matching TC18 §12.3.1.3's \"if requested an acknowledge is sent after storing the request\" rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-017", + "title": "Cyclic heartbeat: empty NTSCF-only PDU on an empty response queue", + "text": "PARTIAL, matching c-RCP's own disposition: a response queue with nothing to transmit shall still emit a heartbeat AVTPDU once its own Flush_time interval elapses (REQ-RMAP-064/065's own respqueue.hpp mechanism) -- the server.hpp-side half (routing an admitted request's own execution result into that response queue in the first place) is out of this module's own scope; server.hpp's part of this cross-module behavior is content-modeling and admission/scheduling only, not response-queue transmission itself.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "partial", + "tc18": "§12.9.4/§12.9.5", + "tc18_master_id": "TC18-12.9.4-002" + }, + { + "id": "REQ-SRV-018", + "title": "RC Server trigger signals 0/1: PTP time-sync established and lost (Table 37)", + "text": "gptp_trigger_evaluate() shall return kGptpTriggerEstablished/kGptpTriggerLost exactly on a genuine unlocked->locked/locked->unlocked transition of its own caller-observed gPTP lock state, and std::nullopt on a repeated observation of the same state or the very first observation (no edge to detect yet); a caller then delivers the fired signal via Endpoint::notify_trigger(source_ep, signal) using this deployment's own RC-Server source_ep convention.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.7.1.3 Table 37", + "tc18_master_id": "TC18-13.7.1.3-001" + }, + { + "id": "REQ-SRV-019", + "title": "admit_with_ack() rejects a compound-wait request's reserved evt", + "text": "Endpoint::admit_with_ack() shall, for a CompoundWait request whose own evt[2:0] equals the reserved comparison mode (011b, acf::compound_wait_evt_valid() false), release the claimed store slot and return AdmitOutcome::Rejected rather than store it, matching TC18 §13.5.1's \"the request shall be ignored\" rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-003" + }, + { + "id": "REQ-SRV-020", + "title": "Each pending COMPOUND_WAIT request is evaluated against the caller-supplied current status independently of any other pending request", + "text": "Endpoint::select_due()/complete() shall evaluate each stored CompoundWait request's own execution condition via acf::compound_wait_match(slot.compound_wait_evt, slot.compound_wait_target, ..., ctx.current_status, ...) using that slot's own stored evt/payload alone, so two stored CompoundWait requests with different payloads produce independent match outcomes against the same caller-supplied current status.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-003" + }, + { + "id": "REQ-SRV-021", + "title": "pending_count() reports the conditional-request store's current occupancy", + "text": "Endpoint::pending_count() shall report exactly how many of the store's kMaxPending slots are currently in_use, incrementing on a successful claim_slot() and decrementing on every release_slot().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5 request-store bookkeeping", + "tc18_master_id": "TC18-13.5-003" + }, + { + "id": "REQ-SRV-022", + "title": "admit_with_ack() reports UnsupportedCmd for a compound-wait request's reserved evt", + "text": "Endpoint::admit_with_ack() shall write WireErrorCode::UnsupportedCmd to *out_error for a rejected CompoundWait admission whose own evt[2:0] is the reserved comparison mode, matching TC18 §13.5.1's own \"err-response with error code = UNSUPPORTED_CMD\" rule.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-003" + }, + { + "id": "REQ-SRV-023", + "title": "drain_one() refuses while the endpoint is disabled", + "text": "Endpoint::drain_one() shall return false and leave out_frame untouched whenever ep_enable() is false, regardless of queue contents, so a disabled endpoint's queue can never be silently drained out from under it.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.3.1.3", + "tc18_master_id": "TC18-12.3.1.3-001" + }, + { + "id": "REQ-SRV-024", + "title": "admit_with_ack() reports a cancellation opcode without storing it", + "text": "Endpoint::admit_with_ack() shall, for a request whose category is request::RequestCategory::Cancellation and which carries no TSCF presentation gate, return AdmitOutcome::Cancellation with out_request_type set to the decoded opcode and leave pending_count() unchanged -- the caller applies the cancellation immediately via the matching cancel_*() method, never through the pending store.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 cancellation opcodes", + "tc18_master_id": "TC18-11.2.3-002" + }, + { + "id": "REQ-SRV-025", + "title": "admit_with_ack() decodes and stores every other conditional request kind", + "text": "Endpoint::admit_with_ack() shall, for Compound/CompoundWait, Triggered, Timed, and Chained requests alike, decode the frame through its own kind-specific rcp::request decode_*_request() call and, on success, store the result in a claimed PendingRequest slot, returning AdmitOutcome::Pending.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1/§13.7/§11.2.3", + "tc18_master_id": "TC18-13.5.1-001" + }, + { + "id": "REQ-SRV-026", + "title": "admit()/admit_with_ack() always write *out_request_type", + "text": "Endpoint::admit_with_ack() shall always assign out_request_type before returning -- the decoded repurposed opcode for a conditional/cancellation request, or std::nullopt for a standard request -- never leaving it in an indeterminate prior-call state.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.2 request_type taxonomy", + "tc18_master_id": "TC18-11.2.2-002" + }, + { + "id": "REQ-SRV-027", + "title": "select_due() does not gate a compound-wait request on the endpoint being idle", + "text": "Endpoint::select_due()'s auxiliary_condition_met() shall NOT consult ctx.endpoint_idle for a CompoundWait request -- only acf::compound_wait_match() against ctx.current_status governs its auxiliary condition -- so a CompoundWait request may become due while the endpoint is busy, unlike Compound/Triggered/Chained.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-003" + }, + { + "id": "REQ-SRV-028", + "title": "select_due() gates a triggered request on the endpoint being idle", + "text": "Endpoint::select_due()'s auxiliary_condition_met() shall require ctx.endpoint_idle for a Triggered request, never reporting it due while the endpoint is busy even once its own trigger-occurrence threshold has been reached.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.7.1.3 Table 37 / §13.5", + "tc18_master_id": "TC18-13.5-001" + }, + { + "id": "REQ-SRV-029", + "title": "select_due() gates a timed request on a locked gPTP time base", + "text": "Endpoint::select_due()'s auxiliary_condition_met() shall require ctx.gptp_locked for a Timed request, never reporting it due -- however far ctx.gptp_now has advanced past its own presentation_time -- while the gPTP time base is unlocked.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.1 Timed requests", + "tc18_master_id": "TC18-11.2.1-001" + }, + { + "id": "REQ-SRV-030", + "title": "select_due() gates a chained request on the endpoint being idle", + "text": "Endpoint::select_due()'s auxiliary_condition_met() shall require ctx.endpoint_idle for a Chained request, never reporting it due even once its own predecessor has finalized and its chain_exec_delay has elapsed, while the endpoint is busy.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 Chained requests", + "tc18_master_id": "TC18-11.2.3-001" + }, + { + "id": "REQ-SRV-031", + "title": "select_due() reports no request as due when none qualifies", + "text": "Endpoint::select_due() shall return false and leave *out_index untouched whenever every stored request's own is_due() evaluates false -- including a non-empty store whose entries simply have not yet met their own execution condition.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5", + "tc18_master_id": "TC18-13.5-001" + }, + { + "id": "REQ-SRV-032", + "title": "select_due()'s due-ness for a non-safety-tagged request is unaffected by ctx.in_safe_state", + "text": "Endpoint::is_due()'s safety-tag gate shall apply only to a request whose request_type is a safety (0x8x) variant -- a non-safety-tagged request's due-ness is governed purely by its own kind-specific condition, regardless of whether ctx.in_safe_state is true or false.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.4 safety-tagged request semantics", + "tc18_master_id": "TC18-13.4-001" + }, + { + "id": "REQ-SRV-033", + "title": "complete() applies SequencerTable::wait_tick() for a compound-wait request", + "text": "Endpoint::complete() shall, for a stored CompoundWait request, call ctx.sequencers->wait_tick(slot.compound, condition_met) with condition_met freshly re-evaluated via acf::compound_wait_match() against ctx.current_status -- advancing the sequencer to next_state only when condition_met is true, and leaving it untouched (while still re-arming for repetition) when false.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-001" + }, + { + "id": "REQ-SRV-034", + "title": "complete() does not advance any sequencer for a triggered, timed, or chained request", + "text": "Endpoint::complete() shall never call ctx.sequencers->tick()/wait_tick() for a Triggered, Timed, or Chained request -- Triggered's own completion calls request::triggered_tick() (no sequencer involvement), and Timed/Chained fall through complete()'s default case (release_slot() only) -- leaving any sequencer state ctx.sequencers points to completely unchanged, even when one is present.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1 / §11.2.1 / §11.2.3", + "tc18_master_id": "TC18-13.5.1-001" + }, + { + "id": "REQ-SRV-035", + "title": "complete() leaves an infinite-repeat request's repeat_count unchanged", + "text": "Endpoint::complete() shall, for a Compound or Triggered request whose repeat_count equals kCompoundRepeatInfinite/kTriggeredRepeatInfinite, leave that field unchanged (never decrement it) and re-arm the request for its next repetition indefinitely.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1 repeat_count", + "tc18_master_id": "TC18-13.5.1-002" + }, + { + "id": "REQ-SRV-036", + "title": "complete() removes a request whose repeat_count is zero", + "text": "Endpoint::complete() shall, for a Compound or Triggered request whose repeat_count is already 0 at completion time, release its store slot and return false rather than re-arm it.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1 repeat_count", + "tc18_master_id": "TC18-13.5.1-002" + }, + { + "id": "REQ-SRV-037", + "title": "complete() always removes a timed or chained request", + "text": "Endpoint::complete() shall unconditionally release the store slot of a Timed or Chained request upon completion, matching their kind's own \"no repetition sub-field\" shape -- neither kind is ever re-armed regardless of any field value.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.1 / §11.2.3", + "tc18_master_id": "TC18-11.2.1-001" + }, + { + "id": "REQ-SRV-038", + "title": "A chained request's delay timer is not restarted by a later evaluation once its predecessor has finalized", + "text": "Endpoint::arm_if_startable() shall, for a Chained request whose armed flag is already true, leave armed_at unchanged on every subsequent select_due() evaluation -- chain_predecessor_done()'s own armed_at assignment is the timer's sole start point, never reset by an intervening not-yet-due evaluation.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 Chained requests", + "tc18_master_id": "TC18-11.2.3-001" + }, + { + "id": "REQ-SRV-039", + "title": "chain_predecessor_done() returns false and changes nothing for a non-chained-request index", + "text": "Endpoint::chain_predecessor_done(index, now) shall return false and leave the store entirely unchanged whenever index does not name a currently-stored Chained request -- an out-of-range index, an unused slot, or a slot holding a different request category.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 Chained requests", + "tc18_master_id": "TC18-11.2.3-001" + }, + { + "id": "REQ-SRV-040", + "title": "cancel_single() removes only the request cancel_attempt() reports canceled, and does not cascade to chained successors", + "text": "Endpoint::cancel_single(clear_transaction_num, state) (clear-single, 0x07) shall locate the stored request whose own transaction_num matches, apply cancel_attempt()'s NotFound/NotCancellable/Canceled classification against the caller-supplied lifecycle state, remove it only on Canceled, and never itself cascade to any other stored request -- cascading to chained successors is cancel_chain_from()'s own separate, explicitly-invoked responsibility (REQ-CANCEL-012).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 clear-single (0x07)", + "tc18_master_id": "TC18-11.2.3-002" + }, + { + "id": "REQ-SRV-041", + "title": "cancel_non_safestate() removes every non-safety-tagged stored request, leaving safety-tagged ones in place", + "text": "Endpoint::cancel_non_safestate() (clear-non-safestate, 0x06) shall remove every stored request that is not a safety (0x8x) variant, leaving every safety-tagged request stored, and return the count removed -- identical in effect to watchdog_purge(), reached by a different triggering event.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§11.2.3 clear-non-safestate (0x06)", + "tc18_master_id": "TC18-11.2.3-002" + }, + { + "id": "REQ-SRV-042", + "title": "admit_with_ack() stores each compound-wait request's own evt and byte_msg_payload independently of every other pending request", + "text": "Endpoint::admit_with_ack() shall, for each admitted CompoundWait request, copy that request's own decoded evt_op into slot->compound_wait_evt and its own decoded payload into slot->compound_wait_target, per PendingRequest slot -- two concurrently-stored CompoundWait requests never share or overwrite one another's comparison mode or target bytes.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.5.1", + "tc18_master_id": "TC18-13.5.1-003" + }, + { + "id": "REQ-RMAP-018", + "title": "RequestStreamConfig zero-initializes request-stream config, with rx_resp_stream_index defaulting to 1", + "text": "A default-constructed RequestStreamConfig shall have every field 0/false, with one deliberate exception: rx_resp_stream_index shall default to 1, not 0, so a freshly reset server can answer a discovery request before any configuration has been written (REQ-RMAP-049).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.7 Table 24, TC18.txt L2917-L2921", + "tc18_master_id": "TC18-12.7.7-001" + }, + { + "id": "REQ-RMAP-019", + "title": "ResponseQueueConfig zero-initializes response-queue config", + "text": "A default-constructed ResponseQueueConfig shall have every field (stream_uid, max_avtpdu_size, queue_size, flush_on_count, flush_time_us) 0.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.9 Table 27, TC18.txt L3402-3444", + "tc18_master_id": "TC18-12.7.9-007" + }, + { + "id": "REQ-RMAP-020", + "title": "ep_id_map::is_ascending recognizes a strictly ascending single-stream table", + "text": "ep_id_map::is_ascending() shall return true when every consecutive pair of entries sharing the same request_stream_index has a strictly increasing byte_bus_id -- a boundary case of REQ-RMAP-056's own composite-key comparison. TC18's own underlying ordering sentence was deleted by the 0.5.1_RC4 rebaseline (§12.7.8 Table 25, formerly Table 23); this function's correct behavior is preserved but no longer traces to a live TC18 MUST.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8 Table 25 (TC18 0.5.1_RC5; renumbered from Table 23), TC18.txt L3364-L3367 -- ordering sentence REMOVED in spec revision 0.5.1_RC4", + "tc18_master_id": "TC18-12.7.8-006" + }, + { + "id": "REQ-RMAP-021", + "title": "ep_id_map::is_ascending recognizes a non-ascending table", + "text": "ep_id_map::is_ascending() shall return false when any consecutive pair of entries sharing the same request_stream_index has an equal or decreasing byte_bus_id. Same REQ-RMAP-020 note on the underlying ordering sentence's own 0.5.1_RC4 deletion applies.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8 Table 25 (TC18 0.5.1_RC5; renumbered from Table 23), TC18.txt L3364-L3367 -- ordering sentence REMOVED in spec revision 0.5.1_RC4", + "tc18_master_id": "TC18-12.7.8-006" + }, + { + "id": "REQ-RMAP-022", + "title": "ep_id_map::is_ascending is vacuously true for zero or one entries", + "text": "ep_id_map::is_ascending() shall return true when count is 0 or 1 -- there is no consecutive pair to violate ascending order. Same REQ-RMAP-020 note applies.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.8 Table 25 (TC18 0.5.1_RC5; renumbered from Table 23), TC18.txt L3364-L3367 -- ordering sentence REMOVED in spec revision 0.5.1_RC4", + "tc18_master_id": "TC18-12.7.8-006" + }, + { + "id": "REQ-RMAP-026", + "title": "svr_req_stream_max/svr_responder_streams_max exist at the correct 8-bit width", + "text": "GeneralMap shall declare svr_req_stream_max (uint8_t, 0x000E, maximum request streams usable to access this server) and svr_responder_streams_max (uint8_t, 0x000F, maximum supported responder queues) at TC18 §12.7.5 Table 20's own correct 8-bit width, both wire-reachable via render()/decode_read_response() at their own addresses.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2918-2922", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-027", + "title": "svr_responder_mem_size/svr_req_mem_size are two distinct 16-bit capacity registers", + "text": "GeneralMap shall declare svr_responder_mem_size (uint16_t, 0x0010, maximum responder-queue memory in 32-bit words) and svr_req_mem_size (uint16_t, 0x0012, maximum memory for EP request queues in 32-bit words) as two distinct, independently-addressed fields, matching TC18 §12.7.5 Table 20, both wire-reachable via render()/decode_read_response().", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2543-2547", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-028", + "title": "svr_sequencers_max is 8-bit with its 0-means-unsupported encoding", + "text": "GeneralMap::svr_sequencers_max (uint8_t, 0x0014) shall hold 0 to mean sequencer operation is not supported and 1..n to give the number of available sequencer state registers, matching TC18 §12.7.5 Table 20, wire-reachable via render()/decode_read_response().", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2548-2550", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-029", + "title": "svr_configuration_lock exists, defaulting to the unlocked 0x00 value", + "text": "GeneralMap::svr_configuration_lock (uint8_t, 0x0015) shall default-construct to 0x00 (permits write access to R/W+ parameters), matching TC18 §12.7.5 Table 20's own default, wire-reachable via render()/decode_read_response(). Live lock enforcement against R/W+-typed fields is REQ-RMAP-055's own separately-tracked shared primitive, not this field's own existence/default/wire-reachability scope.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2551-2553", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-031", + "title": "The reserved octet at 0x0017 is explicitly modeled and always reads 0x00", + "text": "GeneralMap::reserved_0x17 shall default-construct to 0, and render() shall place 0x00 at absolute address 0x0017 for a default-constructed GeneralMap -- no setter path exists anywhere in this codebase to construct a nonzero value (Table 20 is entirely read-only, REQ-RMAP-025), matching TC18 §12.7.5 Table 20's own reserved-register requirement.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2562", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-032", + "title": "svr_io_pin_count is explicitly modeled, giving the HW_config table a declared extent", + "text": "GeneralMap::svr_io_pin_count (uint16_t, 0x0018) shall carry the number of assignable I/O pins, matching TC18 §12.7.5 Table 20 and §12.7.6's own authoritative-extent rule for the HW_config table (Table 21), wire-reachable via render()/decode_read_response().", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2564-2566", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-033", + "title": "svr_hw_cfg_ptr is a bare 16-bit pointer with no spurious capacity", + "text": "GeneralMap::svr_hw_cfg_ptr (uint16_t, 0x001A) shall be a bare pointer field with no accompanying capacity member -- HW_config's own extent is svr_io_pin_count (REQ-RMAP-032), which TC18 §12.7.5 Table 20 gives no separate capacity register for at this address -- wire-reachable via render()/decode_read_response().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2584-2586, L2617", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-035", + "title": "The reserved 16-bit register at 0x0022 is explicitly modeled and preserved as reserved", + "text": "GeneralMap::reserved_0x22 shall default-construct to 0, and render() shall place 0x0000 at absolute address 0x0022 for a default-constructed GeneralMap -- no setter path exists anywhere in this codebase to construct a nonzero value, matching the reserved_0x17 precedent (REQ-RMAP-031) and TC18 §12.7.5 Table 20's own reserved-register requirement.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2599", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-036", + "title": "svr_ep_generic_cfg_ptr/_capacity are correctly sized, addressed, and unit-correct", + "text": "GeneralMap shall declare svr_ep_generic_cfg_ptr (uint16_t, 0x0024, address of the generic endpoint register map) and svr_ep_generic_cfg_capacity (uint16_t, 0x0026, the EP config register section's own length in bytes, not an entry count) as correctly-sized, correctly-unitted scalar fields, matching TC18 §12.7.5 Table 20, wire-reachable via render()/decode_read_response().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2982-2985", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-038", + "title": "svr_ep_functional_cfg_ptr/svr_sequencer_state_ptr are correctly sized and separately addressed", + "text": "GeneralMap shall declare svr_ep_functional_cfg_ptr (uint16_t, 0x002C) and svr_sequencer_state_ptr (uint16_t, 0x002E) as two independent, bare 16-bit pointer scalars with no spurious capacity member, matching TC18 §12.7.5 Table 20, wire-reachable via render()/decode_read_response(). svr_ep_functional_cfg_ptr's own target (TC18 §13.7.1.2 Table 36's RC-Server-specific block) is not itself wire-dispatched -- that is REQ-RMAP-067's own separately-tracked, partial scope.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.7.5 Table 20, TC18.txt L2610-2615", + "tc18_master_id": "TC18-12.7.5-001" + }, + { + "id": "REQ-RMAP-043", + "title": "hw_pin_type's output stage has no separate, exclusive input flag to toggle away from", + "text": "hw_pin::kStageInput/kStageOpenDrain/kStageOpenSource/kStagePushPull shall share one 2-bit sub-field (kStageMask) within hw_pin_type rather than being represented as a separate, mutually-exclusive INPUT/OUTPUT flag pair -- matching TC18 §12.7.6's own \"all outputs are always also an input\" prose, which admits no pure-output-unreadable state to represent. A separate, still-open architecture question for GPIO's own runtime-adjustable per-pin state (a different register from this table's own read-only-on-the-wire hw_pin_type) is out of this table's own scope.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18-gap", + "status": "implemented", + "tc18": "§12.7.6, TC18.txt L2720-2722", + "tc18_master_id": "TC18-12.7.6-004" + }, + { + "id": "REQ-RMAP-073", + "title": "EndpointGenericConfig carries ep_description", + "text": "EndpointGenericConfig::ep_description (uint32_t) shall carry TC18 §13.2 Table 28/31's own user-defined description field (relative address 0x0004, 32 bit, R/W*, no further structure given by TC18), zero-initialized, serialized/patched at its own 0x0004 row offset by ep_generic_cfg::render()/apply_reconfig() (REQ-RMAP-078/079).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3564", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-074", + "title": "EndpointGenericConfig carries ep_tx_buffer_size, in 32-bit words per TC18's own unit", + "text": "EndpointGenericConfig::ep_tx_buffer_size (uint16_t) shall carry TC18 §13.2 Table 28/31's own tx-buffer-size field (relative address 0x0008, 16 bit, R/W*, in 32-bit words, 0x0000 if the endpoint has no tx buffer), zero-initialized, serialized/patched at its own 0x0008 row offset by ep_generic_cfg::render()/apply_reconfig() (REQ-RMAP-078/079).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3565-3569", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-075", + "title": "EndpointGenericConfig carries ep_rx_buffer_size, in 32-bit words per TC18's own unit", + "text": "EndpointGenericConfig::ep_rx_buffer_size (uint16_t) shall carry TC18 §13.2 Table 28/31's own rx-buffer-size field (relative address 0x000A, 16 bit, R/W*, same shape as ep_tx_buffer_size for the endpoint's own rx buffer), zero-initialized, serialized/patched at its own 0x000A row offset by ep_generic_cfg::render()/apply_reconfig() (REQ-RMAP-078/079).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§13.2 Table 28/31, TC18.txt L3570-3574", + "tc18_master_id": "TC18-13.2-001" + }, + { + "id": "REQ-RMAP-082", + "title": "named_signal_string() returns \"unknown\" for an out-of-range value", + "text": "named_signal_string() shall return \"unknown\" for a NamedSignal value outside 0..Count-1 -- the out-of-range default arm, distinct from REQ-RMAP-014's own in-range non-empty guarantee.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented" } ] } diff --git a/include/rcp/regmap.hpp b/include/rcp/regmap.hpp index 292b571..23b6ec4 100644 --- a/include/rcp/regmap.hpp +++ b/include/rcp/regmap.hpp @@ -1,18 +1,18 @@ -// fusa:req REQ-REGMAP-001 -// fusa:req REQ-REGMAP-002 -// fusa:req REQ-REGMAP-003 -// fusa:req REQ-REGMAP-004 -// fusa:req REQ-REGMAP-005 -// fusa:req REQ-REGMAP-006 -// fusa:req REQ-REGMAP-007 -// fusa:req REQ-REGMAP-008 -// fusa:req REQ-REGMAP-009 -// fusa:req REQ-REGMAP-010 -// fusa:req REQ-REGMAP-011 -// fusa:req REQ-REGMAP-012 -// fusa:req REQ-REGMAP-013 -// fusa:req REQ-REGMAP-014 -// fusa:req REQ-REGMAP-015 +// fusa:req REQ-RMAP-001 +// fusa:req REQ-RMAP-002 +// fusa:req REQ-RMAP-003 +// fusa:req REQ-RMAP-004 +// fusa:req REQ-RMAP-005 +// fusa:req REQ-RMAP-006 +// fusa:req REQ-RMAP-007 +// fusa:req REQ-RMAP-008 +// fusa:req REQ-RMAP-009 +// fusa:req REQ-RMAP-010 +// fusa:req REQ-RMAP-011 +// fusa:req REQ-RMAP-012 +// fusa:req REQ-RMAP-013 +// fusa:req REQ-RMAP-014 +// fusa:req REQ-RMAP-015 // // c-RCP-derived content ported in this batch (Phase 17 / cpp-RCP issue #129, // "Phase 4 batch A" — see this file's own "Phase 4 batch A" banner below): @@ -51,12 +51,18 @@ // fusa:req REQ-RMAP-078 // fusa:req REQ-RMAP-079 // fusa:req REQ-RMAP-081 +// fusa:req REQ-RMAP-082 // fusa:req REQ-RMAP-086 // fusa:req REQ-RMAP-087 // // c-RCP-derived content ported in Phase 4 batch B (this same issue/roadmap // entry — see this file's own "Phase 4 batch B" banner below): // fusa:req REQ-RMAP-017 +// fusa:req REQ-RMAP-018 +// fusa:req REQ-RMAP-019 +// fusa:req REQ-RMAP-020 +// fusa:req REQ-RMAP-021 +// fusa:req REQ-RMAP-022 // fusa:req REQ-RMAP-040 // fusa:req REQ-RMAP-041 // fusa:req REQ-RMAP-042 @@ -1389,7 +1395,11 @@ constexpr size_t kRowLen = 4; // own identical, not-spec-derived bound. constexpr size_t kMaxEntries = 64; -// is_ascending — REQ-RMAP-056, ported from c-RCP's +// is_ascending — REQ-RMAP-056 (composite-key fix), and REQ-RMAP-020/021/022 +// (this same function's earlier, single-stream-only strictly-ascending/ +// non-ascending/vacuous-count boundary facts, still true today: within one +// unchanged request_stream_index run the comparison falls back to the exact +// strict byte_bus_id check those three ids describe), ported from c-RCP's // rcp_regmap_ep_id_map_is_ascending(). Read-only diagnostic: true iff // entries[0..count) is strictly ascending in the COMPOSITE key // (request_stream_index, byte_bus_id) -- request_stream_index must never @@ -1626,7 +1636,8 @@ inline bool is_valid_association(const EpIdMappingEntry* entries, size_t count, } // namespace ep_id_map // ── Response / ack queue config (extraction §3.10; TC18 §12.7.9 Table 27) ─── -// ResponseQueueConfig — REQ-RMAP-059..065, replaces this codebase's own +// ResponseQueueConfig — REQ-RMAP-059..065, REQ-RMAP-019 (zero-initializes, +// c-RCP's rcp_regmap_response_queue_cfg_init()), replaces this codebase's own // pre-rewrite v2.x placeholder shape (response_queue_size/ack_queue_size/ // flush_time -- confirmed, before replacing them, that nothing outside // this file/its own tests reads or writes any of those three fields; the diff --git a/tests/test_regmap.cpp b/tests/test_regmap.cpp index a1e8d67..c652146 100644 --- a/tests/test_regmap.cpp +++ b/tests/test_regmap.cpp @@ -1,18 +1,18 @@ -// fusa:test REQ-REGMAP-001 -// fusa:test REQ-REGMAP-002 -// fusa:test REQ-REGMAP-003 -// fusa:test REQ-REGMAP-004 -// fusa:test REQ-REGMAP-005 -// fusa:test REQ-REGMAP-006 -// fusa:test REQ-REGMAP-007 -// fusa:test REQ-REGMAP-008 -// fusa:test REQ-REGMAP-009 -// fusa:test REQ-REGMAP-010 -// fusa:test REQ-REGMAP-011 -// fusa:test REQ-REGMAP-012 -// fusa:test REQ-REGMAP-013 -// fusa:test REQ-REGMAP-014 -// fusa:test REQ-REGMAP-015 +// fusa:test REQ-RMAP-001 +// fusa:test REQ-RMAP-002 +// fusa:test REQ-RMAP-003 +// fusa:test REQ-RMAP-004 +// fusa:test REQ-RMAP-005 +// fusa:test REQ-RMAP-006 +// fusa:test REQ-RMAP-007 +// fusa:test REQ-RMAP-008 +// fusa:test REQ-RMAP-009 +// fusa:test REQ-RMAP-010 +// fusa:test REQ-RMAP-011 +// fusa:test REQ-RMAP-012 +// fusa:test REQ-RMAP-013 +// fusa:test REQ-RMAP-014 +// fusa:test REQ-RMAP-015 // // c-RCP-derived test coverage added in this batch (Phase 17 / cpp-RCP issue // #129, "Phase 4 batch A"), ported from c-RCP's tests/test_regmap.c and the @@ -37,6 +37,38 @@ // fusa:test REQ-RMAP-081 // fusa:test REQ-RMAP-086 // fusa:test REQ-RMAP-087 +// +// Batch 11 fixup: these TEST_CASEs already carried real bracket tags but +// were missing the manifest //fusa:test line cpfusa's own trace tool +// actually scans for (a whole-tree "grep for [REQ-ID]" check is NOT what +// cpfusa checks -- it requires this comment form specifically, per the +// batch-8 lesson). No new test content; genuine tested-coverage caught by +// re-running the real pinned cpfusa binary before merge, not just an +// approximating local check: +// fusa:test REQ-RMAP-018 +// fusa:test REQ-RMAP-019 +// fusa:test REQ-RMAP-020 +// fusa:test REQ-RMAP-021 +// fusa:test REQ-RMAP-022 +// fusa:test REQ-RMAP-023 +// fusa:test REQ-RMAP-026 +// fusa:test REQ-RMAP-027 +// fusa:test REQ-RMAP-028 +// fusa:test REQ-RMAP-029 +// fusa:test REQ-RMAP-031 +// fusa:test REQ-RMAP-032 +// fusa:test REQ-RMAP-033 +// fusa:test REQ-RMAP-034 +// fusa:test REQ-RMAP-035 +// fusa:test REQ-RMAP-036 +// fusa:test REQ-RMAP-037 +// fusa:test REQ-RMAP-038 +// fusa:test REQ-RMAP-039 +// fusa:test REQ-RMAP-043 +// fusa:test REQ-RMAP-073 +// fusa:test REQ-RMAP-074 +// fusa:test REQ-RMAP-075 +// fusa:test REQ-RMAP-082 // Tests for rcp/regmap.hpp — the RC Server register-map data model and EP0 // pseudo-endpoint (ROADMAP.md milestone 45, "RC Server Lifecycle & @@ -78,7 +110,7 @@ TEST_CASE("is_ep0 is true only for EP0's own index", "[regmap][REQ-RMAP-001]") { // ── Generic vs. functional config split ───────────────────────────────────────── TEST_CASE("EndpointGenericConfig and EndpointFunctionalConfig are distinct, independently settable types", - "[regmap][REQ-REGMAP-001]") { + "[regmap][REQ-RMAP-001]") { EndpointGenericConfig generic; generic.ep_type = 0x03; // SPI, per c-RCP's Table 29/30 ep_type enum generic.ep_description = 0x11223344; @@ -93,7 +125,7 @@ TEST_CASE("EndpointGenericConfig and EndpointFunctionalConfig are distinct, inde // ── EP0 whole-map read ─────────────────────────────────────────────────────────── -TEST_CASE("Any client may read the whole register map through EP0", "[regmap][REQ-REGMAP-002]") { +TEST_CASE("Any client may read the whole register map through EP0", "[regmap][REQ-RMAP-002]") { auto map = make_map(2); map.general.vendor_id = 0x1234; ServerLifecycle lc; @@ -106,7 +138,7 @@ TEST_CASE("Any client may read the whole register map through EP0", "[regmap][RE // ── EP0 whole-map write is root-client-only ───────────────────────────────────── -TEST_CASE("Only the root client may write the whole register map through EP0", "[regmap][REQ-REGMAP-003]") { +TEST_CASE("Only the root client may write the whole register map through EP0", "[regmap][REQ-RMAP-003]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -132,7 +164,7 @@ TEST_CASE("Only the root client may write the whole register map through EP0", " // ── Root-client claim is exclusive ────────────────────────────────────────────── TEST_CASE("A second, distinct client cannot claim the root-client slot while it is held", - "[regmap][REQ-REGMAP-004]") { + "[regmap][REQ-RMAP-004]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -156,7 +188,7 @@ TEST_CASE("A second, distinct client cannot claim the root-client slot while it // ── Per-endpoint write restriction ────────────────────────────────────────────── TEST_CASE("A non-root client may only write the functional config of the endpoint it owns", - "[regmap][REQ-REGMAP-005]") { + "[regmap][REQ-RMAP-005]") { auto map = make_map(2); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -189,7 +221,7 @@ TEST_CASE("A non-root client may only write the functional config of the endpoin // pin mapping, queue sizing, and E2E-CRC enable toggles — a privilege // escalation into data reserved to the root client. TEST_CASE("Owning an endpoint does not grant a non-root client write access to its generic config", - "[regmap][REQ-REGMAP-005]") { + "[regmap][REQ-RMAP-005]") { auto map = make_map(2); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -228,7 +260,7 @@ TEST_CASE("Owning an endpoint does not grant a non-root client write access to i REQUIRE(ep0.read_whole_map().generic_configs[1].ep_tx_buffer_size == 8); } -TEST_CASE("Writing to an out-of-range endpoint id is rejected as INVALID_PARAMETER", "[regmap][REQ-REGMAP-005]") { +TEST_CASE("Writing to an out-of-range endpoint id is rejected as INVALID_PARAMETER", "[regmap][REQ-RMAP-005]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -243,7 +275,7 @@ TEST_CASE("Writing to an out-of-range endpoint id is rejected as INVALID_PARAMET // ── Register-locking interacts with EP0 write access ──────────────────────────── TEST_CASE("Generic config writes are refused once the lifecycle locks the generic block", - "[regmap][REQ-REGMAP-006]") { + "[regmap][REQ-RMAP-006]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -261,7 +293,7 @@ TEST_CASE("Generic config writes are refused once the lifecycle locks the generi } TEST_CASE("Functional config remains writable at HW_CONFIGURED but locks at RCP_CONFIGURED", - "[regmap][REQ-REGMAP-006]") { + "[regmap][REQ-RMAP-006]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -281,7 +313,9 @@ TEST_CASE("Functional config remains writable at HW_CONFIGURED but locks at RCP_ // ── General bootstrap register fields (GeneralMap) ────────────────────────────── TEST_CASE("GeneralMap default-constructs zeroed, with the no-root-client sentinel", - "[regmap][REQ-RMAP-003]") { + "[regmap][REQ-RMAP-003][REQ-RMAP-026][REQ-RMAP-027][REQ-RMAP-028][REQ-RMAP-029]" + "[REQ-RMAP-031][REQ-RMAP-032][REQ-RMAP-033][REQ-RMAP-034][REQ-RMAP-035][REQ-RMAP-036]" + "[REQ-RMAP-037][REQ-RMAP-038]") { GeneralMap map; REQUIRE(map.magic == kRegisterMapMagic); @@ -289,15 +323,18 @@ TEST_CASE("GeneralMap default-constructs zeroed, with the no-root-client sentine REQUIRE(map.vendor_id == 0); REQUIRE(map.device_id == 0); REQUIRE(map.svr_ep_count == 0); - REQUIRE(map.svr_req_stream_max == 0); - REQUIRE(map.svr_responder_streams_max == 0); - REQUIRE(map.svr_sequencers_max == 0); - REQUIRE(map.svr_configuration_lock == 0); - REQUIRE(map.svr_responder_mem_size == 0); - REQUIRE(map.svr_req_mem_size == 0); + REQUIRE(map.svr_req_stream_max == 0); // REQ-RMAP-026 + REQUIRE(map.svr_responder_streams_max == 0); // REQ-RMAP-026 + REQUIRE(map.svr_sequencers_max == 0); // REQ-RMAP-028 + REQUIRE(map.svr_configuration_lock == 0); // REQ-RMAP-029 + REQUIRE(map.svr_responder_mem_size == 0); // REQ-RMAP-027 + REQUIRE(map.svr_req_mem_size == 0); // REQ-RMAP-027 REQUIRE(map.svr_implemented_options == 0); REQUIRE(map.svr_root_client_index == kNoRootClient); - REQUIRE(map.svr_hw_cfg_ptr == 0); + REQUIRE(map.reserved_0x17 == 0); // REQ-RMAP-031 + REQUIRE(map.svr_io_pin_count == 0); // REQ-RMAP-032 + REQUIRE(map.reserved_0x22 == 0); // REQ-RMAP-035 + REQUIRE(map.svr_hw_cfg_ptr == 0); // REQ-RMAP-033 REQUIRE(map.svr_request_stream_cfg_capacity == 0); REQUIRE(map.svr_response_stream_cfg_capacity == 0); REQUIRE(map.svr_ep_generic_cfg_ptr == 0); @@ -318,7 +355,7 @@ TEST_CASE("GeneralMap default-constructs zeroed, with the no-root-client sentine REQUIRE(map.svr_device_specific_cfg_capacity == 0); } -TEST_CASE("RegisterMap bootstrap fields hold the values assigned to them", "[regmap][REQ-REGMAP-007]") { +TEST_CASE("RegisterMap bootstrap fields hold the values assigned to them", "[regmap][REQ-RMAP-007]") { RegisterMap m; m.general.magic = kRegisterMapMagic; m.general.svr_version = 0x00010000; @@ -371,7 +408,8 @@ TEST_CASE("Each option bit is independently settable with no sibling requirement // ── GeneralMap Table 20 wire codec (REQ-RMAP-024/025) ──────────────────────────── TEST_CASE("GeneralMap render() places each field at its own TC18-cited absolute address", - "[regmap][REQ-RMAP-024]") { + "[regmap][REQ-RMAP-024][REQ-RMAP-026][REQ-RMAP-027][REQ-RMAP-028][REQ-RMAP-029]" + "[REQ-RMAP-031][REQ-RMAP-032][REQ-RMAP-035]") { GeneralMap map; map.magic = 0x11223344; map.svr_version = 0x00010203; @@ -396,11 +434,20 @@ TEST_CASE("GeneralMap render() places each field at its own TC18-cited absolute REQUIRE(image[0x000A] == 0xCC); REQUIRE(image[0x000C] == 0x00); REQUIRE(image[0x000D] == 0x05); - REQUIRE(image[0x000E] == 0x06); - REQUIRE(image[0x000F] == 0x07); + REQUIRE(image[0x000E] == 0x06); // REQ-RMAP-026 + REQUIRE(image[0x000F] == 0x07); // REQ-RMAP-026 + REQUIRE(image[0x0010] == 0x08); // REQ-RMAP-027 + REQUIRE(image[0x0011] == 0x09); // REQ-RMAP-027 + REQUIRE(image[0x0012] == 0x0A); // REQ-RMAP-027 + REQUIRE(image[0x0013] == 0x0B); // REQ-RMAP-027 + REQUIRE(image[0x0014] == 0x0C); // REQ-RMAP-028 + REQUIRE(image[0x0015] == 0x00); // REQ-RMAP-029 REQUIRE(image[0x0016] == kOptCompoundWait); - REQUIRE(image[0x0018] == 0x0D); - REQUIRE(image[0x0019] == 0x0E); + REQUIRE(image[0x0017] == 0x00); // REQ-RMAP-031: reserved, always 0x00 + REQUIRE(image[0x0018] == 0x0D); // REQ-RMAP-032 + REQUIRE(image[0x0019] == 0x0E); // REQ-RMAP-032 + REQUIRE(image[0x0022] == 0x00); // REQ-RMAP-035: reserved, always 0x00 + REQUIRE(image[0x0023] == 0x00); // REQ-RMAP-035: reserved, always 0x00 } TEST_CASE("GeneralMap render() never places svr_lifecycle_state or svr_root_client_index on the wire", @@ -517,7 +564,7 @@ TEST_CASE("writer_ctx denies via_root_client_ep0 when no root client is configur } TEST_CASE("writer_ctx grants via_owning_stream only for the matching stream index", - "[regmap][REQ-RMAP-010]") { + "[regmap][REQ-RMAP-010][REQ-RMAP-011][REQ-RMAP-012]") { GeneralMap map; EpClient owner; owner.has_owning_stream = true; @@ -639,7 +686,7 @@ TEST_CASE("SvrEpCfg default-constructs with TC18's own stated discovery-timeout // ── EndpointGenericConfig: per-endpoint E2E CRC safe-mode toggles (pre-existing) ─ TEST_CASE("EndpointGenericConfig's ep_*_crc_enable toggles default false and are independently settable", - "[regmap][REQ-REGMAP-015]") { + "[regmap][REQ-RMAP-015]") { EndpointGenericConfig cfg; REQUIRE_FALSE(cfg.ep_req_crc_enable); REQUIRE_FALSE(cfg.ep_ack_crc_enable); @@ -705,7 +752,7 @@ TEST_CASE("ep_req_storage_size_octets_to_words round-trips and rejects non-multi // ── ep_generic_cfg wire codec, READ side (REQ-RMAP-078/081) ────────────────────── TEST_CASE("ep_generic_cfg::render places each field at its own TC18-cited byte offset", - "[regmap][REQ-RMAP-078]") { + "[regmap][REQ-RMAP-078][REQ-RMAP-073][REQ-RMAP-074][REQ-RMAP-075]") { EndpointGenericConfig row; row.ep_type = 0x03; // SPI row.ep_used = true; @@ -890,7 +937,7 @@ TEST_CASE("ep_generic_cfg::apply_reconfig rejects a zero-length write and an out // ── EP-ID / byte_bus_id mapping table ──────────────────────────────────────────── TEST_CASE("EP-ID mapping table preserves client insertion order without re-sorting it", - "[regmap][REQ-REGMAP-010]") { + "[regmap][REQ-RMAP-010]") { RegisterMap m; m.ep_id_mapping.push_back({/*ep_id=*/3, /*byte_bus_id=*/9}); m.ep_id_mapping.push_back({/*ep_id=*/1, /*byte_bus_id=*/7}); @@ -911,7 +958,7 @@ TEST_CASE("EP-ID mapping table preserves client insertion order without re-sorti // ── Response / ack queue config (TC18 §12.7.9 Table 27) ────────────────────────── -TEST_CASE("ResponseQueueConfig fields exist and are settable", "[regmap][REQ-REGMAP-011]") { +TEST_CASE("ResponseQueueConfig fields exist and are settable", "[regmap][REQ-RMAP-011]") { ResponseQueueConfig rqc; rqc.queue_size = 6; rqc.flush_on_count = 6; @@ -921,7 +968,7 @@ TEST_CASE("ResponseQueueConfig fields exist and are settable", "[regmap][REQ-REG // ── Sequencer-state persistence (batch B, pre-existing) ────────────────────────── -TEST_CASE("Sequencer-state registers persist independent 8-bit values", "[regmap][REQ-REGMAP-012]") { +TEST_CASE("Sequencer-state registers persist independent 8-bit values", "[regmap][REQ-RMAP-012]") { RegisterMap m; m.sequencer_states = {0, 0, 0}; m.sequencer_states[1] = 42; @@ -933,7 +980,7 @@ TEST_CASE("Sequencer-state registers persist independent 8-bit values", "[regmap // ── INVALID_PARAMETER ───────────────────────────────────────────────────────────── -TEST_CASE("An out-of-range read target is rejected as INVALID_PARAMETER", "[regmap][REQ-REGMAP-013]") { +TEST_CASE("An out-of-range read target is rejected as INVALID_PARAMETER", "[regmap][REQ-RMAP-013]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -943,7 +990,7 @@ TEST_CASE("An out-of-range read target is rejected as INVALID_PARAMETER", "[regm REQUIRE(ec == make_error_code(RegMapErrc::invalid_parameter)); } -TEST_CASE("Assigning an owner to EP0 itself is rejected as INVALID_PARAMETER", "[regmap][REQ-REGMAP-013]") { +TEST_CASE("Assigning an owner to EP0 itself is rejected as INVALID_PARAMETER", "[regmap][REQ-RMAP-013]") { auto map = make_map(1); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -963,7 +1010,7 @@ TEST_CASE("Assigning an owner to EP0 itself is rejected as INVALID_PARAMETER", " // an out-of-bounds write. TEST_CASE("A RegisterMap whose config vectors disagree with svr_ep_count fails closed instead of allowing OOB access", - "[regmap][REQ-REGMAP-013]") { + "[regmap][REQ-RMAP-013]") { RegisterMap m; m.general.svr_ep_count = 4; // generic_configs / functional_configs deliberately left empty/default @@ -1001,7 +1048,7 @@ TEST_CASE("A RegisterMap whose config vectors disagree with svr_ep_count fails c } TEST_CASE("A RegisterMap with a smaller mismatch (nonzero but undersized config vectors) also fails closed", - "[regmap][REQ-REGMAP-013]") { + "[regmap][REQ-RMAP-013]") { RegisterMap m; m.general.svr_ep_count = 4; m.generic_configs.resize(1); @@ -1022,7 +1069,7 @@ TEST_CASE("A RegisterMap with a smaller mismatch (nonzero but undersized config } TEST_CASE("write_whole_map rejects a replacement map whose config vectors disagree with its own svr_ep_count", - "[regmap][REQ-REGMAP-013]") { + "[regmap][REQ-RMAP-013]") { auto map = make_map(2); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -1043,7 +1090,7 @@ TEST_CASE("write_whole_map rejects a replacement map whose config vectors disagr } TEST_CASE("write_whole_map with a valid replacement map resizes endpoint_owner_ to match the new svr_ep_count", - "[regmap][REQ-REGMAP-005]") { + "[regmap][REQ-RMAP-005]") { auto map = make_map(2); ServerLifecycle lc; Ep0 ep0(map, lc); @@ -1089,7 +1136,7 @@ TEST_CASE("write_whole_map with a valid replacement map resizes endpoint_owner_ // ── Error taxonomies are distinct ──────────────────────────────────────────────── TEST_CASE("The four mandatory register-map error codes are distinct values in their own category", - "[regmap][REQ-REGMAP-014]") { + "[regmap][REQ-RMAP-014]") { auto unauthorized = make_error_code(RegMapErrc::unauthorized_access); auto locked = make_error_code(RegMapErrc::locked_mem_access); auto rejected = make_error_code(RegMapErrc::request_rejected); @@ -1197,7 +1244,7 @@ TEST_CASE("HwPinMapEntry defaults to all-zero and matches c-RCP's row shape", "[ } TEST_CASE("hw_pin bit-layout constants are non-overlapping within their own sub-field", - "[regmap][REQ-RMAP-042]") { + "[regmap][REQ-RMAP-042][REQ-RMAP-013][REQ-RMAP-043]") { REQUIRE((hw_pin::kPullMask & hw_pin::kStageMask) == 0); REQUIRE((hw_pin::kStageMask & hw_pin::kDriveMask) == 0); REQUIRE((hw_pin::kDriveMask & hw_pin::kSchmittTrigger) == 0); @@ -1260,19 +1307,21 @@ TEST_CASE("HwPinMapReconfigErrc values are distinct and carry non-empty messages // ── Per-endpoint-type named-signal index (TC18 §12.7.6 Table 23) ──────────── TEST_CASE("named_signal_string never returns an empty string for a valid signal", - "[regmap][REQ-RMAP-044]") { + "[regmap][REQ-RMAP-044][REQ-RMAP-014]") { for (uint8_t i = 0; i < static_cast(NamedSignal::Count); ++i) { auto sig = static_cast(i); REQUIRE_FALSE(std::string(named_signal_string(sig)).empty()); } } -TEST_CASE("named_signal_string returns \"unknown\" for an out-of-range value", "[regmap][REQ-RMAP-044]") { +TEST_CASE("named_signal_string returns \"unknown\" for an out-of-range value", + "[regmap][REQ-RMAP-044][REQ-RMAP-082]") { REQUIRE(std::string(named_signal_string(NamedSignal::Count)) == "unknown"); REQUIRE(std::string(named_signal_string(static_cast(0xFF))) == "unknown"); } -TEST_CASE("named_signal_string names are unique across the whole index", "[regmap][REQ-RMAP-044]") { +TEST_CASE("named_signal_string names are unique across the whole index", + "[regmap][REQ-RMAP-044][REQ-RMAP-015]") { std::vector names; for (uint8_t i = 0; i < static_cast(NamedSignal::Count); ++i) { names.emplace_back(named_signal_string(static_cast(i))); @@ -1317,7 +1366,7 @@ TEST_CASE("named_signal_ep_signal_nr returns 0 for Count or any other invalid va // ── Request-stream config: appended fields, boundary conversions ──────────── TEST_CASE("RequestStreamConfig's batch-B-appended fields default per TC18's own power-on rule", - "[regmap][REQ-RMAP-047]") { + "[regmap][REQ-RMAP-047][REQ-RMAP-018]") { RequestStreamConfig cfg; REQUIRE(cfg.rx_secure_channel_index == 0); REQUIRE(cfg.rx_ack_stream_index == 0); @@ -1551,7 +1600,8 @@ TEST_CASE("request_stream_cfg::resolve_index returns the 0 sentinel for no match // ── Response / ack queue config wire codec (TC18 §12.7.9 Table 27) ────────── -TEST_CASE("ResponseQueueConfig matches c-RCP's real per-queue row shape", "[regmap][REQ-RMAP-059]") { +TEST_CASE("ResponseQueueConfig matches c-RCP's real per-queue row shape", + "[regmap][REQ-RMAP-059][REQ-RMAP-019]") { ResponseQueueConfig cfg; REQUIRE(cfg.stream_uid == 0); REQUIRE(cfg.max_avtpdu_size == 0); @@ -1645,15 +1695,20 @@ TEST_CASE("EpIdMappingEntry's appended fields default false/0", "[regmap][REQ-RM } TEST_CASE("ep_id_map::is_ascending is true for strictly increasing composite keys", - "[regmap][REQ-RMAP-056]") { + "[regmap][REQ-RMAP-056][REQ-RMAP-020]") { EpIdMappingEntry entries[3] = {{1, 10, 1, false}, {2, 20, 1, false}, {3, 5, 2, false}}; // stream 1: bbid 10 < 20 (ascending); stream 2 > stream 1 (always ascending // regardless of its own bbid, even though 5 < 20). REQUIRE(ep_id_map::is_ascending(entries, 3)); + + // REQ-RMAP-020's own single-stream case: a strictly increasing byte_bus_id + // table within one stream is recognized as ascending. + EpIdMappingEntry single_stream[3] = {{1, 1, 1, false}, {2, 2, 1, false}, {3, 3, 1, false}}; + REQUIRE(ep_id_map::is_ascending(single_stream, 3)); } TEST_CASE("ep_id_map::is_ascending is false for an equal or descending byte_bus_id within one stream", - "[regmap][REQ-RMAP-056]") { + "[regmap][REQ-RMAP-056][REQ-RMAP-021]") { EpIdMappingEntry equal_adjacent[2] = {{1, 10, 1, false}, {2, 10, 1, false}}; REQUIRE_FALSE(ep_id_map::is_ascending(equal_adjacent, 2)); @@ -1667,7 +1722,8 @@ TEST_CASE("ep_id_map::is_ascending is false for a decreasing request_stream_inde REQUIRE_FALSE(ep_id_map::is_ascending(entries, 2)); } -TEST_CASE("ep_id_map::is_ascending is vacuously true for zero or one entries", "[regmap][REQ-RMAP-056]") { +TEST_CASE("ep_id_map::is_ascending is vacuously true for zero or one entries", + "[regmap][REQ-RMAP-056][REQ-RMAP-022]") { REQUIRE(ep_id_map::is_ascending(nullptr, 0)); EpIdMappingEntry one[1] = {{1, 10, 1, false}}; REQUIRE(ep_id_map::is_ascending(one, 1)); diff --git a/tests/test_respqueue.cpp b/tests/test_respqueue.cpp index 4372c29..1a88fd8 100644 --- a/tests/test_respqueue.cpp +++ b/tests/test_respqueue.cpp @@ -5,6 +5,13 @@ // fusa:test REQ-RMAP-064 // fusa:test REQ-RMAP-065 // fusa:test REQ-RMAP-085 +// +// Batch 11 fixup: REQ-SRV-017 (server.hpp) is cross-module -- its own +// scope note says server.hpp's part is content-modeling/admission only, +// so the real test target for its actual claim (a response queue with +// nothing to transmit still emits a heartbeat once Flush_time elapses) +// is here, dual-tagged onto REQ-RMAP-065's existing test: +// fusa:test REQ-SRV-017 // Tests for rcp/respqueue.hpp -- the outbound per-response/ack-stream // transmit queue (TC18 §12.7.9 Table 27, §12.9.4/§12.9.5), brand new to @@ -441,13 +448,21 @@ TEST_CASE("should_flush_by_time fires at or past the configured interval", REQUIRE(RespQueue::should_flush_by_time(1001, 1000)); } -TEST_CASE("should_flush_by_time is independent of queue state", "[respqueue][REQ-RMAP-065]") { +TEST_CASE("should_flush_by_time is independent of queue state", + "[respqueue][REQ-RMAP-065][REQ-SRV-017]") { RespQueue q; uint8_t frame[3] = {0}; // REQ-RMAP-065: the Flush_time trigger must fire the same way whether // the queue is empty or not -- unlike should_flush(), the // flush_on_count trigger, which is false for an empty queue. + // REQ-SRV-017 (cross-module, batch 11): this is the exact "a response + // queue with nothing to transmit still emits a heartbeat once + // Flush_time elapses" claim -- the assertion below, on an empty q, + // before any push(), is that behavior. server.hpp's own scope for + // this id is content-modeling/admission only (its own catalog text), + // so the respqueue.hpp mechanism this test exercises is the real, + // and only, test target. REQUIRE(RespQueue::should_flush_by_time(2000, 1000)); REQUIRE(q.plan_batch(100) == 0); diff --git a/tests/test_server.cpp b/tests/test_server.cpp index 306072b..52cf54b 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -15,12 +15,30 @@ // fusa:test REQ-SRV-015 // fusa:test REQ-SRV-016 // fusa:test REQ-SRV-018 +// fusa:test REQ-SRV-019 +// fusa:test REQ-SRV-020 // fusa:test REQ-SRV-021 +// fusa:test REQ-SRV-022 // fusa:test REQ-SRV-023 +// fusa:test REQ-SRV-024 +// fusa:test REQ-SRV-025 +// fusa:test REQ-SRV-026 +// fusa:test REQ-SRV-027 +// fusa:test REQ-SRV-028 +// fusa:test REQ-SRV-029 +// fusa:test REQ-SRV-030 +// fusa:test REQ-SRV-031 +// fusa:test REQ-SRV-032 +// fusa:test REQ-SRV-033 +// fusa:test REQ-SRV-034 // fusa:test REQ-SRV-035 // fusa:test REQ-SRV-036 +// fusa:test REQ-SRV-037 +// fusa:test REQ-SRV-038 +// fusa:test REQ-SRV-039 // fusa:test REQ-SRV-040 // fusa:test REQ-SRV-041 +// fusa:test REQ-SRV-042 // fusa:test REQ-PWRMODE-028 // fusa:test REQ-CANCEL-012 // fusa:test REQ-ACF-021 @@ -267,7 +285,8 @@ TEST_CASE("admit rejects a frame whose rsp bit is set", "[server][REQ-ACF-021]") // ── admit(): standard requests are unaffected by conditional routing ──────── -TEST_CASE("standard NTSCF request executes immediately when enabled", "[server][REQ-SRV-004]") { +TEST_CASE("standard NTSCF request executes immediately when enabled", + "[server][REQ-SRV-004][REQ-SRV-026]") { Endpoint ep(true); auto frame = standard_abb(5, 1); @@ -277,7 +296,8 @@ TEST_CASE("standard NTSCF request executes immediately when enabled", "[server][ REQUIRE_FALSE(rt.has_value()); } -TEST_CASE("admit reports a cancellation with its opcode, unstored", "[server][REQ-SRV-004]") { +TEST_CASE("admit reports a cancellation with its opcode, unstored", + "[server][REQ-SRV-004][REQ-SRV-024][REQ-SRV-026]") { Endpoint ep(true); auto frame = request::encode_clear_all(5, 1); @@ -311,7 +331,7 @@ TEST_CASE("a request store full of pending entries rejects a new conditional adm } TEST_CASE("CompoundWait admission rejects the reserved evt[2:0]=011b comparison mode", - "[server][REQ-CMP-029]") { + "[server][REQ-CMP-029][REQ-SRV-019][REQ-SRV-022]") { Endpoint ep(true); request::CompoundStep step; auto frame = request::encode_compound_request(request::RequestTypeOpcode::CompoundWait, 5, step, @@ -383,7 +403,7 @@ TEST_CASE("compound never becomes due without a sequencer table", "[server][REQ- } TEST_CASE("due requests execute in priority order regardless of arrival order", - "[server][REQ-SRV-007][REQ-SRV-008]") { + "[server][REQ-SRV-007][REQ-SRV-008][REQ-SRV-025]") { Endpoint ep(true); std::vector states; request::SequencerTable seqs(states); @@ -463,6 +483,324 @@ TEST_CASE("equal-rank requests execute in arrival order", "[server][REQ-SCHED-00 REQUIRE(ep.pending(due)->transaction_num == 62); } +// ── Per-kind select_due() gating (REQ-SRV-027..032) ───────────────────────── + +TEST_CASE("select_due does not gate a compound-wait request on the endpoint being idle", + "[server][REQ-SRV-027]") { + Endpoint ep(true); + std::vector states; + request::SequencerTable seqs(states); + seqs.ensure_size(1); + + request::CompoundStep step; + step.start_state = request::SequencerTable::kDefaultState; + auto frame = request::encode_compound_request(request::RequestTypeOpcode::CompoundWait, 1, step, + /*evt_op=*/0x00, 1, /*payload=*/{0x01}); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + + uint8_t status = 0x01; + TickContext ctx; + ctx.endpoint_idle = false; // deliberately busy + ctx.sequencers = &seqs; + ctx.current_status = &status; + ctx.current_status_len = 1; + + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx); +} + +TEST_CASE("select_due gates a triggered request on the endpoint being idle", + "[server][REQ-SRV-028]") { + Endpoint ep(true); + request::TriggeredStep step; + auto frame = request::encode_triggered_request(request::RequestTypeOpcode::Triggered, 1, step, 1); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.notify_trigger(step.trigger_source_ep, step.trigger_signal_nr) == 1); + + TickContext ctx; + ctx.endpoint_idle = false; // threshold reached, but endpoint busy + size_t due = 0; + REQUIRE_FALSE(ep.select_due(ctx, &due)); + + ctx.endpoint_idle = true; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx); +} + +TEST_CASE("select_due gates a timed request on a locked gPTP time base", "[server][REQ-SRV-029]") { + Endpoint ep(true); + auto frame = *request::encode_timed_request(1, /*presentation_time=*/0, 1); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + + TickContext ctx; + ctx.endpoint_idle = true; + ctx.gptp_locked = false; // presentation instant already reached, but unlocked + ctx.gptp_now = 0; + size_t due = 0; + REQUIRE_FALSE(ep.select_due(ctx, &due)); + + ctx.gptp_locked = true; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx); +} + +TEST_CASE("select_due gates a chained request on the endpoint being idle", "[server][REQ-SRV-030]") { + Endpoint ep(true); + auto frame = request::encode_chained_member(1, /*chain_exec_delay=*/0, false, 1); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.chain_predecessor_done(idx, 0)); + + TickContext ctx; + ctx.endpoint_idle = false; // predecessor done, delay elapsed, but endpoint busy + size_t due = 0; + REQUIRE_FALSE(ep.select_due(ctx, &due)); + + ctx.endpoint_idle = true; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx); +} + +TEST_CASE("select_due reports nothing due when a stored request's own condition never holds", + "[server][REQ-SRV-031]") { + Endpoint ep(true); + request::TriggeredStep step; // trigger_threshold 0, never notified + auto frame = request::encode_triggered_request(request::RequestTypeOpcode::Triggered, 1, step, 1); + + std::optional rt; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, nullptr, nullptr) == + AdmitOutcome::Pending); + + TickContext ctx; + ctx.endpoint_idle = true; + size_t due = 0; + REQUIRE_FALSE(ep.select_due(ctx, &due)); // threshold never reached: nothing qualifies +} + +TEST_CASE("select_due's due-ness for a non-safety-tagged request is unaffected by ctx.in_safe_state", + "[server][REQ-SRV-032]") { + Endpoint ep(true); + request::TriggeredStep step; // not a *Safety variant + auto frame = request::encode_triggered_request(request::RequestTypeOpcode::Triggered, 1, step, 1); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.notify_trigger(step.trigger_source_ep, step.trigger_signal_nr) == 1); + + TickContext ctx; + ctx.endpoint_idle = true; + ctx.in_safe_state = false; // deliberately NOT in the safe state + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); // due anyway: the in_safe_state gate is safety-tagged-only + REQUIRE(due == idx); +} + +// ── CompoundWait: independent per-request evt/payload evaluation and storage ─ + +TEST_CASE("select_due evaluates each pending CompoundWait request's own evt/payload independently " + "against the caller-supplied current status", + "[server][REQ-SRV-020][REQ-SRV-042]") { + Endpoint ep(true); + std::vector states; + request::SequencerTable seqs(states); + seqs.ensure_size(2); + + request::CompoundStep step_a; + step_a.start_state = request::SequencerTable::kDefaultState; + step_a.sequencer_index = 0; + auto frame_a = request::encode_compound_request(request::RequestTypeOpcode::CompoundWait, 1, step_a, + /*evt_op=*/0x00, 81, /*payload=*/{0x42}); + + request::CompoundStep step_b; + step_b.start_state = request::SequencerTable::kDefaultState; + step_b.sequencer_index = 1; + auto frame_b = request::encode_compound_request(request::RequestTypeOpcode::CompoundWait, 1, step_b, + /*evt_op=*/0x00, 82, /*payload=*/{0x99}); + + std::optional rt; + size_t idx_a = 0, idx_b = 0; + REQUIRE(ep.admit(frame_a.data(), frame_a.size(), 0, false, 0, 0, rt, &idx_a, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.admit(frame_b.data(), frame_b.size(), 0, false, 0, 0, rt, &idx_b, nullptr) == + AdmitOutcome::Pending); + + uint8_t status = 0x42; + TickContext ctx; + ctx.endpoint_idle = true; + ctx.sequencers = &seqs; + ctx.current_status = &status; + ctx.current_status_len = 1; + + // Only A's own stored payload (0x42) matches the current status; B's own + // (0x99), stored and evaluated entirely independently, does not. + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx_a); + REQUIRE_FALSE(ep.complete(due, ctx)); // repeat_count 0: removed + + // B never becomes due against this same status, on its own account. + REQUIRE_FALSE(ep.select_due(ctx, &due)); +} + +TEST_CASE("complete() applies SequencerTable::wait_tick for a compound-wait request, advancing " + "its sequencer only once the condition genuinely matches", + "[server][REQ-SRV-033]") { + Endpoint ep(true); + std::vector states; + request::SequencerTable seqs(states); + seqs.ensure_size(1); + + request::CompoundStep step; + step.start_state = request::SequencerTable::kDefaultState; // 1 + step.next_state = 2; + step.sequencer_index = 0; + step.repeat_count = 1; // stays pending across the first (unmatched) completion + auto frame = request::encode_compound_request(request::RequestTypeOpcode::CompoundWait, 1, step, + /*evt_op=*/0x00, 90, /*payload=*/{0x11}); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + + TickContext ctx; + ctx.endpoint_idle = true; + ctx.sequencers = &seqs; + + regmap::SequencerState before = 0; + seqs.state_of(0, before); + REQUIRE(before == request::SequencerTable::kDefaultState); + + // A mismatched status: complete()'s own wait_tick() finds condition_met + // false and leaves the sequencer untouched, re-arming the request. + uint8_t mismatched = 0x00; + ctx.current_status = &mismatched; + ctx.current_status_len = 1; + REQUIRE(ep.complete(idx, ctx)); + regmap::SequencerState after_mismatch = 0; + seqs.state_of(0, after_mismatch); + REQUIRE(after_mismatch == request::SequencerTable::kDefaultState); + + // A matching status: wait_tick() advances the sequencer to next_state, + // and repeat_count (now exhausted) removes the request. + uint8_t matched = 0x11; + ctx.current_status = &matched; + ctx.current_status_len = 1; + REQUIRE_FALSE(ep.complete(idx, ctx)); + regmap::SequencerState after_match = 0; + seqs.state_of(0, after_match); + REQUIRE(after_match == 2); +} + +// ── complete(): no sequencer advance for Triggered/Timed/Chained; both of the +// latter are always removed (REQ-SRV-034, REQ-SRV-037) ─────────────────── + +TEST_CASE("complete() advances no sequencer for a triggered, timed, or chained request, even when " + "one is present, and always removes the latter two", + "[server][REQ-SRV-034][REQ-SRV-037]") { + std::vector states; + request::SequencerTable seqs(states); + seqs.ensure_size(1); + REQUIRE_FALSE(seqs.set_state(0, 5)); // an arbitrary sentinel, distinct from kDefaultState + + TickContext ctx; + ctx.sequencers = &seqs; + ctx.endpoint_idle = true; + ctx.gptp_locked = true; + + { + Endpoint ep(true); + request::TriggeredStep step; + auto frame = request::encode_triggered_request(request::RequestTypeOpcode::Triggered, 1, step, 1); + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.notify_trigger(step.trigger_source_ep, step.trigger_signal_nr) == 1); + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE_FALSE(ep.complete(due, ctx)); // repeat_count 0: removed + regmap::SequencerState s = 0; + seqs.state_of(0, s); + REQUIRE(s == 5); // unchanged + } + { + Endpoint ep(true); + auto frame = *request::encode_timed_request(1, /*presentation_time=*/0, 2); + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE_FALSE(ep.complete(due, ctx)); // REQ-SRV-037: always removed + regmap::SequencerState s = 0; + seqs.state_of(0, s); + REQUIRE(s == 5); // unchanged + } + { + Endpoint ep(true); + auto frame = request::encode_chained_member(1, /*chain_exec_delay=*/0, false, 3); + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + REQUIRE(ep.chain_predecessor_done(idx, 0)); + size_t due = 0; + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE_FALSE(ep.complete(due, ctx)); // REQ-SRV-037: always removed + regmap::SequencerState s = 0; + seqs.state_of(0, s); + REQUIRE(s == 5); // unchanged + } +} + +TEST_CASE("a chained request's delay timer is not restarted by a later select_due evaluation once " + "its predecessor has finalized", + "[server][REQ-SRV-038]") { + Endpoint ep(true); + auto frame = request::encode_chained_member(1, /*chain_exec_delay=*/10, false, 1); + + std::optional rt; + size_t idx = 0; + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); + + TickContext ctx; + ctx.endpoint_idle = true; + ctx.now = 50; + REQUIRE(ep.chain_predecessor_done(idx, 50)); // starts the delay timer at tick 50 + + ctx.now = 55; // 5 of 10 elapsed: not yet + size_t due = 0; + REQUIRE_FALSE(ep.select_due(ctx, &due)); + + ctx.now = 58; // still not due -- this evaluation must not restart the timer + REQUIRE_FALSE(ep.select_due(ctx, &due)); + + ctx.now = 60; // 10 elapsed from the ORIGINAL armed_at (50), not from 58 + REQUIRE(ep.select_due(ctx, &due)); + REQUIRE(due == idx); +} + // ── Repetitions ────────────────────────────────────────────────────────────── TEST_CASE("repeat_count controls how often a request runs", "[server][REQ-SRV-010][REQ-SRV-036]") { @@ -712,12 +1050,13 @@ TEST_CASE("notify_trigger only matches a Triggered request's own selection", // ── Chained: predecessor-done bookkeeping ──────────────────────────────────── TEST_CASE("chain_predecessor_done arms a chained request's exec_delay timer", - "[server][REQ-SRV-012]") { + "[server][REQ-SRV-012][REQ-SRV-025]") { Endpoint ep(true); auto frame = request::encode_chained_member(1, 5, false, 1); std::optional rt; size_t idx = 0; - ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr); + REQUIRE(ep.admit(frame.data(), frame.size(), 0, false, 0, 0, rt, &idx, nullptr) == + AdmitOutcome::Pending); TickContext ctx; ctx.endpoint_idle = true; @@ -735,7 +1074,7 @@ TEST_CASE("chain_predecessor_done arms a chained request's exec_delay timer", } TEST_CASE("chain_predecessor_done returns false for a non-chained or unused index", - "[server][REQ-SRV-012]") { + "[server][REQ-SRV-012][REQ-SRV-039]") { Endpoint ep(true); REQUIRE_FALSE(ep.chain_predecessor_done(0, 0));