# CAN Dump Log Line Format
This is an AI-generated reference for the text log format emitted by [candump](https://manpages.debian.org/testing/can-utils/candump.1.en.html), as parsed by the `socketcan::dump` module (`src/dump.rs`).
## Scope and Source of Truth
This document describes the **log format** produced with the `candump -L` command; the format that [canplayer](https://manpages.debian.org/testing/can-utils/canplayer.1.en.html) can read back and that this crate parses and emits. It does not cover candump's other human-readable output modes, only the log format.
There is no formal specification. The closest thing to an authoritative grammar is the `parse_canframe()` documentation comment in can-utils [`lib.h`](https://github.com/linux-can/can-utils/blob/master/lib.h), and the format is ultimately *defined* by the `lib.c` / `candump.c` source. This document reproduces that grammar and notes where this crate's parser is stricter.
## Line structure
Each line is three space-separated fields, plus an optional fourth when the log was captured with `candump -x`:
```text
(<sec>.<usec>) <iface> <frame> [<direction>]
```
Example lines:
```text
(1735270496.916858) can0 110#00112233
(1735270588.936508) can0 120##500112233445566778899AABB
(1735279041.257318) can1 104#R
(1735279048.349278) can1 110#R4
(1469439874.299654) can1 104#
```
## Grammar (ABNF-style)
```abnf
record = "(" sec "." usec ")" SP iface SP frame [ SP direction ]
sec = 1*DIGIT ; whole seconds since the epoch
usec = 6DIGIT ; microseconds, always exactly 6 digits
iface = 1*VCHAR ; interface name, e.g. "can0", "vcan1"
frame = can-id ( classical / canfd )
can-id = 3HEXDIG ; SFF — standard 11-bit identifier
/ 8HEXDIG ; EFF — extended 29-bit identifier, OR an error frame
classical = "#" ( rtr / data ) [ "_" dlc8 ]
canfd = "##" flags data
rtr = "R" [ HEXDIG ] ; remote frame; optional DLC nibble, absent = 0
; 0..F syntactically; only 0..8 is usable (see below)
data = *( 2HEXDIG ) ; payload bytes; 0..8 for classical, 0..64 for CAN FD
flags = HEXDIG ; single nibble mapped onto canfd_frame.flags
dlc8 = HEXDIG ; "len8 DLC" escape; only meaningful when data is 8 bytes
direction = "R" / "T" ; only present with 'candump -x'
HEXDIG = DIGIT / "A".."F" / "a".."f"
```
## Fields
### Timestamp — `(<sec>.<usec>)`
Absolute time, wrapped in parentheses, as a floating-point `time_t` value, as seconds from the UNIX Epoch. `candump` always emits exactly **six** fractional digits (microsecond resolution). This crate's parser requires exactly six digits and rejects any other count rather than guessing the precision.
### Interface — `<iface>`
The CAN interface name the frame was captured on (e.g. `can0`, `vcan1`, `slcan0`). Up to `IFNAMSIZ - 1` characters.
### Frame — `<can-id><body>`
A CAN identifier in hexadecimal, immediately followed by a body whose leading character(s) select the frame kind.
### Direction — `R` / `T`
Present only when the log was captured with `candump -x` ("print extra message infos, rx/tx brs esi"): a single letter saying which way the frame went on the interface that logged it — `R` received, `T` transmitted. Frames a program sent itself read back as `T` through the socket's loopback.
```text
(1788557985.236417) vcan0 123#DEADBEEF T
(1788557985.239414) vcan0 456#R T
(1788557985.247474) vcan0 20000004#000C000000000000 T
```
It follows every frame form, error frames included, and `canplayer` accepts a line that carries it. This crate parses it into `CanDumpRecord::direction` and writes it back out, so such a line round-trips; a log without the field parses to `None` and renders as three fields.
## Identifier width
The number of hex digits in `can-id` distinguishes the frame format:
- **3 digits** — Standard Frame Format (SFF), an 11-bit identifier (`0x000`..`0x7FF`).
- **8 digits** — Extended Frame Format (EFF), a 29-bit identifier (`0x00000000`..`0x1FFFFFFF`), **or** an error frame (see below).
The width decides, never the numeric value, and the two spellings are not nested: `123#01` is a standard frame with ID `0x123`, while `00000123#01` is an *extended* frame with the same numeric ID. A value that does not fit the width it was written in — `800#01`, which is not a legal 11-bit identifier — is an error rather than being reinterpreted as the other format.
There is no syntactic difference between an extended data/remote frame and an error frame; those two are distinguished only by the `CAN_ERR_FLAG` bit in the parsed numeric identifier.
## Frame body forms
### Classical data frame — `#<data>`
A `#` followed by an even number of hex digits, two per data byte. Zero bytes (`123#`) is valid and means an empty payload. Maximum 8 bytes for classical CAN.
```text
123# SFF, 0 data bytes
123#1122334455667788 SFF, 8 data bytes
12345678#DEADBEEF EFF, 4 data bytes
```
### Remote frame (RTR) — `#R[<dlc>]`
A `#R`, optionally followed by a single hex digit giving the requested DLC. An absent digit means DLC 0. Remote frames carry no data — only the DLC.
```text
104#R RTR, DLC 0
110#R4 RTR, DLC 4
```
The nibble spans `0`..`F` syntactically, but only `0`..`8` is usable. A classical remote frame's DLC field is four bits wide on the wire, yet SocketCAN caps a classical frame's length at `CAN_MAX_DLEN` (8) in both directions: `can_dropped_invalid_skb()` rejects a larger value with `EINVAL` on send, and a driver clamps the received DLC before it reaches userspace. So `candump` never *emits* a nibble above 8 — it prints the already-clamped `can_frame::len` — and a line carrying one can only have been written by hand.
can-utils resolves the excess by discarding it: `parse_canframe()` assigns the DLC only when the nibble is `<= CAN_MAX_DLEN`, so `cansend vcan0 123#RF` transmits DLC 0 and `candump -L` logs it back as `123#R`. This crate rejects such a line instead; see the parser notes below.
CAN FD has no remote-frame concept; RTR applies to classical frames only.
### CAN FD frame — `##<flags><data>`
A double `##` distinguishes CAN FD from classical CAN. The first character after `##` is a single hex nibble carrying the FD flags; the remainder is the payload (even hex digits, up to 64 bytes).
```text
120##500112233445566778899AABB FD, flags=5, 12 data bytes
080##0 FD, flags=0, 0 data bytes
```
#### Flags nibble
The nibble maps directly onto the kernel `canfd_frame.flags` field:
| Bit | Name | Meaning |
|------|-------------|--------------------------------|
| 0x1 | `CANFD_BRS` | Bit Rate Switch |
| 0x2 | `CANFD_ESI` | Error State Indicator |
| 0x4 | `CANFD_FDF` | FD Frame (CAN FD, not classic) |
So `##1…` is BRS, `##5…` is BRS|FDF, etc.
### Error frame — `<8 hex>#<data>`
An error frame is an 8-hex-digit identifier with the `CAN_ERR_FLAG` bit (`0x20000000`) set, followed by `#` and the 8 error-class data bytes. It is syntactically identical to an extended data frame; the `CAN_ERR_FLAG` bit in the parsed identifier is what marks it as an error frame. Error frames never carry the RTR bit.
```text
20000004#0000000000000000 error frame, CAN_ERR_FLAG set, error class 0x4
```
### len8 DLC escape — `#<8 data bytes>_<dlc>`
Classical CAN can encode a raw DLC value greater than 8 while still carrying only 8 data bytes. candump represents this by appending `_` and a single hex DLC nibble after exactly 8 data bytes. The suffix is only meaningful (and only emitted) when the frame has 8 data bytes and a raw DLC of `9`..`F`.
```text
123#1122334455667788_E 8 data bytes, raw DLC = 0xE (14)
```
## Examples
| Line body | Meaning |
|--------------------|------------------------------------------------------|
| `123#` | SFF, empty payload |
| `123#1122334455667788` | SFF, 8 data bytes |
| `123#R` | SFF remote frame, DLC 0 |
| `123#R7` | SFF remote frame, DLC 7 |
| `123#RF` | remote DLC 15 — rejected by this parser (see notes) |
| `123#1122334455667788_E` | SFF, 8 data bytes, raw DLC = 14 |
| `12345678#DEADBEEF`| EFF, 4 data bytes |
| `123##0112233` | CAN FD, flags=0, 3 data bytes |
| `123##5112233` | CAN FD, flags=BRS\|FDF, 3 data bytes |
| `20000004#0000000000000000` | error frame, error class 0x4 |
| `123#DEADBEEF T` | SFF, 4 data bytes, transmitted (`candump -x`) |
| `123#DEADBEEF R` | SFF, 4 data bytes, received (`candump -x`) |
## Notes on this crate's parser
The reader in `src/dump.rs` follows the grammar above, with these deliberate choices:
- The microsecond field must be **exactly six digits**; other lengths are rejected (`ParseError::InvalidTimestamp`). candump always emits six, so this is stricter than the permissive C parser but correct for real candump output.
- Each line is read through a 64 KiB cap so a corrupt log cannot exhaust memory; an over-long line yields `ParseError::InvalidCanFrame`.
- The identifier field must be **exactly three or exactly eight** hex digits, matching `parse_canframe()`. Up to and including v3.x this crate instead inferred the format from the parsed value — `<= CAN_SFF_MASK` meant standard — which was wrong in both directions, since neither format's range is a subset of the other's spelling: `00000123#01` was read as a *standard* frame and re-emitted as `123#01`, while `800#AA` was read as an *extended* frame and re-emitted as `00000800#AA`, having accepted a value that is not a legal 11-bit identifier. As a side effect the old behaviour also accepted widths candump never emits, such as six digits; those are now rejected.
- Remote-frame DLC handling is stricter than `parse_canframe()`'s in two ways: a nibble that does not parse as hex is an error rather than a silent 0, and a nibble above 8 is rejected with `ParseError::InvalidCanFrame` rather than discarded. `CanRemoteFrame` caps its DLC at `CAN_MAX_DLEN` because that is the kernel's own send-side limit, so a line like `123#RF` describes a frame this crate could not transmit in any case, and rejecting it beats reinterpreting it as its opposite, DLC 0. No captured log is affected, since candump cannot emit such a line. An empty DLC still parses as 0.
Note that the library takes the *opposite* action on the same out-of-range value when it arrives as a raw C `can_frame` rather than as log text: `From<can_frame>`/`TryFrom<can_frame>` clamp the length to `CAN_MAX_DLEN` instead of rejecting. A struct handed over by the kernel is data to sanitize — and `From` cannot fail — whereas log text is untrusted input that this parser can and should refuse. See the `normalize_dlc()` doc comment in `src/frame.rs` for the full split.
- Error frames are supported in both directions as of v4.0. The parser branches on `CAN_ERR_FLAG` before decoding the identifier, because the error-class bits sit above `CAN_EFF_MASK` and would otherwise be rejected as an out-of-range extended ID. `Display` emits the identifier **with** `CAN_ERR_FLAG` included, matching candump, so a line round-trips exactly; emitting the bare class bits would produce output that re-parsed as a standard data frame.
- A short error-frame payload is zero-padded out to the full eight bytes, since that is what the kernel always delivers. A hand-written log line with fewer than eight data bytes therefore does not round-trip byte-for-byte.
- The direction field is parsed when present and rejected with `ParseError::InvalidFrameDirection` when the fourth field is neither `R` nor `T`. Lowercase is accepted, although candump only writes uppercase. A fifth field, which candump does not emit, is ignored.
- **The `_dlc` suffix is not supported.** A line containing it is rejected with `ParseError::InvalidCanFrame`. `CanDataFrame` has nowhere to store a raw DLC greater than its data length — `libc::can_frame::len8_dlc` exists in the wrapped struct but is unused by this crate — so accepting the suffix would mean either silently discarding it (making that one field lossy while every other round-trips) or widening the frame type's public API. Deferred pending a decision on `CAN_RAW_CC_LEN8_DLC` support for sockets generally.