# DoIP — ISO 13400 (vendor extension)

> **Library 2.0.0.213** · SAE J2534 v04.04 API

DoIP (Diagnostics over IP) is **not part of the SAE J2534 standard**. The
library exposes it as a vendor extension: protocol `ISO13400_PS` (0x8FFD) plus
four IOCTLs. The adapter handles the DoIP session (UDP discovery, TCP
connection, routing activation, NACK/keep-alive logic); your application
exchanges plain UDS payloads through the usual
`PassThruWriteMsgs`/`PassThruReadMsgs`.

The vehicle is reached through the adapter's Ethernet path (e.g. the OBD-II
connector's Ethernet pins with activation line). Physical-layer bring-up
(link, DHCP) happens on the adapter when the channel connects.

## Session flow

```text
PassThruOpen
  └─ PassThruConnect(device_id, ISO13400_PS, 0, 0, &channel_id)
       ├─ PassThruIoctl(ch, ISO13400_DISCOVER_VEHICLES, NULL, NULL)   ; UDP broadcast, ~2 s
       ├─ PassThruIoctl(ch, ISO13400_GET_VEHICLE_INFO, NULL, &arr)    ; list of ECUs found
       ├─ PassThruIoctl(ch, SET_CONFIG, {SA, ECU IP, port, activation type})
       ├─ PassThruIoctl(ch, ISO13400_CONNECT_TCP, NULL, NULL)         ; TCP to ECU
       ├─ PassThruIoctl(ch, ISO13400_ACTIVATE_ROUTING, NULL, &code)   ; 0x10 = success
       ├─ PassThruWriteMsgs / PassThruReadMsgs                        ; UDS
       └─ PassThruDisconnect
PassThruClose
```

Discovery is optional — if the ECU IP and logical address are known in
advance, go straight to `SET_CONFIG` → `CONNECT_TCP` → `ACTIVATE_ROUTING`.

## IOCTLs

| IOCTL | ID | input | output |
|-------|----|-------|--------|
| `ISO13400_DISCOVER_VEHICLES` | 0x8110 | NULL | NULL |
| `ISO13400_GET_VEHICLE_INFO` | 0x8111 | NULL | `pt_byte_array_t*` |
| `ISO13400_CONNECT_TCP` | 0x8112 | NULL | NULL |
| `ISO13400_ACTIVATE_ROUTING` | 0x8113 | NULL | `uint32_t*` — routing activation response code |

Timing: `DISCOVER_VEHICLES` blocks ~2 s for responses. On a freshly connected
channel the Ethernet link may still be coming up (activation line, link
negotiation, ECU DHCP) — the first `DISCOVER_VEHICLES`/`CONNECT_TCP` can take
up to ~15 s. Use generous timeouts and treat one retry as normal.

`ISO13400_ACTIVATE_ROUTING` writes the Routing Activation response code to
`output`: `0x10` = success, other values are denial reasons per
ISO 13400-2 Table 25.

### GET_VEHICLE_INFO result

`output` is a `pt_byte_array_t`: set `num_of_bytes` to the buffer size before
the call; on return it holds the actual data length. The buffer contains an
array of 40-byte entries:

```c
typedef struct {
    char     vin[18];        /* VIN, null-terminated, offset 0 */
    uint16_t logical_addr;   /* DoIP entity logical address, offset 18 */
    uint8_t  eid[6];         /* Entity ID (MAC), offset 20 */
    uint8_t  gid[6];         /* Group ID, offset 26 */
    uint8_t  further_action; /* offset 32 */
    uint8_t  sync_status;    /* offset 33, 2 bytes padding follow */
    uint32_t ip_addr;        /* entity IPv4, host byte order, offset 36 */
} iso13400_vehicle_info_t;   /* sizeof = 40 */
```

## SET_CONFIG parameters

| Parameter | ID | Description |
|-----------|----|-------------|
| `ISO13400_SOURCE_ADDR` | 0x8100 | Tester logical address (SA), e.g. 0x0E80 |
| `ISO13400_ECU_IP_ADDR` | 0x8102 | ECU/gateway IPv4 as `uint32` (host order, `192.168.12.4` → `0xC0A80C04`) |
| `ISO13400_ECU_TCP_PORT` | 0x8103 | ECU TCP port (default 13400) |
| `ISO13400_T_TCP_INITIAL` | 0x8104 | Initial inactivity timeout |
| `ISO13400_T_TCP_GENERAL` | 0x8105 | General inactivity timeout |
| `ISO13400_T_DIAG_MSG` | 0x8106 | Diagnostic message timeout (P2-equivalent) |
| `ISO13400_ACTIVATION_TYPE` | 0x8107 | Routing activation type (0 = default) |
| `ISO13400_P3_DOIP` | 0x8108 | Inter-message pause, ms (delay after a response before the next TX) |

Do **not** set the ECU logical (target) address via `SET_CONFIG` — the target
address travels in every message (below).

## Message format

`pt_msg_t.data` on an `ISO13400_PS` channel carries a 4-byte logical address
header followed by the UDS payload:

```text
TX:  [TA_hi][TA_lo][SA_hi][SA_lo][UDS request ...]
RX:  [TA_hi][TA_lo][SA_hi][SA_lo][UDS response ...]
```

`rx_status` flags: `TX_MSG_TYPE` — TX confirmation (echo), skip it;
`CONNECTION_LOST` — the ECU closed the DoIP session.

### Example: read VIN (UDS 22 F1 90)

```c
pt_msg_t tx = {0};
tx.protocol_id = ISO13400_PS;
tx.data_size = 7;
tx.data[0] = ta >> 8;  tx.data[1] = ta & 0xFF;   /* ECU logical address  */
tx.data[2] = sa >> 8;  tx.data[3] = sa & 0xFF;   /* tester address       */
tx.data[4] = 0x22; tx.data[5] = 0xF1; tx.data[6] = 0x90;
uint32_t num = 1;
PassThruWriteMsgs(channel_id, &tx, &num, 2000);

pt_msg_t rx = {0};
num = 1;
PassThruReadMsgs(channel_id, &rx, &num, 2000);
/* skip rx_status & TX_MSG_TYPE; positive response: data[4..6] = 62 F1 90,
   data[7..23] = VIN (17 bytes) */
```

## Android note

Through the Kotlin JNI wrapper, `ptIoctl(chnlId, ISO13400_GET_VEHICLE_INFO, 0,
null)` returns the data in `ResIoctl.output` with a different framing: bytes
`[0..3]` hold the actual data length (little-endian), the 40-byte entries
start at offset 4. `ISO13400_ACTIVATE_ROUTING` returns the response code in
`output[0..3]` (little-endian `uint32`). See the demo app's
`connectVehicleDoIPNative()` for a complete working example.

## Raw-socket alternative

Because DoIP is plain TCP/UDP, an application may also implement ISO 13400
framing itself and talk to the ECU directly over a socket, without the J2534
library (the vehicle network must then be reachable from the host OS). The
Android demo app shows both approaches side by side.

## HSFZ (BMW)

BMW's HSFZ protocol is exposed the same way: protocol `HSFZ_PS` with IOCTLs
`HSFZ_DISCOVER_VEHICLES`, `HSFZ_GET_VEHICLE_INFO`, `HSFZ_CONNECT_TCP` and the
same per-message addressing. Contact support for details if you need HSFZ.
