3.4.2. CAN Communication

The CAN gateway transports UDS messages over ISO-TP (ISO 15765-2). It supports physical UDS communication with ECUs using SocketCAN ISO-TP sockets. The can feature is required; on non-Linux systems it additionally requires the can-socketcand feature.

3.4.2.1. Addressing And Configuration

Software Architecture: CAN Addressing And Configuration arch~can-addressing-configuration
status: draft

Each physical ECU connection requires a request CAN ID (tester to ECU) and a response CAN ID (ECU to tester). The gateway accepts 11-bit standard IDs and 29-bit extended IDs. Addressing is resolved in this order:

  1. A [[can.ecu_mappings]] configuration entry for the ECU.

  2. The ECU MDD communication parameters CP_CanPhysReqId and CP_CanRespUSDTId. The MDD CP_UniqueRespIdTable takes precedence when it contains the corresponding values.

Explicit configuration overrides MDD values. An ECU with no complete request/response pair has no CAN connection. The gateway rejects unusable configuration, including an empty usable mapping set, out-of-range IDs, and IDs reserved for the functional TesterPresent broadcast.

3.4.2.2. Physical ISO-TP Exchange

Software Architecture: CAN Physical ISO-TP Exchange arch~can-physical-isotp-exchange
status: draft

The gateway opens a fresh ISO-TP socket for each physical request. Its receive ID is the ECU response ID and its transmit ID is the ECU request ID. TX padding is enabled with 0x00 so the gateway can communicate with ECUs that require padded eight-byte CAN frames. The socket stays open for the entire exchange, which is required to receive a final response after an NRC 0x78.

The CAN bus is shared. Before forwarding a terminal response, the gateway verifies that its response SID belongs to the sent request. Frames that do not match are discarded and do not extend the receive deadline. This avoids accepting unrelated traffic, including a physical negative response to a functional TesterPresent broadcast.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "UDS Layer" as UDS
participant "CAN Gateway" as CAN
participant "ISO-TP Socket\n(request ID / response ID)" as ISOTP
participant "ECU" as ECU

UDS -> CAN: ServicePayload
CAN -> ISOTP: Open socket and send UDS request
ISOTP -> ECU: ISO-TP request

loop Until terminal response or deadline
    ECU --> ISOTP: ISO-TP response
    ISOTP --> CAN: Reassembled UDS bytes

    alt Pending NRC (7F SID 78 / 21 / 94)
        CAN -> CAN: Extend transport deadline
        CAN --> UDS: TransportResponse::Pending
    else Matching final response
        CAN --> UDS: TransportResponse::UdsResponse
        CAN -> ISOTP: Drop socket
    else Unrelated response
        CAN -> CAN: Discard and continue reading
    end
end
@enduml

CAN Physical UDS Exchange

3.4.2.3. NRC Classification

Software Architecture: CAN NRC Classification arch~can-nrc-classification
status: draft

The CAN gateway classifies every incoming UDS response through the shared pending_nrc_from_raw() helper from cda-interfaces/src/uds.rs, identically to the DoIP transport. The classification happens inside exchange.read_response() (cda-comm-can/src/gateway/mod.rs), before request-SID filtering.

The helper recognizes three pending-lifecycle NRCs – 0x78 (Response Pending), 0x21 (Busy Repeat Request), 0x94 (Temporarily Not Available) – and returns a TransportResponse::Pending(PendingNrc) variant. For all other responses (positive or non-pending negative), it returns None and the gateway wraps them with uds_response_from_raw() into a terminal TransportResponse::UdsResponse.

When a pending NRC arrives, the gateway extends the ISO-TP socket receive deadline (can.response_timeout_ms) from the current time and retains the socket. This allows the ECU to send the final response within the extended window. The UDS layer owns the retry policy: it continues waiting after 0x78 and retransmits after 0x21 or 0x94 per the configured CP_RC*Handling parameters (see UDS NRC Handling (arch~uds-nrc-handling)).

@startuml
skinparam backgroundColor #FFFFFF
skinparam componentStyle rectangle

title "CAN NRC Classification Flow"

package "ISO-TP Socket" as ISOTP {
  (Raw CAN frames\nreassembled into\nUDS bytes) as Raw
}

