Skip to content

feat: v0.107.0 — enforce TC18 Table 30's Row-2 evt[2:0] rule for ADC - #153

Merged
SoundMatt merged 2 commits into
mainfrom
feat/adc-evt-row2-enforcement
Aug 1, 2026
Merged

feat: v0.107.0 — enforce TC18 Table 30's Row-2 evt[2:0] rule for ADC#153
SoundMatt merged 2 commits into
mainfrom
feat/adc-evt-row2-enforcement

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Summary

Found during a cross-repo architecture inventory (RELAY docs/RCP-ARCHITECTURE.md): rcp_ep_adc_decode_read_request() never checked evt[2:0] at all. Per TC18 §13.5 Table 30 (TC18.txt:3666-3710), the {ADC, PWM_IN, I2C, LIN, CAN, UART, ISELED, MDIO} endpoint-type row requires evt[2:0] = 000b for a plain request — every other value is either reserved (001b-110b, reject with UNSUPPORTED_CMD) or selects a differently-shaped configuration-write request (111b, §12.7.1) a plain read decoder must not accept.

Adds rcp_acf_evt_row2_is_plain() (acf.h/acf.c) as the shared primitive, wired into ADC's decode_read_request as the first of 7 affected endpoint types. I2C/UART/ISELED/MDIO need the same wiring (tracked in task #63 on the RCP-ARCHITECTURE effort). CAN/LIN each invented a different, spec-incompatible use of evt[2:0] (frame-format / comparison-mode selection) and need a separate redesign — not fixed here.

rcp_ep_adc_decode_read_request() isn't wired into any real server dispatch loop today (confirmed: no caller in src/server.c/src/mock.c), so no observed behavioral impact on any live path — but it's a real conformance defect any future dispatch wiring would inherit.

New public API + a real decode-behavior tightening — MINOR bump.

Test plan

  • Mutation-tested: reverting the evt check makes the new rejection test fail
  • ctest — 64/64 pass
  • cfusa check (v0.5.50) — 0 errors
  • cfusa trace --sec-tested 100 — 977/977 traced and tested (was 976)

Points to RELAY's docs/RCP-ARCHITECTURE.md (the canonical architecture
shared with go-RCP/cpp-RCP/rust-RCP), with this repo's file-path mapping
and current conformance status against it. c-RCP is the reference
implementation for the per-function requirement-tagging convention, the
.fusa-reqs.json tc18/scope/status fields, and the conditional-request
req-id grouping scheme.

