Architecture¶
How the stack’s crates are put together, and where their boundaries fall.
What it covers so far. The set will hold the architecture of every crate in the stack.
It began with uds_services, the one wholly new component, because working out its
architecture was how the crates came to be tied together, so every element in it so far is
uds_services’ (UDSSVC_ARCH_*), and “this crate” below means uds_services. Until
each other crate’s architecture moves here, it lives with the crate:
the stack as a whole, and
uds_on_ip:crates/uds_on_ip/ARCHITECTURE.md, whose crate map, seams and timing ownership are the stack-wide view;simple_doip:crates/simple_doip/ARCHITECTURE.md;uds_session: its requirements, Requirements, and its README;uds_protocol: its README.
Each crate is built against one ISO document and edition, which uds_stack names. A
table, figure, clause or requirement cited with no document named is that crate’s
document’s; any other is named. For uds_services that is ISO 14229-1:2020.
This is the prototype-phase architecture. It is not the SWE.2 architecture and carries no requirement IDs: it records structure and the reasoning behind it, so that the requirement set authored next can be written against the standard rather than against the code.
uds_services
The organising rule¶
One crate per ISO document, with one exception. “Does this belong here?” is answered by
asking which document specifies the behaviour — and ISO 14229-1 is the one document in the
stack too large for that to settle on its own. It is split along format and behaviour:
uds_protocol owns the bits, the bytes and which messages are valid; this crate owns
everything else in ISO 14229-1. UDSSVC_ARCH_0001 states the boundary and what follows
from it, including that owning a behaviour may mean defining the seam where an application
supplies it rather than implementing it here.
Clause 8.7 is the densest part of that scope and the reason the crate exists. It is the dispatch-and-negative-response state machine a UDS server must implement — which validation steps run in which order, which negative response code each failure produces, and, the part most implementations get wrong, when the correct answer is silence rather than a negative response. Its subclauses:
Clause |
Content |
|---|---|
8.7.1 |
General definitions and legend: |
8.7.2 |
General server response behaviour — the mandatory validation sequence (Figure 5) |
8.7.3 |
Requests with a SubFunction: general (Figure 6), physically addressed (Table 4), functionally addressed (Table 5) |
8.7.4 |
Requests without a SubFunction: physically addressed (Table 6), functionally addressed (Table 7) |
8.7.5 |
Pseudo-code example of server response behaviour |
8.7.6 |
Multiple concurrent requests with physical and functional addressing |
Clause 8.7.2 classifies validation steps as mandatory, optional, or manufacturer/supplier specific. That classification is the seam where a caller’s own checks attach, and The dispatch pipeline treats it as a first-class part of the design rather than as an aside.
What the crate is for¶
Anyone can write a match on a service identifier. What is worth centralising is clause 8.7’s validation order and its response/silence rules, because they are easy to get subtly wrong and the failure is invisible when testing against a cooperative client — a physically addressed test tool never exercises the rules that matter most.
The two roles¶
Clause 8.7 bounds what the crate implements. It does not describe what the crate is, and reading only the scope statement gives a misleading picture of half of it.
This crate is the point where an application meets the diagnostic stack, in both
directions. On the server side it defines the interface by which an application integrates
a stack: the handler traits, the assembly, the dispatch. On the client side it defines the
set of requests available to an application, and interprets what comes back. Everything
below it deals in bytes — the binding’s client sends and receives &[u8] and says in
its own documentation that interpreting a negative response “belongs to a higher layer”.
This is that layer, and it is the last one that understands UDS at all.
UDSSVC_ARCH_0019 states the role; Service traits and identifiers and The client surface are
the two halves.
The asymmetry between them is real and is not an inconsistency. A server implements a
trait per service and is called by the dispatcher; a client calls a function per service
and implements nothing. Both are ISO 14229-1 clause 7’s service access point seen from
opposite ends — the client uses .req and .conf, the server .ind and .rsp —
and neither shape fits the other end.
What both halves share is one set of message definitions and one identifier vocabulary — One message vocabulary. That is the deliberate reason both roles are in the first pass rather than one after the other: each role exercises two of the four paths through a message definition, so building one alone proves half the set and shapes the API around that half.
Stated as a goal: an application using this stack should not need to care about UDS much at all — in either role. It defines its identifiers, implements the services it serves, calls the services it needs, and the rest follows.
The set as a graph¶
Every node below is a link to the element it stands for, so the architecture can be read by clicking through it rather than by scrolling. Solid edges are dependencies; the containment edges show which stages comprise the dispatch pipeline.
This diagram is generated from the elements and their links, not drawn. It cannot disagree with the pages that follow.
Two roots and a deliberate gap are visible in it. UDSSVC_ARCH_0001, the scope
boundary, and UDSSVC_ARCH_0012, the trait shape, are depended on and depend on nothing
— everything else is downstream of what the crate is for and how an application talks to
it. The six seams carry no containment edge, because they are not part of any component:
giving them a synthetic parent would tidy the diagram by inventing something that is not
there.
All architecture elements¶
ID |
Title |
Status |
Origin |
Source |
|---|---|---|---|---|
The crate owns ISO 14229-1's behaviour; uds_protocol owns its format |
draft |
derived |
||
The dependencies are uds_protocol, uds_session and the codec — never a binding |
draft |
derived |
||
Transport bindings are optional and additive |
draft |
derived |
||
Dispatch is an ordered pipeline of validation stages |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.2 Figure 5; ISO 14229-1:2020 8.7.3.1 Figure 6; ISO 14229-1:2020 8.7.5 |
|
Decoding is delegated, and its failures settle the code uds_protocol assigns |
draft |
derived |
||
The mandatory precondition stage follows Figure 5's order |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.2 Figure 5; ISO 14229-1:2020 10.2 Table 23 |
|
The sub-function branch excludes service identifier 0x31 |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.2 Figure 5; ISO 14229-1:2020 8.7.3.1 Figure 6 |
|
Partial data-parameter support yields a positive response |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.1; ISO 14229-1:2020 8.7.3.2 Table 4; ISO 14229-1:2020 8.7.4.2 Table 6; ISO 14229-1:2020 11.2.1 |
|
Suppression is decided last, from the code and the addressing mode |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.3.3 Table 5; ISO 14229-1:2020 8.7.4.3 Table 7; ISO 14229-1:2020 8.7.5; ISO 14229-1:2020 A.1 |
|
A negative response is written as three bytes, not returned |
draft |
application-layer-standard |
ISO 14229-1:2020 8.5; ISO 14229-1:2020 8.6 |
|
Optional and manufacturer-specific checks are caller-supplied |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.2; ISO 14229-1:2020 8.7.2 Figure 5; ISO 14229-1:2020 8.7.3.1 Figure 6 |
|
Each service is its own trait |
draft |
derived |
||
Assembly is explicit, and the macro is the crate's const evaluator |
draft |
derived |
||
The application owns its identifier enumerations |
draft |
derived |
||
There is no Ctx; the addressing triple is the pipeline's only extra input |
draft |
derived |
||
Dispatch is asynchronous and returns its outcome unsettled; the driver settles it |
draft |
derived |
||
A response is written into a sink, never returned owned |
draft |
derived |
||
Nothing calls into this crate; the inbound path is the driver's own |
draft |
derived |
||
The crate is the stack's integration surface, in both directions |
draft |
derived |
||
A client issues requests over the application's own identifiers |
draft |
application-layer-standard |
ISO 14229-1:2020 7.1; ISO 14229-1:2020 7.3.2 |
|
A negative response is a response, and this crate interprets it |
draft |
application-layer-standard |
ISO 14229-1:2020 8.6 |
|
A functionally addressed request yields a lending sequence |
draft |
derived |
||
No response expected is a normal client outcome |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.3.2 Table 4; ISO 14229-1:2020 8.7.4.2 Table 6 |
|
One identifier vocabulary serves both roles |
draft |
derived |
||
One set of message definitions serves both roles |
draft |
derived |
||
The application supplies each identifier's record structure |
draft |
application-layer-standard |
ISO 14229-1:2020 11.2.1; ISO 14229-1:2020 11.2.3.1 |
|
The same definitions build for the embedded server and the host client |
draft |
derived |
||
The client core is sans-io; awaiting is a layer above it |
draft |
derived |
||
This crate declares the transport seam and calls it |
draft |
derived |
||
An async runtime is assumed; none is depended on |
draft |
derived |
||
A response-pending is an ordinary transmission, not a seam |
draft |
derived |
||
This crate decides and composes the response-pending |
draft |
derived |
||
Each service declares whether it may answer response-pending |
draft |
derived |
||
The stack owns every protocol concern it can model |
draft |
derived |
||
Protocol state is owned by the crate and created at assembly |
draft |
derived |
||
The transfer lifecycle is a state machine this crate owns |
draft |
application-layer-standard |
ISO 14229-1:2020 15.2.4 Table 444; ISO 14229-1:2020 15.4.1; ISO 14229-1:2020 15.4.2.3; ISO 14229-1:2020 15.4.4; ISO 14229-1:2020 15.5.4 |
|
The security access sequence is a state machine this crate owns |
draft |
application-layer-standard |
ISO 14229-1:2020 10.4.2; ISO 14229-1:2020 10.4.4; ISO 14229-1:2020 Annex I Table I.1; ISO 14229-1:2020 Annex I Table I.2 |
|
A session transition is classified here and applied to configuration state |
draft |
application-layer-standard |
ISO 14229-1:2020 10.2 Figure 7 Key |
|
Clause 17 is a client sequence, not a server obligation |
draft |
application-layer-standard |
ISO 14229-1:2020 17.1; ISO 14229-1:2020 17.2 |
|
This crate drives the stack |
draft |
derived |
||
Time arrives on the transport seam; there is no clock seam |
draft |
derived |
||
Each service's own negative response order is normative and this crate's |
draft |
application-layer-standard |
ISO 14229-1:2020 8.7.2 Figure 5; ISO 14229-1:2020 8.7.3.1 Figure 6; ISO 14229-1:2020 Figures 11, 20-23, 26-35 |
Derived architecture elements¶
The ones whose reasoning exists nowhere but in this document.
ID |
Title |
Status |
|---|---|---|
The crate owns ISO 14229-1's behaviour; uds_protocol owns its format |
draft |
|
The dependencies are uds_protocol, uds_session and the codec — never a binding |
draft |
|
Transport bindings are optional and additive |
draft |
|
Decoding is delegated, and its failures settle the code uds_protocol assigns |
draft |
|
Each service is its own trait |
draft |
|
Assembly is explicit, and the macro is the crate's const evaluator |
draft |
|
The application owns its identifier enumerations |
draft |
|
There is no Ctx; the addressing triple is the pipeline's only extra input |
draft |
|
Dispatch is asynchronous and returns its outcome unsettled; the driver settles it |
draft |
|
A response is written into a sink, never returned owned |
draft |
|
Nothing calls into this crate; the inbound path is the driver's own |
draft |
|
The crate is the stack's integration surface, in both directions |
draft |
|
A functionally addressed request yields a lending sequence |
draft |
|
One identifier vocabulary serves both roles |
draft |
|
One set of message definitions serves both roles |
draft |
|
The same definitions build for the embedded server and the host client |
draft |
|
The client core is sans-io; awaiting is a layer above it |
draft |
|
This crate declares the transport seam and calls it |
draft |
|
An async runtime is assumed; none is depended on |
draft |
|
A response-pending is an ordinary transmission, not a seam |
draft |
|
This crate decides and composes the response-pending |
draft |
|
Each service declares whether it may answer response-pending |
draft |
|
The stack owns every protocol concern it can model |
draft |
|
Protocol state is owned by the crate and created at assembly |
draft |
|
This crate drives the stack |
draft |
|
Time arrives on the transport seam; there is no clock seam |
draft |