The dispatch pipeline¶
Clause 8.7 as a sequence of stages: what each one decides, which negative response code it can produce, and where a caller’s own checks attach.
The order is not an implementation detail. Figure 5 and Figure 6 fix it, and a server that reorders the checks produces a different negative response code for the same request — correct-looking behaviour that fails conformance.
Overview¶
A request is processed by stages in a fixed order. Each stage either passes the request to the next or settles it with a response code; the last stage decides whether the settled outcome is transmitted at all.
Two properties follow from the shape and are relied on throughout:
|
The cascade in full¶
Every check, in order, with the code it produces. The nesting is the point: the first check to fail settles the request, which is why reordering the checks changes the answer rather than merely the code path.
The mandatory path. Optional and manufacturer-specific checks
(UDSSVC_ARCH_0011) are omitted; each attaches at a stated position.¶
Two things this diagram makes visible that the tables below do not. Authentication sits above the session check, so a request in the wrong session from an unauthenticated client is answered 0x34 and never reaches the 0x7F branch. And every exit converges on the suppression gate — including the negative ones, and including silence. No stage decides whether its own answer is transmitted.
Decode¶
Architecture Element: Decoding is delegated, and its failures settle the code uds_protocol assigns UDSSVC_ARCH_0005
|
Rationale: 0x13 is a clause 8.7 outcome, but message length and format are properties of
the encoding, which is The unmodelled-service carve-out is the part that is easy to get wrong. A service identifier that names no service at all never reaches the decode: the support
check reads the service byte alone and settles it 0x11 first, as ISO 14229-1:2020
8.7.5’s pseudo-code does — its outer A request with no service identifier is complete without a response. ISO
14229-1:2020 8.7.5’s pseudo-code begins |
Mandatory preconditions¶
The stage evaluates Figure 5’s mandatory checks in the standard’s order:
Figure 5 places three optional checks and two manufacturer/supplier-specific hooks
around this sequence; they are The implemented order is Figure 5’s, literally: the empty request
( Check 3 is partly fixed by the standard, and this crate answers the fixed part.
Clause 10.2’s Table 23 states, for each service it lists, whether it is available in the
Five further rows are footnoted and are genuinely the application’s, because the footnote makes them conditional on something only the application knows:
So the check is the conjunction of Table 23’s answer, where Table 23 gives one, and the
application’s declaration otherwise. This follows What it costs, stated because it is the first place this crate overrides an
integrator rather than merely constraining one: a vehicle programme whose own session
matrix disagrees with Table 23 on one of the twelve will find this crate refusing the
request. Table 23 is normative and the refusal is correct, but whether an escape hatch is
owed — and if so whether it is per service or whole-table — is not settled here. Note
also that four of the twelve (0x2A, 0x2F, 0x87, 0x84) have no Two orderings here are worth stating explicitly, because both are counter-intuitive and both are observable. Figure 5 is an image in the markdown conversion of the standard and was read from the PDF on 2026-09-16; both orderings are confirmed there verbatim:
|
After the mandatory preconditions, a request for a service that carries a SubFunction
parameter enters the sub-function stage, except for service identifier 0x31
( Figure 6’s stage, in its order:
The service’s exact-length check is its service-specific check, after this stage:
ISO 14229-1:2020 8.7.5’s pseudo-code tests Row 2 is decided only for a service that has a stage, which is every service with a SubFunction this crate has a trait for. A listed service that carries a SubFunction but has no stage would pass row 2 and reach the decode: it would then be answered 0x11 through the wildcard, or the decode’s code if the request does not decode. The macro documentation records the same rule. Rows 2, 4 and 5 are one lookup per service trait. Whether a SubFunction is
supported at all, in which sessions, and behind which security levels are deployment
facts, so the application states them, in the service’s own typed SubFunction, and the
pipeline decides the code. Annex A says 0x7E “shall be supported by each diagnostic
service with a SubFunction parameter”, so every such trait answers all three. It answers
them at once, with an
Figure 6 also places a request-sequence check (0x24) in its optional column; it is
The 0x31 exclusion is drawn from Figure 5’s decision node, which reads “service with
SubFunction, but not SID 0x31”. It is not an editorial slip and it must be honoured:
The trait shape this implies is settled, and it settles open question 5.
An earlier version argued the three methods from 0x31 being “the only service whose
handler decides |
Data parameters¶
For a request carrying data parameters, the stage classifies the server’s support for them as ALL, At least 1, or None, per clause 8.7.1:
This is a normative classification, not a policy choice. It means a
A second rule from the same clause, easy to miss and easy to violate by being helpful: clause 11.2.1 states that a request message may contain the same data identifier more than once, and that the server shall treat each as a separate parameter and respond with data for each as often as requested. Duplicates are not de-duplicated. A dispatcher that collected the requested identifiers into a set — the obvious way to write it — would answer a request naming an identifier twice with one record, and be non-conformant. The same clause gives the server one limit it may impose: “the server may limit the
number of dataIdentifiers that can be simultaneously requested as agreed upon by the
vehicle manufacturer and system supplier”. That is a per-server constant of the kind
The rule shapes the service trait signatures: a per-identifier handler must be callable
once per requested identifier, and “unsupported identifier” must be distinguishable
from “identifier supported but the read failed”. The first contributes to the ALL / At
least 1 / None classification; the second is a processing error that settles the whole
request with its own code. See |
Suppression¶
Architecture Element: Suppression is decided last, from the code and the addressing mode UDSSVC_ARCH_0009
|
The final stage takes the settled response code, the addressing mode, the
Three rules, all from clause 8.7.5’s pseudo-code and Tables 5 and 7:
The gate. The response-pending override is drawn first because it short-circuits both suppressions.¶ Rule 1 is the rule most likely to be implemented wrongly, and the reasoning behind it is worth carrying: a functionally addressed request reaches every server on the bus, so a bus-wide chorus of “I do not support that” would be worse than useless. Note what it does not cover — a processing error on a request whose service, sub-function and at least one data parameter were all supported is still answered negatively, because that answer is specific to this server rather than a report of non-participation. Rule 3 reads a fact this crate owns rather than an input it is given.
The gate reads “offered and accepted”, where clause 8.7.5 writes “sent”, and the
divergence is deliberate. A submission the session layer refuses is not a send, and the
refusal is reported synchronously, so What remains is the accepted submission whose transmission later fails. The asymmetry settles the rest. Treating an accepted offer as sent can cost one message a
waiting client accepts; treating a sent response-pending as unsent produces silence where
the standard requires a final response — the failure that needs both a slow handler and
functional addressing to appear, and so will not show up in ordinary testing. Closing the
gap the other way, by tracking the Rule 2 has a corollary for services without a SubFunction parameter: they have no
|
A transmitted negative response is the negative response service identifier 0x7F, the service identifier of the request being answered, and the negative response code. It is written into the caller’s sink by the same path as a positive response. The request’s original service identifier is echoed, including for a request whose
service this server does not model — which is why |
When a handler has not settled the request and a response-pending becomes due, this crate decides whether one is admissible and, if it is, composes it. The driver transmits; it does not decide, and it does not assemble the bytes. Admissibility is the conjunction of two facts, both resolved before the handler runs:
the service is implemented by this server ( Rationale: ISO 14229-2:2021 states the prohibition and states it in terms only this
layer can evaluate. Its REQ 5.6 requires that services the server does not support use a
What this crate implements is the predicate, not the requirement. It holds no
ISO 14229-2:2021 REQ 5.6’s own case is discharged without a check at all.
ISO 14229-1:2020 Figure 5’s first mandatory condition settles an unsupported service at
0x11 in the precondition stage, so no handler runs and The bytes are this crate’s for the reason What this element does not claim is the timing. When a response-pending becomes due,
how often it may repeat, and whether one was accepted are ISO 14229-2’s and reach this
crate only as Two consequences recorded elsewhere. The suppression override of |
The service-specific check¶
Architecture Element: Each service's own negative response order is normative and this crate's UDSSVC_ARCH_0042
|
Figure 5 ends at a box reading “Specific SID CHECK” and Figure 6 at one reading “Specific SID checks”. That box is not the end of the standard’s ordering — it is a handover to fifteen further normative figures, one per service, each fixing the order in which that service’s own negative response codes are evaluated. Each is introduced by the same sentence in its service’s clause: “The evaluation sequence is documented in Figure N.” The figures are 11, 20, 21, 22, 23, 26, 27, 28, 29, 30, 31, 32, 33, 34 and 35. The ordering is normative, so it is this crate’s, by It is built for the services this crate stages, and recorded as a gap for the
rest. The seam is the second of the candidates an earlier draft weighed — a fixed set of
typed pre-handler lookups per service, rather than a declared sequence of predicates per
trait or generated code per figure. Each check a figure places before the handler is a
typed answer the stage asks for in the figure’s order: an An earlier version of this set had no element here at all, and none of |
Extension points¶
Architecture Element: Optional and manufacturer-specific checks are caller-supplied UDSSVC_ARCH_0011
|
||||||||||||||||||||||||||||||||||||||||
Clause 8.7.2 classifies validation steps as mandatory, optional, or manufacturer/supplier specific. This crate implements the mandatory steps and provides attachment points for the rest, at the positions Figures 5 and 6 place them:
These are attachment points rather than implementations because none of them can be
decided here, with one exception. Whether the server is busy is a property of the
application’s own scheduling, and ISO 14229-1 explicitly reserves the
manufacturer/supplier column. The SubFunction security check is decided here: which
levels unlock a SubFunction is manufacturer-defined, but whether one of them is unlocked
is The position is the part this crate owns and the part worth centralising. A caller that implements its own busy check in the wrong place answers 0x11 where the standard requires 0x21, and no amount of care inside the check itself fixes that. The two hooks in Figure 5 are distinct and differently placed — one manufacturer, one supplier — and an earlier version of this element gave them a single row reading “Figure 5 has two such hooks”. They are separated above because a caller attaching a check to the wrong one of them gets the wrong precedence against the service-identifier and security checks between them. A transcription trap in the source, recorded so it is not re-derived. The two figures draw these hooks with opposite polarity: Figure 5’s nodes ask “failure detected?” and take the NRC exit on YES, while Figure 6’s asks a check question and takes the NRC exit on NO. The behaviour is the same; only the drawing differs. Whoever authors the requirement should read the figure rather than copy this table. Figure 5’s busy check also carries a Key note narrowing it: the request “cannot be
accepted because another diagnostic task is already requested and in progress by a
different client”. That is narrower than “the server is busy”, and it is a per-channel
question under Clause 8.7.2 carries a note that, given the choices available across these figures, a specific negative response code is not guaranteed for every possible test-pattern sequence. This crate therefore fixes the mandatory order exactly and leaves the optional checks to the caller, rather than choosing a set of optional checks on the caller’s behalf and presenting the result as the conformant one. |
The stages, generated¶
The same pipeline, drawn from the elements rather than by hand. Every node links to the element it stands for.
The edges leaving the group are the interesting part: the stages depend on the seams and on the API surface, never the reverse. A stage that needed something from a later stage would show up here as a cycle.
Concurrency¶
Clause 8.7.6 is a subclause of clause 8.7, but the behaviour it describes is not this
crate’s. It states that a server has one diagnostic protocol instance, that any received
request occupies it until processing completes, and it carves out two exceptions: a
functionally addressed TesterPresent with the suppress bit set must bypass that
occupancy, and a request in the 0x00–0x0F service range must abort an active service
outside that range and start the default session, unless a programming session is active.
Two details of that wording matter to where the line falls, and both were missing from an earlier version of this passage.
The second exception is conditional. The clause opens “If a server supports services
in the range of 0x00 to 0x0F receives diagnostic requests in the range of 0x00 to 0x0F …”.
The whole rule is predicated on the server implementing something in that range — which is a
fact UDSSVC_ARCH_0013’s assembly list holds and a byte-level driver cannot see. That
strengthens the proposed split rather than complicating it: classification needs what this
crate knows.
Occupancy ends on silence too. The instance is held “until the request message is
processed (with final response sent or application call without response)”, so a
Responded::Suppressed, which the driver reaches by settling the dispatch outcome
(UDSSVC_ARCH_0016), releases it exactly as a transmitted response does. A driver that
released the instance only on a transmission would deadlock on the silence clause 8.7
requires.
Occupying a resource, bypassing it, and aborting an in-flight service are properties of
the driver loop, not of a single dispatch, and UDSSVC_ARCH_0040 made that loop this
crate’s. ServiceSet::is_concurrent_exception classifies, and the driver acts: the
keep-alive TesterPresent bypasses the occupied instance, and anything else arriving
mid-service is answered busyRepeatRequest (0x21) as a busy refusal
(UDSS_LLR_0187). Open question 1 records how that was settled; see
Open questions.