Skip to content

refactor!: framework-owned response emission + context regrouping (v2.0.0)#92

Open
w1ne wants to merge 44 commits into
developfrom
refactor/phase1-response-builder
Open

refactor!: framework-owned response emission + context regrouping (v2.0.0)#92
w1ne wants to merge 44 commits into
developfrom
refactor/phase1-response-builder

Conversation

@w1ne

@w1ne w1ne commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Two staged architecture phases plus hardening, shipped together as v2.0.0 (breaking).

Phase 1 — framework-owned response emission. Response emission, suppressPosRsp, and post-response ordering move out of every service handler into the dispatch framework. execute_handler is the single authority for how/when a response reaches the wire, removing the bug class behind #76 and #80 at its root.

Phase 2 — context regrouping. The runtime uds_ctx_t's ~40 flat fields are grouped into session / security / server / client / scratch sub-structs. Per-dispatch scratch is reset at top-level entry, making per-request flag leaks (the #80 class) impossible by construction. Byte-identical — purely structural.

Breaking changes

  • Handler contract: void handler(uds_ctx_t*, const uint8_t*, uint16_t, uds_result_t *out) with uds_ok/uds_nrc/uds_pending/uds_none. All core services + the 4 examples migrated. uds_send_response/uds_send_nrc remain public for app code.
  • Context fields regrouped (not API): ctx->active_sessionctx->session.active, ctx->security_levelctx->security.level, etc. Affects only code reading uds_ctx_t fields directly.

Bug fixes shipped alongside

  • ECUReset (0x11) responds before resetting; no post-reset lockout (0x11 ecu_reset function still have error bug, please fix it #76, 0x11 ecu_reset function still have error bug, please fix it #80).
  • ECUReset nested in a SecuredDataTransmission (0x84) now waits for the outer secured response before resetting, and is cancelled if that response fails to encode (tester gets an NRC, ECU does not reboot); a captured ResponseOnEvent (0x86) inner dispatch never resets.
  • Emission fails closed: a handler returning without a result is rejected (generalReject), never a stray positive.
  • ISO-TP: first CF after FC.CTS is sent immediately; STmin gates only between CFs.
  • 0x84/0x86: an oversized inner response is no longer silently truncated (NRC 0x14 / event drop).
  • MISRA forbidden-call check matches calls, not comment words (was a latent CI false-positive).

Tests & verification

  • Behaviour-preserving across both phases: no pre-existing expected-byte assertion changed.
  • Net-hardening wave: stateful multi-service sequences, ISO-TP transport edges, NRC-priority combinations, 0x84/0x36 combinations, fail-closed + scratch-non-leak + nested-0x84 reset-ordering locks.
  • 68/68 tests, 0 warnings, ~97% line coverage. cppcheck + clang-format-18 + MISRA (3/3) + all 11 examples build+run — all verified in the CI Docker image.
  • New nightly workflow builds and runs every host example (per-PR CI only built a subset).

Migration

See CHANGELOG 2.0.0. Handler: return uds_send_response(ctx,n)uds_ok(out,n); return uds_send_nrc(ctx,sid,nrc)uds_nrc(out,nrc); return UDS_PENDINGuds_pending(out). Context: re-point any direct uds_ctx_t field reads at the new sub-structs.

Known limitations (documented, not silent)

0x84 still shares config->tx_buffer with its copied-out inner dispatch under the single-threaded non-reentrant contract; the context-field layout may evolve in a future major; tests are host-simulation (mocked transport + virtual clock), not on-wire. Client/server split is Phase 3.

w1ne added 30 commits June 22, 2026 14:35
… NONE/after-action, regression-safe rollout)
Convert uds_internal_handle_security_access and
uds_internal_handle_authentication to v2 void signature with uds_result_t *out.
Table entries for SID_SECURITY_ACCESS and SID_AUTHENTICATION now use handler_v2
column. The 0x29 handler retains ctx->suppress_pos_resp = false to ensure
execute_handler always emits the positive response regardless of the request bit.
Convert read_memory_by_addr and write_memory_by_addr to v2 void handler
signature. Move table entries for UDS_SID_READ_MEM_BY_ADDR and
UDS_SID_WRITE_MEM_BY_ADDR to handler_v2 column (NULL in handler).
All emission paths now use uds_ok/uds_nrc helpers; no uds_send_* calls.
Migrate uds_internal_handle_link_control (0x87) and
uds_internal_handle_access_timing (0x83) to v2 void handler signature.
Table entries in core_services[] updated to handler_v2 column (handler=NULL).
Migrate uds_internal_handle_io_control to v2 void handler signature.
Point core_services[] UDS_SID_IO_CONTROL_BY_ID entry at handler_v2.
Convert uds_internal_handle_routine_control, request_download,
transfer_data, request_transfer_exit, request_file_transfer, and
request_upload to v2 void(ctx, data, len, uds_result_t*) contract.
Point SIDs 0x31/0x34/0x35/0x36/0x37/0x38 at handler_v2 in core_services.
Transfer-state logic (flash_sequence counter) unchanged.
Convert outer emission calls in uds_internal_handle_secured_data to the
v2 contract (void handler with uds_result_t *out). All inner-dispatch
and secure-capture machinery is byte-for-byte unchanged.
… is suppressed

