Getting Started with CAN Bus Reverse Engineering on Linux
Modern vehicles are essentially networks on wheels. A typical car manufactured after 2008 contains anywhere from 20 to 100 Electronic Control Units (ECUs) — engine management, transmission, ABS, airbags, infotainment, door modules, climate control — all communicating over internal bus protocols. The dominant protocol is CAN bus (Controller Area Network), a robust, differential-signalling serial bus originally developed by Robert Bosch GmbH starting in 1983 (released publicly in 1986) for automotive applications.
For anyone involved in automotive security research, penetration testing of vehicle systems, or even custom ECU development, the ability to sniff and inject CAN frames is the fundamental skill. It is the equivalent of being able to run Wireshark on a network — except the network is inside a car, and the packets control the brakes, steering, and powertrain.
This article is the first in a series on automotive reverse engineering. We will cover:
- What CAN bus is and how it relates to the OBD2 port in your car
- How to choose the right CAN adapter for Linux (on a budget)
- Setting up a CANable adapter with Linux SocketCAN
- Sniffing live CAN traffic from a vehicle
- Writing a Python script to send and receive CAN frames
The goal: by the end of this article, you will have a fully working Linux-based CAN bus sniffing and injection setup that connects to any vehicle’s OBD2 port for under £30.
CAN Bus & OBD2 Primer
What is CAN Bus?
Controller Area Network (CAN) is a message-based protocol designed for reliable communication in noisy electrical environments. It uses a twisted-pair differential signal (CAN-High and CAN-Low) that is highly resistant to electromagnetic interference — critical in a vehicle where ignition coils, alternators, and electric motors generate significant noise.
Key characteristics:
| Property | Value |
|---|---|
| Physical layer | Twisted pair, differential signalling (ISO 11898) |
| Topology | Multicast bus (all nodes receive all messages) |
| Max speed | 1 Mbit/s (CAN 2.0), 8 Mbit/s (CAN FD) |
| Frame size | Up to 8 data bytes (CAN 2.0), 64 bytes (CAN FD) |
| Arbitration | Non-destructive bitwise (lower ID = higher priority) |
| Error handling | CRC, bit monitoring, stuff bit check, ACK |
CAN frames have a simple structure:
┌──────────┬──────────┬─────────┬───────────────────┬───────┬──────┐
│ SOF (1b) │ ID (11b) │ RTR(1b) │ Data (0-8 bytes) │ CRC │ ACK │
│ │ or 29b │ IDE(1b) │ DLC (4b) + payload │ 15b │ 1b │
└──────────┴──────────┴─────────┴───────────────────┴───────┴──────┘
SOF = Start of Frame
ID = Arbitration ID (message identifier, also determines priority)
RTR = Remote Transmission Request
IDE = Identifier Extension (standard 11-bit vs extended 29-bit)
DLC = Data Length Code (how many data bytes follow)
CRC = Cyclic Redundancy Check
ACK = Acknowledgement slot
Every node on the bus sees every frame. There is no “source address” or “destination address” in the traditional sense — the arbitration ID identifies the type of message, not the sender or receiver. ECUs filter frames by ID to determine which ones they care about.
The OBD2 Port
The On-Board Diagnostics (OBD2) connector is a standardised 16-pin interface mandated in all vehicles sold in the US since 1996 (EU since 2001 for petrol, 2004 for diesel). It is usually located under the dashboard, near the steering column.
For CAN bus access, the pins we care about are:
| Pin | Signal | Description |
|---|---|---|
| 6 | CAN-High | Positive differential line |
| 14 | CAN-Low | Negative differential line |
| 16 | +12V | Battery power (always on) |
| 4, 5 | GND | Chassis and signal ground |
Important: Not all pins in the OBD2 port are CAN bus. The port carries multiple protocols (ISO 9141, KWP2000, SAE J1850) on different pins, depending on the vehicle. Most vehicles from ~2008 onwards use CAN (ISO 15765-4) as the primary diagnostic protocol, but always verify.
Common CAN Bus Speeds
Different vehicle manufacturers use different CAN bus baud rates. The most common:
| Vehicle Type | Typical Speed | Notes |
|---|---|---|
| Most passenger cars | 500 kbit/s | Standard for diagnostics (OBD2) |
| Ford (HS-CAN) | 500 kbit/s | High-speed bus |
| Ford (MS-CAN) | 125 kbit/s | Medium-speed bus (body/interior) |
| Toyota | 500 kbit/s | Diagnostics bus |
| VAG (VW/Audi/Skoda/SEAT) | 500 kbit/s | All buses typically 500k |
| GM | 500 kbit/s | High-speed bus |
| Motorcycles | 500 kbit/s or 250 kbit/s | Varies by manufacturer |
| Heavy vehicles (J1939) | 250 kbit/s | Trucks, buses, construction |
If you do not know the baud rate, start at 500 kbit/s. If candump shows nothing (or errors), try 250 kbit/s. We will cover auto-detection later.
Hardware Selection: Choosing a CAN Adapter
There are dozens of CAN bus adapters on the market. They range from £4 ELM327 clones to £2,200+ Vector CANalyzer setups. For reverse engineering work on Linux, we need an adapter that supports raw CAN frame access (not just OBD2 protocol translation) and integrates with SocketCAN — the Linux kernel’s native CAN subsystem.
Why Not ELM327?
The £4-11 ELM327 USB adapter is tempting — it is ubiquitous and cheap. However, the ELM327 is a protocol translator, not a CAN interface. It speaks OBD2 protocols (ISO 15765-4) internally, and while it does have AT commands for raw CAN access (AT MA for monitor all, AT SH for setting header), it suffers from critical limitations:
- Slow serial interface (38,400-115,200 baud) — cannot keep up with busy CAN buses
- Small internal buffer — drops frames under load
- No accurate timestamps
- Limited injection capability — cannot send arbitrary CAN frames reliably
- Inconsistent clone quality — cheap clones vary wildly in behaviour
An ELM327 is fine for reading OBD2 PIDs (RPM, speed, DTCs). It is not suitable for CAN bus sniffing and reverse engineering.
Tool Comparison
| Adapter | Price (GBP) | Linux Support | Interface | CAN FD | K-Line | Isolation | Notes |
|---|---|---|---|---|---|---|---|
| CANable | £18-26 | SocketCAN native (gs_usb) |
USB | No | No | No | Open-source, best value |
| CANable Pro | £33-44 | SocketCAN native (gs_usb) |
USB | No | No | Yes | Galvanic isolation |
| CANable 2.0 | £33-44 | SocketCAN native (gs_usb) |
USB-C | Yes | No | Yes | CAN FD, USB-C |
| Seeed USB-CAN Analyzer | £11-18 | python-can only (serial) | USB | No | No | No | Slow serial protocol |
| PCAN-USB (PEAK) | £74-96 | SocketCAN native (peak_usb) |
USB | No | No | No | Industry standard, reliable |
| PCAN-USB FD | £111-148 | SocketCAN native | USB | Yes | No | Yes | Professional CAN FD option |
| Kvaser Leaf v3 | ~£229 | SocketCAN + CANLIB | USB | Yes | No | Yes | 8,000 msg/s, pro-grade |
| Intrepid ValueCAN 4 + Vehicle Spy | £600-1,500+ | SocketCAN via icsscand |
USB | Yes | Yes | Yes | K-Line, LIN, UDS, multi-protocol |
| Vector VN1630 | £1,500+ | Proprietary driver | USB | Yes | No | Yes | Gold standard, overkill for RE |
A Note on K-Line
K-Line (ISO 9141-2 / ISO 14230, also known as KWP2000) is an older single-wire, UART-based diagnostic protocol that predates CAN. While most vehicles built after 2008 use CAN as their primary diagnostic bus, K-Line is still present in many vehicles — and critically, it is often the protocol used to communicate with immobiliser ECUs. Manufacturers such as Hyundai-Kia continue to use K-Line for immobiliser communication even in vehicles where the rest of the diagnostic traffic runs over CAN.
K-Line uses OBD2 pin 7 (K-Line) and optionally pin 15 (L-Line) — different pins from CAN (pins 6 and 14). It operates at 10,400 baud and uses a request-response model rather than CAN’s broadcast model. Because K-Line requires a different physical transceiver and signalling, CAN-only adapters like the CANable cannot speak K-Line. If your reverse engineering target involves immobiliser or security-level access on K-Line-equipped vehicles, you need a multi-protocol tool that supports both CAN and K-Line — such as the Intrepid ValueCAN 4 with Vehicle Spy, or an ELM327 adapter (which does support K-Line, albeit with the limitations discussed earlier).
Our Choice: CANable
For this series, we use the CANable — an open-source USB-to-CAN adapter that costs around £18-26. It is the cheapest adapter that provides genuine raw CAN bus access with native Linux SocketCAN support.
The CANable hardware is simple: an STM32F042 microcontroller paired with a MCP2551 (or compatible) CAN transceiver. It runs open-source firmware (either candleLight for native gs_usb SocketCAN, or canable_slcan for serial SLCAN mode). At 500 kbit/s it handles a fully loaded CAN bus without dropping frames.
What you need to buy:
| Item | Price | Source |
|---|---|---|
| CANable adapter | £18-26 | canable.io, Amazon, eBay |
| OBD2-to-DB9 cable (or breakout) | £4-7 | Amazon, AliExpress |
| Total | ~£22-33 |
Caveat: The standard CANable does not have galvanic isolation. This means the vehicle’s electrical system is directly connected to your computer’s ground. In practice this is fine for quick sessions via the OBD2 port, but if you are tapping into raw bus wires or working on vehicles with suspect electrical systems, consider the CANable Pro (£33-44) which includes galvanic isolation, or the CANable 2.0 (£33-44) which adds CAN FD support and USB-C.
Architecture Overview

