3. Architecture

3.1. Overview

' SPDX-License-Identifier: Apache-2.0
' SPDX-FileCopyrightText: 2026 The Contributors to Eclipse OpenSOVD (see CONTRIBUTORS)
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0

@startditaa -E
Architecture Classic Diagnostic Adapter

                               +-----------------------------------+        +-----------------+
                               | SOVD                              |        |  Plugins        |
                               +----------------+------------------+<-------+ +-------------+ |
                               |   HTTP-Server  |      OpenAPI     |        | :  Security   : |
                               +----------------+------------------+        | +-------------+ |
                                                |                           | :   Logging   : |
                                                |                           | +-------------+ |
                                                v                           | :   Tracing   : |
+----------------------+       +-----------------------------------+        | +-------------+ |
| Communication Layer  |       |       Diagnostic Tester           |        | :   Safety    : |
|                      |       |                                   |        | +-------------+ |
|+---------+----------+|<------+                                   |        |                 |
||  DoIP   |   UDS    ||       |                                   |        |                 |
|+---------+----------+|       |                                   |        |                 |
|                      |       |                                   |        |                 |
|                      |       |                                   |        |                 |
|                      |       |                                   |        |                 |
|                      |       |                                   |        |                 |
|                      |       |  +-----------------------------+  |        |                 |
|                      |       |  |  ECU Management / Detection |  |        |                 |
|                      |       |  +-----------------------------+  |        |                 |
|                      |       |                                   |        |                 |
|                      |       |  +-----------------------------+  |<-------+                 |
|                      |       |  |        UDS Payload          |  |        |                 |
|                      |       |  |         Converter           |  |        |                 |
|                      |       |  +------------+----------------+  |        |                 |
|                      |       |               |                   |        |                 |
|                      |       |               v                   |        |                 |
|                      |       |  +-----------------------------+  |        |                 |
|                      |       |  |   Diagnostic Database       |  |        |                 |
|                      |       |  +-----------------------------+  |        |                 |
|                      |       +-----------------------------------+        |                 |
|                      |                        ^                           |                 |
|                      |                        |                           |                 |
|                      |<-----------------------|---------------------------+                 |
|                      |                        |                           |                 |
|                      |       +------+     +---+--+                        |                 |
|                      |       |ODX{d}+---->|MDD{d}|                        |                 |
+----------------------+       +------+     +------+                        +-----------------+
@endditaa

3.1.1. SOVD

The SOVD block manages incoming SOVD requests and translates them into calls to the Diagnostic Tester API. It includes an HTTP server to receive SOVD requests and an OpenAPI generator to create documentation for API endpoints.

Additionally, the OpenAPI generator can be used as a standalone component to generate comprehensive documentation for ECU variants.

The functionality is divided into three distinct modules:

  1. HTTP Server

  2. Translation between SOVD and Diagnostic Tester API

  3. OpenAPI Generation

3.1.2. Diagnostic Tester

The Diagnostic Tester component provides an API for plugins and the SOVD layer. This API handles UDS payload conversion, manages ECU variant detection, and maintains the diagnostic runtime database.

It encapsulates functionality similar to a traditional offboard tester, optimized for the SOVD use case, and supports only the UDS protocol.

3.1.2.1. Diagnostic runtime database

The diagnostic runtime database is consulted to translate named parameters and services into UDS. It contains all diagnostic descriptions (.mdd) of the ECUs provided at startup and allows for runtime switches and additions/removal of underlying diagnostic descriptions, while no active diagnostic communication is in progress.

In terms of requirements, the database needs to minimize memory consumption while delivering maximum performance for the most common calls.

3.1.2.2. API

In the diagnostic tester, an API needs to be provided to utilize its functionality. The API itself needs to be close to the MCD-3 D specification, to enable future use-cases of the diagnostic tester core.

3.1.2.3. UDS payload conversion

As an internal module, the UDS payload conversion is mainly responsible to convert a set of named input parameters (Diagnostic Service, request parameters) into a UDS payload, and also back from UDS payload into named parameters.

3.1.3. Plugins

Plugins are responsible for significant portions of the CDAs functionality which are often vendor specific. As an example, security through jwt tokens is solved differently by different vendors, so the mechanism for their verification and interpretation into access rights needs to be customizable.

The same is true for logging, tracing and safety. Lastly, vendors might want to add custom endpoints with custom functionality, which would also be done through plugins.

3.1.4. Communication Layer

In the communication layer everything related to the communication with ECUs is handled. This includes periodically sent tester presents, timing and connection parameters. It provides an API to logically communicate with ECUs using their addresses (functional/physical) and handles the execution order, parallelization and link state.

3.1.4.1. UDS

Implementation of UDS communication, with handling of NRCs, tester presents (either physical or functional, flag for every connection per address), timeouts, retries and actual data.

3.1.4.2. DoIP

Implementation of DoIP communication with handling of timeouts.

3.2. General

3.2.1. Storage Access

Software Architecture: Storage Access API arch~system-storage-access-abstraction
status: draft

The Storage Access API provides an abstraction layer for storage access, allowing the CDA to interact with different types of storage systems (e.g., local file system, databases) without being tightly coupled to a specific implementation.

To achieve atomicity and consistent behavior across different storage implementations, the API defines the following semantics:

  • All operations must be atomic, meaning that either all changes are applied successfully, or none are applied in case of an error.

  • The API should provide a consistent interface for reading and writing data, regardless of the underlying storage system.

  • Error handling should be standardized, allowing the CDA to gracefully handle storage-related exceptions and errors.

  • Keys shall be handled case-insensitive, to simplify usage, and to avoid issues with different storage implementations.

@startuml
package "Storage Access API" {
    +enum StorageError {
        CollectionNotFound(String)
        KeyNotFound(String)
        PermissionDenied(String)
        TransactionError(String)
        NoSpaceLeft(String)
        Io(String)
        Corruption(String)
        Other(String)
    }

    +enum CollectionName {
        DiagnosticDatabase
        DiagnosticDatabaseNextUpdate
        DiagnosticDatabaseBackup
        Configuration
        Custom(String)
    }

    +interface Storage {
        +get_collection(collection: CollectionName) -> Result<Collection, StorageError>
        +get_or_create_collection(collection: CollectionName) -> Result<Collection, StorageError>
        +create_collection(tx: &TransactionCtx, collection: CollectionName) -> Result<Collection, StorageError>
        +delete_collection(tx: &TransactionCtx, collection: CollectionName) -> Result<(), StorageError>
        +copy_collection(tx: &TransactionCtx, source: CollectionName, destination: CollectionName) -> Result<(), StorageError>
    }

    +interface Collection {
        +read(key: String) -> Result<RandomAccessData, StorageError>
        +write(tx: &TransactionCtx, key: String, data: &ReadableStream) -> Result<(), StorageError>
        +delete(tx: &TransactionCtx, key: String) -> Result<(), StorageError>
        +delete_all(tx: &TransactionCtx) -> Result<(), StorageError>
        +metadata(key: String) -> Result<Metadata, StorageError>
        +list() -> Result<Vec<String>, StorageError>
        +len() -> Result<usize, StorageError>
    }

    +interface Metadata {
        +name() -> Result<String, StorageError>
        +data_size() -> Result<u64, StorageError>
        +custom_props() -> Result<Vec<MetadataProperty>, StorageError>
    }

    +interface MetadataProperty {
        +key() -> String
        +value() -> String
    }

    Storage ..> Collection
    Storage ..> CollectionName
    Collection ..> StorageError
    Collection ..> Metadata
    Storage ..> StorageError
    Metadata ..> StorageError
    Metadata ..> MetadataProperty
}
@enduml

A transaction context shall be created through a transaction { ... } block, which ensures that all operations within the block are treated as a single atomic transaction. If any operation within the block fails, the entire transaction will be rolled back, and the result of the transaction will be an error.

If all operations succeed, the transaction will be committed, and the result will be a success.

Should an unexpected interruption event (power-off, reset) occur during a transaction, the transaction must be rolled back on the next startup, to ensure consistency of the storage state.

For reading data, random access to the data must be supported, to allow for efficient reading of arbitrary chunks without needing to load it entirely into memory. This might be required for memory efficient handling of the diagnostic database.

The get_or_create_collection function creates an implicit transaction for the creation of the collection only, if it does not exist. If the collection already exists, it simply returns it.

3.2.2. Persistence

Software Architecture: Persistence API arch~system-persistence-api
status: draft

The Persistence API provides a durable key-value storage abstraction. Data is organized into Buckets, each representing a named, logically separated set of key-value pairs. The API is accessed through an exchangeable provider, enabling different storage backends without affecting consuming code.

@startuml
package "Persistence API" {
    +enum PersistenceError {
        BucketNotFound(String)
        KeyNotFound(String)
        IoError(String)
        Other(String)
    }

    +interface Persistence {
        +get(bucket: String, key: String) -> Result<String, PersistenceError>
        +set(bucket: String, key: String, value: String) -> Result<(), PersistenceError>
        +delete(bucket: String, key: String) -> Result<(), PersistenceError>
        +contains(bucket: String, key: String) -> Result<bool, PersistenceError>
        +list_keys(bucket: String) -> Result<Vec<String>, PersistenceError>
        +flush() -> Result<(), PersistenceError>
    }

    Persistence ..> PersistenceError
}
@enduml

The Persistence interface is the single access point for all persistence operations. Callers specify the target Bucket by name alongside the key for each operation. Bucket management (creation, lifecycle) is handled transparently by the provider implementation.

The flush operation explicitly persists all buffered data to the underlying storage media. Providers that buffer writes in memory shall guarantee that all data is durable after a successful flush call.

Providers are exchangeable at compile time, allowing the use of alternative backends (e.g., an in-memory provider for testing purposes) without modifying consuming code.

Software Architecture: Default redb Persistence Provider arch~system-default-redb-persistence-provider
status: draft

The default persistence provider uses redb as its storage backend. It implements the Persistence interface with the following characteristics:

  • All write operations (set, delete) are performed within ACID transactions, ensuring durability and consistency.

  • The database file path is configurable.

  • On unexpected interruption (power-off, crash), redb guarantees that uncommitted transactions are rolled back on the next open, preserving data integrity.

3.2.3. Systemd Watchdog Integration

Software Architecture: Systemd Watchdog Integration arch~system-sd-notify-watchdog-integration

The systemd watchdog integration is implemented as an optional background task that bridges the CDA health system with the systemd service manager notification protocol.

Startup Detection

At initialization, the component checks whether the process was launched by systemd and whether the watchdog is enabled. If either condition is not met, no task is spawned and the CDA operates without watchdog integration.

Notification Interval

The notification interval is derived from the systemd-configured watchdog timeout, reduced by a safety margin to ensure notifications arrive before systemd considers the service unresponsive.

Health Aggregation

On each tick, the task queries all registered health providers and folds their individual statuses into a single aggregated status. The folding semantics are:

  • A single failed provider causes the aggregate to be failed.

  • All providers must report healthy for the aggregate to be healthy.

  • While any provider is still pending or starting, the aggregate remains in the starting state.

State Machine

The notification sent to systemd is determined by the transition between the previous and current aggregated status:

@startuml
[*] --> Starting
Starting --> Up : all providers healthy\n(notify: Ready)
Up --> Up : still healthy\n(notify: Watchdog)
Up --> Failed : provider degraded\n(notify: WatchdogTrigger)
@enduml

Shutdown

The task terminates gracefully when the application shutdown signal is received.

3.3. SOVD-API

3.3.1. Introduction

Eclipse OpenSOVD Classic Diagnostic Adapter aims to be compatible with the ISO/DIS 17978-3:2025 SOVD standard.

This chapter specifies the specific implementation of that standard, as well as extensions to it, which are required for some use-cases.

3.3.2. HTTP

Software Architecture: SOVD-API over HTTP arch~sovd-api-http-server
status: draft
links incoming: req~sovd-api-http-server

The SOVD-API is based on HTTP/1.1 as transport protocol, and available through an configurable TCP port.

3.3.3. SOVD-API

3.3.3.1. Data Types

Software Architecture: ODX to JSON data type mapping arch~sovd-api-data-types-mapping-iso17978
status: draft

Data types must be mapped as follows:

ODX to JSON data type mapping

ODX data type

JSON data type (format)

Comment

A_ASCIISTRING

string

A_BOOLEAN

boolean

A_BYTEFIELD

string (byte | hex)

see Data Type A_BYTEFIELD as Hex

A_FLOAT32

number (float)

A_FLOAT64

number (double)

A_INT32

integer (int32)

A_UINT32

integer (int64)

A_UNICODE2STRING

string

A_UTF8STRING

string

Primitive JSON types

All primitive JSON types (string, array, number, integer, boolean, and object) can be used.

For strings, the following format identifiers can be used:

JSON string formats

JSON type

JSON format

Comment

string

byte

Base64-encoded binary data

string

hex

Hexadecimal-encoded binary data (e.g. f0cacc1a). Can be prefixed with 0x and contain spaces.

string

uuid