package "mod.rs: exchange.read_response()" as ReadResp {
  (pending_nrc_from_raw?) as NrcCheck
  (Match SID to\nsent request) as SidMatch
  (uds_response_from_raw) as FinalWrap
}

package "TransportResponse" as RespPkg {
  (TransportResponse::Pending\n{PendingNrc}) as Pending
  (TransportResponse::UdsResponse\n{ServicePayload}) as Final
}

package "ISO-TP Deadline" as Deadline {
  (Extend receive\ndeadline on\npending NRC) as Extend
}

Raw --> NrcCheck : Reassembled UDS bytes

NrcCheck --> Extend : [YES]\n0x78 / 0x21 / 0x94
NrcCheck --> SidMatch : [NO]

Extend --> Pending : TransportResponse::Pending
SidMatch --> FinalWrap : [matching SID]
SidMatch --> Raw : [unrelated SID]\ndiscard and continue

FinalWrap --> Final : TransportResponse::UdsResponse

note bottom of NrcCheck
    Shared classification using
    pending_nrc_from_raw() from
    cda-interfaces/src/uds.rs.
    Identical logic for both
    CAN and DoIP transports.
end note

note bottom of SidMatch
    CAN verifies that the
    response SID belongs to
    the sent request. Frames
    from unrelated ECUs or
    functional broadcasts
    are discarded.
end note

note bottom of Extend
    The gateway restarts the
    can.response_timeout_ms
    deadline from the current
    time, keeping the ISO-TP
    socket open for the ECU's
    final response.
end note

note bottom of Final
    Carries all non-pending
    responses: positive (SID | 0x40)
    and non-pending NRCs (0x22,
    0x31, 0x33, ...).
end note
@enduml

CAN NRC Classification Flow

The classification functions are defined in cda-interfaces/src/uds.rs:

  • pending_nrc_from_raw(data, source_address) – recognises 0x78/0x21/0x94 and returns the corresponding PendingNrc variant; returns None for all other responses

  • uds_response_from_raw(data, source_address, target_address) – wraps any remaining response (positive or non-pending negative) into a ServicePayload

Unlike the DoIP transport, CAN does not intercept TesterPresent NRCs (0x7F 0x3E xx) at the transport level. The functional TesterPresent broadcast uses a separate path and its NRC responses are logged at debug level by the keep-alive task.

3.4.2.4. Discovery And Keep-Alive

Software Architecture: CAN Discovery And Keep-Alive arch~can-discovery-keepalive
status: draft

CAN has no connection-establishment event. The gateway probes every configured ECU at startup and marks it discovered when any non-empty response is received, including a negative response. The default probe is physical TesterPresent (3E 00). Optional fallback probes can be configured for ECUs that do not answer this request.

Undiscovered ECUs are re-probed sequentially every five seconds. A physical request with no received frame for at least the probe timeout also removes that ECU from the discovered set, allowing the rediscovery task to recover it after wake-up or reboot. An on-demand probe is also made when a caller checks a currently undiscovered ECU.

Independently, the gateway can send TesterPresent with the suppressPositiveResponse bit (3E 80) on the functional broadcast ID to keep ECUs awake. It uses CP_CanFuncReqId when available and otherwise 0x7DF. This broadcast is a keep-alive only; CAN functional diagnostic requests are not implemented.

@startuml
skinparam backgroundColor #FFFFFF
skinparam sequenceArrowThickness 2

participant "CAN Gateway" as CAN
participant "Configured ECU" as ECU
participant "Variant Detection" as Variant

== Startup ==
CAN -> ECU: Physical TesterPresent (3E 00)
alt ECU answers
    ECU --> CAN: Any non-empty UDS response
    CAN -> Variant: Report discovered ECU
else No response
    CAN -> CAN: Mark ECU undiscovered
end

== Background Rediscovery ==
loop Every 5 seconds, undiscovered ECUs only
    CAN -> ECU: Configured probe sequence
    alt ECU answers
        ECU --> CAN: Any non-empty UDS response
        CAN -> Variant: Report rediscovered ECU
    end
end
@enduml

CAN Discovery And Rediscovery

3.4.2.5. Functional Communication Limitation

Note

Functional transport for CAN is currently not supported. Tracked via https://github.com/eclipse-opensovd/classic-diagnostic-adapter/issues/417