Skip to content

rewrite(phase4): mock.hpp batch D1 — wire fragment.hpp/respqueue.hpp for E2E fragmented dispatch - #149

Merged
SoundMatt merged 1 commit into
rewrite/v3-from-c-rcpfrom
phase4/mock-d
Aug 22, 2026
Merged

SoundMatt merged 1 commit into
rewrite/v3-from-c-rcpfrom
phase4/mock-d

Conversation

@SoundMatt

Copy link
Copy Markdown
Owner

Part of the v3.0.0 ground-up rewrite (#129, ROADMAP.md Phase 17), branch rewrite/v3-from-c-rcp. First of two sub-batches (D1/D2) making up Phase 4's fourth and final batch. Built on batches A (admission), B (response suppression + regmap/discovery), and C (single-member E2E dispatch with RxSequenceGuard/StreamFaultTracker/RxWatchdog).

Closes another day-one gap

fragment.hpp and respqueue.hpp were both fully ported in Phase 1 (fixed-capacity, zero heap allocation, with the c-RCP issue #614/#616 oversized-reassembly lesson baked in from day one) but neither was ever wired into any dispatch path. This closes that.

Ported

REQ-E2E-038/039/046, REQ-ISELED-025, REQ-FRAG-*. New dispatch_e2e_fragment() feeds fragments into a per-request-stream fragment::Reassembler, validates compute_fragmented_crc(), then delegates into the existing single-member dispatch path. New maybe_fragment_response() slices an oversized genuine response via fragment::plan() onto a respqueue::RespQueue. Confirmed via source read that c-RCP's own fragment-dispatch has no sequence-gate or TSCF variant reachable here — mirrors batch C's own "NTSCF-only" simplification; both deferred to D2's frame-level dispatch.

Oversized-reassembly error path — verified real, not silent

Distinct tests prove: (1) a reassembled total over the ACF wire ceiling produces a genuine RequestRejected ErrorResponse, and (2) the Reassembler's own internal capacity bound produces a no-wire-response rejection matching c-RCP's RCP_MOCK_DISPATCH_REJECTED precedent. Symmetric coverage on the response side too.

Bug found and fixed

acf::decode_acf_abb/gbb()'s known-lenient pad-trimming gap (a documented Phase-1 TODO) would have corrupted both the reassembled payload and the fragmented-CRC comparison whenever pad != 0. Added a local trim_wire_pad() helper scoped to this batch.

Verification

13 new TEST_CASEs covering plain passthrough, genuine reassembly, out-of-order rejection, CRC mismatch + stream-fault latch, both oversized paths, three response-fragmentation scenarios, accessor behavior. mock.hpp: 2162→2723 lines. test_mock.cpp: 1832→2349 lines, 77→89 TEST_CASEs (764 assertions).

Full tree rebuilds clean (0 errors/warnings), 58/58 ctest suites pass (100%) — independently re-verified from a fresh build.

…for E2E fragmented dispatch

Part of the v3.0.0 ground-up rewrite (cpp-RCP #129, ROADMAP.md Phase 17),
branch rewrite/v3-from-c-rcp. First of two sub-batches (D1/D2) making up
Phase 4's fourth and final batch. Built on batches A (admission), B
(response suppression + regmap/discovery), and C (single-member E2E
dispatch with RxSequenceGuard/StreamFaultTracker/RxWatchdog).

fragment.hpp and respqueue.hpp were both fully ported in Phase 1
(fixed-capacity, zero heap allocation, with the c-RCP issue #614/#616
oversized-reassembly error-handling lesson baked in from day one) but
neither was ever wired into any dispatch path. This closes that gap.

## Ported (c-RCP src/mock.c:2175-2504, rcp_mock_server_dispatch_e2e_fragment)

REQ-E2E-038/039/046, REQ-ISELED-025, REQ-FRAG-*. Confirmed by direct
source read that c-RCP's own fragment-dispatch function has no
sequence-gate call and no TSCF variant reachable from this rewrite's
scope -- mirroring batch C's own established "NTSCF-only, no per-frame
seq gate" simplification for dispatch_e2e(), neither
dispatch_e2e_fragment_tscf nor a sequence-number gate were ported here;
both are legitimately deferred to D2's future frame-level
dispatch_frame_e2e().

## New public surface on mock::Server

- dispatch_e2e_fragment(client, stream_id, fragment, out_resp,
  out_resp_payload) -- feeds fragments into a per-request-stream
  fragment::Reassembler (keyed by
  regmap::request_stream_cfg::resolve_index(), same indexing as
  seq_trackers_/rx_watchdogs_/stream_status_). On completion validates
  e2e::compute_fragmented_crc() (over the first fragment's header +
  reassembled payload, deliberately ignoring unwrap()'s own wrong
  single-frame verdict, matching c-RCP's own comment), then delegates
  into the existing single-member decode_and_dispatch()/dispatch()
  path -- no duplicated CRC-unwrap/admission logic.
- maybe_fragment_response(...) -- new (c-RCP's own equivalent,
  rcp_mock_server_dispatch_multi_response(), is a per-endpoint
  registered-handler pattern this rewrite's hardcoded
  ten-dispatch_*()-method architecture has no analog for). Slices an
  oversized genuine response via fragment::plan() and pushes each
  fragment onto a respqueue::RespQueue keyed by rx_resp_stream_index
  (1-based, resolved against regs_.response_streams -- multiple
  request streams can share one response queue).
- Accessors: fragment_reassembler(stream_id), resp_queue_for_stream(
  stream_id) -- nullable pointers mirroring c-RCP's own
  rcp_mock_server_fragment_reassembler() precedent.
- Three new DispatchErrc values: fragment_pending, fragment_rejected
  (both no-wire-response, mirroring c-RCP's identical dispositions),
  response_fragmented (real multi-frame response now on the RespQueue).

## Oversized-reassembly error path (c-RCP issue #614/#616) -- verified
real, not silent

After CRC validation, the reassembled total is checked against the ACF
wire ceiling before re-encoding; on overflow, set_error_response()
produces a genuine RequestRejected ErrorResponse. Verified by test with
a payload under the Reassembler's own capacity but over the ACF wire
ceiling, asserting response_kind_of(resp) == ErrorResponse -- distinct
from a separate test proving the Reassembler's own internal
kErrTooLarge bound produces fragment_rejected with no wire response,
matching c-RCP's own RCP_MOCK_DISPATCH_REJECTED precedent. Symmetric
coverage on the response side: an unconfigured response-stream also
produces a genuine RequestRejected ErrorResponse rather than dropping
silently.

## Bug found and fixed

acf::decode_acf_abb()/decode_acf_gbb() are deliberately lenient (a
documented Phase-1 TODO) and don't trim wire pad octets off the
decoded payload -- left as-is this would have corrupted both the
reassembled payload and the fragmented-CRC comparison whenever pad !=
0. Added a local trim_wire_pad() helper applied to both intermediate-
and final-fragment decodes, scoped to this batch rather than touching
the shared decoder (out of D1's fragmentation-only scope).

## Build-clean fix

std::array<fragment::Reassembler, N>/std::array<respqueue::RespQueue, N>
member declarations with a trailing {} triggered a "converting from
initializer list would use explicit constructor" warning (both types
have explicit sole constructors) -- fixed by dropping the {}.

## Deferred (doc-only TODO update)

apply_cancellation()'s ClearSingle TODO relabeled from
TODO(phase4-batch-d) to TODO(phase4-batch-d2) now that D is split;
D1's own dispatch_e2e_fragment() still routes everything through
admit_and_classify(), so AdmitOutcome::Cancellation remains as
unreachable as it was after batch A -- no behavior touched.

## Verification

13 new TEST_CASEs: plain-command-mode passthrough, unresolvable-stream
fallback, never-fragmented fallback, genuine 2-fragment reassembly,
out-of-order rejection + recovery, fragmented-CRC mismatch + stream-
fault latch, both oversized paths, three response-fragmentation
scenarios (successful multi-entry split with byte-exact reassembly,
unconfigured-queue rejection, max_avtpdu_size-driven tighter
splitting), accessor nullptr behavior.

include/rcp/mock.hpp: 2162 -> 2723 lines. tests/test_mock.cpp: 1832 ->
2349 lines, 77 -> 89 TEST_CASEs (764 assertions). Full tree rebuilds
clean (0 errors, 0 warnings), 58/58 ctest suites pass (100%) --
independently re-verified from a fresh build, not just the porting
agent's own report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Matt <47545907+SoundMatt@users.noreply.github.com>
@SoundMatt
SoundMatt merged commit d02cd34 into rewrite/v3-from-c-rcp Aug 22, 2026
22 checks passed
@SoundMatt
SoundMatt deleted the phase4/mock-d branch August 22, 2026 07:52
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