UUID identifier according to RFC 4122 (https://www.rfc-editor.org/rfc/rfc4122)

string

uri

Absolute URI according to RFC 3986 (https://www.rfc-editor.org/rfc/rfc3986)

string

uri-reference

Relative URI according to RFC 3986 (https://www.rfc-editor.org/rfc/rfc3986)

string

json-pointer

Pointer to a specific value within the JSON according to RFC 6901 (https://www.rfc-editor.org/rfc/rfc6901)

Note

TODO More string formats required?

Mapping of complex data types

Note

TODO Mapping of complex data types

3.3.3.2. Bulk Data

Software Architecture: Bulk-Data Endpoints arch~sovd-api-bulk-data
status: draft

Bulk-data endpoints allow the management of bulk data, like files that are to be used for flashing.

Paths are required to be in the following structure: /bulk-data/{category}/{id}. For extensions, the name bulk-data may only be used at the end of a path element.

Bulk Data endpoints

Method

Path

Description

GET

/bulk-data/{category}

Retrieves a list of entries in that category and their IDs

GET

/bulk-data/{category}/{entry-id}

Downloads the data for the entry. The MIME type is determined by the server and the content of the data.

POST

/bulk-data/{category}

Uploads data to the category. Additional metadata (e.g. filename) can be provided through Content-Disposition: form-data

DELETE

/bulk-data/{category}

Requests the deletion of all data for that category

DELETE

/bulk-data/{category}/{entry-id}

Requests the deletion of a specific entry

Note

IMPORTANT: All calls to the aforementioned endpoints can fail with reasonable HTTP status codes (e.g. 401, 403, 409, 501), depending on the context and state.

3.3.3.3. Entities

Software Architecture: Components Entity Collection arch~sovd-api-components-entity-collection
status: draft

The /components endpoint serves as the entry point for discovering available ECU entities.

GET /components

Returns a list of all ECU entities that have been loaded from diagnostic descriptions (MDD files). Each item in the list contains the ECU name, a lowercase identifier, and a URI reference to the individual component resource.

The response may include additional fields beyond the standard items list. These additional fields group ECUs based on configurable conditions evaluated against the diagnostic description metadata. The names and filter criteria for these additional fields are defined in the application configuration.

GET /components/{ecu-name}

Returns detailed information about a specific ECU entity, including:

  • The ECU identifier and name

  • Variant information (name, base variant flag, connectivity state, and logical address)

  • URI references to the standardized resource collection endpoints: data, operations, configurations, faults, modes, locks, and extension endpoints

The connectivity state of an ECU reflects its current diagnostic reachability and variant detection status:

ECU connectivity states

State

Description

Online

The ECU is reachable and has a detected variant

Offline

The ECU has not been contacted since startup

NotTested

Variant detection has not yet been performed

Duplicate

Multiple variants match the ECU response, superseded by a more specific match

Disconnected

The ECU was previously reachable but is no longer responding

NoVariantDetected

The ECU responded but no matching variant was found

Optionally, diagnostic description metadata (SDGs) for the ECU can be included in the response through a query parameter.

Software Architecture: Standardized Resource Collection Mapping arch~sovd-api-standardized-resource-collection-mapping

Every ECU with a mdd file is an entity within the /components entity collection.

This doesn’t include the mdd files used for functional communication (see Functional communication).

3.3.3.4. ECU resource collection

Software Architecture: ECU Resource Collection arch~sovd-api-ecu-resource-collection
status: draft

Each ECU entity must provide a standardized resource collection as defined in ISO 17978-3, chapter 5.4.2.

The resource collection for ECUs is defined in an OpenAPI Specification: ECU Resource Collection Specification

3.3.3.5. SDG/SD Metadata

Software Architecture: Component SDG/SDs arch~sovd-api-component-sdgsd
status: draft

Special Data Groups (SDGs) and Special Data (SDs) from the diagnostic description can be retrieved through an opt-in query parameter x-sovd2uds-includesdgs (with alias x-include-sdgs). When set to true, the response includes the SDG/SD metadata instead of or in addition to the normal response data.

ECU-level SDGs

On the GET /components/{ecu-name} endpoint, including SDGs adds an sdgs property to the ECU response object. The SDGs returned are those associated with the ECU entity in the diagnostic description (retrieved without a specific service context).

Service-level SDGs

On the GET /components/{ecu-name}/data/{data-identifier} endpoint, when SDGs are requested, the endpoint returns the SDGs associated with the diagnostic service instead of the normal data response. The response contains an items map keyed by a combination of the service name and its action type, where each entry holds the list of SDGs for that service action.

Operation-level SDGs

On the GET /components/{ecu-name}/operations/{operation-identifier} endpoint, when SDGs are requested, the endpoint returns the SDGs associated with the diagnostic service instead of the normal data response. The response contains an items map keyed by a combination of the service name and its action type, where each entry holds the list of SDGs for that service action.

Note

TODO We need to define handling for asynchronous operations, since they consist of multiple services with (possibly conflicting) SDGs/SDs - current idea would be add dummy SDGs at the top, with the si set to the “original” type of the operation

Data format

The SDG/SD structure is recursive. Each entry in the list is one of two types:

SD entry fields

Field

Type

Description

value

string (optional)

The value of the SD

si

string (optional)

Semantic information – a descriptor or key for the entry

ti

string (optional)

Text information – the textual content of the entry

SDG entry fields

Field

Type

Description

caption

string (optional)

The name of the group

si

string (optional)

Semantic information – a descriptor or key for the group

sdgs

list (optional)

A nested list of SD and SDG entries, allowing arbitrary nesting depth

SD and SDG entries are distinguished by their structure – an entry with a sdgs or caption field is an SDG, while an entry with value or ti fields is an SD.

3.3.3.6. Data Resources – SID 2216 & 2E16

Software Architecture: Data Resources arch~sovd-api-data-resources
status: draft

Data resources for ECUs are available in the standardized resource collection within the path /components/{ecu-name}/data.

The data main path returns a list of the data identifiers available as /data/{data-identifier}, as well as metadata.

A data identifier in the list is described with the following attributes (all strings):

Data identifier attributes

Attribute

Description

id

Path element ID (i.e. short name)

name

Name of the element (i.e. long name)

category

Category of the element

Naming

Names for data resources are determined by taking all diag-services defined for 2216 and 2E16 – their short name is taken as a base and processed by removing configurable prefixes/suffixes, to determine the data identifier within the /data/{data-identifier} path.

3.3.3.7. Categories

Software Architecture: Data Identifier Categories arch~sovd-api-data-identifier-categories
status: draft

The category of a data identifier must be mappable with configuration, in which the functional class name is mapped to a category name.

The following standard categories are defined by the standard:

Standard categories

Name

Description

identData

Identification data – everything related to the identification of an ECU/vehicle

currentData

Measurement data that can dynamically change

storedData

Parameters stored in the ECU

sysInfo

System information - data related to system resources that can change dynamically (e.g. memory consumption)

Additional custom categories must be prefixed with x-sovd2uds-, or, in custom vendor configuration, with a vendor-specific prefix different from x-sovd2uds.

Services without a mapping should be ignored to allow a separation between configuration and data services.

3.3.3.8. Configurations – SID 2216 & 2E16

Software Architecture: Configuration Resources arch~sovd-api-configuration-resources
status: draft

Names for data resources are determined by taking all diag-services defined for 2216 and 2E16, and filtering them for a configurable functional class name. Their short name is taken as a base and processed by removing configurable prefixes/suffixes, to determine the data identifier within the /configurations/{data-identifier} path.

The returned item properties for the /configurations item list are:

Configuration item properties

Attribute

Description

id

Path element ID (i.e. short name)

name

Name of the element (i.e. long name)

type

Always parameter

x-sovd2uds-serviceAbstract

Array of strings containing the SIDs and data identifier as a hexadecimal string (e.g. [“2E1234”, “221234”])

Note

x-sovd2uds-serviceAbstract is an extension to the standard.

Rationale for serviceAbstract

Coding data files might not include the matching name for a service, or detailed JSON parameters that would be required to code an ECU. Therefore, a “reverse lookup” to the name can be required, so a client without access to the diagnostic description is able to code an ECU just with the 2E 1234 <payload> data, utilizing the application/octet-stream extension for a PUT /configurations/{data-identifier} call.

3.3.4. Operations

Software Architecture: Synchronous and Asynchronous Operations arch~sovd-api-operations-handling
status: draft

Operations in the CDA are Routines (3116), Reset (1116), and an extension to configure communication parameters (Communication Parameters (ComParams)).

Reset – SID 1116

For compatibility with SOVD version 1.0 and earlier, the operations /operations/ecureset and /operations/reset to reset an ECU must be supported.

Routines – SID 3116

All services with the SID 3116 are considered for operations – as with data, their short names are preprocessed by removing configurable prefixes/suffixes to determine routine identifiers available as the /operations/{routine-identifier} path.

The items in the list of items available under /operations must include the following attributes:

Operation list item attributes

Attribute

Type

Description

id

string

Path element for the routine identifier (i.e. short name)

name

string

Name of the routine (long name)

proximity_proof_required

boolean

Always false

asynchronous_execution

boolean

Either true or false, depending on the defined subfunctions for the routine

Synchronous – Start only

When a routine only defines the Start (0x01) subfunction, it is considered synchronous. This means that the return for asynchronous_execution in the list will be false, and that a call to execute the routine with POST /operations/{routine-name}/executions is executed synchronously and will directly return the response from the ECU with HTTP status 200 OK.

Note

Operations without a Start subfunction can exist in the operations list but will fail execution with an error unless the x-sovd2uds-suppressService query parameter is set to true.

Asynchronous – Stop and/or RequestResults

When a routine has Stop (0x02) and/or RequestResults (0x03) subfunctions defined, it is considered asynchronous. This means that the return for asynchronous_execution in the list will be true, and that a call to execute the routine with POST /operations/{routine-name}/executions is executed on the ECU and will return the response from the ECU, as well as an id and the other asynchronous properties required by the standard for calling the RequestResults subfunction with GET /operations/{routine-name}/executions/{id}.

The POST request returns HTTP status 202 ACCEPTED with an execution identifier.

Additionally, by calling DELETE /operations/{routine-name}/executions/{id}, it’s possible to call the Stop subfunction of the routine.

Subfunction Requirements

If any of the required subfunctions are not available in the diagnostic database, the call will result in an error:

  • POST requires the Start (0x01) subfunction to be defined

  • GET requires the RequestResults (0x03) subfunction to be defined

  • DELETE requires the Stop (0x02) subfunction to be defined

These requirements can be bypassed using the x-sovd2uds-suppressService query parameter. Since an entry in the list is still required, as well as the operation being asynchronous, the definition of either RequestResults or Stop is a prerequisite.

Force Parameter

If DELETE is called and an ECU error is encountered, the id will not be deleted unless the query parameter x-sovd2uds-force is set to true. This allows the client to handle returned errors and to call the Stop subfunction again.

When x-sovd2uds-force=true, the execution is removed from tracking even if the Stop request fails or returns a negative response.

Stop Response Data

When a Stop subfunction returns non-empty response data, the DELETE endpoint returns HTTP status 200 OK with the response data in the body, instead of the standard 204 NO CONTENT. This allows clients to access any data returned by the Stop operation.

Note

This is an extension to the standard to support Stop operations that return response data.

Rationale for POST Response Data

When executing an asynchronous function, there’s no good way to return the response of the routine with the GET to the id-endpoint, since that endpoint should only return the status of the RequestResults call. Therefore, the response of the routine is returned directly when executing the routine with POST in addition to the id.

Note

This is a deviation from the standard, but is required to allow clients to handle routine responses properly.

3.3.4.1. IOControl – SID 2A16

Note

Not supported at this time

3.3.5. Modes

3.3.5.1. Session – SID 1016

Software Architecture: Session Endpoints arch~sovd-api-session-management
status: draft

The endpoint /modes/session can be used to determine the current ECU session, as well as trying to switch into a different session.

Session endpoints

Method

Path

Description

GET

/modes/session

Returns the current session

PUT

/modes/session

Tries to switch into the specified session

The format for the request body is:

{
  "value": "<session name>",
  "mode_expiration": 3600
}

The names of the sessions for the field value are determined by the short name for the state in the ECU’s state chart for the SID 1016 services. It is case-insensitive.

The field mode_expiration is optional. If set, it determines the time in seconds that the session should be active. Once that time expires, the session is automatically reset to the default session.

In the response body, id and value must be included.

See also chapter 7.16 in ISO 17978-3.

3.3.5.2. Security – SID 2716

Software Architecture: Security Access Endpoints arch~sovd-api-security-access-modes
status: draft

The endpoints are available under the path /modes/security.

Works similarly to Session defined in the previous chapter. The names of the security access levels are determined through the state charts for the SID 2716 services.

3.3.5.3. Authentication – SID 2916

Software Architecture: Authentication Endpoints arch~sovd-api-authentication-modes
status: draft

Note

This is technically a deviation from Table 343 in the ISO API. The table in the ISO is misleading, since 8.3.2 and 8.3.3 describe them separately.

The endpoints are available under /modes/authentication. A PUT call needs to provide a request body containing value with the desired subfunction (names are determined by the UDS standard), and a parameters field containing all request parameters.

Diagnostic data descriptions have to specify the used services including the subfunction individually, so the request parameters can be converted into UDS payloads.

3.3.5.4. Communication Control – SID 2816

Software Architecture: Communication Control Endpoints arch~sovd-api-communication-control-modes
status: draft

To control the communication parameters of an ECU, the path /modes/commctrl is offered, which can be called similarly to Session (without expiration).

The attribute value allows the following subfunction names based on the UDS standard:

  • enableRxAndEnableTx

  • enableRxAndDisableTx

  • disableRxAndEnableTx

  • disableRxAndDisableTx

Matching 2816 service entries must be present in the diagnostic description. Parameters can be provided through an additional parameters attribute.

Note

Other values are not supported.

3.3.5.5. DTC Setting – SID 8516

Software Architecture: DTC Setting Endpoints arch~sovd-api-dtc-setting-modes
status: draft

To control the DTC settings of an ECU, the path /modes/dtcsetting is offered, which can be called similarly to Session (without expiration).

The attribute value allows the values off and on, to call the corresponding subfunctions on the ECU.

Matching 8516 service entries must be present in the diagnostic description. Parameters can be provided through an additional parameters attribute.

Note

Other specific extensions to the values are not supported.

3.3.6. Faults – SID 1416 & 1916

Software Architecture: Faults endpoint arch~sovd-api-faults-endpoint
status: draft

The following operations must be implemented:

Faults endpoints

Method

Path

Description

GET

/faults

Retrieves a list of DTCs stored in the ECU:

  • To filter the DTCs, the query parameter status can be used.

GET

/faults/<dtc>

Retrieves detailed information about the DTC:

  • Can include snapshot and extended data within the environment_data object, when the query parameter include-extended-data or include-snapshot are set to true.

DELETE

/faults

Clears all DTCs stored in the ECU

DELETE

/faults/<dtc>

Clears the provided DTC from the ECU

The query parameter status[<key>]=<value> can be used to query/filter the returned DTCs based on their status. It can be used multiple times to combine different status flags. The values correspond to the DTC status bits defined in ISO 14229-1.

Available keys:

  • confirmedDtc

  • pendingDtc

  • testFailed

  • testFailedSinceLastClear

  • testFailedThisOperationCycle

  • testNotCompletedSinceLastClear

  • testNotCompletedThisOperationCycle

  • warningIndicatorRequested

All values are either boolean values (true/false), or a bit value (0/1).

Additionally, a special key called mask is available, which takes a hexadecimal mask as a value to allow filtering by the complete status byte. Using other keys together with mask is not supported.

3.3.7. Version Endpoint

Software Architecture: API Version Endpoint Registration Function arch~sovd-api-version-registration-function

The CDA provides a version registration function that sets up version-related endpoints during initialization.

Registration Function

The function accepts the following parameters: vendor_name, implementation_version, commit, build_date.

It always registers the standard /version-info endpoint, and optionally registers the default /data/version and /apps/sovd2uds/data/version endpoints.

The fields base_uri and api_version are determined by the function.

Standard Endpoint: /version-info

The /version-info endpoint (without version prefix) returns a JSON response per ISO 17978-3 §7.4.1:

{
    "sovd_info": [
        {
            "version": "<sovd_api_version>",
            "base_uri": "<relative uri-reference to base of sovd_api_version>",
            "vendor_info": {
                "name": "<vendor_name>",
                "version": "<implementation_version>",
                "commit": "<commit>",
                "build_date": "<build_date>"
            }
        }
    ]
}

Optional Vendor-Specific Endpoints: /data/version, /apps/sovd2uds/data/version

When enabled, these endpoints are registered as static data endpoints returning:

{
    "id": "version",
    "data": {
        "name": "<vendor_name>",
        "api": {
            "version": "<api_version>"
        },
        "implementation": {
            "version": "<implementation_version>",
            "commit": "<commit>",
            "build_date": "<build_date>"
        }
    }
}

Both endpoint types are available immediately after the HTTP server starts and do not require any ECU communication.

Note

The current implementation only registers /data/version and /apps/sovd2uds/data/version

3.3.8. Error Codes & Messages

Note

todo define

3.4. Extensions to ISO standard API

3.4.1. Flash-API

3.4.1.1. Introduction

Flashing via UDS generally follows the following sequence. OEMs might choose to call additional services or modify the sequence.

' SPDX-License-Identifier: Apache-2.0
' SPDX-FileCopyrightText: 2026 The Contributors to Eclipse OpenSOVD (see CONTRIBUTORS)
'
' See the NOTICE file(s) distributed with this work for additional
' information regarding copyright ownership.
'
' This program and the accompanying materials are made available under the
' terms of the Apache License Version 2.0 which is available at
' https://www.apache.org/licenses/LICENSE-2.0

@startuml
title flash-sequence using standard UDS

participant Client
participant CDA
participant ECU
participant "Other ECUs" as OtherECUs

!pragma teoz true

opt Unidirectional Authentication APCE
    note right of Client : This authentication is typically required for all ECUs, to be able to set DTC/commctrl on them as well
    Client -> CDA : POST /modes/authentication (verifyCertificateUnidirectional)
    activate CDA
    CDA -> ECU : Authentication (29 01 02 ...)
    activate ECU
    ECU -> ECU : Generate Challenge
    ECU -> CDA : ACK (with challenge)
    deactivate ECU
    CDA -> Client : HTTP 200 (OK with challenge)
    deactivate CDA

    Client -> Client : Sign challenge

    Client -> CDA : POST /modes/authentication (proofOfOwnership)
    activate CDA
    CDA -> ECU : Authentication (29 03 ...)
    activate ECU
    ECU -> ECU : Verify
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA
end

opt Communication Control and DTC handling
    Client -> CDA : POST /modes/dtcsetting (off)
    activate CDA
    CDA -> OtherECUs : ControlDTCSetting (85 02)
    activate OtherECUs
    OtherECUs -> CDA : ACK
    deactivate OtherECUs
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA

    Client -> CDA : POST /modes/commctrl (enableRxAndDisableTx)
    activate CDA
    CDA -> OtherECUs : CommunicationControl (28 01)
    activate OtherECUs
    OtherECUs -> CDA : ACK
    deactivate OtherECUs
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA
end

group Switch into Bootloader
    note over CDA: All /operations-calls can also be asynchronous,\ndepending on definition of the routine

    Client -> CDA : PUT /modes/session (programming)
    activate CDA
    CDA -> ECU : Programming Session (10 02)
    activate ECU
    note over ECU : ECU reboots, reconnection etc.
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA
end

group Security Access for Bootloader
    Client -> CDA : POST /modes/security (request seed level xx)
    activate CDA
    CDA -> ECU : Security Access Request Seed (27 xx)
    activate ECU
    ECU -> CDA : ACK (seed)
    deactivate ECU
    CDA -> Client : HTTP 200 (OK with seed)
    deactivate CDA

    Client -> Client : Sign Seed

    Client -> CDA: POST /modes/security (send key level xx)
    activate CDA
    CDA -> ECU : Security Access Send Key (key) (27 xx+1 ...)
    activate ECU
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA
end

group Prepare Flash Memory
    Client -> CDA: POST /operations/eraseMemory/executions
    activate CDA
    CDA -> ECU : eraseMemory (31 01 FF 00)
    activate ECU
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client : HTTP 200 (OK)
    deactivate CDA
end

loop Transfer all segments
    group RequestDownload
        Client -> CDA: PUT /x-sovd2uds-download/requestdownload
        activate CDA
        CDA -> ECU : RequestDownload (34 ...)
        activate ECU
        ECU -> CDA : ACK
        deactivate ECU
        CDA -> Client: HTTP 200 (OK)
        deactivate CDA
    end

    group TransferData
        Client -> CDA: POST /x-sovd2uds-download/flashtransfer
        activate CDA
        CDA -> Client: HTTP 200 (OK with flash transfer id)
        deactivate CDA

        loop until data transfer is completed
            CDA -> ECU: TransferData (36 xx ...)
            activate CDA
            activate ECU
            ECU -> CDA : ACK
            deactivate ECU
            deactivate CDA
        end
        & loop poll transfer status until finished
            note right of Client: Polling is independent\nto transfer of data
            Client -> CDA: GET /x-sovd2uds-download/flashtransfer/{id}
            activate CDA
            CDA -> Client: HTTP 200 (OK with transfer status)
            deactivate CDA
        end
    end

    group TransferExit
        Client -> CDA: PUT /x-sovd2uds-download/transferexit
        activate CDA
        CDA -> ECU : RequestTransferExit (37)
        activate ECU
        ECU -> CDA : ACK
        deactivate ECU
        CDA -> Client: HTTP 200 (OK)
        deactivate CDA
    end
end

opt Verification of transferred data
    Client -> CDA: POST /operations/verifyData/executions
    activate CDA
    CDA -> ECU : Verify data was transferred successfully (31 01 xx yy)
    activate ECU
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client: HTTP 200 (OK)
    deactivate CDA
end


group Check if all dependencies are ok
    Client -> CDA: POST /operations/checkProgrammingDependencies/executions
    activate CDA
    CDA -> ECU: checkProgrammingDependencies (31 01 FF 01)
    activate ECU
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client: HTTP 200 (OK)
    deactivate CDA
end

opt Reset ECU if required
    note right of Client: There's an issue with using the standard /status/reset in CDA, see Issue 40
    Client -> CDA: PUT /status/reset (ResetType)
    activate CDA
    CDA -> ECU: Reset (11 xx)
    activate ECU
    note over ECU : ECU reboots, reconnection etc.
    ECU -> CDA : ACK
    deactivate ECU
    CDA -> Client: HTTP 200 (OK)
    deactivate CDA
end

note right of Client: restore authentication/safe-state/etc.
@enduml

To allow the flashing functionality shown above, the SOVD-API from ISO 17978-3 needs to be extended with the functionality defined in this document.

The standard doesn’t define how the required services should be mapped in the Classic Diagnostic Adapter.

3.4.1.2. API

Software Architecture: Management of flash files arch~sovd-api-flash-file-management
status: draft
links incoming: req~sovd-api-flashing

Motivation

To flash an ECU, the CDA needs to have access to the files that should be flashed. This API allows listing the files that are available for flashing.

Endpoints

Flash file management

Method

Path

Description

Notes

GET

/apps/sovd2uds/bulk-data/flashfiles

Returns a list of entries that represent files in the configured flash folder and its subfolders.

Flash folder needs to be configured

Software Architecture: Flash data transfer arch~sovd-api-flash-data-transfer
status: draft
links incoming: req~sovd-api-flashing

Motivation

To flash an ECU, the CDA needs to be able to transfer the flash data to the ECU. This API allows transferring the data in block-sized chunks, as required by UDS.

Endpoints

All paths are prefixed with /components/{ecu-name}.

Flash data transfer endpoints

Method

Path

Description

Notes

PUT

/x-sovd2uds-download/requestdownload

Calls the RequestDownload service 34~16~

Returns the response of the RequestDownload service

POST

/x-sovd2uds-download/flashtransfer

Transfers data in the file given by id from an offset for a given length, using configurable chunk sizes (block size), and a configurable starting sequence number. It uses repeated calls to service 36~16~ to transfer the data.

Returns an object with an id to be used to retrieve status Plans: The API will be extended to also allow starting the transfer directly with absolute file paths

GET

/x-sovd2uds-download/flashtransfer

Retrieve the ids of the running flash transfers

GET

/x-sovd2uds-download/flashtransfer/{id}

Retrieve the status of the transfer with id

PUT

/x-sovd2uds-download/transferexit

Calls the RequestTransferExit service 37~16~

Returns the response of the RequestTransferExit service

3.4.1.3. Configuration

Software Architecture: Flash folder configuration arch~sovd-api-flash-folder-configuration
status: draft

Motivation

The CDA needs to know where to find the files that should be flashed to the ECUs. This configuration allows setting the flash folder.

Configuration Parameter

The following configuration parameter must be available in the CDA configuration:

  • flash_files_path: Path to the folder where flash files are stored. The CDA must search this folder and its

    subfolders for files available through the bulk-data/flashfiles endpoints.

3.4.2. Functional communication

Software Architecture: Diagnostic description & Configuration arch~functional-communication-dd-configuration
status: draft

Information about the available functional groups, the available services in those groups, and their communication parameters must be provided in a separate diagnostic description.

The diagnostic description’s MDD filename, in which the information for functional communication is contained, must be configurable. When no file is configured, functional communication is not available.

A configuration option in the CDA can further filter the available functional groups from the diagnostic description.

Rationale

Extracting a standardized resource collection for functional communication from individual ECU descriptions is challenging and non-transparent when extracting common functional services from all ECU files. Therefore, we chose to do this via a separate diagnostic description file.

This also follows the general pattern of one MDD file to an available standardized resource collection.

3.4.2.1. API

Software Architecture: Functional Communication API arch~functional-communication-api

Functional group functionality - if available - must be available in the /functions/functionalgroups/{group-name} path.

Within that path, a standardized resource collection (chapter 5.4.2 in ISO/DIS 17978-3) must be available, with the linked semantics.

Software Architecture: Functional Communication ECU-Lock behavior arch~functional-communication-locks
status: draft

Locking a functional group will start sending functional Tester Presents to the functional DoIP addresses of all DoIP Entities, and stop sending non-functional Tester Presents.

Lock Options

There can be an option to restore the previous ECU locks (and their Tester Presents).

Software Architecture: Functional Communication - Data arch~functional-communication-data
status: draft

Data

Since functional communication returns data from multiple ECUs, the /data/{data-identifier} endpoint must return, within the top level of data, the name of the ECU as the key, and only then its returned data (if any) as the value.

In case of errors, the errors structures must still return the type DataError[]. Inside a DataError, the JSON pointer must always point to the data/{ecu-name}/... element (including the ECU name), or, in case of communication/timeout errors, just to the ECU entry /data/{ecu-name}. A regular GenericError response with a failing HTTP status code (4xx, 5xx) is only acceptable when no communication was performed and the request failed beforehand.

Note

The content-type application/octet-stream is only supported for requests.

Software Architecture: Functional Communication - Operations arch~functional-communication-operations
status: draft

Same principle as with data, except that the top-level element name is parameters.

Note

The content-type application/octet-stream is only supported for requests.

Software Architecture: Functional Communication - Modes arch~functional-communication-modes
status: draft

The following modes must be supported for functional groups when the underlying diagnostic description contains them:

  1. session

  2. dtcsetting

  3. commctrl

3.4.3. Communication Parameters (ComParams)

Software Architecture: Communication Parameters API arch~sovd-api-comparams
status: draft

Note

Communication parameter handling is exposed through a comparam endpoint in operations.

Motivation

When using the CDA to communicate with classic ECUs, a client needs the ability to modify the communication parameters like timeouts and retries on-the-fly. This API provides a way to do this.

Retrieving & Modifying with a lock

ComParam operations

Method

Path

Description

POST

/operations/comparam/executions

Creates an id, can also directly contain parameters to be modified

GET

/operations/comparam/executions/{id}

Returns the current communication parameters

PUT

/operations/comparam/executions/{id}

Modifies the communication parameters

DELETE

/operations/comparam/executions/{id}

Resets the communication parameters to their original state

These operations require a lock on the entity. Only one execution of communication parameters per entity is allowed.

Software Architecture: Retrieve Communication Parameters without Lock arch~sovd-api-comparams-without-lock
status: draft
links incoming: arch~sovd-api-comparams

Note

This is a small extension to the ISO standard

To allow retrieving the communication parameters without a lock, a GET on /operations/comparam?todo must also return the current parameters.

Rationale

Clients without a lock might want to log the current communication parameters for informational purposes, so they should be able to retrieve them.

Handling this with the POST/GET semantic with only a single execution would make the handling extremely complicated for parallel clients with & without locks.

Example for directly retrieving communication parameters:

{
  "item": {
    "id": "comparam",
    "name": "Communication parameters",
    "asynchronous_execution": true,
    "proximity_proof_required": false
  },
  "parameters": {
    "CP_P6Max": {
      "value": "4500000",
      "unit": {
        "factor_to_si_unit": 1e-06
      }
    },
    "CP_RC78Handling": {
      "value": "Continue until RC78 timeout"
    },
    "...": {
      "...": "..."
    }
  }
}

3.4.4. MDD Embedded Files API

Software Architecture: MDD Embedded Files API arch~sovd-api-mdd-embedded-files
status: draft

Note

This is a SOVD-API extension.

Motivation

When an additional private SOVD-Server is tasked with providing functionality that replaces classic offboard-tester functionality, additional data from the single-ecu-jobs embedded in the original pdx-file can be required.

The odx-converter offers an option to include code-files from the pdx, as well as partial contents from those files. This API allows the retrieval of these files.

Embedded Content Retrieval

The API to retrieve embedded files utilizes a bulk-data endpoint, as defined in Bulk Data

Endpoints within /components/{ecuName}:

MDD embedded files endpoints

Method

Path

Description

GET

/x-sovd2uds-bulk-data/mdd-embedded-files

Returns a list of items which represent the files and their metadata

GET

/x-sovd2uds-bulk-data/mdd-embedded-files/{id}

Returns an item, which is the data that was embedded

Other methods are not allowed (e.g. data can’t be modified), and will return an HTTP 405 error code.

3.4.5. Single ECU Jobs

Software Architecture: Single ECU Jobs Extension arch~sovd-api-single-ecu-jobs
status: draft

Motivation

When a client wants to replace the classical single-ecu-jobs defined in the odx description, it could need the original input/output parameters from those jobs. This API provides access to the data for the single-ecu-jobs, which were present in the original odx at the time of conversion.

Retrieving data

Note

The available data may depend on the currently detected variant, since the ecu jobs are defined for variants.

The following paths will be available within the /components/{ecuName} path:

Single ECU Jobs endpoints

Method

Path

Description

GET

/x-single-ecu-jobs

Retrieves a list of single ecu job items

GET

/x-single-ecu-jobs/{id}

Reads the data for an entry

Read the Single ECU Jobs OpenAPI specification for details: Single ECU Jobs Specification

3.4.6. ECU Variant Detection

Software Architecture: ECU Variant Detection via SOVD-API arch~sovd-api-ecu-variant-detection
status: draft

Motivation

Some ECUs have different variants, which support different functionality. To provide the correct functionality, the CDA needs to be able to detect the variant in use. This variant may change at any point due to the nature of the ECUs software. Clients may need to trigger this explicitly to ensure correct functionality.

Variant Detection Trigger

A POST on the path /components/{ecuName} must trigger a variant detection

3.5. Communication

3.5.1. DoIP Communication

DoIP Communication is described in the ISO 13400 standard. The CDA implements DoIP as transport layer for UDS diagnostic communication with vehicle ECUs.

The communication parameters depend on the logical link used for the communication, filtered by configuration and actual ECU detection/availability.

3.5.1.1. Protocol Versions

Software Architecture: DoIP Protocol Version Support arch~doip-protocol-versions
status: draft

The CDA supports multiple DoIP protocol versions as defined in ISO 13400-2. The protocol version is included in the DoIP header of every message to indicate which version of the standard the message conforms to.

Supported Versions

Value

Version

Standard

0x01

ISO 13400-2:2010

Initial release

0x02

ISO 13400-2:2012

Default version used by the CDA

0x03

ISO 13400-2:2019

Updated release

0x04

ISO 13400-2:2019/Amd1

Amendment 1

0xFF

Default / any

Wildcard version identifier

Version Selection

The protocol version is configurable. The default version is ISO 13400-2:2012 (0x02). UDP broadcast messages for vehicle identification shall use the default/any version (0xFF), as the tester does not know which protocol version the DoIP entity supports at discovery time.

3.5.1.2. Message Framing

Software Architecture: DoIP Message Framing arch~doip-message-framing
status: draft
links incoming: req~doip-message-framing

All DoIP messages share a common 8-byte header followed by a variable-length payload. The codec layer is responsible for encoding outgoing messages and decoding incoming messages from the byte stream.

Header Structure

Byte(s)

Field

Description

0

Protocol Version

DoIP protocol version identifier

1

Inverse Version

Bitwise inverse of the protocol version (0xFF XOR version), used for integrity check

2-3

Payload Type

16-bit identifier of the payload type (big-endian)

4-7

Payload Length

32-bit length of the payload in bytes (big-endian), excluding the header

Supported Payload Types

Value

Payload Type

Direction

Transport

0x0000

Generic NACK

Response

TCP/UDP

0x0001

Vehicle Identification Request

Request

UDP

0x0002

Vehicle Identification Request (by EID)

Request

UDP

0x0003

Vehicle Identification Request (by VIN)

Request

UDP

0x0004

Vehicle Announcement Message (VAM)

Response

UDP

0x0005

Routing Activation Request

Request

TCP

0x0006

Routing Activation Response

Response

TCP

0x0007

Alive Check Request

Request

TCP

0x0008

Alive Check Response

Response

TCP

0x8001

Diagnostic Message

Request/Response

TCP

0x8002

Diagnostic Message ACK

Response

TCP

0x8003

Diagnostic Message NACK

Response

TCP

Decoding Behavior

The decoder reads the 8-byte header first. If insufficient data is available, it waits for more data to arrive on the stream. Once the header is complete, it reads the number of bytes indicated by the payload length field and dispatches to the appropriate payload parser based on the payload type.

3.5.1.3. Communication Parameters

Software Architecture: DoIP Communication Parameters arch~doip-communication-parameters
status: draft

The DoIP communication layer is parameterized through a set of communication parameters (COM parameters) that control addressing, timeouts, and retry behavior. These parameters are sourced from the diagnostic database (MDD files) and can vary per logical link.

DoIP communication parameters

Name

Function

Default value

Comment

CP_DoIPLogicalGatewayAddress

Logical address of a DoIP entity. In case of a directly reachable DoIP entity it is equal to CP_DoIPLogicalEcuAddress, otherwise data is sent via this address to the CP_DoIPLogicalEcuAddress

0

CP_DoIPLogicalEcuAddress

Logical/Physical address of the ECU

0

CP_DoIPLogicalFunctionalAddress

Functional address of the ECU

0

CP_DoIPLogicalTesterAddress

Logical address of the tester

0

CP_DoIPNumberOfRetries

Number of retries for specific diagnostic message NACKs

3 (for OUT_OF_MEMORY)

Retry count is configured per NACK code

CP_DoIPDiagnosticAckTimeout

Maximum time the tester waits for an ACK or NACK from the DoIP entity

1s

CP_DoIPRetryPeriod

Period between retries after specific NACK conditions are encountered

200ms

CP_DoIPRoutingActivationTimeout

Maximum time allowed for the ECU’s routing activation response

30s

CP_RepeatReqCountTrans

Number of retries in case of a transmission error, receive error, or transport layer timeout

3

CP_DoIPConnectionTimeout

Timeout after which a connection attempt should have been successful

30s

CP_DoIPConnectionRetryDelay

Delay before attempting to reconnect

5s

CP_DoIPConnectionRetryAttempts

Number of attempts to retry connection before giving up

100

Note

When these parameters are sourced from MDD files, multiple files could define different values for the same logical address due to duplicated logical addresses.

3.5.1.4. Vehicle Identification

Software Architecture: Vehicle Identification arch~doip-vehicle-identification
status: draft

Vehicle identification is the process by which the CDA discovers DoIP entities on the network. It uses UDP broadcast to solicit Vehicle Announcement Messages from all reachable DoIP entities.

Discovery Process

  1. A UDP socket is created and bound to the configured tester address and gateway port (default: 13400). The socket is configured with broadcast capability and address reuse.

  2. A Vehicle Identification Request (VIR, payload type 0x0001) is broadcast to 255.255.255.255 on the gateway port.

  3. Vehicle Announcement Messages (VAM, payload type 0x0004) are collected within a timeout window.

  4. VAM responses are filtered by subnet mask: only responses from IP addresses within the tester’s subnet (tester_address AND tester_subnet) are accepted.

  5. Each accepted VAM is matched against known ECU logical addresses from the diagnostic databases.

Vehicle Announcement Message Content

Each VAM contains:

  • Vehicle Identification Number (VIN, 17 bytes)

  • Logical address of the DoIP entity (2 bytes)

  • Entity Identification (EID, 6 bytes)

  • Group Identification (GID, 6 bytes)

  • Further action code (routing activation required or no further action)

  • Optional VIN/GID synchronization status

Spontaneous VAM Listener

After initial discovery, a background task continuously listens on the gateway port for spontaneous VAM broadcasts. This handles:

  • Gateways coming online after the initial VIR broadcast

  • Gateways reconnecting after a temporary disconnection

When a new or known VAM is received, the system establishes or re-uses the connection and triggers variant detection for the associated ECUs.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "UDP Socket\n(port 13400)" as UDP
participant "DoIP Entity A" as GWA
participant "DoIP Entity B" as GWB

== Initial Discovery ==
CDA -> UDP: Create socket\n(broadcast, address reuse)
CDA -> UDP: VIR broadcast to\n255.255.255.255:13400

UDP -> GWA: Vehicle Identification\nRequest (0x0001)
UDP -> GWB: Vehicle Identification\nRequest (0x0001)

GWA --> UDP: VAM (0x0004)\n[logical_addr, VIN, EID, GID]
GWB --> UDP: VAM (0x0004)\n[logical_addr, VIN, EID, GID]

CDA -> CDA: Filter VAMs by\nsubnet mask
CDA -> CDA: Match logical addresses\nto MDD databases

== Spontaneous Listener (background) ==
CDA -> UDP: Listen for spontaneous VAMs
...
GWA --> UDP: Spontaneous VAM\n(entity came online)
UDP --> CDA: New VAM received
CDA -> CDA: Establish connection\nand trigger variant detection
@enduml

Vehicle Identification Sequence

3.5.1.5. Connection Establishment

Software Architecture: DoIP Connection Establishment arch~doip-connection-establishment
status: draft

After a DoIP entity is discovered via vehicle identification, a TCP connection is established to enable diagnostic communication.

TCP Connection

A TCP connection is initiated to the discovered gateway IP address on the configured gateway port (default: 13400). The connection attempt is bounded by the CP_DoIPConnectionTimeout parameter.

Retry Behavior

If the initial connection attempt fails or times out, the system retries according to:

  • Wait CP_DoIPConnectionRetryDelay between attempts

  • Retry up to CP_DoIPConnectionRetryAttempts times

  • On success: break the retry loop and proceed

  • On exhaustion of all retries: report connection failure

If the connection was initiated as part of a diagnostic request, a timeout error is reported to the caller.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "DoIP Entity" as ECU

group Establish TCP Connection
    CDA -> ECU: TCP connect\n(gateway_ip:13400)
    activate ECU
    ECU --> CDA: TCP connected
    note right: Maximum of\nCP_DoIPConnectionTimeout
    deactivate ECU
end

group Connection Attempt Fails / Times Out
    loop CP_DoIPConnectionRetryAttempts times
        note right of CDA: Wait for\nCP_DoIPConnectionRetryDelay
        CDA -> ECU: TCP connect\n(gateway_ip:13400)
        activate ECU
        ECU --> CDA: TCP connected
        deactivate ECU
        note right: Maximum of\nCP_DoIPConnectionTimeout
        note right of CDA: Break loop on success,\ncontinue on failure
    end
end
@enduml

DoIP Connection Establishment

3.5.1.6. Routing Activation

Software Architecture: Routing Activation arch~doip-routing-activation
status: draft

After establishing a TCP connection, routing activation must be performed before diagnostic messages can be exchanged. This registers the tester’s logical address with the DoIP entity.

Request

The CDA sends a Routing Activation Request (payload type 0x0005) containing:

  • Source address: the tester’s logical address (2 bytes)

  • Activation type: Default (0x00)

  • Reserved buffer (4 bytes, set to zero)

Response Handling

The response contains an activation code that determines the outcome:

Routing Activation Response Codes

Code

Meaning

CDA Behavior

0x10

Successfully activated

Proceed with diagnostic communication

0x11

Activated, confirmation required

Treat as success

0x07

Denied, encrypted TLS connection required

Fall back to TLS connection (see TLS Connection Support (arch~doip-tls))

0x00

Denied, unknown source address

Report routing error

0x01

Denied, all TCP sockets full

Report routing error

0x02

Denied, TCP socket already connected

Report routing error

0x03

Denied, source already active

Report routing error

0x04

Denied, missing authentication

Report routing error

0x05

Denied, rejected confirmation

Report routing error

0x06

Denied, unsupported activation type

Report routing error

The routing activation response must be received within CP_DoIPRoutingActivationTimeout.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "DoIP Entity" as ECU

== Routing Activation ==
CDA -> ECU: Routing Activation Request (0x0005)\n[tester_addr, type=Default]
activate ECU
note right: Maximum of\nCP_DoIPRoutingActivationTimeout

alt Activation Successful (0x10)
    ECU --> CDA: Routing Activation Response\n[code=SuccessfullyActivated]
    deactivate ECU
    note right of CDA: Connection ready\nfor diagnostics

else TLS Required (0x07)
    ECU --> CDA: Routing Activation Response\n[code=DeniedEncryptedTLSRequired]
    deactivate ECU
    CDA -> CDA: Close plain TCP connection

    CDA -> ECU: TCP connect to TLS port (3496)
    activate ECU
    CDA <-> ECU: TLS Handshake
    CDA -> ECU: Routing Activation Request (0x0005)\n[tester_addr, type=Default]
    ECU --> CDA: Routing Activation Response\n[code=SuccessfullyActivated]
    deactivate ECU
    note right of CDA: TLS connection ready\nfor diagnostics

else Denied (any other code)
    ECU --> CDA: Routing Activation Response\n[code=Denied*]
    deactivate ECU
    note right of CDA: Report routing error
end
@enduml

Routing Activation Sequence

3.5.1.7. TLS Connection Support

Software Architecture: TLS Connection Support arch~doip-tls
status: draft
links incoming: req~doip-tls

The CDA supports TLS-secured DoIP connections as defined in ISO 13400. TLS is activated as a fallback when a DoIP entity requires encrypted communication.

TLS Activation Trigger

TLS is not used by default. It is activated when a Routing Activation Response returns the code DeniedRequestEncryptedTLSConnection (0x07). The CDA then:

  1. Closes the plain TCP connection

  2. Establishes a new TCP connection to the TLS port (default: 3496)

  3. Performs the TLS handshake

  4. Re-sends the Routing Activation Request over the secured connection

TLS Configuration

  • Minimum TLS version: TLS 1.2

  • Maximum TLS version: TLS 1.3

  • Certificate verification can be enforced, with configurable trusted CA certificates

3.5.1.8. Diagnostic Message Exchange

Software Architecture: Diagnostic Message Exchange arch~doip-diagnostic-message
status: draft

Diagnostic messages carry UDS (Unified Diagnostic Services) data between the CDA and ECUs through the DoIP transport layer.

Sending a Diagnostic Message

  1. The connection for the target ECU’s gateway is looked up by gateway logical address.

  2. The ECU lock is acquired to serialize requests per ECU.

  3. Any pending messages in the receive buffer are cleared.

  4. The Diagnostic Message (0x8001) is sent containing the tester’s source address, the target ECU address, and the UDS payload.

  5. On transmission failure, the message is retried up to CP_RepeatReqCountTrans times.

ACK/NACK Handling

After sending, the CDA waits for an acknowledgement within CP_DoIPDiagnosticAckTimeout:

  • Diagnostic Message ACK (0x8002): The message was accepted by the DoIP entity. The ACK may contain the original message for verification. If the contained message does not match the sent message, the CDA continues waiting for the correct ACK.

  • Diagnostic Message NACK (0x8003): The message was rejected. The NACK code indicates the reason. Depending on the code and CP_DoIPNumberOfRetries, the message may be retried after CP_DoIPRetryPeriod.

  • Generic NACK (0x0000): A protocol-level error occurred. Reported as error.

  • Timeout: No ACK or NACK received within the timeout. Reported as timeout error.

Receiving the Diagnostic Response

After a successful ACK, the CDA waits for the diagnostic response. Multiple intermediate responses may be received before the final response:

  • NRC 0x78 (Response Pending): The ECU needs more time. The CDA continues waiting according to CP_RC78Handling and CP_RC78CompletionTimeout, using the enhanced timeout CP_P6Star.

  • NRC 0x21 (Busy, Repeat Request): The ECU is busy. Handling depends on CP_RC21Handling, CP_RC21CompletionTimeout, and CP_RC21RequestTime.

  • NRC 0x94 (Temporarily Not Available): Handling depends on CP_RC94Handling, CP_RC94CompletionTimeout, and CP_RC94RequestTime.

  • Final Response: The complete UDS response is returned to the caller.

Functional Addressing

For functional group communication, a single diagnostic message is sent to the gateway using the functional address (CP_DoIPLogicalFunctionalAddress). Responses are collected from multiple ECUs simultaneously. ECUs that do not respond within the timeout are reported individually.

Auto-ACK on Receive

When the CDA receives a diagnostic message from a DoIP entity, it automatically sends a Diagnostic Message ACK back. This behavior is configurable.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "DoIP Entity\n(Gateway)" as GW
participant "ECU" as ECU

== Send Diagnostic Request ==
CDA -> GW: Diagnostic Message (0x8001)\n[tester_addr -> ecu_addr, UDS request]
activate GW

alt ACK received
    GW --> CDA: Diagnostic Message ACK (0x8002)
    note right: Within\nCP_DoIPDiagnosticAckTimeout

else NACK received
    GW --> CDA: Diagnostic Message NACK (0x8003)\n[nack_code]
    note right of CDA: Retry based on\nCP_DoIPNumberOfRetries\nand CP_DoIPRetryPeriod
    deactivate GW
end

== Await Diagnostic Response ==
GW -> ECU: Forward UDS request
activate ECU

alt Response Pending (NRC 0x78)
    ECU --> GW: NRC 0x78 (Response Pending)
    GW --> CDA: Diagnostic Message (0x8001)\n[NRC 0x78]
    note right of CDA: Continue waiting per\nCP_RC78Handling\nwith CP_P6Star timeout

    ECU --> GW: UDS positive response
    GW --> CDA: Diagnostic Message (0x8001)\n[UDS response]
    deactivate ECU
    deactivate GW
    CDA -> GW: Diagnostic Message ACK (0x8002)

else Direct Response
    ECU --> GW: UDS response
    GW --> CDA: Diagnostic Message (0x8001)\n[UDS response]
    deactivate ECU
    deactivate GW
    CDA -> GW: Diagnostic Message ACK (0x8002)
end
@enduml

Diagnostic Message Exchange (Physical Addressing)

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "DoIP Entity\n(Gateway)" as GW
participant "ECU A" as ECUA
participant "ECU B" as ECUB

== Functional Request (one-to-many) ==
CDA -> GW: Diagnostic Message (0x8001)\n[tester_addr -> functional_addr,\nUDS request]
activate GW
GW --> CDA: Diagnostic Message ACK (0x8002)

GW -> ECUA: Forward UDS request
activate ECUA
GW -> ECUB: Forward UDS request
activate ECUB

== Collect Responses from Multiple ECUs ==
ECUA --> GW: UDS response
GW --> CDA: Diagnostic Message (0x8001)\n[ECU A response]
deactivate ECUA
CDA -> GW: Diagnostic Message ACK (0x8002)

ECUB --> GW: UDS response
GW --> CDA: Diagnostic Message (0x8001)\n[ECU B response]
deactivate ECUB
deactivate GW
CDA -> GW: Diagnostic Message ACK (0x8002)

note right of CDA: ECUs that do not respond\nwithin timeout are reported\nas individual timeouts
@enduml

Diagnostic Message Exchange (Functional Addressing)

3.5.1.9. Alive Check

Software Architecture: Alive Check arch~doip-alive-check
status: draft
links incoming: req~doip-alive-check

The alive check mechanism verifies that the TCP connection to a DoIP entity is still active during periods of inactivity.

Periodic Check

When no diagnostic messages have been sent on a connection for a defined idle interval, the CDA sends an Alive Check Request (0x0007) to the DoIP entity.

Response Handling

ECU Support Detection

Not all DoIP entities implement the alive check mechanism. The CDA tracks whether a DoIP entity has ever responded to an Alive Check Request. A missing response is only treated as a connection loss when the entity has previously demonstrated support by sending at least one Alive Check Response. If the entity has never responded to an alive check, the absence of a response is not considered a failure.

3.5.1.10. Connection Management

Software Architecture: DoIP Connection Management arch~doip-connection-management
status: draft

The CDA manages DoIP TCP connections with automatic recovery from connection failures.

Per-Gateway Connection Architecture

Each DoIP gateway has a single TCP connection that is shared by all ECUs behind that gateway. The connection is split into independent sender and receiver tasks that coordinate to avoid simultaneous read/write operations. All ECUs behind a gateway are multiplexed by their logical addresses over this shared connection.

Connection Reset and Recovery

A connection reset is triggered by:

  • Failed alive check (no response within timeout)

  • Connection closed by the remote side

  • Send failure on the connection

The reset process:

  1. Acquire exclusive access to both the send and receive sides of the connection

  2. Attempt to re-establish the TCP connection and perform routing activation

  3. On failure: retry with CP_DoIPConnectionRetryDelay up to CP_DoIPConnectionRetryAttempts

  4. On success: swap the new connection in place and resume normal operation

  5. On exhaustion of all retries: the connection is considered permanently lost

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "Sender Task" as ST
participant "Receiver Task" as RT
participant "Reset Task" as RESET
participant "DoIP Entity" as GW

== Normal Operation ==
ST -> GW: Diagnostic Messages
GW --> RT: Diagnostic Responses

== Connection Failure Detected ==
RT -> RESET: Trigger reset\n(alive check failed /\nconnection closed)
activate RESET

RESET -> RESET: Acquire send +\nreceive locks

loop CP_DoIPConnectionRetryAttempts
    RESET -> GW: TCP connect
    activate GW

    alt Connection successful
        GW --> RESET: TCP connected
        RESET -> GW: Routing Activation\nRequest
        GW --> RESET: Routing Activation\nResponse (success)
        deactivate GW
        RESET -> RESET: Swap new connection\nin place
        note right of RESET: Resume normal operation
    else Connection failed
        note right of RESET: Wait\nCP_DoIPConnectionRetryDelay
        deactivate GW
    end
end
deactivate RESET
@enduml

Connection Reset and Recovery

3.5.1.11. DoIP Error Handling

Software Architecture: DoIP Error Handling arch~doip-error-handling
status: draft
links incoming: req~doip-error-handling

The DoIP communication layer handles various error conditions that can occur during connection establishment, routing activation, and diagnostic message exchange.

Error Categories

Category

Condition

Behavior

Connection Closed

TCP connection unexpectedly closed by remote side

Trigger connection reset

Decode Error

Received message could not be decoded

Log error and continue

Invalid Message

Unexpected message type received in current state

Report error to caller

Connection Timeout

TCP connect did not complete within CP_DoIPConnectionTimeout

Retry per connection retry parameters

Routing Error

Routing activation denied (non-TLS denial codes)

Report routing error

Send Failure

Message could not be written to the TCP stream

Retry per CP_RepeatReqCountTrans, trigger reset on persistent failure

ACK Timeout

No ACK/NACK within CP_DoIPDiagnosticAckTimeout

Report timeout to caller

Diagnostic NACK

DoIP entity rejected diagnostic message

Retry per CP_DoIPNumberOfRetries with CP_DoIPRetryPeriod delay, based on NACK code

3.5.2. UDS Communication (DoIP)

The UDS (Unified Diagnostic Services) application layer sits above the DoIP transport layer and implements the request-response protocol defined in ISO 14229. It handles service payload construction, response matching, negative response code processing, tester present session keepalive, and functional group communication.

Communication parameters control timing, retry behavior, and tester present generation. These are sourced from the diagnostic database (MDD files) and may vary per logical link.

3.5.2.1. Communication Parameters

Software Architecture: UDS Communication Parameters arch~uds-communication-parameters
status: draft

The UDS application layer is parameterized through a set of communication parameters (COM parameters) that control response timeouts, NRC handling policies, and tester present behavior. These parameters are sourced from the diagnostic database (MDD files) and can vary per logical link.

Response Timing Parameters

Name

Function

Default value

Comment

CP_P6Max

Timeout after sending a successful request, for the complete reception of the response message (in uS)

1000000

In case of a timeout, CP_RepeatReqCountApp has to be used to retry until exhausted, or a completion timeout is reached

CP_P6Star

Enhanced timeout after receiving a NRC 0x78 to wait for the complete reception of the response message (in uS)

1000000

CP_RepeatReqCountApp

Repetition of last request in case of timeout, transmission or receive error

2

Only applies to application layer messages

NRC Handling Parameters

Name

Function

Default value

Comment

CP_RC21Handling

Repetition mode in case of NRC 21

Continue until RC21 timeout

0 = Disabled
1 = Continue handling negative responses until CP_RC21CompletionTimeout
2 = Continue handling unlimited

CP_RC21CompletionTimeout

Time period the tester accepts for repeated NRC 0x21 and retries, while waiting for a positive response in uS

25000000

CP_RC21RequestTime

Time between a NRC 0x21 and the retransmission of the same request (in uS)

200000

CP_RC78Handling

Repetition mode in case of NRC 78

Continue until RC78 timeout

0 = Disabled
1 = Continue handling negative responses until CP_RC78CompletionTimeout
2 = Continue handling unlimited

CP_RC78CompletionTimeout

Time period the tester accepts for repeated NRC 0x78, and waits for a positive response (in uS)

25000000

CP_RC94Handling

Repetition mode in case of NRC 94

Continue until RC94 timeout

0 = Disabled
1 = Continue handling negative responses until CP_RC94CompletionTimeout
2 = Continue handling unlimited

CP_RC94CompletionTimeout

Time period the tester accepts for repeated NRC 0x94, and waits for a positive response (in uS)

25000000

CP_RC94RequestTime

Time between a NRC 0x94 and the retransmission of the same request (in uS)

200000

Tester Present Parameters

Name

Function

Default value

Comment

CP_TesterPresentHandling

Define Tester Present generation

Enabled

0 = Do not generate
1 = Generate Tester Present Messages

CP_TesterPresentAddrMode

Addressing mode for sending Tester Present

Physical

0 = Physical
1 = Functional, not relevant in CDA case

CP_TesterPresentReqResp

Define expectation for Tester Present responses

Response expected

0 = No response expected
1 = Response expected

CP_TesterPresentSendType

Define condition for sending tester present

On idle

0 = Fixed periodic
1 = When bus has been idle (Interval defined by CP_TesterPresentTime)

CP_TesterPresentMessage

Message to be sent for tester present

3E00

CP_TesterPresentExpPosResp

Expected positive response (if required)

7E00

CP_TesterPresentExpNegResp

Expected negative response (if required)

7F3E

A tester present error should be reported in the log, tester present sending should be continued

CP_TesterPresentTime

Timing interval for tester present messages in uS

2000000

Note

When these parameters are sourced from MDD files, multiple files could define different values for the same logical address due to duplicated logical addresses.

3.5.2.2. Request-Response Flow

Software Architecture: UDS Request-Response Flow arch~uds-request-response
status: draft
links incoming: req~uds-request-response

The UDS application layer implements the request-response flow using per-ECU semaphores for serialization, a SID-specific lookup table for response matching, and a layered retry strategy split between the UDS and DoIP layers.

Per-ECU Semaphore

A semaphore with a permit count of 1 is allocated per ECU logical address. Because the key is the logical address, ECUs that share a logical address (e.g., before variant detection) implicitly share the same semaphore. The semaphore is acquired before the request is sent and held for the entire send-and-receive cycle, including any NRC-driven waiting or retransmission. It is released only after the final response is received or an error occurs.

Request Transmission

The UDS layer constructs a payload containing the tester source address, target ECU address, and UDS request data. This payload is passed to the DoIP transport layer for transmission. On DoIP-level transmission failure, retries are handled by the transport layer per CP_RepeatReqCountTrans.

Response Matching Algorithm

Before sending, the UDS layer extracts a prefix of the request payload whose length is determined by a SID-to-length lookup table. This prefix is used to match the eventual response:

Response match length by SID

SID

Length

Description

0x14

1

ClearDiagnosticInformation (SID only)

0x22

3

ReadDataByIdentifier (SID + 2-byte DID)

0x2E

3

WriteDataByIdentifier (SID + 2-byte DID)

0x31

4

RoutineControl (SID + sub-function + 2-byte RID)

0x34

1

RequestDownload (SID only)

0x35

1

RequestUpload (SID only)

0x37

1

RequestTransferExit (SID only)

default

2

SID + sub-function byte (or similar)

For a positive response, the first byte equals the sent SID plus 0x40 (ISO 14229 positive response bitmask) and the subsequent bytes up to the match length equal the corresponding bytes of the original request. If the request payload is shorter than the SID-specific match length, the match is performed only up to the available payload length. For a negative response, the first byte is 0x7F and the second byte equals the sent SID.

NRC 0x78, 0x21, and 0x94 are parsed at the DoIP layer and delivered to the UDS layer as typed response variants. These are processed by the NRC handling logic (see UDS NRC Handling (arch~uds-nrc-handling)) before SID matching is applied to the final response.

Timeout and Retry Strategy

The caller may optionally override the default response timeout. When NRC 0x78 is received, the active timeout switches from CP_P6Max to CP_P6Star. Application- layer retries (CP_RepeatReqCountApp) are independent of DoIP transport-layer retries (CP_RepeatReqCountTrans).

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "Caller" as Caller
participant "CDA\n(UDS Layer)" as UDS
participant "DoIP\nTransport" as DoIP
participant "ECU" as ECU

== Request ==
Caller -> UDS: UDS request (service, payload)
activate UDS

UDS -> UDS: Acquire per-ECU\nsemaphore (by logical addr)
note right: 10s timeout\nfor acquisition

UDS -> UDS: Extract request prefix\nfor response matching\n(length varies by SID)

UDS -> DoIP: ServicePayload\n[tester_addr, ecu_addr, data]
DoIP -> ECU: Diagnostic Message (0x8001)
ECU --> DoIP: Diagnostic Message ACK

== Response Matching ==
ECU --> DoIP: UDS response
DoIP --> UDS: Response data

alt Positive response (SID + 0x40 + echoed prefix)
    UDS -> UDS: Prefix match confirmed
    UDS --> Caller: Response data
else Negative response (0x7F + SID)
    UDS -> UDS: SID match confirmed
    UDS --> Caller: Negative response
else Unmatched response
    UDS -> UDS: Log warning, discard
    UDS -> UDS: Continue waiting\nfor matching response
end

UDS -> UDS: Release per-ECU\nsemaphore
deactivate UDS
@enduml

UDS Request-Response Flow

3.5.2.3. NRC Handling

Software Architecture: UDS NRC Handling arch~uds-nrc-handling
status: draft
links incoming: req~uds-nrc-handling

The UDS application layer implements a dual-loop architecture for handling Negative Response Codes (NRCs). The outer loop handles retransmission (for NRC 0x21 and 0x94), while the inner loop handles continued waiting (for NRC 0x78). Each NRC type has an independent handling policy and timing configuration.

Dual-Loop Architecture

  • Outer loop ('send): Transmits the UDS request to the DoIP layer. On NRC 0x21 (Busy, Repeat Request) or NRC 0x94 (Temporarily Not Available), control returns to this loop after a configured delay, causing the request to be retransmitted.

  • Inner loop ('read_uds_messages): Waits for responses from the DoIP layer. On NRC 0x78 (Response Pending), the timeout is extended and the loop continues waiting without retransmission.

NRC 0x78 – Response Pending

When the ECU signals NRC 0x78, it has accepted the request but needs more time. The CDA switches to the enhanced timeout CP_P6Star and continues waiting in the inner loop. The handling policy CP_RC78Handling determines behavior:

  • Disabled (0): Do not handle; report as negative response.

  • Continue until timeout (1): Keep waiting until CP_RC78CompletionTimeout.

  • Continue unlimited (2): Keep waiting indefinitely.

NRC 0x21 – Busy, Repeat Request

When the ECU signals NRC 0x21, it is temporarily busy. The CDA waits for CP_RC21RequestTime and then retransmits the original request by breaking back to the outer loop. The handling policy CP_RC21Handling determines behavior:

  • Disabled (0): Do not handle; report as negative response.

  • Continue until timeout (1): Retry until CP_RC21CompletionTimeout.

  • Continue unlimited (2): Retry indefinitely.

NRC 0x94 – Temporarily Not Available

When the ECU signals NRC 0x94, the requested resource is temporarily unavailable. The CDA waits for CP_RC94RequestTime and then retransmits the original request. The handling policy CP_RC94Handling determines behavior:

  • Disabled (0): Do not handle; report as negative response.

  • Continue until timeout (1): Retry until CP_RC94CompletionTimeout.

  • Continue unlimited (2): Retry indefinitely.

NRC Classification at Transport Layer

NRC 0x78, 0x21, and 0x94 are parsed at the DoIP transport layer and delivered to the UDS application layer as typed response variants (ResponsePending, BusyRepeatRequest, TemporarilyNotAvailable) rather than raw messages. This allows the UDS layer to apply the appropriate handling logic (retry, wait, or report) based on the NRC type and configured policy. All other NRCs are delivered as standard negative responses for SID-based matching.

Policy Validation

Before acting on any NRC, the CDA validates the handling policy and checks the elapsed time against the configured completion timeout. If the policy is disabled or the timeout has been exceeded, the NRC is reported to the caller as a terminal negative response.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA\n(UDS Layer)" as UDS
participant "DoIP\nTransport" as DoIP
participant "ECU" as ECU

== Outer Loop ('send): Transmit Request ==
UDS -> DoIP: UDS request
DoIP -> ECU: Diagnostic Message

== Inner Loop ('read_uds_messages): Await Response ==

alt NRC 0x78 (Response Pending)
    ECU --> DoIP: NRC 0x78
    DoIP --> UDS: ResponsePending
    note right of UDS: Switch timeout to CP_P6Star\nValidate CP_RC78Handling policy\nStay in inner loop (no retransmit)
    ECU --> DoIP: Final response
    DoIP --> UDS: UDS response

else NRC 0x21 (Busy, Repeat Request)
    ECU --> DoIP: NRC 0x21
    DoIP --> UDS: BusyRepeatRequest
    note right of UDS: Validate CP_RC21Handling policy\nWait CP_RC21RequestTime
    UDS -> UDS: Break to outer loop\n(retransmit request)
    UDS -> DoIP: UDS request (retransmit)
    DoIP -> ECU: Diagnostic Message
    ECU --> DoIP: Final response
    DoIP --> UDS: UDS response

else NRC 0x94 (Temporarily Not Available)
    ECU --> DoIP: NRC 0x94
    DoIP --> UDS: TemporarilyNotAvailable
    note right of UDS: Validate CP_RC94Handling policy\nWait CP_RC94RequestTime
    UDS -> UDS: Break to outer loop\n(retransmit request)
    UDS -> DoIP: UDS request (retransmit)
    DoIP -> ECU: Diagnostic Message
    ECU --> DoIP: Final response
    DoIP --> UDS: UDS response

else Direct Response
    ECU --> DoIP: UDS response
    DoIP --> UDS: UDS response
    note right of UDS: Within CP_P6Max
end
@enduml

UDS NRC Handling – Dual-Loop Architecture

3.5.2.4. Tester Present

Software Architecture: UDS Tester Present arch~uds-tester-present
status: draft
links incoming: req~uds-tester-present

The CDA maintains active diagnostic sessions with ECUs by periodically sending UDS Tester Present (0x3E) messages. Tester present generation is driven by the lock lifecycle: tasks are started when locks are acquired and stopped when locks are released.

Lock-Driven Lifecycle

Tester present tasks are tied to the SOVD lock mechanism:

  • Component (ECU) lock: Acquiring a lock on a single ECU starts a physical tester present task for that ECU, sending to the ECU’s physical address.

  • Functional group lock: Acquiring a lock on a functional group starts functional tester present tasks for each gateway ECU in the group, sending to each gateway’s functional address.

  • Vehicle lock: Does not start any tester present tasks.

If CP_TesterPresentHandling is set to “Disabled” (0) for an ECU, no tester present task is started for that ECU regardless of the lock type.

When a lock is released, the associated tester present tasks are stopped and the ECU’s session and security access state are reset.

Duplicate Prevention

Active tester present tasks are tracked in a HashMap keyed by ECU name. Before starting a new task, the system checks whether a task already exists for that ECU. Only one tester present task (physical or functional) can be active per ECU at any time.

Task Implementation

Each tester present task is a background async task that runs a periodic loop:

  1. Wait for the configured interval (CP_TesterPresentTime, default 2 seconds).

  2. Construct the tester present message from CP_TesterPresentMessage and send it through the standard UDS send path.

  3. If CP_TesterPresentReqResp indicates a response is expected, await and validate the response.

  4. If sending takes longer than the interval, log an error and continue.

The interval uses a delay-on-miss strategy: if a tick is missed (e.g., due to slow sending), the next tick is delayed rather than bursting to catch up.

Message Format

The tester present message is constructed from CP_TesterPresentMessage (default: [0x3E, 0x00]). When CP_TesterPresentReqResp indicates no response is expected, the suppress-positive-response bit (0x80) is OR-ed onto the sub-function byte, producing [0x3E, 0x80]. In this mode the message is sent with expect_response = false, meaning the CDA waits only for the DoIP-level acknowledgement and does not await a UDS-level response. When a response is expected, the message is sent as-is and the CDA validates the response against CP_TesterPresentExpPosResp and CP_TesterPresentExpNegResp.

The target address depends on the tester present type:

  • Physical (ECU lock): Sent to the ECU’s physical logical address.

  • Functional (functional group lock): Sent to the ECU’s functional logical address.

Functional Group Resolution

When starting functional tester present, the system resolves the functional group to its member ECUs and starts individual tester present tasks for each gateway ECU in the group (ECUs whose logical address equals their gateway address). Each gateway receives its own dedicated background task sending to that gateway’s functional address.

Error Handling

  • Tester present NRCs received from the DoIP layer are logged at debug level but do not cause task termination. The tester present task continues sending on the next interval.

  • Send failures (e.g., connection loss) are handled by the standard UDS send path, which may trigger DoIP connection recovery. The tester present task continues attempting to send on subsequent intervals.

COM Parameter Usage

All tester present COM parameters are loaded from the diagnostic database per ECU. The tester present task evaluates them as follows:

  • CP_TesterPresentTime – The sending interval in microseconds (default: 2,000,000 µS = 2 s). The periodic loop waits this duration between sends.

  • CP_TesterPresentHandling – Controls whether tester present messages are generated. When set to “Disabled” (0), no tester present task shall be started for the ECU even when a lock is held. When set to “Enabled” (1, default), tester present messages are generated normally.

  • CP_TesterPresentAddrMode – Addressing mode for tester present messages. When set to “Physical” (0, default), messages are sent to the ECU’s physical logical address. When set to “Functional” (1), messages are sent to the ECU’s functional logical address. The lock type takes precedence: functional group locks always use functional addressing regardless of this parameter.

  • CP_TesterPresentReqResp – Whether a UDS-level response is expected. When set to “No response expected” (0), the suppress-positive-response bit (sub-function 0x80) is set on the message and the task does not await a UDS-level response. When set to “Response expected” (1, default), the task awaits a response and validates it against the expected positive and negative response patterns.

  • CP_TesterPresentSendType – Sending strategy. When set to “Fixed periodic” (0), tester present messages are sent at the configured interval regardless of other bus activity. When set to “On idle” (1, default), tester present messages are sent only when no other diagnostic communication has occurred on the connection within the interval defined by CP_TesterPresentTime.

  • CP_TesterPresentMessage – The raw message bytes for the tester present request (default: [0x3E, 0x00]). When CP_TesterPresentReqResp indicates no response expected, the suppress-positive-response bit is OR-ed onto the sub-function byte (e.g., [0x3E, 0x00] becomes [0x3E, 0x80]).

  • CP_TesterPresentExpPosResp – Expected positive response bytes (default: [0x7E, 0x00]). Used to validate the ECU response when CP_TesterPresentReqResp indicates a response is expected.

  • CP_TesterPresentExpNegResp – Expected negative response prefix (default: [0x7F, 0x3E]). When a negative response is received, it is logged but does not cause the tester present task to stop.

Note

The current implementation uses only CP_TesterPresentTime at runtime. All other COM parameters are loaded from the database but are not yet evaluated. The implementation currently hardcodes the message as [0x3E, 0x80] (suppress positive response), uses fixed periodic sending, and always generates tester present when a lock is held.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "SOVD\nLock Manager" as LM
participant "CDA\n(UDS Layer)" as UDS
participant "DoIP\nTransport" as DoIP
participant "ECU" as ECU

LM -> UDS: acquire component lock (ECU)
UDS -> UDS: check_tester_present_active(ECU)
note right: No existing task found

UDS -> UDS: Start physical TP task\n(interval = CP_TesterPresentTime)
activate UDS #LightBlue

loop Every CP_TesterPresentTime
    UDS -> DoIP: [0x3E, 0x00] to ECU\nphysical address
    DoIP -> ECU: Tester Present
    ECU --> DoIP: [0x7E, 0x00]
    DoIP --> UDS: Tester Present Response
end

note over LM, ECU: This shows the typical flow.\nActual message content, timing, and response\nhandling depend on the CP_TesterPresent*\ncommunication parameters.

LM -> UDS: release component lock (ECU)
UDS -> UDS: Stop physical TP\ntask for ECU
deactivate UDS
@enduml

Tester Present – Component Lock

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "SOVD\nLock Manager" as LM
participant "CDA\n(UDS Layer)" as UDS
participant "DoIP\nTransport" as DoIP
participant "ECU A\n(Gateway)" as ECUA

LM -> UDS: acquire functional group lock
UDS -> UDS: Resolve group to\ngateway ECUs

UDS -> UDS: Start functional TP task\nfor ECU A (gateway)
activate UDS #LightGreen

loop Every CP_TesterPresentTime
    UDS -> DoIP: [0x3E, 0x80] to ECU A\nfunctional address
    DoIP -> ECUA: Tester Present
    ECUA --> DoIP: ACK
end

LM -> UDS: release functional group lock
UDS -> UDS: Stop functional TP\ntask for ECU A
deactivate UDS
@enduml

Tester Present – Functional Group Lock

3.5.2.5. Functional Communication

Software Architecture: UDS Functional Communication arch~uds-functional-communication
status: draft

The CDA supports functional group communication, where a single UDS request is sent to multiple ECUs simultaneously using functional addressing. ECUs are grouped by their gateway, and each gateway receives one functional request with responses collected from all ECUs behind it in parallel.

Functional Group Resolution

A functional group is resolved to its member ECUs from the diagnostic database. The following filters are applied:

  • Only physical ECUs are included (virtual/description ECUs are excluded).

  • Only ECUs that are currently online (detected during vehicle identification) are included.

Grouping by Gateway

ECUs in the functional group are grouped by their gateway logical address:

  • An ECU whose logical address equals its gateway address is the gateway ECU itself. It provides the UDS parameters, tester address, and functional address for its group.

  • ECUs whose logical address differs from their gateway address are placed behind the corresponding gateway.

Each gateway group produces one diagnostic request targeted at the gateway’s functional address (CP_DoIPLogicalFunctionalAddress).

Parallel Gateway Communication

When a functional group spans multiple gateways, the CDA sends to all gateways in parallel. For each gateway, the flow is:

  1. Construct a ServicePayload with the gateway’s tester address as source and the functional address as target.

  2. Send the diagnostic message once to the gateway via the DoIP transport layer.

  3. Wait for responses from all expected ECUs behind the gateway, in parallel.

Response Collection

After the gateway accepts the functional request (DoIP ACK), the DoIP transport layer demultiplexes incoming responses by source address. Each ECU behind the gateway has its own receive channel, allowing responses to be collected concurrently. ECUs that do not respond within CP_P6Max are reported as individual timeout errors.

No NRC Handling on Functional Path

Unlike physical (unicast) communication, the functional communication path does not implement UDS-level NRC 0x21/0x78/0x94 handling. NRC responses on the functional path are returned as-is to the caller.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "Caller" as Caller
participant "CDA\n(UDS Layer)" as UDS
participant "DoIP Transport\n(Gateway 1)" as GW1
participant "ECU A\n(behind GW1)" as ECUA
participant "ECU B\n(behind GW1)" as ECUB
participant "DoIP Transport\n(Gateway 2)" as GW2
participant "ECU C\n(behind GW2)" as ECUC

== Resolve Functional Group ==
Caller -> UDS: Functional group request
activate UDS
UDS -> UDS: Resolve group to\nonline ECUs
UDS -> UDS: Group ECUs by\ngateway address

== Parallel Send to Gateways ==
par Gateway 1
    UDS -> GW1: Diagnostic Message\n[functional_addr, UDS request]
    GW1 --> UDS: ACK

    par Collect Responses
        GW1 -> ECUA: Forward UDS request
        GW1 -> ECUB: Forward UDS request
        ECUA --> GW1: UDS response
        GW1 --> UDS: Response (ECU A)
        ECUB --> GW1: UDS response
        GW1 --> UDS: Response (ECU B)
    end

else Gateway 2
    UDS -> GW2: Diagnostic Message\n[functional_addr, UDS request]
    GW2 --> UDS: ACK

    GW2 -> ECUC: Forward UDS request
    ECUC --> GW2: UDS response
    GW2 --> UDS: Response (ECU C)
end

UDS --> Caller: Aggregated results\n{ECU A: response, ECU B: response,\nECU C: response}
deactivate UDS

note right of Caller: ECUs that do not respond\nwithin CP_P6Max are reported\nas individual timeouts
@enduml

UDS Functional Communication Flow

3.6. Diagnostic Tester

The Diagnostic Tester component provides the core functionality for communicating with vehicle ECUs using UDS (Unified Diagnostic Services) over DoIP (Diagnostics over IP). This document defines its architecture.

3.6.1. Startup Behavior

3.6.1.1. Startup Sequence

Software Architecture: Startup Sequence arch~dt-startup-sequence
status: draft
links incoming: req~dt-startup-sequence

The CDA startup is orchestrated by the main application entry point, which coordinates initialization of all subsystems in a defined order to ensure proper dependency resolution and graceful degradation on partial failures.

Component Initialization Order

The startup sequence proceeds through the following phases:

  1. Configuration Phase: Load configuration from TOML file, apply CLI argument overrides, and validate configuration sanity before proceeding. If the configuration file cannot be loaded (e.g., file not found, parse error), the system falls back to default configuration values and logs a warning. Configuration validation failures after loading are fatal and prevent startup.

  2. Tracing Phase: Initialize logging and tracing subsystems based on configuration (terminal output, file logging, OpenTelemetry, DLT).

  3. HTTP Server Phase: Launch the web server with a dynamic router that supports deferred route registration.

  4. Health Registration Phase (conditional, see Health Monitoring (arch~dt-health-monitoring) ): Register component-specific health providers (main, database, doip) to enable granular health status reporting. Health monitoring is an optional build-time feature. When the health feature is disabled, the CDA starts without health endpoints and providers, and all health-related registration steps are skipped. Health status is only retrievable through the health endpoint when this feature is enabled.

  5. Vehicle Data Loading Phase: Load diagnostic databases (MDD files) and, depending on the configured initialization mode, initialize the communication layer.

    Immediate mode (default):

    • Parallel MDD file loading

    • DoIP gateway creation (VIR/VAM exchange, TCP connections)

    • UDS manager creation

    • Asynchronous variant detection startup

    Deferred mode (see Deferred Initialization (arch~dt-deferred-initialization)):

    • Parallel MDD file loading proceeds as normal

    • DoIP gateway creation, UDS manager creation, and variant detection are not performed during startup. Instead, these steps are postponed until a trigger event occurs (first diagnostic request or explicit plugin API activation)

  6. Route Registration Phase: Register SOVD API routes, version endpoints, and OpenAPI documentation routes on the dynamic router. In deferred mode, ECU-specific routes are registered with handlers that trigger initialization on first access (on-demand mode) or return a pending status until explicitly activated (plugin API mode).

  7. Ready Phase: When the health feature is enabled, update the main health status to “Up” indicating the CDA’s HTTP API is operational. In deferred mode, the DoIP health provider remains in “Pending” state until communication initialization is triggered and completed (see Deferred Initialization (arch~dt-deferred-initialization)). When the health feature is disabled, this phase is a no-op.

Shutdown Signal Handling

A shareable shutdown signal is created and propagated to all long-running tasks. This enables coordinated shutdown when receiving SIGTERM or Ctrl+C at any startup phase, including during database loading and DoIP initialization.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "main()" as Main
participant "Configuration" as Config
participant "Tracing" as Trace
participant "HTTP Server" as HTTP
participant "Health State" as Health
participant "Database Loader" as DB
participant "DoIP Gateway" as DoIP
participant "UDS Manager" as UDS

Main -> Config: load_config()
activate Config
Config -> Config: validate_sanity()
Config --> Main: Configuration
deactivate Config

Main -> Trace: setup_tracing(config)
activate Trace
Trace --> Main: TracingGuards
deactivate Trace

Main -> HTTP: launch_webserver()
activate HTTP
HTTP --> Main: (DynamicRouter, ServerTask)
note right: Server running, no routes yet

opt Health feature enabled
    Main -> Health: add_health_routes()
    activate Health
    Health -> Health: register main provider (Starting)
    Health --> Main: HealthState
    deactivate Health
end

Main -> DB: load_databases()
activate DB
note right: See arch~dt-database-loading
DB --> Main: Databases loaded
deactivate DB

alt Immediate communication initialization (default)
    Main -> DoIP: create_diagnostic_gateway()
    activate DoIP
    note right: See arch~dt-doip-gateway-init
    DoIP --> Main: DoipDiagGateway
    deactivate DoIP

    Main -> UDS: create_uds_manager()
    activate UDS
    UDS -> UDS: spawn variant detection task
    UDS --> Main: UdsManager
    deactivate UDS
else Deferred communication (on-demand or plugin API)
    note over Main,UDS
        DoIP gateway creation, UDS manager
        creation, and variant detection are
        postponed until triggered
        (see arch~dt-deferred-initialization)
    end note
end

Main -> HTTP: add_vehicle_routes()
Main -> HTTP: add_static_data_endpoint() (version)
Main -> HTTP: add_openapi_routes()

opt Health feature enabled
    Main -> Health: update_status(Up)
    note right: HTTP API operational.\nDoIP remains "Pending"\nin deferred mode.
end

Main -> Main: await shutdown_signal
deactivate HTTP
@enduml

Startup Component Interaction

3.6.1.2. Database Loading

Software Architecture: Database Loading arch~dt-database-loading
status: draft
links incoming: req~dt-database-loading

Diagnostic databases (MDD files) are loaded in parallel to minimize startup time, with careful handling of duplicates and failures to ensure robust operation.

Note

Database loading always occurs during startup, regardless of the initialization mode. Even when deferred initialization is configured, MDD files are loaded immediately so that the SOVD API can expose ECU metadata (names, capabilities) before communication is established. Only the DoIP gateway creation and variant detection are deferred.

Parallel Loading Strategy

The database loader discovers all .mdd files in the configured directory and sorts them by file size in descending order. Files are then distributed into chunks for parallel processing. The chunk size is calculated as:

chunk_size = file_count / (parallel_load_tasks + 1)

The number of parallel load tasks is configurable. Processing larger files first ensures optimal utilization of parallel workers, as smaller files naturally fill remaining capacity.

Per-File Processing

For each MDD file, the loader:

  1. Extracts the diagnostic description chunk from the MDD container

  2. Creates a diagnostic database from the FlatBuffer payload

  3. Creates an ECU manager with protocol settings and communication parameters

  4. Extracts embedded file chunks (JAR files, partial files) for the file manager

Duplicate ECU Handling

When multiple MDD files define the same ECU name:

  • Same logical address: The database with the highest revision is retained; others are discarded with a warning log.

  • Different logical addresses: Both databases are marked as invalid and excluded from the final database map, as this represents an inconsistent configuration.

After loading, ECUs sharing the same logical address (from different database files with different ECU names) are identified and tracked for variant detection disambiguation.

Health Status Integration

When health monitoring is enabled (see Health Monitoring (arch~dt-health-monitoring)), a database health provider is registered with initial status “Starting”. After loading completes:

  • Status transitions to “Up” if at least one database was loaded successfully

  • Status transitions to “Failed” if no databases could be loaded

Failure Isolation

Individual MDD file loading failures are logged but do not prevent other files from loading. The loader continues processing all discovered files regardless of individual failures.

3.6.1.3. DoIP Gateway Initialization

Software Architecture: DoIP Gateway Initialization arch~dt-doip-gateway-init
status: draft
links incoming: req~dt-doip-gateway-init

The DoIP gateway establishes communication with vehicle DoIP entities through a discovery and connection establishment protocol defined in ISO 13400.

Note

When deferred initialization is configured (see Deferred Initialization (arch~dt-deferred-initialization)), the entire DoIP gateway initialization described below is postponed until a trigger event occurs. When health monitoring is enabled, the health provider for the DoIP component remains in “Pending” state until initialization is triggered.

Socket Creation

A UDP socket is created and bound to the configured tester address and gateway port. The socket is configured with:

  • Broadcast capability enabled

  • Address reuse enabled (and port reuse on Unix systems)

  • Non-blocking mode for async operation

Vehicle Identification

The gateway broadcasts a Vehicle Identification Request (VIR) to 255.255.255.255 on the configured gateway port. It then collects Vehicle Announcement Messages (VAM) from responding DoIP entities within a timeout window.

Subnet Filtering

VAM responses are filtered based on the configured subnet mask. Only responses from IP addresses within the tester’s subnet (determined by tester_address AND tester_subnet) are accepted. This prevents discovery of DoIP entities on unrelated networks.

Gateway-to-ECU Mapping

For each discovered gateway (identified by its logical address in the VAM), the system:

  1. Establishes a TCP connection to the gateway’s IP address

  2. Performs routing activation to enable diagnostic communication

  3. Creates send/receive channels for each ECU associated with that gateway

  4. Maps ECU logical addresses to their gateway connection index

Spontaneous VAM Listener

After initial discovery, a background task continuously listens for spontaneous VAM broadcasts. This handles scenarios where:

  • A gateway comes online after initial startup

  • An existing gateway reconnects after temporary disconnection

When a new VAM is received, the system establishes a connection (if not already connected) and triggers variant detection for the associated ECUs.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CDA" as CDA
participant "UDP Socket" as UDP
participant "Gateway 1" as GW1
participant "Gateway 2" as GW2
participant "TCP Connection" as TCP

note over CDA,TCP
    When deferred initialization is configured,
    this entire sequence is postponed until a
    trigger event (first request or plugin API call).
end note

== Discovery Phase ==
CDA -> UDP: create_socket(tester_ip, gateway_port)
CDA -> UDP: broadcast VIR to 255.255.255.255

UDP -> GW1: VIR
UDP -> GW2: VIR

GW1 --> UDP: VAM (logical_address=0x1010)
GW2 --> UDP: VAM (logical_address=0x2020)

CDA -> CDA: filter VAMs by subnet mask
CDA -> CDA: match VAM addresses to MDD databases

== Connection Phase (per gateway) ==
CDA -> TCP: connect(gateway_ip, port)
activate TCP
TCP --> CDA: connected

CDA -> TCP: Routing Activation Request
TCP --> CDA: Routing Activation Response
note right: Connection ready for diagnostics
deactivate TCP

== Continuous Listening ==
CDA -> UDP: listen_for_vams() (background task)
note right: Handle late/reconnecting gateways
@enduml

.. note:: In case of a TLS required activation response, the connection is reestablished with TLS enabled.

DoIP Gateway Discovery and Connection

3.6.1.4. Deferred Initialization

Software Architecture: Deferred Initialization arch~dt-deferred-initialization
status: draft

The CDA supports deferred initialization of ECU communication to enable scenarios where the HTTP API must be available before vehicle communication begins.

Dynamic Router Architecture

The HTTP server is launched with a dynamic router that supports adding routes after the server has started. This enables:

  1. Immediate availability of health endpoints during startup (when health feature is enabled)

  2. Deferred registration of SOVD API routes after ECU discovery

  3. Hot-reloading of routes when the diagnostic database is updated at runtime

Initialization Triggers

When deferred initialization is configured, DoIP gateway creation and ECU discovery are postponed until one of the following triggers:

  • On-demand: First diagnostic request to any ECU endpoint triggers initialization

  • Plugin API: A custom plugin calls the initialization API based on application-specific conditions (e.g., security unlock, session establishment)

Pre-initialization State

While initialization is deferred:

  • When health monitoring is enabled, health endpoints report status for available components (configuration, HTTP server)

  • ECU-specific endpoints return an appropriate status code indicating pending initialization

  • The dynamic router is prepared to receive SOVD routes once initialization completes

Initialization Sequence

Once triggered, initialization proceeds identically to the immediate initialization path: DoIP gateway creation, TCP connection establishment, UDS manager creation, and variant detection. Upon completion, SOVD routes are registered and, when health monitoring is enabled, health status transitions to “Up”.

3.6.1.5. Health Monitoring

Software Architecture: Health Monitoring arch~dt-health-monitoring
status: draft

Health monitoring is an optional build-time feature that provides an HTTP endpoint for querying the aggregate and per-component health status of the CDA. Health status is only retrievable through the health endpoint when this feature is enabled at build time.

Feature Enabled Behavior

When the health feature is enabled:

  1. During the HTTP Server Phase, health routes are registered on the dynamic router immediately after the server starts, making health status queryable before any SOVD API routes are available.

  2. During the Health Registration Phase, component-specific health providers are registered for each major subsystem (main, database, doip). Each provider reports granular status for its component.

  3. The health endpoint returns an aggregate status derived from all registered component providers:

    • Starting: At least one component is in Pending or Starting state

    • Up: All components have successfully initialized

    • Failed: At least one component has failed

  4. Health status transitions occur as components progress through their initialization lifecycle (see health status transitions below).

Feature Disabled Behavior

When the health feature is disabled at build time:

  • No health endpoints are registered on the HTTP server

  • No health providers are created for any component

  • Health status is not retrievable through any endpoint or API

  • All health-related registration steps in the startup sequence are skipped

  • The CDA operates normally without any health monitoring overhead

Component Health Providers

When enabled, the following component health providers are registered:

Health Providers

Component

Key

Failure Condition

Main

main

Fatal startup error

Database

database

No databases loaded

DoIP

doip

Gateway creation failed

Health Status Transitions

@startuml
skinparam backgroundColor #FFFFFF
skinparam stateArrowThickness 2

[*] --> Pending : Component registered\n(initialization not yet started)

Pending --> Starting : Initialization begins
Starting --> Up : Initialization successful
Starting --> Failed : Initialization failed

state Pending {
}

state Starting {
}

state Up {
}

state Failed {
}

note right of Pending
  Used for components whose initialization
  is deferred (e.g., DoIP gateway when
  deferred initialization is configured).
  Pending and Starting both contribute to
  an overall "Starting" aggregate status.
end note
@enduml

Component Health State Transitions

3.6.2. ECU Detection and Variant Detection

3.6.2.1. ECU Discovery

Software Architecture: ECU Discovery arch~dt-ecu-discovery
status: draft
links incoming: req~dt-ecu-discovery

ECU discovery establishes the mapping between diagnostic database definitions (MDD files) and physical DoIP communication endpoints.

Database-to-Gateway Mapping

During database loading, each ECU’s logical gateway address is extracted from the MDD. A mapping structure is built that associates each gateway logical address with the list of ECU logical addresses accessible through it.

VAM Matching

When a VAM is received, its logical address is matched against the ECU addresses from loaded databases. A match indicates that the ECU defined in the MDD is physically present and reachable through the responding gateway.

Connection Association

For discovered ECUs, the system maintains:

  • A mapping from ECU logical addresses to their gateway connection index

  • A list of active gateway connections with per-ECU send/receive channels

This structure enables routing diagnostic messages to the correct gateway and ECU.

ECU Name Mapping

A secondary mapping tracks ECU names to logical addresses for supporting SOVD API requests that reference ECUs by name rather than address. This associates each gateway logical address with the list of ECU names accessible through it.

Duplicate Address Detection

ECUs sharing the same logical address (from different MDD files with different ECU names) are tracked as potential duplicates. Each ECU manager stores references to other ECU names that share the same address. Variant detection determines which ECU definition is correct for the physical ECU.

3.6.2.2. Variant Detection

Software Architecture: Variant Detection arch~dt-variant-detection
status: draft
links incoming: req~dt-variant-detection

Variant detection identifies the correct ECU software variant from multiple possible definitions by querying the ECU and matching responses against defined patterns.

Detection Request Channel

A message channel connects the DoIP gateway to the UDS manager for variant detection coordination. When a VAM is received (either during startup or from spontaneous announcements), the gateway sends a list of ECU names requiring variant detection through this channel.

Asynchronous Detection

Variant detection runs asynchronously to avoid blocking startup. A dedicated task receives ECU names from the channel and spawns individual detection tasks per ECU. This enables parallel variant detection across multiple ECUs.

Detection Process

For each ECU requiring variant detection:

  1. Prepare: Extract the set of diagnostic services required for variant identification from the MDD variant patterns (services referenced in matching_parameter elements)

  2. Execute: Send each diagnostic service request to the ECU and collect responses

  3. Evaluate: Match response parameter values against variant patterns. A variant matches when all its matching_parameter conditions are satisfied (expected value equals received value for the specified output parameter)

  4. Update State: Set the ECU state based on detection result (Online, Offline, NoVariantDetected, or Duplicate)

Duplicate Resolution

When multiple ECU definitions share the same logical address, variant detection determines which definition matches the physical ECU. The matching ECU transitions to Online state; non-matching ECUs with the same address transition to Duplicate state and their databases are effectively disabled.

Fallback Behavior

When variant detection fails to find a matching pattern:

  • If fallback_to_base_variant is enabled: The ECU uses the base variant definition and transitions to NoVariantDetected state

  • If disabled: The ECU remains in NotTested state with an error logged

3.6.2.3. ECU States

Software Architecture: ECU States arch~dt-ecu-states
status: draft
links incoming: req~dt-ecu-states

ECU state management tracks the lifecycle of each ECU from registration through variant detection and ongoing communication.

States

The following states are maintained:

  • NotTested: Initial state after registration; variant detection has not yet been performed

  • Online: ECU is reachable and variant has been successfully detected

  • NoVariantDetected: ECU is reachable but no matching variant pattern was found

  • Duplicate: ECU shares its logical address with another ECU identified as the correct variant

  • Offline: ECU was tested but could not be reached; it has never been successfully online since registration or last re-detection

  • Disconnected: ECU was previously online but communication has been lost

The distinction between Offline and Disconnected reflects whether the ECU has ever been successfully communicated with. An ECU that fails its first contact attempt transitions to Offline; an ECU that was previously Online, NoVariantDetected, or Disconnected and loses communication transitions to Disconnected.

State Storage

ECU state is maintained within the ECU manager structure, which wraps the diagnostic database and adds runtime state information. The state is queryable through the SOVD API component endpoints.

State Transitions

State transitions are triggered by:

  • DoIP Events: VAM reception, connection establishment/loss, routing activation success/failure

  • Variant Detection: Detection success, failure, or duplicate identification

  • API Requests: Explicit re-detection requests via POST to ECU endpoint

  • Communication Errors: Timeout, NACK, or connection closure during diagnostic requests

Concurrent Access

ECU state is protected by a read-write lock to enable concurrent read access from multiple API handlers while ensuring exclusive write access during state transitions. The database map associates each ECU name with its concurrency-protected state manager.

State Query

The SOVD API exposes ECU state through the component collection endpoint. Clients can query individual ECU status or list all ECUs with their current states. The state is included in the component response to inform clients of ECU availability.

3.6.3. Error Handling

Software Architecture: Startup Error Handling arch~dt-error-handling
status: draft
links incoming: req~dt-error-handling

The CDA implements graceful degradation during startup to maximize availability even when individual components fail.

Error Type Hierarchy

Application errors are categorized through a structured error type hierarchy. The following error types are relevant during startup:

  • InitializationFailed: Critical startup failure (e.g., socket creation failed)

  • ConfigurationError: Invalid configuration (prevents startup)

  • ConnectionError: DoIP connection issues (per-gateway, non-fatal)

  • ResourceError: Database loading issues (per-file, non-fatal)

  • DataError: MDD parsing issues (per-file, non-fatal)

Additionally, the following error types may occur during runtime after startup has completed:

  • RuntimeError: Errors during diagnostic operations (e.g., UDS communication failures, variant detection errors)

  • NotFound: Requested resource (ECU, service, parameter) could not be found

  • ServerError: Internal server errors during request processing

Component Health Integration

When health monitoring is enabled (see Health Monitoring (arch~dt-health-monitoring)), component failures are reflected through health provider status transitions. Health providers and their status transitions are defined in the health monitoring architecture.

Graceful Degradation Behaviors

  • No databases loaded: Configurable via exit_no_database_loaded. When true, the application exits with an error. When false, the CDA continues with an empty ECU list.

  • Individual database failure: Logged and skipped; other databases continue loading.

  • DoIP connection failure: The affected gateway’s ECUs are marked as Offline; other gateways and ECUs remain operational.

  • Variant detection failure: ECU transitions to Offline (if unreachable) or NoVariantDetected state; diagnostic operations may still be attempted with base variant.

  • Deferred initialization failure: When deferred initialization is triggered (by first request or plugin API) and the subsequent DoIP gateway creation or UDS manager creation fails, the error is reported to the caller. When health monitoring is enabled, the DoIP health provider transitions to “Failed” state. The HTTP server and non-ECU endpoints remain operational. Subsequent trigger attempts may retry initialization.

  • Configuration file load failure: The system falls back to default configuration values and logs a warning. Startup continues with defaults, which may be overridden by CLI arguments.

  • Configuration validation failure: Startup is aborted with a descriptive error message.

Shutdown Handling

Shutdown signals (SIGTERM, Ctrl+C) are handled gracefully at any startup phase:

  • During database loading: Loading tasks are aborted and the process exits

  • During DoIP initialization: Connections are not established and the process exits

  • During deferred initialization: If initialization was triggered but not yet complete, in-progress connections are aborted and the process exits

  • After full initialization: The HTTP server completes pending requests before shutdown

All shutdown paths ensure resources are properly released through structured cleanup and tracing guards that flush logs on drop.

3.7. Plugins

3.7.1. Overview

The plugin system in the Classic Diagnostic Adapter (CDA) provides extensibility for vendor-specific functionality that cannot be standardized across all implementations. Plugins enable customization of security mechanisms, authentication flows, and other domain-specific requirements while maintaining the core diagnostic functionality.

The plugin architecture is designed around trait-based interfaces that allow runtime polymorphism and flexible configuration. This approach ensures that the CDA can adapt to different deployment environments and vendor requirements without requiring modifications to the core codebase.

3.7.2. Security Plugin Architecture

The security plugin system is the primary plugin implementation within the CDA, responsible for authentication, authorization, and access control for REST calls.

Core Traits

The security plugin system is built around several key traits that define the plugin interface:

SecurityPlugin

The main trait that combines authentication and authorization capabilities:

pub trait SecurityPlugin: Any + SecurityApi + AuthApi {
    fn as_auth_plugin(&self) -> &dyn AuthApi;
    fn as_security_plugin(&self) -> &dyn SecurityApi;
}

AuthApi

Provides access to user claims:

pub trait AuthApi: Send + Sync + 'static {
    fn claims(&self) -> Box<&dyn Claims>;
}

SecurityApi

Validates diagnostic service requests based on security policies:

pub trait SecurityApi: Send + Sync + 'static {
    fn validate_service(&self, service: &DiagnosticService) -> Result<(), DiagServiceError>;
}

SecurityPluginLoader

Combines initialization and authorization request handling capabilities:

pub trait SecurityPluginLoader:
    SecurityPluginInitializer + AuthorizationRequestHandler + Default + 'static
{
}

3.7.3. Plugin Lifecycle

The security plugin follows a specific lifecycle during request processing:

  1. Middleware Registration: The security plugin middleware is registered during router setup

  2. Request Interception: Each incoming request passes through the security middleware

  3. Plugin Initialization: The plugin extracts authentication information from request headers and creates the plugin instance

  4. Request Processing: The initialized plugin instance is made available to route handlers

  5. Service Validation: Diagnostic services are validated against security policies before execution

3.7.4. Future Extensions

The plugin architecture is designed to support additional plugin types:

3.7.4.1. Logging Plugins

  • Custom log formatting and destinations

  • Integration with external logging systems

  • Performance metrics collection

3.7.4.2. Safety Plugins

  • Functional safety compliance validation

  • Diagnostic session safety checks

  • Emergency shutdown procedures

3.7.4.3. Custom Endpoint Plugins

  • Vendor-specific API extensions

  • Additional data formats and protocols

  • Integration with external systems

3.7.5. Diagnostic Database Update Plugin

Software Architecture: Diagnostic Database Update Plugin arch~plugin-diagnostic-database-update

Endpoints and Security

By default, all modifying actions to any endpoint require an exclusive vehicle lock. It must be ensured, that no diagnostic operations are in progress, when the “Apply” action is triggered - this includes functional and component locks.

Only the subject of the lock is allowed to use the endpoints. This ensures that the database isn’t used while it is being updated, and that no 3rd party could add additional files to the update while it is being prepared, which could lead to security issues.

This behavior and additional security requirements must be modifiable through a trait provided to the plugin, to support more specific OEM requirements for security and individual environments during the update process.

The diagnostic database update plugin must provide the following bulk-data categories/endpoints:

Bulk-Data Paths for Diagnostic Database Update Preparation

Method

Path

Description

GET

/apps/sovd2uds/bulk-data/runtimefiles-current

Return a list of items in the currently active diagnostic database.

GET

/apps/sovd2uds/bulk-data/runtimefiles-nextupdate

Returns a list of the next update of the diagnostic database. Initially it shows the existing diagnostic database, and applies all pending updates to it, to show the state of the diagnostic database after applying the pending updates.

POST

/apps/sovd2uds/bulk-data/runtimefiles-nextupdate

Adds files to the next update of the diagnostic database, using multipart form data. The files provided through this endpoint are added to the pending update.

DELETE

/apps/sovd2uds/bulk-data/runtimefiles-nextupdate

Removes all pending changes to the next update of the diagnostic database, to reset the state of the next update to the currently active database.

DELETE

/apps/sovd2uds/bulk-data/runtimefiles-nextupdate/{id}

Deletes the file from the pending update - in case of a file that was previously part of the current database, it’ll be deleted in the current database upon applying the next update.

GET

/apps/sovd2uds/bulk-data/runtimefiles-backup

Returns a list of items of the previously used diagnostic database, which can be used to roll back the diagnostic database in case of issues.

DELETE

/apps/sovd2uds/bulk-data/runtimefiles-backup

Deletes the backup of the previously used diagnostic database, to free up storage space. This also means that rolling back to the previous state isn’t possible anymore after deleting the backup.

Note

The following query parameters must be supported for the GET endpoints:

  • x-sovd2uds-include-hash (string, default: not present – supported is only sha256) - to include file hashes of the files

  • x-sovd2uds-include-file-size (boolean, default: false) - to include file sizes of the files

  • x-sovd2uds-include-revision (boolean, default: false) - to include the revision inside the files

Limitations to bulk-data operations

For Security reasons, none of the endpoints should allow retrieval of the files by default - there may be an option to enable it. Adding or deleting files must only be allowed in the runtimefiles-nextupdate category, and not for the runtimefiles-backup or runtimefiles-current category, to avoid security issues, and to ensure consistency of the backup and current state of the diagnostic database.

File Handling

The id for the files within the diagnostic database update plugin must be the file name, to ensure consistency when files are overwritten, deleted, or added.

File names must be handled case-insensitively on all operating systems to make usage regardless of OS consistent, to avoid duplicated entries, and to allow case-insensitive paths for deletion.

There must be an option to normalize file names to the name of the ECU they belong to, to ensure consistency and to avoid duplicated entries for the same ECU with different file names.

Files must be verifiable through a trait provided to the plugin before being applied as the new current state.

The verification includes, but is not limited to, signature verification, hash verification, and version checks of the currently active database, as well as the new one.

Application of the update

To delete all pending updates from runtimefiles-nextupdate, or to delete the backup in runtimefiles-backup DELETE on the respective bulk-data endpoint must be supported.

To apply all the pending updates to the current diagnostic database, an additional endpoint is required:

POST /apps/sovd2uds/bulk-data/runtimefiles-nextupdate/executions with a JSON-payload containing the property mode, with the following possible values (all case-insensitive):

  • Apply - to apply the pending updates.

  • Rollback - to roll back to the backup state of the diagnostic database (also clears pending nextupdate)

  • Cleanup - to reset all pending updates, as well as deleting the backup

The same endpoint must also be made available as /apps/sovd2uds/operations/diagnostic-database-update to allow triggering the actions through a standard compliant operation.

After applying, or rolling back the diagnostic database, the new database must be active immediately, without requiring a restart of the CDA, and the old state must be available as a backup until the next update is applied, the backup is deleted, or a cleanup is initiated. The state of nextupdate must also be reset after applying or rolling back, to ensure that pending updates aren’t reapplied unintentionally after a rollback, and to ensure that the state of the next update is consistent with the currently active database.

Atomicity

Every action must be atomically applied, meaning that if any part of the action fails, the entire action must be rolled back, and the state of the diagnostic database while the adapter is running must be consistent with either the state before the action, or the state after the action, but not a partially applied state.

This also applies to power cycles and crashes during the application of the update, to ensure this, journaling and transactional file handling can be used, but the exact mechanism is up to the implementation of the plugin. This may require flushing filesystem caches frequently to guarantee consistency.

3.7.6. DLT Logging Plugin

Software Architecture: DLT Logging arch~plugin-dlt-logging

The DLT logging integration adds an optional tracing output that forwards application log and trace events to the AUTOSAR Diagnostic Log and Trace (DLT) daemon via the DLT system library.

Integration into the Tracing Stack

The DLT output is realized as an additional subscriber layer within the existing layered tracing architecture. The tracing system composes multiple output layers (terminal, file, OpenTelemetry, DLT) into a single subscriber. When DLT is enabled, its layer receives the same tracing events as all other layers and translates them into DLT log messages.

@startuml
skinparam componentStyle rectangle

component "Application Code" as app
component "Tracing Subscriber Registry" as registry
component "Terminal Layer" as term
component "File Layer" as file
component "OpenTelemetry Layer" as otel
component "DLT Layer" as dlt
component "DLT System Library" as libdlt
component "DLT Daemon" as daemon

app -down-> registry : tracing events
registry -down-> term
registry -down-> file
registry -down-> otel
registry -down-> dlt
dlt -down-> libdlt : FFI
libdlt -down-> daemon : IPC
@enduml

Compile-Time Feature Gating

The entire DLT integration is guarded by a compile-time feature flag. When the feature is not active:

  • The DLT system library is not linked.

  • The DLT subscriber layer is not compiled.

  • Context annotation macros evaluate to no-ops that the compiler optimizes away, resulting in zero runtime overhead.

When the feature is active, the DLT layer is only added to the tracing subscriber if the runtime configuration also enables it.

Runtime Enablement

At startup, the tracing initialization checks the DLT configuration. If DLT support is compiled in but disabled in the configuration, the DLT layer is not registered, and no connection to the DLT daemon is established.

Log Level Mapping

Application trace levels are mapped to their DLT equivalents by the DLT subscriber layer, ensuring that severity-based filtering in DLT tooling works as expected.

Software Architecture: DLT Logging - Configuration arch~plugin-dlt-logging-configuration
status: draft

The DLT logging configuration is part of the application-wide logging configuration and is deserialized from the configuration file.

The configuration contains the following parameters:

DLT Logging Configuration Parameters

Parameter

Description

Default

Application ID

A short identifier (max 4 ASCII characters) registered with the DLT daemon to identify the application.

CDA

Application Description

A human-readable description (max 256 ASCII characters) registered with the DLT daemon.

Bridges SOVD to UDS for ECU communication.

Enabled

A boolean toggle that controls whether the DLT layer is registered at startup.

true

The application ID is validated against DLT protocol constraints during initialization. An invalid application ID (e.g. exceeding 4 characters) prevents the DLT layer from being created and results in a startup error.

Software Architecture: DLT Logging - Context Annotation arch~plugin-dlt-logging-context-annotation
status: draft

Each subsystem of the CDA annotates its tracing spans with a DLT context identifier. The DLT subscriber layer reads the context identifier from the span metadata and uses it to route the log message to the appropriate DLT context.

A helper macro is provided to annotate tracing spans with context identifiers. The macro is feature-gated:

  • When DLT support is compiled in, the macro expands to the context identifier string, which is attached to the span as metadata.

  • When DLT support is not compiled in, the macro expands to a no-op value that the tracing framework discards, ensuring that no DLT-related metadata is recorded.

The following context identifiers are used by the CDA subsystems:

DLT Context Identifiers

Context ID

Subsystem

MAIN

Application entry point and initialization

CORE

Diagnostic kernel (ECU management, variant detection, schema handling)

DOIP

DoIP communication layer

UDS

UDS protocol layer

DB

Diagnostic database loading and parsing

SOVD

SOVD web server layer