- Add UDS_RESULT_NONE kind and uds_none() helper to uds_config.h
- Add case UDS_RESULT_NONE: break in execute_handler (no emit, no state change)
- Fix uds_internal_handle_secured_data: suppressed-inner path uses uds_none()
  instead of uds_ok(out, 0u), preventing a spurious zero-length tp_send call
- Add regression test: test_secured_inner_suppressed_emits_nothing asserts no
  outer frame is emitted when the inner 0x3E with suppressPosRsp bit is wrapped
- Update design spec to reflect UDS_RESULT_NONE is retained for this one path
…P edge paths

Adds test_core_coverage (logging filter, uds_client_request error/happy/mutex
paths, uds_init validation/strict/NVM, uds_process S3/P2*/RCRRP/periodic, client
response handling, busy-repeat, emit/send-NRC capture branches, secured-data
errors) and test_tp_isotp_coverage (NULL guards, send-frame failure, N_Bs
arm+expire, oversized/FF-failure sends, invalid escape FFs, wrong-SN CF, short
FC, full TX flow STmin decode).

Drives uds_core.c and uds_tp_isotp.c to near-full line coverage and closes the
last uncovered function (uds_client_request).
Adds test_service_coverage, _coverage2 and _coverage3 covering reachable
negative/error paths across the service handlers:
- session (0x10) configured P2/P2* echo, safety session bit
- security (0x27) seed-negative, handler-supplied NRC; auth (0x29) negative
- IO control (0x2F) DID session/security denial, callback negative
- link control (0x87) verify-specific baud, length errors, transition errors
- access timing (0x83) set-given and readback
- memory (0x23/0x3D) ALFID, too-long, parse-fail, callback-negative
- flash (0x31/0x34/0x35/0x36/0x37/0x38) no-callback, ALFID, parse-fail,
  callback-negative, seq rollover, last-block replay, out-of-order, exit
- data (0x22/0x24/0x2A/0x2C/0x2E) misconfigured DID, scaling/dynamic negative,
  WDBI session denial, periodic stop-all/update/table-full
- maintenance (0x11/0x14/0x19) reset suppress, clear-negative, DTC list
  negative / response-too-long / per-record overflow / length errors across
  subfunctions 0x04/0x07/0x08/0x09/0x14/0x42/0x55/0x0B/0x0C/0x15, legacy
  fn_dtc_read negative
- ROE (0x86) length/nesting/slot-full rejections
DTC store: register-update-existing, counter saturation at 0x7F and -128 floor,
report on unknown DTC no-op, and extdata_cb (unknown/too-small/good).

ResponseOnEvent (0x86): less-than/equal comparison operators, onDTCStatusChange
match, reportActivatedEvents, clear, finite-window expiry, medium/slow timer
rates, empty-capture emit, NULL trigger, serialize/deserialize argument and
buffer guards.
Wires test_core_coverage, test_service_coverage{,2,3} and test_tp_isotp_coverage
into the ctest build.
Collapse the v1/v2 scaffold: make the out-param handler contract the sole
public interface. Remove the legacy int-returning handler field and the
temporary uds_handler_v2_t typedef from uds_service_entry_t. The public
uds_service_handler_t is now void(*)(ctx, data, len, uds_result_t *out).

execute_handler is unconditional: no legacy branch, no NULL guard. The
emission switch (PENDING/NRC/NONE/POSITIVE) and post-emit reset block are
unchanged.

All 27 core_services[] initializers updated (NULL placeholder dropped,
handler fn moved to position 5). All user_services handlers in tests and
examples migrated to the out-param form.

Version bumped to 2.0.0. CHANGELOG entry added with migration recipe.
…min gates only between CFs)

uds_rx_fc() CTS branch left timer_st uninitialised, causing the STmin check
to block the first CF until the raw timestamp exceeded STmin — violating
ISO 15765-2 §6.5.5.5, which requires the first CF to be sent immediately.
Add first_cf_after_fc flag: set on CTS, cleared after first CF is sent;
subsequent CFs retain full STmin enforcement via timer_st.
…ized ROE response

uds_internal_dispatch_captured() now resets secure_capture_overflow before
dispatch (preventing stale-flag leakage from a prior 0x84 path) and returns
UDS_ERR_BUFFER_TOO_SMALL on overflow instead of the ambiguous zero length.
roe_emit_slot documents the explicit drop contract for the async ROE case where
no live requester can receive an NRC.  uds_send_response suppress-positive path
also clears the overflow flag for consistency.

