Skip to content

Commit 4ac8eae

Browse files
committed
rewrite(phase6): batch 1 — ACF/AVTP/WIREERR catalog re-derivation
Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. Phase 6 (requirement catalog), batch 1 of 13. ## Background cpp-RCP's .fusa-reqs.json (412 entries) is missing hundreds of requirement entries the code was already ported to implement in Phases 1-5, and cpp-RCP's code already references 1015 distinct REQ-* ids via //fusa:req/[REQ-XXX] tags against only 412 defined entries -- 603 orphaned citations left behind by porting agents as unresolved "ported from c-RCP REQ-XXX, see file.c:NNN" provenance notes. Phase 6 walks these existing citations prefix-by-prefix and turns each into a real catalog entry (or a documented gap), rather than re-deriving requirements from scratch. ## Prefix-scheme decision Kept REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* as their own prefixes reusing c-RCP's exact id numbers 1:1, rather than folding into cpp-RCP's existing REQ-WIRE-*/REQ-ACF-*/REQ-ERR-* buckets -- the code already had orphaned citations using c-RCP's exact numbering (REQ-ACF-044, REQ-AVTP-014/021/022/023), signaling that was the intended scheme all along. Existing REQ-WIRE-*/REQ-ACF-*/REQ-ERR-* entries are untouched; duplicate-coverage mappings recorded below for anyone reconciling the two schemes later. ## New catalog entries (36) - REQ-ACF-{001,004,012,013,014,017,019,021,023,024,025-031,032,033, 038,044,047-053} (28 entries) - REQ-AVTP-{013,014,015,021,022,023,031} (7 entries) - REQ-WIREERR-001 (WireErrorCode enum matches TC18 Table 27/30 numbering) Each has a real //fusa:req tag in acf.hpp/avtp.hpp and //fusa:test + Catch2 bracket tag coverage in test_acf.cpp/test_avtp.cpp. 3 new tests added where coverage was genuinely missing: acf_category()/ avtp_category() message-uniqueness (REQ-ACF-001, REQ-AVTP-015), encoder-forces-correct-type-despite-wrong-caller-header (REQ-ACF-004/ 038), WireErrorCode numeric-value pinning (REQ-WIREERR-001). Also fixed a mislabeled inline comment in avtp.hpp that cited REQ-AVTP-022 for behavior that's actually REQ-AVTP-031's (tscf_reserved_all_zero). ## Genuine gaps found (9, filed to .fusa-reqs-pending.json, not the catalog) - REQ-ACF-006/040: encode_acf_abb/_gbb honor a caller-supplied nonzero acf_msg_length instead of always recomputing -- a documented, deliberate divergence needed by e2e.hpp's CRC-trailer accounting, flagged here for an explicit accept/reject call rather than silently carried forward. - REQ-ACF-009/041: decoders never validate declared acf_msg_length against buffer size -- matches the code's own pre-existing TODO(phase1-followup) comment. - REQ-ACF-011: encode_acf_gbb doesn't zero message_timestamp when mtv=false. - REQ-ACF-018: no read_size_or_segment_num kind-selector; existing doc comment contradicts TC18's op-based rule. - REQ-AVTP-012: no combined stream_id+byte_bus_id address-equality construct anywhere. - REQ-WIREERR-002: no WireErrorCode-to-string function. - REQ-WIREERR-005: SequencerNotKnown enumerator has zero consumers (owner-tracking not yet built). ## Judgment calls, not filed as gaps (documented here, not in the pending file) - REQ-ACF-037: cpp's op is a 2-state bool, not c-RCP's 3-state enum -- the OP_NONE fallback case is structurally inapplicable, not missing. - REQ-AVTP-016/027 (transport retain/release refcounting): cpp-RCP uses RAII via l2.hpp/udp.hpp/shmem.hpp instead of a manual-refcount transport abstraction -- an architecture divergence, and neither has a TC18 citation in c-RCP's own catalog, so they don't fit the pending file's TC18-scope contract. ## Existing-duplicate mappings (no action; c-RCP id -> cpp-RCP id already covering it) ACF: 002/022/034/035/036/037->WIRE-010, 005/039->WIRE-013, 007/008-> WIRE-014, 010->WIRE-004, 015/016/046->WIRE-006, 020->WIRE-004, 042/043/045->WIRE-005. AVTP: 001/003->WIRE-001, 002/004->WIRE-002, 005/006->WIRE-013, 007/008->WIRE-003, 010/011/025->WIRE-007. ## Out of scope (implemented, but in other files -- noted only, not this batch's concern) AVTP-009/017-020/024/028/029/030/032-034 (l2.hpp, udp.hpp, shmem.hpp, lifecycle.hpp, mock.hpp, server.hpp); WIREERR-003/004/006/007 (e2e.hpp, lifecycle.hpp, request.hpp, pwm.hpp) -- confirmed genuinely implemented there, will get their own tags when those prefixes' batches run. ## Verification .fusa-reqs.json: 412 -> 448 entries (+36, 3672 -> 4092 lines). .fusa-reqs-pending.json: 3 -> 12 entries (+9, 35 -> 123 lines). include/rcp/acf.hpp: +28 lines. include/rcp/avtp.hpp: +14 lines net. tests/test_acf.cpp: +84 net. tests/test_avtp.cpp: +22 net. Independently re-verified (not just the porting agent's own report): confirmed programmatically that zero of the 412 pre-existing catalog entries were modified or deleted (semantic id-keyed diff against the pre-batch base, not just a raw line diff -- the raw diff shows churn in unrelated CLI/RELAY/Discovery entries that's pure JSON re-serialization noise, not content change), and that zero orphaned REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* tags remain anywhere in acf.hpp/ avtp.hpp/test_acf.cpp/test_avtp.cpp. Full tree rebuilt from scratch: 0 errors, 0 warnings, 58/58 ctest suites pass (100%). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> ## CI gate fix found while watching this PR's checks `cpfusa trace`'s sec-tested gate failed: it was a 17% floor (64/376 at the time it was written) against `secTestedRequirements/totalRequirements`, but its own comment already stated the real intent was "a regression floor against the exact, CYBERSECURITY.md-enumerated subset" losing test coverage -- not a claim about what fraction of the codebase is security-relevant. A percentage-of-total is the wrong shape for that intent once the total catalog is deliberately growing (exactly what Phase 6 does): this batch's 36 new, correctly non-cybersecurity REQ-ACF-*/REQ-AVTP-*/REQ-WIREERR-* entries dropped the ratio from 72/412=17.5% to 72/448=16.1%, failing a floor that was never meant to track total-catalog size. Converted ci.yml's "Enforce security-test coverage" step to an absolute-count floor (>= 72, the current CYBERSECURITY.md-enumerated total) instead -- this still catches the real regression the step exists for (an enumerated id losing its test tag, or being dropped from severity:cybersecurity) without being diluted by legitimate, intentional catalog growth elsewhere. Without this fix, every subsequent Phase 6 batch would hit the same false failure as the denominator keeps growing toward c-RCP's 1282-entry scale. Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
1 parent 4db668c commit 4ac8eae