The setup is straightforward: the CANable adapter bridges USB to CAN. On the Linux side, the gs_usb kernel driver exposes a SocketCAN network interface (can0). The python-can library and can-utils command-line tools communicate through this interface. On the vehicle side, we connect to the OBD2 port, which provides direct access to the diagnostic CAN bus.
Setting Up CANable on Linux
Step 1: Install can-utils and dependencies
$ sudo apt update
$ sudo apt install can-utils
This installs the core CAN command-line tools:
| Tool | Purpose |
|---|---|
candump |
Dump all CAN traffic to stdout or logfile |
cansend |
Send a single CAN frame |
cansniffer |
Interactive sniffer with colour-coded changes |
cangen |
Generate random CAN traffic |
canplayer |
Replay logged CAN traffic |
canbusload |
Calculate CAN bus load percentage |
Step 2: Plug in the CANable and verify
Plug the CANable into a USB port. The gs_usb kernel module should load automatically:
$ dmesg | tail -5
[ 847.231445] gs_usb 1-2:1.0: Configuring for 1 interfaces
[ 847.231523] gs_usb 1-2:1.0: Device connected
[ 847.231528] can_device: can0 registered
Verify the interface exists:
$ ip link show can0
3: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10
link/can
Attention! If can0 does not appear, your CANable may be running the SLCAN firmware instead of candleLight. Check by looking at the USB device:
$ lsusb | grep -i can
# Look for "Geschwister Schneider" or "canable.io"
# If you see a generic CDC/serial device instead, flash candleLight firmware
Step 3: Bring up the CAN interface
Configure the bitrate and bring the interface up:
$ sudo ip link set can0 up type can bitrate 500000
$ ip link show can0
3: can0: <NOARP,UP,LOWER_UP> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
link/can
The UP,LOWER_UP state confirms the interface is active and the CAN transceiver is online.
To bring the interface down after you are done:
$ sudo ip link set can0 down
To make the interface persistent across reboots, create a systemd service or add to /etc/network/interfaces:
# /etc/network/interfaces.d/can0
auto can0
iface can0 can static
bitrate 500000
Connecting to the Vehicle
Step 1: Wire the OBD2 connector
If you bought a pre-made OBD2-to-DB9 cable, it should already have the CAN pins mapped correctly. If you are building your own:
OBD2 Pin DB9 Pin (CANable)
┌──────┐
│ 6 │ CAN-H →──── 2 (CAN-H)
│ 14 │ CAN-L →──── 7 (CAN-L)
│ 5 │ GND →──── 3 (GND)
│ 16 │ +12V →──── DO NOT CONNECT
└──────┘
**Never connect pin 16 (+12V battery) to your adapter or computer.**
Attention! Only connect pins 6 (CAN-H), 14 (CAN-L), and optionally 5 (GND). Do not connect pin 16 (+12V battery) to anything — it provides direct battery voltage and can damage USB equipment.
Step 2: Connect and verify
- Plug the OBD2 connector into the vehicle’s OBD2 port (under the dashboard).
- Plug the CANable into your laptop’s USB port.
- Turn the vehicle’s ignition to ON (position II, engine not running is fine for testing).
- Bring up the CAN interface and start sniffing:
$ sudo ip link set can0 up type can bitrate 500000
$ candump can0
If everything is wired correctly and the baud rate matches, you should see a stream of CAN frames:
can0 0CF00400X [8] 80 7E 00 00 00 00 00 00
can0 18FEF100X [8] FF FF FF FF 00 00 FF FF
can0 18FEDF00X [8] FF FF 00 00 00 00 00 00
can0 0CF00300X [8] 00 00 00 00 00 00 00 00
can0 18FEF200X [8] FF FF 00 00 00 00 FF FF
Each line shows: interface arbitration_ID [DLC] data_bytes
Step 3: If you see nothing
If candump shows no output:
- Wrong baud rate — Try 250 kbit/s, then 125 kbit/s:
bash $ sudo ip link set can0 down $ sudo ip link set can0 up type can bitrate 250000 $ candump can0 - Wiring issue — Verify CAN-H (pin 6) and CAN-L (pin 14) are correct. Swap them and try again.
- Vehicle not on — Ensure ignition is at least in the ON position.
- No CAN on OBD2 — Some older vehicles (pre-2008) may use a different protocol. Check the vehicle’s manual.
To check for errors on the interface:
$ ip -s link show can0
3: can0: <NOARP,UP,LOWER_UP> mtu 16 qdisc pfifo_fast state UP mode DEFAULT group default qlen 10
link/can
RX: bytes packets errors dropped overrun mcast
12480 1560 0 0 0 0
TX: bytes packets errors dropped carrier collsns
0 0 0 0 0 0
A non-zero errors count in RX usually indicates a baud rate mismatch.
CAN Traffic Sniffing with can-utils
candump — Raw Frame Capture
candump is the workhorse. It captures every CAN frame on the bus:
$ candump can0
can0 123 [8] 01 02 03 04 05 06 07 08
can0 456 [8] AA BB CC DD EE FF 00 11
can0 7DF [8] 02 01 0C 00 00 00 00 00
Log to a file for later analysis:
$ candump -l can0
This creates timestamped log files (candump-2026-04-17_123456.log). You can replay them with canplayer.
Filter by arbitration ID:
$ candump can0,7DF:FFF
This shows only frames with ID 0x7DF (the standard OBD2 request ID). The filter syntax is ID:MASK where MASK bits set to 1 must match.
cansniffer — Interactive Live View
cansniffer provides a real-time, colour-coded view of CAN traffic. It highlights changed bytes in red, making it easy to spot which frames are active and what is changing:
$ cansniffer can0
This is extremely useful for reverse engineering. For example, if you press the accelerator pedal while cansniffer is running, you will see specific bytes in specific frames changing in real time — those are the engine RPM and throttle position signals.
Python CAN Communication
Now we get to the practical part: writing a Python script to communicate with the vehicle over CAN bus. We will use the python-can library, which provides a unified interface across all SocketCAN-compatible adapters.
Step 1: Install python-can
$ pip install python-can
Step 2: Basic CAN Frame Send/Receive
The following script demonstrates the fundamentals: connecting to the CAN bus, sending frames, and receiving responses.
Full source code is available at:
https://github.com/Hunt-Benito/getting-started-with-can-bus-reverse-engineering-on-linux
Key functions from the PoC:
import can
import time
import struct
BUS_CONFIG = {
"interface": "socketcan",
"channel": "can0",
"bitrate": 500000,
}
OBD2_REQUEST_ID = 0x7DF
OBD2_RESPONSE_ID = 0x7E8
def create_bus():
return can.Bus(**BUS_CONFIG)
def send_obd2_request(bus, pid, mode=0x01):
data = [0] * 8
data[0] = 0x02 # Single-frame length
data[1] = mode # Mode (01 = current data)
data[2] = pid # PID
msg = can.Message(
arbitration_id=OBD2_REQUEST_ID,
data=data,
is_extended_id=False,
)
bus.send(msg)
def wait_for_response(bus, pid, timeout=2.0):
deadline = time.time() + timeout
while time.time() < deadline:
msg = bus.recv(timeout=0.1)
if msg is None:
continue
if msg.arbitration_id == OBD2_RESPONSE_ID:
if len(msg.data) >= 4 and msg.data[2] == pid:
return msg
return None
Step 3: Reading Standard OBD2 PIDs
Standard OBD2 PIDs (Parameter IDs) let you query common vehicle data. The request is always sent to arbitration ID 0x7DF, and the ECU responds on 0x7E8 (or 0x7E9, 0x7EA for additional ECUs).
| PID | Description | Formula |
|---|---|---|
| 0x0C | Engine RPM | (A * 256 + B) / 4 |
| 0x0D | Vehicle Speed | A (km/h) |
| 0x05 | Engine Coolant Temp | A - 40 (°C) |
| 0x2F | Fuel Tank Level | (100 * A) / 255 (%) |
| 0x0A | Fuel Pressure | A * 3 (kPa) |
| 0x0B | Intake Manifold Pressure | A (kPa) |
| 0x0E | Timing Advance | (A / 2) - 64 (°) |
| 0x42 | Control Module Voltage | (A * 256 + B) / 1000 (V) |
Where A is the first data byte of the response payload and B is the second.
Here is how to decode a few common PIDs:
def decode_rpm(msg):
if len(msg.data) >= 5:
a = msg.data[3]
b = msg.data[4]
return (a * 256 + b) / 4.0
return None
def decode_speed(msg):
if len(msg.data) >= 4:
return msg.data[3]
return None
def decode_coolant_temp(msg):
if len(msg.data) >= 4:
return msg.data[3] - 40
return None
Step 4: Full Working Example
The complete PoC script (available in the repository) performs the following:
- Connects to the CAN bus via SocketCAN
- Queries supported PIDs from the ECU
- Reads and decodes engine RPM, vehicle speed, and coolant temperature
- Sends a raw CAN frame to demonstrate injection
- Monitors all traffic for a configurable duration
Usage:
$ sudo python3 can_probe.py --interface can0 --bitrate 500000 --mode monitor
Sample output:
[CAN Probe] Connecting to can0 @ 500000 bps...
[CAN Probe] Bus online. Querying ECU...
[CAN Probe] === OBD2 PID Query ===
[CAN Probe] Supported PIDs: 0C 0D 05 0A 0B 0E 2F 42 ...
[CAN Probe] --- Engine RPM (PID 0x0C) ---
[CAN Probe] TX → 7DF: 02 01 0C 00 00 00 00 00
[CAN Probe] RX ← 7E8: 03 41 0C 1A 5C 00 00 00
[CAN Probe] Engine RPM: 1703.0 rpm
[CAN Probe] --- Vehicle Speed (PID 0x0D) ---
[CAN Probe] TX → 7DF: 02 01 0D 00 00 00 00 00
[CAN Probe] RX ← 7E8: 03 41 0D 00 00 00 00 00
[CAN Probe] Vehicle Speed: 0 km/h
[CAN Probe] --- Coolant Temperature (PID 0x05) ---
[CAN Probe] TX → 7DF: 02 01 05 00 00 00 00 00
[CAN Probe] RX ← 7E8: 03 41 05 5B 00 00 00 00
[CAN Probe] Coolant Temperature: 51 °C
[CAN Probe] Monitoring CAN traffic (10s)...
can0 0CF00400X [8] 80 7E 00 00 00 00 00 00
can0 18FEF100X [8] FF FF FF FF 00 00 FF FF
can0 0CF00300X [8] 00 00 00 00 00 00 00 00
can0 7E0 [8] 02 01 00 00 00 00 00 00
can0 7E8 [8] 06 41 00 08 00 02 00 00
... (1524 frames in 10s)
[CAN Probe] Done. Captured 1524 frames.
Step 5: Sending a Raw CAN Frame
Beyond OBD2 queries, the script also demonstrates sending arbitrary CAN frames — which is where reverse engineering begins:
def send_raw_frame(bus, arb_id, data_bytes, is_extended=False):
msg = can.Message(
arbitration_id=arb_id,
data=data_bytes,
is_extended_id=is_extended,
)
bus.send(msg)
Example — sending a test frame on ID 0x123:
$ sudo python3 can_probe.py --interface can0 --mode inject --id 0x123 --data "01,02,03,04"
[CAN Probe] Injecting frame: ID=0x123 DATA=[01 02 03 04]
[CAN Probe] Frame sent successfully.
Attention! Injecting arbitrary CAN frames into a live vehicle can have unpredictable effects. Never inject frames into a moving vehicle. Always work with the vehicle stationary, ignition ON, engine off, in a safe environment. Use a CAN bus simulator (such as canbus-utils or a second CANable in loopback) for initial testing.
Practical Tips for Automotive Reverse Engineering
Identifying Signal Maps
The core challenge in automotive CAN reverse engineering is mapping arbitration IDs to physical signals. When you press the brake pedal, turn the steering wheel, or adjust the window, specific bytes in specific CAN frames change. The process is:
- Start
cansnifferorcandump -lto capture a baseline - Perform a single action (e.g., press brake pedal)
- Note which arbitration IDs and bytes changed
- Repeat the action multiple times to confirm the mapping
- Vary the action (light press vs. hard press) to understand the encoding
Bus Load and Filtering
A typical vehicle CAN bus carries 1,000-3,000 frames per second. During capture, use filters to reduce noise:
$ candump can0,0C0:7F0
This masks to IDs 0x0C0-0x0CF (a common range for engine/transmission ECU messages in many vehicles).
Virtual CAN for Testing
You can create a virtual CAN interface for testing your Python scripts without any hardware:
$ sudo modprobe vcan
$ sudo ip link add dev vcan0 type vcan
$ sudo ip link set vcan0 up
$ python3 can_probe.py --interface vcan0 --mode monitor
Safety & Legal Considerations
This cannot be overstated:
| Rule | Why |
|---|---|
| Never inject frames on a moving vehicle | Unintended actuator commands can cause loss of control |
| Always work with engine off, ignition ON | Reduces risk while maintaining bus activity |
| Use a fuse-protected OBD2 cable | Prevents damage from voltage spikes |
| Verify your wiring before connecting | Incorrect wiring can damage the CAN transceiver |
| Check local regulations | Some jurisdictions restrict vehicle ECU tampering |
| Work on your own vehicle or with explicit permission | Unauthorised access to vehicle systems may be illegal |
Automotive security research is a legitimate and valuable field, but it must be conducted responsibly. The techniques in this article are for educational purposes and authorised research only.
What’s Next
In the next article of this series, we will tackle the security layer that guards access to restricted vehicle functions: security-level authentication and immobiliser ECU reverse engineering. Most modern ECUs implement a challenge-response authentication mechanism (often based on seed-key exchange under UDS diagnostic sessions) that must be satisfied before the ECU accepts commands for reading/writing memory, flashing firmware, or altering security-critical parameters. We will reverse engineer the seed-key algorithm used by a real immobiliser ECU, implement the key computation in Python, and demonstrate how to elevate diagnostic session security levels — a prerequisite for any advanced automotive reverse engineering work.
SOURCES
Bosch CAN Specification 2.0: https://www.bosch-semiconductors.com/media/oth/can2_0.pdf
ISO 15765-4 (Road vehicles — diagnostics on CAN): https://www.iso.org/standard/64655.html
SocketCAN Documentation (Linux kernel): https://www.kernel.org/doc/html/latest/networking/can.html
python-can Library: https://github.com/hardbyte/python-can
can-utils (Linux CAN Utilities): https://github.com/linux-can/can-utils
CANable Open Hardware: https://canable.io/
candleLight Firmware: https://github.com/candle-usb/candleLight_fw
SAE J1979 (OBD2 PIDs): https://www.sae.org/standards/content/j1979_201702/
SAE J2284 (High-Speed CAN for Vehicles): https://www.sae.org/standards/content/j2284_3_202003/
OBD2 PID Reference (Wikipedia): https://en.wikipedia.org/wiki/OBD-II_PIDs
linux-can SocketCAN Wiki: https://github.com/linux-can/socketcan-doc
Vehicle Spy 3 (Intrepid Control Systems): https://intrepidcs.com/products/software/vehicle-spy/
ISO 9141-2 (Road vehicles — K-Line diagnostics): https://www.iso.org/standard/26366.html
ISO 14230 (KWP2000 — Keyword Protocol 2000): https://www.iso.org/standard/36480.html