fix(isotp): clear first_cf_after_fc on TX abort paths (defensive)

Add iso->first_cf_after_fc = 0u to uds_rx_fc FC_OVA/default, uds_rx_sf
half-duplex abort, and uds_rx_ff half-duplex abort so a stale flag cannot
bypass STmin enforcement if a future refactor changes the reachability of
ISOTP_TX_SENDING_CF without a fresh FC.CTS.

Adds regression test test_roe_capture_overflow_drops_frame (64/64 pass).
Adds 12 stateful-flow regression tests covering:
- Security gating, session-change re-lock, S3 timeout re-lock
- Seed/session/key sequencing (NRC 0x24)
- RCRRP recovery, NVM warm-start, auth deauth gate
- suppressPosRsp for 0x85 and 0x2A
- 0x84 inner overflow then ROE (cross-feature stale-flag clear)
- comm_state persistence across S3
- Security lockout persistence across session change
- Periodic subscription survival across S3 timeout
- Silence -Wunused-parameter/-Wunused-function in test_addressing_dispatch,
  test_async, test_concurrency, test_nvm_persistence, test_safety_gate
  via (void) casts
- Give test_safety_check_passes a functional body and register it
  in test_safety_gate main() with setup_full/teardown
- Remove dead setup/mock_service_handler/g_user_services in
  test_safety_gate (never registered, caused -Wunused-function)
…ment, remove duplicate test

- Add test_seq_suppress_pos_rsp_19 (0x19 subfn 0x01 reportNumberOfDTCByStatusMask
  with bit7 set) to verify suppress flag does not leak into subsequent requests
- Assert ctx.periodic_count == 1 after suppressed 0x2A subscribe in
  test_seq_suppress_pos_rsp_2A to prove the subscription was registered
- Fix misleading comment in test_seq_nvm_warmstart_gated_works: security_mask
  is 2u (not 1), corrected to "level=1 satisfies security_mask=2u"
- Remove test_pass from test_safety_gate.c; it was an exact behavioural
  duplicate of test_safety_check_passes (same g_safe_state, req, expected bytes)
Lock correct behaviour for SN wrap (19 CFs, 1..15→0..3), half-duplex
FF-abort, N_Bs timeout after FC.WAIT, FF_DL exact-buffer-fit, FF_DL
one-over-buffer (FC.OVFLW), STmin=0x7F, STmin=0xF1 microsecond band,
and N_Cr timer refresh per CF (both pass and timeout variants).
w1ne added 14 commits June 22, 2026 20:17
…uite

Locks the handle_request() check ordering in uds_core.c:
- session (0x7F) before sub-length (0x13), sub validity (0x12),
  min-length (0x13), and security (0x33)
- sub validity (0x12) before min-length (0x13) and security (0x33)
- min-length (0x13) before security (0x33)
- security (0x33) before safety hook (0x22)

Also locks 0x36 mid-transfer mismatch (0x24), 0xFF->0x00 wrap,
wrap mismatch; 0x84 inner-PENDING surfaced as encrypted NRC 0x78,
inner-NRC encrypted and returned; functional 0x3E+suppress silent,
functional session-gated NRC 0x7F suppressed.

15 new tests; 68/68 pass, 0 build warnings.
execute_handler now pre-inits the result descriptor to generalReject (0x10)
and splits the POSITIVE case from a defensive default, so a handler that
returns without describing a result is rejected rather than emitting stale
tx_buffer contents. uds_ok/uds_nrc/uds_pending/uds_none fully initialise the
descriptor so correctness no longer depends on caller pre-zeroing. Adds a
fail-closed regression test and documents the 0x84 shared-buffer limitation
and the host-simulation test scope in the changelog.
The stdlib scan used grep -rnw, so the word 'abort' in a comment ('clear on
TX abort') tripped the forbidden-call gate. Require a word boundary plus a
call paren so real abort()/malloc( are still caught while comment text and
identifiers like transfer_exit are not. Restores the precise 'abort' wording.
Per-PR CI only builds a subset of examples; the handler contract is public
API and the examples are its migration guide, so they must not rot unbuilt.
Adds a scheduled (and on-demand) job that builds and runs every host example
against the Docker image, with success-marker assertions for the crypto pair.
Pure reformatting (no logic change): the session/security/server/client/scratch
field renames shifted line lengths and comment alignment; clang-format-18 (the CI
formatter) reflows them. Isolated from the substantive change for review.
…reset

A 0x11 unwrapped from SecuredDataTransmission (0x84) ran its deferred reset
inside the captured inner dispatch, rebooting before the outer secured
response was emitted (and even when the outer returned an NRC). Gate the
framework reset on !secure_capturing so it runs after the outer dispatch;
cancel it on the 0x84 encode-fail / responseTooLong paths; and never reset
from a captured ResponseOnEvent (0x86) dispatch. Adds nested-0x84 ordering
regression tests (mutation-verified: both fail without the guard).
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