7 files changed

Lines changed: 774 additions & 95 deletions

File tree

‎.fusa-reqs-pending.json‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,94 @@
3030
"asil": "ASIL-B",
3131
"tc18": "§11.2.2.5 Figure 12 / Table 10 (\"presentation_time\"), TC18.txt L1596-1649",
3232
"gap": "Confirmed absent (2026-08-07): presentation_time appears nowhere in include/ or src/ except one comment. RequestTypeOpcode::Timed is recognized for opcode validity and priority category only (request.hpp:201,312) -- grepped for is_due/due_at/scheduled_time/delay_us/delay_ms and RequestTypeOpcode::Timed usage sitewide, found zero scheduling/dispatch logic anywhere. There is currently no mechanism in this codebase that defers a Timed request's execution until any point in time at all; the opcode is decodable but has no distinguishing runtime behavior from a standard request."
33+
},
34+
{
35+
"id": "REQ-ACF-006",
36+
"title": "encode_acf_abb shall recompute acf_msg_length from the actual payload, ignoring any caller-supplied value",
37+
"text": "rcp::acf::encode_acf_abb shall derive the encoded acf_msg_length field from payload.size(), ignoring any acf_msg_length value present in the caller-supplied AcfMessageInfo.",
38+
"standard": "iso26262",
39+
"level": "ASIL-B",
40+
"asil": "ASIL-B",
41+
"tc18": "§11.2.1 Table 4 (\"acf_msg_length: number of quadlets of data contained in this message\"), TC18.txt L1154",
42+
"gap": "Confirmed divergence (2026-08-22): encode_acf_abb (acf.hpp:428-447) only computes acf_msg_length via compute_acf_msg_length() when the caller leaves AcfMessageInfo::acf_msg_length at its default (0) -- see acf.hpp's own header comment, lines 44-64 (\"acf_msg_length auto-fill\"), which documents this as a DELIBERATE divergence from c-RCP: rcp/e2e.hpp's apply_acf_length_adjustment() needs to bake a trailing CRC32 trailer's +1 quadlet into the header before encoding, which requires encode_acf_abb() to honor a caller-supplied nonzero acf_msg_length rather than recomputing it unconditionally. c-RCP's own rcp_acf_encode_abb() always recomputes, ignoring the caller's header value entirely, which is what REQ-ACF-006 literally requires. This is a real, working, and load-bearing behavioral difference (not an oversight) -- filed here for FuSa reviewers to make an explicit accept/reject call on the divergence (accept the documented rationale, or require encode_acf_abb() to gain a separate strict-recompute entry point) rather than silently losing the c-RCP-conformant requirement text. Affects both ACF_ABB (this id) and ACF_GBB (REQ-ACF-040, same root cause in encode_acf_gbb, acf.hpp:486-499)."
43+
},
44+
{
45+
"id": "REQ-ACF-009",
46+
"title": "decode_acf_abb shall reject a declared acf_msg_length that describes a payload extending past the buffer",
47+
"text": "rcp::acf::decode_acf_abb shall return AvtpErrc::short_buffer whenever the header's declared acf_msg_length describes a payload extending past the end of the supplied buffer.",
48+
"standard": "iso26262",
49+
"level": "ASIL-B",
50+
"asil": "ASIL-B",
51+
"tc18": "§11.2.1 Table 4's acf_msg_length field, TC18.txt L1154",
52+
"gap": "Confirmed absent (2026-08-22): decode_acf_abb (acf.hpp:472-481) checks only that the buffer is at least kAcfCommonHeaderLen bytes long; it never compares the decoded acf_msg_length field against the actual buffer size, and treats everything from the header to the end of whatever buffer it was given as payload. acf.hpp's own \"TODO(phase1-followup)\" comment (lines 449-471) states this outright: c-RCP's stricter decode_abb()/decode_gbb() (which treat acf_msg_length*4 as the message's authoritative byte length and reject a short buffer) is 'not ported' because every real payload builder in this tree hands encode_acf_abb()/_gbb() an arbitrary-length payload without pre-padding to a quadlet boundary, so compute_acf_msg_length()'s ceiling-rounded value is not generally byte-exact and enforcing this strictly today would reject frames this library's own encoders legitimately produce. A genuine, currently-intentional gap versus TC18/c-RCP; the same TODO names the fix (make every payload builder pre-pad via pad_len() and set `pad` accordingly) as a later-phase prerequisite. Affects both ACF_ABB (this id) and ACF_GBB (REQ-ACF-041, same function pair, acf.hpp:501-512)."
53+
},
54+
{
55+
"id": "REQ-ACF-011",
56+
"title": "encode_acf_gbb shall zero the message_timestamp region for an untimed (mtv=false) message",
57+
"text": "rcp::acf::encode_acf_gbb shall write an all-zero message_timestamp region whenever the caller's AcfMessageInfo::mtv is false, regardless of the message_timestamp value passed in.",
58+
"standard": "iso26262",
59+
"level": "ASIL-B",
60+
"asil": "ASIL-B",
61+
"gap": "Confirmed absent (2026-08-22): encode_acf_gbb (acf.hpp:486-499) writes whatever message_timestamp value the caller passes via avtp::detail::put_u64 unconditionally -- it never inspects info.mtv before writing the 8-byte timestamp region. A caller that passes mtv=false together with a nonzero message_timestamp gets that nonzero value serialized onto the wire verbatim, not zeroed. No test in test_acf.cpp exercises mtv=false with a nonzero message_timestamp argument. Real, currently-unguarded gap versus c-RCP's rcp_acf_encode_gbb()."
62+
},
63+
{
64+
"id": "REQ-ACF-018",
65+
"title": "A read_size_or_segment_num classifier shall select read_size vs segment_num by the op bit, per TC18 Table 4/6",
66+
"text": "A function equivalent to c-RCP's rcp_acf_read_size_or_segment_num_kind() shall interpret AcfMessageInfo::read_size_or_segment_num as read_size when op is false (read) and as segment_num otherwise (write), per TC18 §11.2.1 Table 4 and §11.2.2.1 Table 6.",
67+
"standard": "iso26262",
68+
"level": "ASIL-B",
69+
"asil": "ASIL-B",
70+
"tc18": "§11.2.1, TC18.txt L1169 (Table 4); §11.2.2.1, TC18.txt L1280 (Table 6)",
71+
"gap": "Confirmed absent (2026-08-22): acf.hpp has no read_size_or_segment_num_kind()-equivalent classifier at all -- the field is an opaque round-tripped uint16_t (acf.hpp:216). Its own doc comment reads \"read_size when !ms, segment_num when ms\" (gated on the `ms`/more-segments flag, acf.hpp:216 and the Message struct's own comment at acf.hpp:1073), which is a DIFFERENT selection criterion than TC18's op-based rule c-RCP implements -- for a plain (ms=0) write request, c-RCP's rule says the field is segment_num (op != READ) while cpp-RCP's own comment says it's read_size (ms == 0); these views only agree for reads. In practice this hasn't caused an observed bug because every real endpoint module (i2c.hpp:457/485, iseled.hpp:636/657, spi.hpp:756/788, uart.hpp:778/800) independently reimplements its own local read-vs-write gate instead of consulting one shared classifier, and fragmentation (which would set ms=1) is deferred to v2.8.0 per acf.hpp's own scope note -- so the field is only ever consumed as read_size today. Still a real, confirmed-missing shared primitive, and the ms-based doc comment should be reconciled with TC18's op-based rule before fragmentation support lands."
72+
},
73+
{
74+
"id": "REQ-ACF-040",
75+
"title": "encode_acf_gbb shall recompute acf_msg_length from the actual payload, ignoring any caller-supplied value",
76+
"text": "rcp::acf::encode_acf_gbb shall derive the encoded acf_msg_length field from payload.size(), ignoring any acf_msg_length value present in the caller-supplied AcfMessageInfo.",
77+
"standard": "iso26262",
78+
"level": "ASIL-B",
79+
"asil": "ASIL-B",
80+
"tc18": "§11.2.1 Table 4 (\"acf_msg_length: number of quadlets of data contained in this message\"), TC18.txt L1154",
81+
"gap": "GBB half of REQ-ACF-006's gap: encode_acf_gbb (acf.hpp:486-499) has the identical \"0 = auto, nonzero = caller override\" contract, documented for the same rcp/e2e.hpp CRC-trailer reason at acf.hpp:44-64. See REQ-ACF-006's gap text for the full rationale; both ids share one root cause and one fix."
82+
},
83+
{
84+
"id": "REQ-ACF-041",
85+
"title": "decode_acf_gbb shall reject a declared acf_msg_length that describes a payload extending past the buffer",
86+
"text": "rcp::acf::decode_acf_gbb shall return AvtpErrc::short_buffer whenever the header's declared acf_msg_length describes a payload extending past the end of the supplied buffer.",
87+
"standard": "iso26262",
88+
"level": "ASIL-B",
89+
"asil": "ASIL-B",
90+
"tc18": "§11.2.1 Table 4's acf_msg_length field, TC18.txt L1154",
91+
"gap": "GBB half of REQ-ACF-009's gap: decode_acf_gbb (acf.hpp:501-512) has the identical lenient contract (only the fixed kAcfGbbMessageInfoLen is checked, never the declared acf_msg_length against buffer size), governed by the same \"TODO(phase1-followup)\" comment at acf.hpp:449-471. See REQ-ACF-009's gap text for the full rationale; both ids share one root cause and one fix."
92+
},
93+
{
94+
"id": "REQ-AVTP-012",
95+
"title": "AVTP address equality shall require a matching byte_bus_id, not just stream_id",
96+
"text": "A combined stream_id + byte_bus_id address type shall compare equal only when both stream_id AND byte_bus_id match, so that a request is routed to an endpoint only when both TC18-required address components agree.",
97+
"standard": "iso26262",
98+
"level": "ASIL-B",
99+
"asil": "ASIL-B",
100+
"tc18": "§13.1 (\"The byte_bus_id is only unique in the context of a STREAM_ID and requests are only passed to the endpoint if both match.\"), TC18.txt L3898-3899; confirming §12.9.1 (\"In dependence on the stream_id and byte_bus_id the RC Server determines the endpoint that is addressed. If the lookup of the byte_bus_id in the context of the stream_id does not point to an Endpoint, the request is dropped without further notification.\"), TC18.txt L3591-3593",
101+
"gap": "Confirmed absent (2026-08-22): no AvtpAddress-equivalent struct combining StreamId and ByteBusId with its own equality operator exists anywhere in cpp-RCP (grepped every include/rcp/*.hpp for \"AvtpAddress\"/\"avtp_address\"/a combining Address struct -- zero hits; avtp::ByteBusId is a bare uint16_t alias, avtp.hpp:191, with no pairing construct). include/rcp/admin.hpp:99 has a comment explicitly noting c-RCP's rcp_avtp_addr_t as a thing not yet ported. The dispatch-time address matching this would support (an inbound frame's stream_id+byte_bus_id pair resolving to exactly one endpoint) happens ad hoc inside rcp/mock.hpp's dispatch tables (out of this batch's edit scope, and itself not confirmed to implement the combined-match rule as a single testable predicate). Real, TC18-cited (§13.1/§12.9.1) gap; natural home is rcp/avtp.hpp."
102+
},
103+
{
104+
"id": "REQ-WIREERR-002",
105+
"title": "A wire_error_string-equivalent function shall return a unique, non-empty message per WireErrorCode",
106+
"text": "A function equivalent to c-RCP's rcp_wire_error_string() shall return a unique, non-empty human-readable message for each rcp::acf::WireErrorCode enumerator.",
107+
"standard": "iso26262",
108+
"level": "QM",
109+
"asil": "QM",
110+
"gap": "Confirmed absent (2026-08-22): no to-string/message function exists for rcp::acf::WireErrorCode anywhere in cpp-RCP. acf_category() (acf.hpp:126-141) covers AcfErrc (decode-time errors: bad_acf_msg_type/bad_acf_msg_length) -- a completely different enum from WireErrorCode (the Table 27/30 numeric codes carried in an ErrorResponse's byte_msg_payload, acf.hpp:693-711). WireErrorCode is a plain enum class, not a std::error_code category, so it has no .message()-style accessor at all today. QM-level ergonomics gap (c-RCP's own REQ-WIREERR-002 is asil QM), genuinely missing rather than architecturally divergent; natural home is rcp/acf.hpp alongside the WireErrorCode definition."
111+
},
112+
{
113+
"id": "REQ-WIREERR-005",
114+
"title": "A sequencer wire-error classifier shall distinguish SEQUENCER_NOT_KNOWN (unknown index) from UNAUTHORIZED_ACCESS (wrong owner)",
115+
"text": "A function equivalent to c-RCP's rcp_sequencer_wire_error() shall map a sequencer-access rejection to WireErrorCode::SequencerNotKnown when the referenced sequencer index does not exist at all, and to WireErrorCode::UnauthorizedAccess when it exists but the requester is not its owner.",
116+
"standard": "iso26262",
117+
"level": "ASIL-B",
118+
"asil": "ASIL-B",
119+
"tc18": "§12.7.10 Table 28 (own-access-control rule, \"Request_stream_index... allowed to access this sequencer\"), TC18.txt L3460-3493; §12.9.6 Table 30 (SEQUENCER_NOT_KNOWN=2, UNAUTHORIZED_ACCESS=3), TC18.txt L3812-3844",
120+
"gap": "Confirmed absent (2026-08-22): rcp::acf::WireErrorCode::SequencerNotKnown (acf.hpp:695) is defined but grepped as having ZERO consumers anywhere in include/ or tests/ -- no caller ever produces it. This is consistent with rcp/request.hpp's own explicit \"TODO(phase1-followup)\" (lines 219-225) acknowledging that c-RCP's REQ-SEQ-013/014-style per-sequencer-owner/access-control gate is not yet ported: SequencerTable has no owner-per-sequencer concept to distinguish \"unknown index\" from \"real sequencer, wrong owner\" in the first place, so there is nothing yet to map to these two distinct wire codes. Real, TC18-cited (§12.7.10 Table 28) gap; the WireErrorCode enumerator itself lives in rcp/acf.hpp (in this batch's scope) but the classifier logic's natural home is rcp/request.hpp (out of this batch's scope, and already tracked there via that file's own TODO)."
33121
}
34122
]
35123
}

0 commit comments

Comments
 (0)