No code change.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
Found during a cross-repo architecture inventory (RELAY
docs/RCP-ARCHITECTURE.md, task tracking c-RCP's Table 30 centralization):
rcp_ep_adc_decode_read_request() never checked evt[2:0] at all. Per TC18
§13.5 Table 30 (TC18.txt:3666-3710), the {ADC, PWM_IN, I2C, LIN, CAN,
UART, ISELED, MDIO} endpoint-type row requires evt[2:0] = 000b for a
plain request; every other value is either reserved (001b-110b, reject
with UNSUPPORTED_CMD) or selects a differently-shaped configuration-write
request (111b, §12.7.1) that a plain read decoder must not accept.
ADC's own doc comment already said "the ACF header's evt field is always
encoded/decoded as 0" -- true for the encoder, never enforced on decode.

Adds rcp_acf_evt_row2_is_plain() (acf.h/acf.c) as the shared primitive
this endpoint-type row's rule should be checked with -- the actual
centralization the architecture doc calls for -- and wires it into
ADC's decode_read_request as the first of 7 affected endpoint types.
I2C/UART/ISELED/MDIO need the same wiring (tracked); CAN/LIN each
invented a *different*, spec-incompatible use of evt[2:0] (frame-format
selection, comparison-mode selection) and need a separate redesign
moving that behavior into their existing functional-config structs
instead -- not fixed here.

rcp_ep_adc_decode_read_request() is not currently wired into any real
server dispatch loop (confirmed: no caller in src/server.c/src/mock.c),
so this has no observed behavioral impact on any live path today, but is
a real conformance defect any future dispatch wiring would inherit.

Mutation-tested: reverting the evt check makes the new rejection test
fail (decode incorrectly succeeds on a reserved evt[2:0] value).

New public API (RCP_EP_ADC_ERR_BAD_EVT, rcp_acf_evt_row2_is_plain) and a
real, if not-yet-live, decode-behavior tightening -- MINOR bump.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit 4556c51 into main Aug 1, 2026
23 checks passed
@SoundMatt
SoundMatt deleted the feat/adc-evt-row2-enforcement branch August 1, 2026 22:17
SoundMatt added a commit that referenced this pull request Aug 1, 2026
…REAKING) (#155)

* feat: v0.108.0 — enforce TC18 Table 30's Row-2 evt[2:0] rule for I2C/UART/ISELED/MDIO

Completes the Table 30 centralization started in #153 (ADC): none of
I2C's decode_transfer_request, UART's decode_write_request/
decode_read_request, ISELED's decode_command_request, or MDIO's
decode_read_request/decode_write_request checked evt[2:0] at all,
despite every one of their own encoders already hardcoding it to 0
("no channel selector" per each module's own file header). Per TC18
§13.5 Table 30, this endpoint-type row's only legal evt[2:0] value for a
plain request is 000b; every other value is either reserved (reject with
UNSUPPORTED_CMD) or selects a differently-shaped configuration-write
request (111b, §12.7.1) these decoders must not accept.

All five now call the shared rcp_acf_evt_row2_is_plain() primitive
(acf.h/acf.c, introduced in #153) at the same point in their validation
sequence ADC already established.

None of these decode functions are wired into any real server dispatch
loop today (same finding as #153), so no observed behavioral impact on
any live path -- but each is a real conformance defect any future
dispatch wiring would inherit.

Mutation-tested per endpoint: reverting each evt check makes exactly its
own new rejection test(s) fail, and no others.

Remaining Table 30 work, tracked separately: CAN and LIN each invented a
different, spec-incompatible use of evt[2:0] (frame-format selection,
comparison-mode selection) and need a real redesign, not this same
additive fix.

New public API (5 new error variants) and real decode-behavior
tightening across 5 modules -- MINOR bump.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>

* fix: v0.109.0 — CAN FrameFormat lives in the payload, not evt[2:0] (BREAKING)

Verified directly against TC18 §13.7.11.3 Figure 39 (rendered from the
source PDF at 400dpi, not just the text extraction): FrameFormat is the
payload's leading quadlet's top 3 bits, packed with a 29-bit CAN ID
(right-aligned for an 11-bit base id, per the same section's own note) --
NOT evt[2:0], which for CAN (a member of TC18 §13.5 Table 30's {ADC,
PWM_IN, I2C, LIN, CAN, UART, ISELED, MDIO} row) has the same ordinary
meaning every other endpoint in that row gives it: 000b plain, 111b
configuration-write, else UNSUPPORTED_CMD.

This module's own doc comments candidly admitted the evt[2:0] packing
was invented, modeled on ep_spi.h's real, TC18-sanctioned evt[2:0]
channel-selector row -- without checking that CAN belongs to the *other*
Table 30 row, whose evt[2:0] values TC18 reserves. REQ-CANEP-031 had even
formally catalogued the wrong claim as "implemented". Found while
investigating this repo's Table 30 centralization (task tracking:
CAN/LIN evt[2:0] non-conformance) and confirmed by rendering the actual
spec figure rather than trusting the existing design's own reasoning.

Fix: pack frame_format into the leading quadlet
(write_prefix/read_prefix), leave evt at 0 everywhere this module
encodes a request/response, and check evt[2:0] via the shared
rcp_acf_evt_row2_is_plain() primitive (acf.h/acf.c, from #153/#154) on
decode. Adds RCP_EP_CAN_ERR_BAD_EVT and RCP_EP_CAN_ERR_BAD_ARBITRATION_ID
(the latter enforcing Figure 39's own "11-bit id shall be right aligned"
rule, previously unchecked). rcp_ep_can_decode_frame_response_fragment()
no longer outputs frame_format (it isn't determinable per-fragment once
it moves into the payload) -- rcp_ep_can_decode_reassembled_frame_response()
now derives it from the reassembled buffer instead of taking it as an
input, the only place it's actually recoverable after fragmentation.

Golden-vector tested: a hand-computed leading-quadlet value
((1<<29)|0x01ABCDEF = 0x21ABCDEF for CEFF, id 0x01ABCDEF), independently
derived from Figure 39 rather than round-tripped through the encoder
under test. Mutation-tested: dropping the format bits from the packed
quadlet fails exactly the golden-vector and round-trip tests. Also fixes
a pre-existing test (test_tc18_gaps_ep2.c) that had encoded the same
wrong evt[2:0] assumption as its own expected behavior. Verified clean
under ASan/UBSan.

Neither rcp_ep_can_decode_frame_request() nor _decode_frame_response()
is wired into any real server dispatch loop today (no caller in
src/server.c/src/mock.c), so no observed behavioral impact on any live
path -- but this was a real interop-breaking wire-format defect any
future dispatch wiring, or any external client implementing TC18
correctly, would have hit immediately.

BREAKING: every function's own wire encoding changes for every
frame_format value, not just an added validation -- no c-RCP CAN
endpoint traffic (real or test) was wire-compatible with a conformant
TC18 peer before this fix, so there is no meaningful backward-compat
concern to preserve.

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>

---------

Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant