Position in the stack¶
Where this crate sits, what it depends on, and what a transport swap replaces.
The stack, one package per ISO document. Dashed edges are the message vocabulary rather than a layer boundary.¶
uds_session is drawn to the side of this crate rather than under a binding, because
UDSSVC_ARCH_0040 has this crate own the Session instance: it supplies every input
and drains every action. A binding never touches it.
uds_protocol is drawn to one side with dashed edges because it is not a layer: it is
the message vocabulary this crate and both bindings speak. Nothing is above or below it.
uds_on_can and ISO-TP appear because the shape of the stack is the argument for the
byte seam being where it is. Neither exists yet.
Both application roles meet the stack at the same crate, which is UDSSVC_ARCH_0019.
They are drawn as separate components because they are separate programs — a tester and an
ECU — not two faces of one. What they share is the identifier vocabulary between them,
which is the one thing the diagram cannot show.
Scope¶
Architecture Element: The crate owns ISO 14229-1's behaviour; uds_protocol owns its format UDSSVC_ARCH_0001
|
Rationale: the stack is organised one crate per ISO document, so “does this belong here?” must be answerable by checking which document specifies the behaviour. ISO 14229-1 is the one document too large for that rule to settle on its own, and it is split in two:
That is the whole boundary, and it leaves no clause unassigned. It is the only exception
to the one-crate-per-ISO-document rule in the stack: ISO 14229-2 is Owning a behaviour means implementing it or defining the seam where it attaches. Much
of ISO 14229-1’s behaviour is unownable by a library — what What is excluded is what another document specifies: message encoding, timers, session state, and transport. Clause 8.7 is the densest part of the scope and the reason the crate exists, not its
boundary. It is the dispatch-and-negative-response state machine of The dispatch pipeline, it
is what most implementations get wrong, and it is what is worth centralising — but the
set already reaches clause 7 for the service access point ( In scope and not built. Recorded here rather than in Open questions, because these are not unsettled — they are simply absent, and a reader should not have to infer from silence that a clause was decided against:
This bounds what the crate implements, not what it is. Every concern in What this crate does not own is argued against this boundary. |
Architecture Element: The crate is the stack's integration surface, in both directions UDSSVC_ARCH_0019
|
Rationale: left unwritten, this is the property most likely to be designed against by accident. Beyond implementing clause 8.7, this crate is where an application meets the diagnostic stack. It defines the interface by which a server integrates a stack — the handler traits, the assembly, and the dispatch of Service traits and identifiers — and the set of requests available to a client application (The client surface). No other crate in the stack offers either. Three consequences follow, and each is visible elsewhere in this document:
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. |
Where ISO 14229-1 specifies behaviour, this crate implements it. Where ISO 14229-1 defers behaviour to the vehicle manufacturer or to the ECU, this crate implements the shape the standard does fix and delegates only the part it defers — through an interface designed so that the compliant implementation is the easy one to write. Rationale: the crate exists because clause 8.7’s rules are easy to get subtly wrong and
the failures are invisible against a cooperative client. That argument does not stop at
clause 8.7. Every protocol rule left to an integrator is reimplemented once per vehicle
programme and got wrong in the same ways each time, and The test, applied to any behaviour in scope:
ISO 14229-1:2020 10.4 is the worked example, because it does both things in one clause.
The shape is fixed: Misuse-resistance is a design obligation, not a documentation one. Where an error can
be made unrepresentable it is, in preference to warning against it: |
This crate owns the run loop. It supplies Two words, because the stack has two things and one name for them. The application
layer is ISO 14229-1’s, which is this crate; ISO 14229-2 names it as the session
layer’s service user, and Rationale: ISO 14229-2 defines a service interface between the session layer and its
user and names that user as the ISO 14229-1 layer. An arrangement in which a third party
sits between them — turning the crank on the session layer and calling into this crate —
describes a component no standard mentions, and it is the component that went a full
design cycle with no owner:
Three consequences.
What “drains its outputs” means concretely, since this element asserted the loop’s
existence before the contract on the other side of it was fixed. Four properties of the loop body are load-bearing and none of them is obvious from the description above. They are recorded here because each is a borrow-order constraint that reads as an arbitrary stylistic choice, and each would be “simplified” away by a reader who did not know what it was for.
The loop is split into helpers — Four milestone-1 limits of the driver, stated here because the code cites this element for them:
What this costs is honesty about the server side’s shape. The dispatch pipeline of
|
Dependencies¶
Architecture Element: The dependencies are uds_protocol, uds_session and the codec — never a binding UDSSVC_ARCH_0002
|
This crate depends on Rationale: a typed server must follow its application to any transport unchanged, so a
binding cannot appear in this crate’s dependency list in any form. It does not need to:
this crate declares An intermediate version had these seams declared by ``uds_session``, on the ground
that ISO 14229-2 specifies the application-facing service interface. Right about the
document, wrong about the component — that interface is between the session layer and
this crate, so with An earlier version of this element forbade depending on ``uds_session``, on the
ground that it was private and would make this crate unpublishable, and took session and
security state as parameters to avoid it. That constraint is gone — What survives from that reasoning is the part that was never about publishing: two crates tracking the same state is how they come to disagree, and no arrangement here should produce one. |
Rationale: a “Binding” now means a transport implementation, not a host for a driver. Under
An earlier version of this element gave each binding a Cargo feature —
What is not portable, stated so nobody is surprised. The diagnostic conversation moves between transports; connection setup does not. DoIP has TCP connections, routing activation and vehicle identification; CAN has none of them. Defining identifiers and implementing handlers is portable. Establishing and configuring the link is a real seam in the application, and no arrangement of this crate removes it. |
How a request crosses the stack¶
The layering diagram says what the pieces are. This says what happens, and it is where the seams earn their keep.
One physically addressed request reaching a server, from the wire to a handler and back. The client-side path is in The client surface.¶
The box is the point. Everything between the transport handing up an event and the transport
being handed bytes back is inside this crate: it reads the transport, turns the session
layer’s crank, dispatches, and transmits. Nothing calls into it, which is
UDSSVC_ARCH_0018, and session state never arrives as a parameter because this crate
holds it (UDSSVC_ARCH_0035) — which is why dispatch takes the request bytes and a
sink and nothing else.
Two details of the drain are drawn deliberately, because both are load-bearing
(UDSSVC_ARCH_0040). An input does not return one output: it returns a reaction, which
the driver drains through Reaction::outputs() and then consumes with finish(), whose
value is that input’s verdict — and a refused submission is exactly what
UDSSVC_ARCH_0009’s suppression gate needs to know about. And t_data_req is called
from inside the drain rather than after it, because every Transmit must reach the
transport, not only the last one.
An earlier version of this diagram had a binding in the driving position, handing this crate
bytes and a Ctx read from uds_session, and taking a Responded back. That
component does not exist.
The final alt is not error handling. Both branches are specified outcomes of clause
8.7. Which one applies depends on the addressing mode, which arrives on Ctx, and on
whether this dispatch submitted a response-pending (UDSSVC_ARCH_0032) — neither of which
inspecting the request bytes would reveal.
Rationale: the layers below are already asynchronous and not optionally so:
Every deployment of this stack is assumed to have an async executor available — tokio
on a host, What this assumption buys is recorded where it is spent: |
One graph, and it is worth saying so¶
This page used to carry a section warning that the layering diagram and the Cargo dependency
graph were different shapes, and that confusing them was the most common way to misread the
stack. Under UDSSVC_ARCH_0040 they are the same shape, and the warning is retired rather
than deleted because the arrangement it described was deliberate and is worth knowing was
left behind.
Processing order and Cargo dependency now point the same way at every edge.¶
A caller and its callee are still opposite ends of one Cargo edge — uds_services calls
uds_on_ip and uds_on_ip names uds_services — which is the ordinary
trait-inversion shape, not a peculiarity of this stack. What has gone is the case where
neither crate named the other and both met at an interface owned by a third crate below
them both.
The retired arrangement, for the record: uds_on_ip called uds_services across a
RequestHandler that uds_session declared, so the caller and the callee met at an
interface owned by a crate below both of them, and neither named the other in its manifest.
That followed from a binding hosting the driver. UDSSVC_ARCH_0018 records why nothing
calls into this crate now, and UDSSVC_ARCH_0029 why the one seam below it is its own.