diff --git a/.fusa-reqs-pending.json b/.fusa-reqs-pending.json index 262239f..9f4ff17 100644 --- a/.fusa-reqs-pending.json +++ b/.fusa-reqs-pending.json @@ -1415,6 +1415,186 @@ "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)." + }, + { + "id": "REQ-DISC-001", + "title": "rcp_discovery_should_drop() flags any non-NTSCF subtype", + "text": "should_drop_discovery() shall return true for any avtp_subtype value other than the NTSCF subtype, including the TSCF subtype, independent of lifecycle state or time-sync capability.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 Table 18 (\"subtype: NTSCF\"), TC18.txt L2747", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-001 (\"Discovery request framing — byte_bus_id 0, register-map address 0\"). Genuinely implemented+tested: should_drop_discovery() (discovery.hpp:249-251) is a real, directly-testable constexpr function -- tests/test_discovery.cpp's three \"[REQ-DISC-001]\"-tagged TEST_CASEs (lines 141-151) exercise TSCF, NTSCF, and an unrecognized subtype byte." + }, + { + "id": "REQ-DISC-002", + "title": "rcp_discovery_encode_request() builds an NTSCF-framed ACF_ABB read at the discovery bus", + "text": "encode_discovery_request() shall encode a full NTSCF-framed ACF_ABB message addressed to kDiscoveryByteBusId with op READ, carrying the given read_size and transaction_num and no payload.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 Table 18 (\"Discovery request\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=none), TC18.txt L2746-2758", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-002 (\"Discovery is NTSCF-only; a TSCF-headed discovery request is dropped\"). Genuinely implemented+tested: make_discovery_request()/encode_discovery_request() (discovery.hpp:303-329) build exactly this frame -- tests/test_discovery.cpp's \"[REQ-DISC-002]\"-tagged TEST_CASEs (lines 155-193) exercise the header fields, register address, and the NTSCF-headed output." + }, + { + "id": "REQ-DISC-003", + "title": "rcp_discovery_decode_request() recovers the requester, read_size, and transaction_num", + "text": "decode_discovery_request() shall, on success, populate out_hdr's stream_id as the requester and out_info's read_size_or_segment_num/transaction_num from the ACF header.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 Table 18 (\"Discovery request\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=none), TC18.txt L2746-2758", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-003 (\"The first discovery request claims the discovery stream\"). Genuinely implemented+tested: decode_discovery_request() (discovery.hpp:341-346) recovers requester/read_size/transaction_num -- tests/test_discovery.cpp's \"decode_discovery_request round-trips an encoded discovery request\" TEST_CASE (line 172, tagged [REQ-DISC-002][REQ-DISC-003]) exercises exactly this." + }, + { + "id": "REQ-DISC-004", + "title": "rcp_discovery_decode_request() drops a TSCF-headed frame", + "text": "decode_discovery_request() shall return DiscoveryErrc::tscf_headed_request_dropped when the frame's AVTP subtype is not NTSCF, before attempting any ACF-level parsing.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 (\"AVTPDUs having a TSCF header are dropped without further response, as well as requests in ACF_GBB format.\"), TC18.txt L2743", + "tc18_master_id": "TC18-12.6.1-002", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-004 (\"The discovery claim lapses after Discovery_TimeOut\"). Genuinely implemented+tested: \"A TSCF-headed discovery request is dropped, not decoded\" TEST_CASE (test_discovery.cpp:197, tagged [REQ-DISC-004]) directly exercises decode_discovery_request()'s early NTSCF-only check (detail::decode_common_frame(), discovery.hpp:264-293)." + }, + { + "id": "REQ-DISC-005", + "title": "rcp_discovery_decode_request() rejects a non-ACF_ABB message", + "text": "decode_discovery_request() shall return DiscoveryErrc::bad_msg_type when the NTSCF payload is not an ACF_ABB message.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 (\"...as well as requests in ACF_GBB format.\"), TC18.txt L2743; Table 18 (\"acf_msg_type: ACF_ABB\"), TC18.txt L2753", + "tc18_master_id": "TC18-12.6.1-002", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-005 (\"A claim does not block concurrent discovery reads from other clients\"). Genuinely implemented+tested: \"decode_discovery_request rejects a non-ACF_ABB message type\" TEST_CASE (test_discovery.cpp:212, tagged [REQ-DISC-005]) builds a raw ACF_GBB frame and confirms bad_msg_type." + }, + { + "id": "REQ-DISC-006", + "title": "rcp_discovery_decode_request() rejects the wrong byte_bus_id", + "text": "decode_discovery_request() shall return DiscoveryErrc::wrong_bus when the ACF header's byte_bus_id is not kDiscoveryByteBusId.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 Table 18 (\"Byte_bus_id: 00000000000b\"), TC18.txt L2754", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-006 (\"Only the active claim holder may issue a configuration request\"). Genuinely implemented+tested: \"decode_discovery_request rejects the wrong byte_bus_id\" TEST_CASE (test_discovery.cpp:226, tagged [REQ-DISC-006]) confirms wrong_bus for byte_bus_id 7." + }, + { + "id": "REQ-DISC-007", + "title": "rcp_discovery_decode_request() rejects a non-read op", + "text": "decode_discovery_request() shall return DiscoveryErrc::wrong_op when the ACF header's op is not read.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 Table 18 (\"op: 1b (read request)\"), TC18.txt L2756", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-007 (\"Discovery-stream claiming is scoped to HW_UNCONFIGURED/HW_CONFIGURED\"). Genuinely implemented+tested: \"decode_discovery_request rejects a write op\" TEST_CASE (test_discovery.cpp:240, tagged [REQ-DISC-007]) confirms wrong_op for op=true (write)." + }, + { + "id": "REQ-DISC-008", + "title": "rcp_discovery_decode_request() rejects a short or empty buffer", + "text": "decode_discovery_request() shall return DiscoveryErrc::short_frame when the input buffer is empty or shorter than the frame's declared header-plus-payload length.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.1 (\"A discovery request is a read request. The discovery request format equals the format shown in Table 18.\"), TC18.txt L2741-2742", + "tc18_master_id": "TC18-12.6.1-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-008 (\"DiscoveryErrc is a distinct error category with non-empty messages\"). Genuinely implemented+tested: \"decode_discovery_request rejects a short frame\" / \"...rejects an empty buffer\" TEST_CASEs (test_discovery.cpp:254,264, tagged [REQ-DISC-008]) confirm short_frame for a 2-byte buffer and a null/zero-length one." + }, + { + "id": "REQ-DISC-009", + "title": "rcp_discovery_encode_response() payload length always equals the requested read_size", + "text": "encode_discovery_response() shall encode a response whose ACF payload length equals read_size exactly, regardless of how many octets of the general register slice are actually defined.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "tc18": "§12.6.2 (\"The RC Server will respond with the part of the RC Server's register map as starting from 0x0000 and its length being determined by the read_size in the discovery request.\"), TC18.txt L2778-2779", + "tc18_master_id": "TC18-12.6.2-001", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, discovery module). The behavior itself is genuinely implemented and tested in include/rcp/discovery.hpp / tests/test_discovery.cpp. The blocker is that cpp-RCP's pre-existing REQ-DISC-001..009 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 9 entries were numbered independently, by the order features appeared in discovery.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-DISC-001..009 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-DISC-009 (\"A server answers discovery in every lifecycle state\"). Genuinely implemented+tested: \"A discovery response's payload always spans exactly read_size octets\" TEST_CASE (test_discovery.cpp:356, tagged [REQ-DISC-009]) confirms payload.size() == read_size for both a small (4) and large (40) read_size." + }, + { + "id": "REQ-RELAY-001", + "title": "rcp_wallclock_ms() returns wall-clock epoch milliseconds", + "text": "rcp_wallclock_ms() shall return the number of milliseconds elapsed since the Unix epoch, per the host's wall-clock (calendar) time source.", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, adapt/relay module). The behavior itself is genuinely implemented and tested elsewhere in this codebase. The blocker is that cpp-RCP's pre-existing REQ-RELAY-001..005 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 5 entries were numbered independently, by the order features appeared in adapt.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-RELAY-001..005 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-RELAY-001 (\"RELAY spec version constant is current (§19.4)\"). Genuinely implemented+tested: no direct cpp-RCP port of c-RCP's clock.c rcp_wallclock_ms() exists as a distinct named function -- response_to_message() (rcp/adapt.hpp) uses std::chrono::system_clock::now() inline for msg.timestamp rather than a wrapped, separately-testable wall-clock accessor. This is a genuine, narrower gap than the other 4 collision entries in this range (no dedicated function to cite as already-tested); noted honestly rather than force-citing an inline call site as equivalent coverage." + }, + { + "id": "REQ-RELAY-002", + "title": "relay_protocol_string() returns a unique name per protocol", + "text": "relay::to_string(Protocol) shall return a unique, non-empty string for each defined relay::Protocol constant, matching the RELAY spec's protocol identifier names (e.g. \"RCP\").", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, adapt/relay module). The behavior itself is genuinely implemented and tested elsewhere in this codebase. The blocker is that cpp-RCP's pre-existing REQ-RELAY-001..005 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 5 entries were numbered independently, by the order features appeared in adapt.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-RELAY-001..005 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-RELAY-002 (\"RELAY Protocol enum and mandatory error sentinels (§3, §5.1)\"). Genuinely implemented+tested: relay::to_string(Protocol) (include/relay/relay.hpp) is a real function covering all six Protocol values -- tests/test_relay.cpp's \"to_string(Protocol) returns a unique, non-empty name per protocol\" TEST_CASE (added this pass) exercises it exhaustively." + }, + { + "id": "REQ-RELAY-003", + "title": "relay_message_t lifecycle: init, free, set_id are safe and non-leaking", + "text": "relay::Message's construction/destruction/id-assignment shall be safely-freeable and non-leaking, and re-assigning id shall replace (not merely append to) any prior value.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, adapt/relay module). The behavior itself is genuinely implemented and tested elsewhere in this codebase. The blocker is that cpp-RCP's pre-existing REQ-RELAY-001..005 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 5 entries were numbered independently, by the order features appeared in adapt.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-RELAY-001..005 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-RELAY-003 (\"RCP-to-RELAY error equivalence mapping (§5.2)\"). Genuinely implemented+tested: architecture divergence, not a missing feature: relay::Message (include/relay/relay.hpp) is a plain struct of std::string/std::vector/std::map members with C++ RAII value semantics -- init()/free()/set_id() as distinct, separately-callable, separately-testable C functions do not exist because there is nothing for them to do beyond what every Message's constructor/destructor/assignment operator already guarantees for free via the standard library (no manual zero-init, no manual free-then-null, no manual deep-copy-on-set required or possible to get wrong). No dedicated test exists because there is no distinct behavior left to pin beyond ordinary C++ value semantics, which this codebase does not re-test per struct." + }, + { + "id": "REQ-RELAY-004", + "title": "relay_message_t meta map: set upserts, get looks up, both deep-copy", + "text": "relay::Message::meta's key/value assignment shall upsert (replace any existing entry with the same key rather than appending a duplicate) and its lookup shall return the value for a given key or indicate absence.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, adapt/relay module). The behavior itself is genuinely implemented and tested elsewhere in this codebase. The blocker is that cpp-RCP's pre-existing REQ-RELAY-001..005 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 5 entries were numbered independently, by the order features appeared in adapt.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-RELAY-001..005 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-RELAY-004 (\"Adapt() wraps a Controller as a relay::Caller (§10.3)\"). Genuinely implemented+tested: architecture divergence, not a missing feature: relay::Message::meta is a plain std::map (include/relay/relay.hpp) -- operator[]/find() already provide exactly the upsert/lookup-or-absent semantics c-RCP's relay_message_set_meta()/_get_meta() exist to hand-roll over a C map, with the same deep-copy-by-value guarantee std::string already provides. tests/test_adapt.cpp's/tests/test_relay.cpp's extensive use of msg.meta[\"...\"] = ... followed by msg.meta.at(\"...\") lookups throughout both files exercises this pervasively, just never as a dedicated named TEST_CASE, since there is no cpp-RCP-specific behavior here beyond std::map's own." + }, + { + "id": "REQ-RELAY-005", + "title": "rcp_message_to_request() maps a Message to a protocol-native request per §15.7.5", + "text": "rcp_message_to_request() shall pack a relay_message_t's payload and meta fields into a protocol-native request for the given rcp_adapt_op_t, per this module's own per-operation field table, addressed to the given byte_bus_id (or, for RCP_ADAPT_OP_DISCOVERY, framed as a full NTSCF AVTPDU addressed via requester_stream_id) and self-identified by the given transaction_num, reporting RCP_ADAPT_ERR_ENCODE on any packing failure.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "gap": "NOT a behavioral gap -- an id-collision traceability gap (2026-08-22, cpp-RCP Phase 6 batch 12, adapt/relay module). The behavior itself is genuinely implemented and tested elsewhere in this codebase. The blocker is that cpp-RCP's pre-existing REQ-RELAY-001..005 catalog range predates this rewrite's later convention of reusing c-RCP's exact ids: those 5 entries were numbered independently, by the order features appeared in adapt.hpp at the time, and this id is already taken in .fusa-reqs.json by a materially different, already-catalogued requirement. Adding a second, conflicting entry under the same id would corrupt the catalog's own id uniqueness and is not attempted here. Filed so a future renumbering-cleanup batch (out of this batch's own scope) can resolve the REQ-RELAY-001..005 collision zone and migrate this entry in. Already taken by: this catalog's own REQ-RELAY-005 (\"RELAY core bindings and RCP message mapping (§18.2, §14, §15.7.5)\"). Genuinely implemented+tested: message_to_request() (rcp/adapt.hpp) genuinely implements+tests the SHAPE of this claim -- packing relay::Message.payload/meta into an acf::AcfMessageInfo request, addressed via byte_bus_id decoded from Message.id -- but per a generic op/evt_op/read_size field set (this bridge's own simpler design, see rcp/adapt.hpp's own \"Phase 4 rewrite\" header-comment section), not c-RCP's richer per-rcp_adapt_op_t field table, and it has no RCP_ADAPT_ERR_ENCODE-equivalent packing-failure path of its own (message_to_request() only fails on an undecodable msg.id) -- see REQ-RELAY-006's own new catalog entry for the response-side half of this same architecture divergence, documented explicitly there rather than force-fit into this collision-blocked id." + }, + { + "id": "REQ-RELAY-007", + "title": "rcp_adapt_op_kind()/_string()/_from_string() classify and round-trip every operation", + "text": "rcp_adapt_op_kind() shall report the endpoint-type family (rcp_adapt_ep_kind_t) each rcp_adapt_op_t value belongs to; rcp_adapt_op_string() shall return a unique, non-empty canonical name for each defined rcp_adapt_op_t value; rcp_adapt_op_from_string() shall parse that name back to the same value, and shall report false for any unrecognized or NULL name.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "gap": "Deliberate architecture divergence, not an oversight (confirmed 2026-08-22, cpp-RCP Phase 6 batch 12): rcp/adapt.hpp has no rcp_adapt_op_t-equivalent per-operation opcode enum at all, and therefore no rcp_adapt_op_kind()/_string()/_from_string() to test. This port's Adapt()/RcpCallerAdapter design routes purely by byte_bus_id (Message.id) plus a generic op/evt_op/read_size meta field set -- the same 'primitives, not a wrapped chokepoint' choice rcp/record.hpp's and rcp/observe.hpp's own RequestFn already make -- rather than binding one Caller to exactly one of 13 endpoint-type families the way c-RCP's rcp_adapt(transport, local_stream_id, byte_bus_id, kind) does. tests/test_adapt.cpp's own header comment (lines 45-52) explicitly documents this as a test case c-RCP's test_adapt.c has that this file deliberately does not port, 'because they exercise concepts with no cpp-RCP counterpart in this header's own simpler design'. Filed here rather than force-adding a hollow enum+two functions solely to satisfy this id -- a future batch may reconsider if a real per-op classification need emerges." + }, + { + "id": "REQ-RELAY-011", + "title": "rcp_adapt_strerror() returns a unique human-readable message", + "text": "rcp_adapt_strerror() shall return a unique, non-empty message for each defined rcp_adapt_errc_t constant.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "gap": "Deliberate architecture divergence, not an oversight (confirmed 2026-08-22, cpp-RCP Phase 6 batch 12): rcp/adapt.hpp defines no rcp_adapt_errc_t-equivalent custom error category at all -- RcpCallerAdapter's own failure paths (message_to_request() decode failure, an empty RequestFn) report plain std::errc::invalid_argument/std::errc::not_connected/std::errc::function_not_supported directly, reusing the C++ standard library's own category and its already-unique-per-code std::generic_category().message() rather than minting a parallel 'rcp.adapt' category with its own strerror-equivalent the way c-RCP's rcp_adapt_errc_t/rcp_adapt_strerror() do. There is therefore no adapt-specific message-uniqueness property left for a dedicated test to pin beyond what libstdc++/libc++'s own generic_category() already guarantees and this codebase does not re-test." + }, + { + "id": "REQ-RELAY-013", + "title": "RCP_SPEC_VERSION aliases RELAY_SPEC_VERSION", + "text": "RCP_SPEC_VERSION shall be defined and shall equal RELAY_SPEC_VERSION, per the RELAY spec's required package-level SpecVersion export (§17 requirement 12, §19.4).", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "gap": "Confirmed absent, arguably not applicable under this port's architecture (2026-08-22, cpp-RCP Phase 6 batch 12): grepped rcp/*.hpp and relay/*.hpp for a SpecVersion/kSpecVersion symbol in the rcp:: namespace -- none exists. c-RCP is a two-package C codebase (a standalone relay.h/.c RELAY binding library, and rcp.h/.c layered on top) where RCP_SPEC_VERSION genuinely needs to be its own definition asserted equal to RELAY's, so the two packages cannot silently drift. cpp-RCP is a single header-only library sharing one translation unit's worth of namespaces (rcp::/relay::) with only one spec-version constant defined anywhere -- relay::kRelaySpecVersion (include/relay/relay.hpp), already tested in tests/test_relay.cpp's \"kRelaySpecVersion is 2.0\" TEST_CASE -- so there is no second, rcp::-scoped constant to duplicate or drift from. Filed as a gap rather than silently assumed satisfied: if a future consumer needs an explicitly RCP-namespaced version symbol (e.g. for a CLI --version surface), one should be added and this id revisited." + }, + { + "id": "REQ-RELAY-015", + "title": "Relay caller retain increments refcount and returns the same pointer", + "text": "rcp_relay_caller_retain() shall increment the caller's refcount and return the same pointer passed in, and shall be a no-op returning NULL when passed NULL.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "gap": "Deliberate architecture divergence, not an oversight (confirmed 2026-08-22, cpp-RCP Phase 6 batch 12): Adapt() returns std::unique_ptr (rcp/adapt.hpp), not a manually-refcounted rcp_relay_caller_t* -- C++ RAII ownership (move-only unique_ptr, or a caller wrapping it in shared_ptr if shared ownership is genuinely needed) supersedes c-RCP's own hand-rolled retain/release refcount protocol entirely; there is no retain() function and no refcount field to test. tests/test_adapt.cpp's own header comment (lines 69-75) explicitly documents this as a test case c-RCP's test_adapt.c has that this file deliberately does not port: 'std::unique_ptr already gives Adapt()'s return value RAII lifetime with no manual retain/release protocol to test.'" } ] } diff --git a/.fusa-reqs.json b/.fusa-reqs.json index 550f8ed..d8eaa41 100644 --- a/.fusa-reqs.json +++ b/.fusa-reqs.json @@ -11493,6 +11493,344 @@ "asil": "ASIL-A", "scope": "tc18", "status": "implemented" + }, + { + "id": "REQ-DISC-010", + "title": "encode_discovery_response() populates the general slice from regmap::GeneralMap", + "text": "encode_discovery_response() shall encode the response's leading kDiscoveryGeneralSliceLen octets, when present, as big-endian magic (32 bit at offset 0), svr_version (32 bit at offset 4), vendor_id (16 bit at offset 8), device_id (16 bit at offset 10), and svr_ep_count (16 bit at offset 12), taken from map.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Discovery response\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=RC Server's register map content starting from address 0x00000), TC18.txt L2777-2805", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-011", + "title": "encode_discovery_response() zero-fills beyond the general slice", + "text": "encode_discovery_response() shall zero-fill any response payload octets beyond kDiscoveryGeneralSliceLen when read_size exceeds it.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 (\"...length being determined by the read_size in the discovery request.\"), TC18.txt L2778-2779", + "tc18_master_id": "TC18-12.6.2-001" + }, + { + "id": "REQ-DISC-012", + "title": "decode_discovery_response() recovers a valid discovery result", + "text": "decode_discovery_response() shall, on success (empty std::error_code), populate out_result with valid = true, the response frame's own NTSCF stream_id as server_stream_id, and the general slice fields decoded from the payload at the offsets REQ-DISC-010 defines, with svr_version read as a 32-bit field.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Discovery response\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=RC Server's register map content starting from address 0x00000), TC18.txt L2777-2805", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-013", + "title": "decode_discovery_response() rejects a payload shorter than the general slice", + "text": "decode_discovery_response() shall return DiscoveryErrc::short_frame when the ACF payload is shorter than kDiscoveryGeneralSliceLen octets.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Discovery response\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=RC Server's register map content starting from address 0x00000), TC18.txt L2777-2805", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-014", + "title": "decode_discovery_response() applies the same AVTP/ACF-level checks as a request", + "text": "decode_discovery_response() shall return DiscoveryErrc::tscf_headed_request_dropped, ::bad_msg_type, ::wrong_bus, or ::wrong_op under the same conditions decode_discovery_request() does (both go through the shared detail::decode_common_frame()).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.1 (\"AVTPDUs having a TSCF header are dropped without further response, as well as requests in ACF_GBB format.\"), TC18.txt L2743 -- the same AVTP/ACF-level checks apply to responses", + "tc18_master_id": "TC18-12.6.1-002" + }, + { + "id": "REQ-DISC-015", + "title": "DiscoveryClaim starts unheld with the given timeout", + "text": "DiscoveryClaim's constructor shall leave the claim unheld (has_active_claim()==false for any now) with timeout_ set to the given (or kDefaultTimeout, ~20ms) value. Ported from c-RCP's rcp_discovery_claim_init(); cpp-RCP folds initialization into the constructor rather than a separate init function.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...If the RC Server does not receive configuration requests on the stream for a certain time (Discovery_TimeOut) it will drop the discovery stream and is open for a new discovery.\"), TC18.txt L2733-2735", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-016", + "title": "A never-held or lapsed DiscoveryClaim is open to a new grant", + "text": "DiscoveryClaim shall treat a never-held claim, or one whose deadline has passed, as available to be granted anew: has_active_claim() shall report false and a subsequent on_discovery_request() shall report ClaimOutcome::Claimed for such a claim. Ported from c-RCP's rcp_discovery_claim_is_open(); cpp-RCP folds this check into on_discovery_request()'s/has_active_claim()'s own private lapsed() predicate rather than exposing a standalone is_open() query.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...for a certain time (Discovery_TimeOut) it will drop the discovery stream and is open for a new discovery.\"), TC18.txt L2734-2735", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-017", + "title": "on_discovery_request() grants an open claim to the requester", + "text": "DiscoveryClaim::on_discovery_request() shall, when the claim is open (see REQ-DISC-016) and server_state is HwUnconfigured or HwConfigured, return ClaimOutcome::Claimed and set the claim's holder to client and its deadline to now + timeout_.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"The first discovery request the RC server receives in HW_UNCONFIGURED or HW_CONFIGURED state is used to set the discovery stream from which now on configuration requests will be accepted.\"), TC18.txt L2732-2734", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-018", + "title": "on_discovery_request() does not preempt an active claimant", + "text": "DiscoveryClaim::on_discovery_request() shall leave an already-held, not-yet-lapsed claim by a different claimant unchanged, returning ClaimOutcome::HeldByOther.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...but only the first one will be allowed as configuration stream. If others send configuration requests they will be rejected and get an error response.\"), TC18.txt L2736-2737", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-019", + "title": "may_configure() checks held, deadline, and identity", + "text": "DiscoveryClaim::may_configure() shall return true if and only if the claim is held, now is strictly before its deadline, and its holder equals client.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...but only the first one will be allowed as configuration stream.\"), TC18.txt L2736", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-020", + "title": "on_configuration_request() refreshes the deadline for the claimant", + "text": "DiscoveryClaim::on_configuration_request() shall, when client currently holds the claim, extend the claim's deadline to now + timeout_ and return true.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...for a certain time (Discovery_TimeOut) it will drop the discovery stream...\"), TC18.txt L2734-2735 -- receiving configuration requests on the stream is what resets the timeout", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-021", + "title": "on_configuration_request() rejects a non-claimant without mutating state", + "text": "DiscoveryClaim::on_configuration_request() shall return false and leave the claim unchanged when client is not the current claimant, including when the claim has already lapsed.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...but only the first one will be allowed as configuration stream. If others send configuration requests they will be rejected...\"), TC18.txt L2736-2737", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-022", + "title": "release() unconditionally clears the claim", + "text": "DiscoveryClaim::release() shall clear held state regardless of the claim's prior state.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6 (\"...it will drop the discovery stream and is open for a new discovery.\"), TC18.txt L2734-2735", + "tc18_master_id": "TC18-12.6-003" + }, + { + "id": "REQ-DISC-023", + "title": "DiscoveryCache::put() stores at most one entry per server_stream_id", + "text": "DiscoveryCache::put() shall overwrite an existing entry whose server_stream_id matches result's in place, and otherwise append a new entry growing internal storage as needed.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 (\"For each RC Server the RC Client can read the register map full or partially and maintain a table of relevant discovered RC Servers and their endpoints.\"), TC18.txt L2780-2781", + "tc18_master_id": "TC18-12.6.2-002" + }, + { + "id": "REQ-DISC-024", + "title": "Every DiscoveryErrc reports a unique, non-empty message in its own category", + "text": "discovery_category().message() shall return a non-empty, distinct message for each DiscoveryErrc value, and each make_error_code(DiscoveryErrc) shall report discovery_category() as its category. C++ std::error_code idiom replacing c-RCP's rcp_discovery_strerror().", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented" + }, + { + "id": "REQ-DISC-025", + "title": "discovery_response_fragment_count() delegates its answer to fragment::plan_count()", + "text": "discovery_response_fragment_count() shall return fragment::plan_count(read_size, max_fragment_payload)'s answer for every input.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Acf_msg_length: ≤ read_size of discovery request, limited to max size of register map\"), TC18.txt L2798-2799", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-026", + "title": "encode_discovery_response_fragmented() splits the discovery general-register-slice payload into correctly-numbered NTSCF-framed ACF fragments", + "text": "encode_discovery_response_fragmented() shall encode discovery_response_fragment_count() full NTSCF-framed ACF_ABB frames whose ms/read_size_or_segment_num fields follow rcp::fragment::plan() (the final fragment carrying read_size itself) and whose byte_bus_id/op/transaction_num/server_stream_id match encode_discovery_response()'s own single-frame encoding, producing exactly one frame identical to that single-frame encoding when read_size already fits in one fragment.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Acf_msg_length: ≤ read_size of discovery request, limited to max size of register map\"), TC18.txt L2798-2799", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-027", + "title": "decode_discovery_response_fragment() surfaces a fragment's ms bit, segment_num, server_stream_id, and raw payload, validated the same way the unfragmented response decoder is", + "text": "decode_discovery_response_fragment() shall, on success, populate out_server_stream_id, out_ms, and out_segment_num from the decoded frame, and out_payload as a copy of the ACF payload, applying the same AVTP/ACF-level validation decode_discovery_response() applies.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Discovery response\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=RC Server's register map content starting from address 0x00000), TC18.txt L2777-2805", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-028", + "title": "decode_discovery_reassembled_response() parses a fully reassembled general-register-slice payload into a discovery result", + "text": "decode_discovery_reassembled_response() shall, given a reassembled buffer of at least kDiscoveryGeneralSliceLen octets, populate out_result identically to how decode_discovery_response() would have for the same original, unfragmented response payload, and shall return DiscoveryErrc::short_frame for a shorter buffer.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 Table 19 (\"Discovery response\": subtype=NTSCF, acf_msg_type=ACF_ABB, Byte_bus_id=00000000000b, op=1b/read, byte_msg_payload=RC Server's register map content starting from address 0x00000), TC18.txt L2777-2805", + "tc18_master_id": "TC18-12.6.2-004" + }, + { + "id": "REQ-DISC-029", + "title": "A discovery request while the discovery stream is already claimed is answered with a stream-occupied error", + "text": "PARTIAL: DiscoveryClaim::on_discovery_request() (discovery.hpp) returns a ClaimOutcome -- HeldByOther when a different client requests while the claim is already held by an unlapsed claimant, AlreadyHeld when the current claimant itself re-requests -- TC18 Figure 17's own two \"Discovery request received\" transitions applied uniformly regardless of requester identity, per that method's own doc comment. A caller has a real signal to act on instead of an unreportable silent decline. STILL OPEN: DISCOVERY_STREAM_OCCUPIED is a Figure-17-diagram-only label with no corresponding numbered code in TC18 §12.9.6 Table 30 (rcp_wire_error_t stops at RCP_ERROR_CHAIN_ERROR, 17) -- unlike LOCKED_CONFIG_ACCESS (which cleanly maps onto RCP_ERROR_LOCKED_MEM_ACCESS, the only numbered code with a semantically matching name), no numbered code here has an obviously corresponding meaning, so this codebase does not invent one; which wire error code (if any) a caller should send for the HeldByOther/AlreadyHeld case remains a genuine, unresolved ambiguity (same class as REQ-ACF-012's RCP_ACF_MTV_UNCERTAIN).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18-gap", + "status": "partial", + "tc18": "§12.3, TC18.txt L2447-L2450 and L2481-L2483 (Figure 17, \"RC Server lifecycle states\", TC18.txt L2517)", + "tc18_master_id": "TC18-12.3-002" + }, + { + "id": "REQ-DISC-030", + "title": "DiscoveryCache::find() returns the matching entry or nullptr", + "text": "DiscoveryCache::find() shall return a pointer to the matching entry or nullptr if none is on record.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18": "§12.6.2 (\"For each RC Server the RC Client can read the register map full or partially and maintain a table of relevant discovered RC Servers and their endpoints.\"), TC18.txt L2780-2781", + "tc18_master_id": "TC18-12.6.2-002" + }, + { + "id": "REQ-RELAY-006", + "title": "response_to_message() maps an ACF response to a relay::Message per §15.7.5", + "text": "response_to_message() shall map an already-decoded acf::AcfMessageInfo response and its payload into a relay::Message, setting id from byte_bus_id (endpoint_id_to_relay_id()), payload verbatim, and meta[\"rcp.response_kind\"]/meta[\"rcp.err\"]/meta[\"rcp.read_size\"] from the response's response_kind/err/read_size_or_segment_num fields. Architecture divergence from c-RCP's rcp_response_to_message(): this port takes an already-decoded AcfMessageInfo (the wire decode already happened inside the caller-supplied RequestFn) rather than raw bytes, so it is a pure, unconditional mapping with no RCP_ADAPT_ERR_DECODE failure path of its own, and it maps per this bridge's own generic op/evt_op/read_size field set rather than c-RCP's richer per-operation field table (see rcp/adapt.hpp's own \"Phase 4 rewrite\" header comment).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-15.7.5-002" + }, + { + "id": "REQ-RELAY-008", + "title": "RcpCallerAdapter::send() maps a message to a request and transmits it, discarding the reply", + "text": "RcpCallerAdapter::send() shall convert msg via message_to_request(), invoke the wrapped RequestFn with it, and report the RequestFn's own std::error_code, reporting std::errc::not_connected for an empty (default-constructed) RequestFn and std::errc::invalid_argument when msg.id does not decode. Architecture divergence from c-RCP's rcp_adapt(): this port binds no single endpoint-type kind or byte_bus_id at construction (Adapt() takes only a RequestFn) -- each relay::Message names its own destination via Message.id, so there is no RCP_ADAPT_ERR_ENCODE \"op kind doesn't match the adapter's own bound kind\" check to make (see rcp/adapt.hpp's own header comment on why there is no per-op opcode enum in this design, REQ-RELAY-007's own pending entry).", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-10.6-001" + }, + { + "id": "REQ-RELAY-009", + "title": "RcpCallerAdapter::call() maps a request, transmits it, and maps the one reply back", + "text": "RcpCallerAdapter::call() shall perform the same message_to_request() conversion and RequestFn invocation as send(), and on success map the RequestFn's own out_info/out_payload back to a relay::Message via response_to_message(), reporting the RequestFn's own std::error_code (and an empty Message) on failure. Architecture divergence from c-RCP's own adapter: blocking for exactly one reply frame is delegated entirely to the wrapped RequestFn's own synchronous call semantics rather than a lower-level rcp_avtp_transport_t wait loop this port does not have.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-10.2-001" + }, + { + "id": "REQ-RELAY-010", + "title": "RcpCallerAdapter::subscribe() reports not-supported: no native TC18 periodic status stream exists", + "text": "RcpCallerAdapter::subscribe() shall always return std::errc::function_not_supported (and a null Channel), since TC18 defines no generic periodic Status-equivalent stream for any endpoint type to forward -- matching every other ADAPTed application bridge in this codebase's own subscribe()-has-no-analog convention.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-10.5-001" + }, + { + "id": "REQ-RELAY-012", + "title": "Adapt() binds a RequestFn without blocking or connecting", + "text": "Adapt(fn) shall wrap fn as a relay::Caller and return it immediately, without blocking or transmitting any traffic itself. Architecture divergence from c-RCP's rcp_adapt(transport, local_stream_id, byte_bus_id, kind): this port has no transport/address/endpoint-kind parameters of its own to bind at construction -- fn itself owns whatever connection, stream identity, and addressing binding it closes over (see rcp/adapt.hpp's own header comment, \"there is no unified client-side send() chokepoint left to wrap\"); the \"does not block, wraps immediately\" property this id actually tests for is preserved unchanged.", + "standard": "iso26262", + "level": "ASIL-B", + "asil": "ASIL-B", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-10.3-001" + }, + { + "id": "REQ-RELAY-014", + "title": "relay::relay_category().message() returns a unique human-readable message per sentinel", + "text": "relay::relay_category().message(ev) shall return a unique, non-empty message for each defined relay::Errc constant. C++ std::error_category idiom replacing c-RCP's relay_strerror(); implemented in include/relay/relay.hpp, tested in tests/test_relay.cpp (not tests/test_adapt.cpp).", + "standard": "iso26262", + "level": "ASIL-A", + "asil": "ASIL-A", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-5.1-002" + }, + { + "id": "REQ-RELAY-016", + "title": "relay::Channel::is_closed() reports the channel's closed state", + "text": "relay::Channel::is_closed() shall report false before close() and true after it. C++ member-function idiom replacing c-RCP's relay_message_channel_is_closed(); implemented in include/relay/relay.hpp, tested in tests/test_relay.cpp (not tests/test_adapt.cpp).", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-18.2-003" + }, + { + "id": "REQ-RELAY-017", + "title": "Native rcp:: errors report equivalence to RELAY common-error sentinels", + "text": "rcp::ErrClosed/rcp::ErrTimeout/rcp::ErrBusy shall be std::error_condition-equivalent to relay::ErrClosed/relay::ErrTimeout/relay::ErrTimeout respectively; rcp::ErrNotFound shall be equivalent to relay::ErrNotConnected; rcp::ErrAlreadyExists shall be equivalent to none of RELAY's four mandatory sentinels. C++ std::error_code/std::error_condition equivalence-operator idiom replacing c-RCP's explicit rcp_errc_to_relay_errc() query function; implemented in include/rcp/rcp.hpp's error_condition overloads, tested in tests/test_relay.cpp's §5.2 block (not tests/test_adapt.cpp). This is the real, non-colliding home for the equivalence-mapping claim cpp-RCP's own pre-existing (id-collision-blocked) REQ-RELAY-003 entry already makes under the wrong id -- see that id's own pending-catalog entry.", + "standard": "iso26262", + "level": "QM", + "asil": "QM", + "scope": "tc18", + "status": "implemented", + "tc18_master_id": "TC18-RELAY-5.2-001" } ] } diff --git a/include/rcp/adapt.hpp b/include/rcp/adapt.hpp index 3745ecb..9ad986c 100644 --- a/include/rcp/adapt.hpp +++ b/include/rcp/adapt.hpp @@ -3,6 +3,25 @@ // fusa:req REQ-RELAY-003 // fusa:req REQ-RELAY-004 // fusa:req REQ-RELAY-005 +// fusa:req REQ-RELAY-006 +// fusa:req REQ-RELAY-008 +// fusa:req REQ-RELAY-009 +// fusa:req REQ-RELAY-010 +// fusa:req REQ-RELAY-012 +// +// REQ-RELAY-014/016/017 (Phase 6 batch 12): genuinely implemented, but in +// include/relay/relay.hpp (relay::relay_category()/Channel::is_closed()) and +// include/rcp/rcp.hpp (rcp::ErrClosed/ErrTimeout/ErrBusy/ErrNotFound/ +// ErrAlreadyExists's std::error_condition equivalence to relay::Errc) rather +// than in this file -- tagged here anyway, matching this codebase's own +// pre-existing convention of concentrating every REQ-RELAY-* //fusa:req tag +// in this one header regardless of which file the behavior actually lives +// in (see REQ-RELAY-001..005 above, whose own real implementations already +// span clock.c/relay.c/rcp.c-equivalent territory in c-RCP terms). Tests +// live in tests/test_relay.cpp, the file that actually exercises them. +// fusa:req REQ-RELAY-014 +// fusa:req REQ-RELAY-016 +// fusa:req REQ-RELAY-017 // RELAY application interface adapter for cpp-RCP (§10.3, §18.2). // diff --git a/include/rcp/discovery.hpp b/include/rcp/discovery.hpp index 4ff438a..b4e8abb 100644 --- a/include/rcp/discovery.hpp +++ b/include/rcp/discovery.hpp @@ -22,6 +22,11 @@ // fusa:req REQ-DISC-022 // fusa:req REQ-DISC-023 // fusa:req REQ-DISC-024 +// fusa:req REQ-DISC-025 +// fusa:req REQ-DISC-026 +// fusa:req REQ-DISC-027 +// fusa:req REQ-DISC-028 +// fusa:req REQ-DISC-030 // TC18 requirements-corpus completeness pass: REQ-DISC-029 is catalogued in // this module's own requirements catalog with a "tc18" citation and a diff --git a/tests/test_adapt.cpp b/tests/test_adapt.cpp index edfd3ca..64b8eef 100644 --- a/tests/test_adapt.cpp +++ b/tests/test_adapt.cpp @@ -3,6 +3,11 @@ // fusa:test REQ-RELAY-003 // fusa:test REQ-RELAY-004 // fusa:test REQ-RELAY-005 +// fusa:test REQ-RELAY-006 +// fusa:test REQ-RELAY-008 +// fusa:test REQ-RELAY-009 +// fusa:test REQ-RELAY-010 +// fusa:test REQ-RELAY-012 // rcp/adapt.hpp conformance and behavioral-equivalence tests (RELAY spec // §10.3, §15.7.5) — cpp-RCP issue #129, ROADMAP.md Phase 17 ("Phase 4") diff --git a/tests/test_relay.cpp b/tests/test_relay.cpp index 58e2fd2..d4153ae 100644 --- a/tests/test_relay.cpp +++ b/tests/test_relay.cpp @@ -3,6 +3,14 @@ // fusa:test REQ-RELAY-003 // fusa:test REQ-RELAY-004 // fusa:test REQ-RELAY-005 +// fusa:test REQ-RELAY-006 +// fusa:test REQ-RELAY-008 +// fusa:test REQ-RELAY-009 +// fusa:test REQ-RELAY-010 +// fusa:test REQ-RELAY-012 +// fusa:test REQ-RELAY-014 +// fusa:test REQ-RELAY-016 +// fusa:test REQ-RELAY-017 // RELAY conformance tests (RELAY spec §18.2, §5.1, §5.2, §10.3, §14, §19.4). // @@ -48,6 +56,24 @@ TEST_CASE("relay: Protocol enum values match spec §3", "[relay][conformance]") REQUIRE(static_cast(relay::Protocol::SOMEIP) == 6); } +// to_string(Protocol) — c-RCP's relay_protocol_string() equivalent: a +// unique, non-empty name per defined protocol constant. +TEST_CASE("relay: to_string(Protocol) returns a unique, non-empty name per protocol", + "[relay][conformance]") { + const relay::Protocol protos[] = {relay::Protocol::CAN, relay::Protocol::DDS, + relay::Protocol::LIN, relay::Protocol::MQTT, + relay::Protocol::RCP, relay::Protocol::SOMEIP}; + const size_t proto_count = sizeof(protos) / sizeof(protos[0]); + for (size_t i = 0; i < proto_count; ++i) { + auto name = relay::to_string(protos[i]); + REQUIRE_FALSE(name.empty()); + for (size_t j = 0; j < i; ++j) { + REQUIRE(name != relay::to_string(protos[j])); + } + } + REQUIRE(relay::to_string(relay::Protocol::RCP) == "RCP"); +} + // ── §5.1: Mandatory error sentinels ─────────────────────────────────────────── TEST_CASE("relay: mandatory error sentinels exist", "[relay][conformance]") { @@ -134,6 +160,16 @@ TEST_CASE("relay: Channel recv returns nullopt after close with empty queue", "[ REQUIRE_FALSE(ch.recv().has_value()); } +// is_closed() reports the channel's closed state (REQ-RELAY-016) — c-RCP's +// relay_message_channel_is_closed() equivalent. +TEST_CASE("relay: Channel is_closed reports false before close and true after", + "[relay][channel]") { + relay::Channel ch(4); + REQUIRE_FALSE(ch.is_closed()); + ch.close(); + REQUIRE(ch.is_closed()); +} + // ── §10.3: Adapt() wraps a RequestFn as relay::Caller ──────────────────────── // mock_request_fn wires a fresh rcp::mock::Server (v2.12.0) as the RequestFn // every test case below adapts — the same "client-side send-equivalent