Skip to main content

netget/server/can/
frame.rs

1//! The CAN frame: representation, validation and the SocketCAN wire encoding.
2//!
3//! **This module is pure.** No socket, no `cfg(target_os)`, no configuration, no global state.
4//! That is deliberate and it is the whole reason this protocol can be `Experimental` rather than
5//! unverifiable: `AF_CAN` exists only in the Linux kernel, so the transport in
6//! [`super::transport`] cannot be executed on the machine that wrote it — but everything that
7//! *decides what the bytes are* can be, and is, asserted against literal values in
8//! `tests/server/can/frame_test.rs`.
9//!
10//! Keep it pure. The moment it reads a socket or a config value, the only provable part of this
11//! protocol stops being provable. (`bluetooth_ble_beacon/payload.rs` and `lldp/codec.rs` are the
12//! same arrangement for the same reason.)
13//!
14//! # Two frame formats, one struct
15//!
16//! [`CanFrame`] covers classic CAN 2.0 and CAN FD, because a SocketCAN reader gets both off the
17//! same socket and the model should not have to think about which struct it is holding.
18//!
19//! | | Classic CAN 2.0 | CAN FD |
20//! |---|---|---|
21//! | Identifier | 11-bit (standard) or 29-bit (extended) | same |
22//! | Payload | 0-8 bytes | 0-8, 12, 16, 20, 24, 32, 48 or 64 bytes |
23//! | DLC | equals the byte count | a 4-bit **length code**, not a byte count, above 8 |
24//! | RTR | yes — a request for data, carrying none | **does not exist** |
25//! | BRS / ESI | no | yes |
26//!
27//! ## The DLC trap
28//!
29//! For classic CAN, DLC and length are the same number and nothing goes wrong. For CAN FD they
30//! are not: DLC 9 means **12** bytes, 15 means **64**. Writing `dlc = len` for an FD frame
31//! produces a frame that is silently the wrong size, and reading `len = dlc` truncates one. That
32//! is the classic implementation error in every CAN stack, so [`dlc_for_len`] and [`len_for_dlc`]
33//! are separate named functions with the table written out, and the tests pin all sixteen codes
34//! against literal values in both directions.
35//!
36//! FD also does not permit an arbitrary length: there is no encoding for 9, 10, 11, 13... bytes.
37//! [`dlc_for_len`] therefore **rejects** those rather than rounding, because rounding up would
38//! append bytes the model did not write and rounding down would drop bytes it did.
39//!
40//! # The SocketCAN wire layout
41//!
42//! [`CanFrame::to_wire_bytes`] and [`CanFrame::from_wire_bytes`] implement the kernel's
43//! `struct can_frame` (16 octets) and `struct canfd_frame` (72 octets) from `<linux/can.h>`,
44//! little-endian, which is what an `AF_CAN` socket reads and writes verbatim. Two things use it:
45//! the UDP test transport carries exactly these bytes in a datagram, and the action executor
46//! hands them to the server loop as `ActionResult::Output`. Having the layout here rather than
47//! in the transport means the format is covered by tests on a machine with no CAN stack at all.
48
49use anyhow::{anyhow, bail, Context, Result};
50use serde_json::{json, Value};
51
52// =================================================================================================
53// SocketCAN identifier flags — <linux/can.h>
54// =================================================================================================
55
56/// `CAN_EFF_FLAG`: the identifier is a 29-bit extended one.
57pub const CAN_EFF_FLAG: u32 = 0x8000_0000;
58/// `CAN_RTR_FLAG`: remote transmission request — a request for data, carrying none.
59pub const CAN_RTR_FLAG: u32 = 0x4000_0000;
60/// `CAN_ERR_FLAG`: this is an error frame; the identifier field is a bitmask of error classes.
61pub const CAN_ERR_FLAG: u32 = 0x2000_0000;
62/// `CAN_SFF_MASK`: 11 significant bits.
63pub const CAN_SFF_MASK: u32 = 0x0000_07FF;
64/// `CAN_EFF_MASK`: 29 significant bits.
65pub const CAN_EFF_MASK: u32 = 0x1FFF_FFFF;
66
67/// `CANFD_BRS`: bit-rate switch — the data phase ran at the faster bit rate.
68pub const CANFD_BRS: u8 = 0x01;
69/// `CANFD_ESI`: error state indicator — the transmitting node was error-passive.
70pub const CANFD_ESI: u8 = 0x02;
71/// `CANFD_FDF`: this is an FD frame. Set by the kernel on `canfd_frame`; carried for fidelity.
72pub const CANFD_FDF: u8 = 0x04;
73
74/// `CAN_MTU`: `sizeof(struct can_frame)`.
75pub const CAN_MTU: usize = 16;
76/// `CANFD_MTU`: `sizeof(struct canfd_frame)`.
77pub const CANFD_MTU: usize = 72;
78
79/// Largest classic CAN payload.
80pub const CAN_MAX_DLEN: usize = 8;
81/// Largest CAN FD payload.
82pub const CANFD_MAX_DLEN: usize = 64;
83
84// =================================================================================================
85// Error-class bits — the identifier field of an error frame
86// =================================================================================================
87
88/// `CAN_ERR_TX_TIMEOUT`
89pub const CAN_ERR_TX_TIMEOUT: u32 = 0x0000_0001;
90/// `CAN_ERR_LOSTARB` — arbitration lost
91pub const CAN_ERR_LOSTARB: u32 = 0x0000_0002;
92/// `CAN_ERR_CRTL` — controller problems; `data[1]` says which
93pub const CAN_ERR_CRTL: u32 = 0x0000_0004;
94/// `CAN_ERR_PROT` — protocol violation; `data[2]`/`data[3]` say which
95pub const CAN_ERR_PROT: u32 = 0x0000_0008;
96/// `CAN_ERR_TRX` — transceiver status
97pub const CAN_ERR_TRX: u32 = 0x0000_0010;
98/// `CAN_ERR_ACK` — no acknowledgement on transmit
99pub const CAN_ERR_ACK: u32 = 0x0000_0020;
100/// `CAN_ERR_BUSOFF` — the controller took itself off the bus
101pub const CAN_ERR_BUSOFF: u32 = 0x0000_0040;
102/// `CAN_ERR_BUSERROR` — bus error (the counter, not a state change)
103pub const CAN_ERR_BUSERROR: u32 = 0x0000_0080;
104/// `CAN_ERR_RESTARTED` — the controller restarted
105pub const CAN_ERR_RESTARTED: u32 = 0x0000_0100;
106/// `CAN_ERR_CNT` — the error counters in `data[6]`/`data[7]` are valid
107pub const CAN_ERR_CNT: u32 = 0x0000_0200;
108
109/// `CAN_ERR_CRTL_RX_OVERFLOW`
110pub const CAN_ERR_CRTL_RX_OVERFLOW: u8 = 0x01;
111/// `CAN_ERR_CRTL_TX_OVERFLOW`
112pub const CAN_ERR_CRTL_TX_OVERFLOW: u8 = 0x02;
113/// `CAN_ERR_CRTL_RX_WARNING`
114pub const CAN_ERR_CRTL_RX_WARNING: u8 = 0x04;
115/// `CAN_ERR_CRTL_TX_WARNING`
116pub const CAN_ERR_CRTL_TX_WARNING: u8 = 0x08;
117/// `CAN_ERR_CRTL_RX_PASSIVE`
118pub const CAN_ERR_CRTL_RX_PASSIVE: u8 = 0x10;
119/// `CAN_ERR_CRTL_TX_PASSIVE`
120pub const CAN_ERR_CRTL_TX_PASSIVE: u8 = 0x20;
121/// `CAN_ERR_CRTL_ACTIVE` — back to error-active
122pub const CAN_ERR_CRTL_ACTIVE: u8 = 0x40;
123
124// =================================================================================================
125// DLC ⇄ length
126// =================================================================================================
127
128/// The CAN FD length for each of the sixteen data length codes.
129///
130/// Written out rather than computed. The published table (ISO 11898-1:2015) is
131/// `0..=8` verbatim, then `12, 16, 20, 24, 32, 48, 64` — a sequence with three different step
132/// sizes, which is exactly why implementations get it wrong when they try to be clever.
133pub const FD_DLC_TO_LEN: [usize; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64];
134
135/// How many payload bytes a data length code stands for.
136///
137/// `fd` selects the table: on a classic frame every code above 8 still means 8 bytes (the
138/// controller transmits eight and the surplus code is meaningless), while on an FD frame codes
139/// 9-15 mean 12, 16, 20, 24, 32, 48 and 64.
140///
141/// A `dlc` above 15 cannot occur — the field is four bits — and is clamped rather than panicking,
142/// because this is on the receive path and a corrupt value must not take the server down.
143pub fn len_for_dlc(dlc: u8, fd: bool) -> usize {
144    let dlc = (dlc & 0x0F) as usize;
145    if fd {
146        FD_DLC_TO_LEN[dlc]
147    } else {
148        dlc.min(CAN_MAX_DLEN)
149    }
150}
151
152/// The data length code that encodes exactly `len` payload bytes.
153///
154/// Rejects any length the format cannot express, rather than rounding. For CAN FD the encodable
155/// lengths are 0-8, 12, 16, 20, 24, 32, 48 and 64; a 9-byte payload has **no** representation, and
156/// silently padding it to 12 would put three bytes on the wire that the model did not write.
157pub fn dlc_for_len(len: usize, fd: bool) -> Result<u8> {
158    if fd {
159        FD_DLC_TO_LEN
160            .iter()
161            .position(|&candidate| candidate == len)
162            .map(|dlc| dlc as u8)
163            .ok_or_else(|| {
164                anyhow!(
165                    "a CAN FD frame carries 0-8, 12, 16, 20, 24, 32, 48 or 64 bytes; {len} is not \
166                     an encodable length. Pad the payload to the next encodable size yourself if \
167                     that is what you mean - this codec will not invent bytes."
168                )
169            })
170    } else if len <= CAN_MAX_DLEN {
171        Ok(len as u8)
172    } else {
173        Err(anyhow!(
174            "a classic CAN frame carries at most 8 bytes; {len} given. Set \"fd\": true for a CAN \
175             FD frame, which carries up to 64."
176        ))
177    }
178}
179
180// =================================================================================================
181// Bus state
182// =================================================================================================
183
184/// The CAN controller's error state, as the error-confinement rules of ISO 11898-1 define it.
185///
186/// A node starts *error-active* and shouts about every error it sees. As its error counters rise
187/// past 127 it becomes *error-passive* and stops interfering. Past 255 it goes *bus-off* and
188/// disconnects itself entirely: it transmits nothing and receives nothing until it is restarted.
189/// This ladder is why an error frame is never a suitable way to report a NetGet failure — see
190/// this protocol's `CLAUDE.md`.
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum BusState {
193    /// Normal. Errors are signalled actively.
194    ErrorActive,
195    /// A counter passed 96: still active, but close to the limit.
196    ErrorWarning,
197    /// A counter passed 127: the node no longer signals errors actively.
198    ErrorPassive,
199    /// A counter passed 255: the node has removed itself from the bus.
200    BusOff,
201}
202
203impl BusState {
204    /// The name used in event data and in the log.
205    pub fn as_str(self) -> &'static str {
206        match self {
207            BusState::ErrorActive => "error_active",
208            BusState::ErrorWarning => "error_warning",
209            BusState::ErrorPassive => "error_passive",
210            BusState::BusOff => "bus_off",
211        }
212    }
213}
214
215// =================================================================================================
216// The frame
217// =================================================================================================
218
219/// One CAN frame, classic or FD, in or out.
220///
221/// `data` is the payload as bytes; how it is *written* in JSON is the `encoding` field's job
222/// ([`CanFrame::from_action`]), never a guess.
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct CanFrame {
225    /// 11-bit or 29-bit identifier, without any of the SocketCAN flag bits.
226    pub id: u32,
227    /// True for a 29-bit identifier. **Explicit, never inferred from magnitude**: `0x123` is a
228    /// perfectly legal extended identifier and is a different frame from standard `0x123`.
229    pub extended: bool,
230    /// Remote transmission request: a request for data, carrying none. Not valid on FD.
231    pub rtr: bool,
232    /// This is an error frame. The identifier is then a bitmask of error classes, not an address.
233    pub error: bool,
234    /// CAN FD rather than classic CAN 2.0.
235    pub fd: bool,
236    /// Bit-rate switch: the data phase used the faster bit rate. FD only.
237    pub brs: bool,
238    /// Error state indicator: the transmitter was error-passive. FD only, receive-side only.
239    pub esi: bool,
240    /// Payload. Empty for an RTR frame, whose length lives in `rtr_dlc`.
241    pub data: Vec<u8>,
242    /// The number of bytes an RTR frame is *requesting*. Meaningless on any other frame.
243    pub rtr_dlc: u8,
244}
245
246impl CanFrame {
247    /// A classic data frame.
248    pub fn classic(id: u32, extended: bool, data: Vec<u8>) -> Result<Self> {
249        let frame = Self {
250            id,
251            extended,
252            rtr: false,
253            error: false,
254            fd: false,
255            brs: false,
256            esi: false,
257            data,
258            rtr_dlc: 0,
259        };
260        frame.validate()?;
261        Ok(frame)
262    }
263
264    /// A CAN FD data frame.
265    pub fn fd(id: u32, extended: bool, data: Vec<u8>, brs: bool) -> Result<Self> {
266        let frame = Self {
267            id,
268            extended,
269            rtr: false,
270            error: false,
271            fd: true,
272            brs,
273            esi: false,
274            data,
275            rtr_dlc: 0,
276        };
277        frame.validate()?;
278        Ok(frame)
279    }
280
281    /// A remote transmission request for `dlc` bytes.
282    pub fn remote(id: u32, extended: bool, dlc: u8) -> Result<Self> {
283        let frame = Self {
284            id,
285            extended,
286            rtr: true,
287            error: false,
288            fd: false,
289            brs: false,
290            esi: false,
291            data: Vec::new(),
292            rtr_dlc: dlc,
293        };
294        frame.validate()?;
295        Ok(frame)
296    }
297
298    /// Everything that must hold before a frame can be encoded.
299    ///
300    /// Called by every constructor and again by [`Self::to_wire_bytes`], so a frame built by
301    /// hand cannot skip it.
302    pub fn validate(&self) -> Result<()> {
303        if self.error {
304            // An error frame's "identifier" is a class bitmask, so the 11/29-bit rules do not
305            // apply to it. NetGet never *builds* one; this branch exists for frames read off the
306            // bus, which are already whatever the controller said they were.
307            //
308            // The payload length is checked all the same, and it must be checked *here* rather
309            // than after this return: `to_wire_bytes` copies into `out[8..8 + data.len()]` of a
310            // fixed 16- or 72-octet buffer, so an over-long error frame is a panic in a `pub fn`
311            // on a struct with `pub` fields, not a short frame. A panic inside a connection task
312            // is swallowed by `tokio::spawn`, which is the failure mode this repository has hit
313            // three times.
314            dlc_for_len(self.data.len(), self.fd)?;
315            return Ok(());
316        }
317
318        let limit = if self.extended {
319            CAN_EFF_MASK
320        } else {
321            CAN_SFF_MASK
322        };
323        if self.id > limit {
324            bail!(
325                "identifier 0x{:X} does not fit in {} bits (maximum 0x{:X}). {}",
326                self.id,
327                if self.extended { 29 } else { 11 },
328                limit,
329                if self.extended {
330                    "29 bits is the largest CAN identifier there is."
331                } else {
332                    "Set \"extended\": true for a 29-bit identifier."
333                }
334            );
335        }
336
337        if self.rtr {
338            if self.fd {
339                bail!(
340                    "CAN FD has no remote frames: the RTR bit was reused as RRS and is always \
341                     dominant. Send an RTR request as a classic frame (\"fd\": false), or send a \
342                     data frame."
343                );
344            }
345            if !self.data.is_empty() {
346                bail!(
347                    "a remote frame carries no data by definition; {} byte(s) were given. Its DLC \
348                     names how many bytes are being *requested*.",
349                    self.data.len()
350                );
351            }
352            if self.rtr_dlc as usize > CAN_MAX_DLEN {
353                bail!(
354                    "a remote frame can request at most 8 bytes; dlc {} given",
355                    self.rtr_dlc
356                );
357            }
358            return Ok(());
359        }
360
361        if self.brs && !self.fd {
362            bail!(
363                "the bit-rate switch is a CAN FD feature; a classic frame has one bit rate. Set \
364                 \"fd\": true, or drop \"brs\"."
365            );
366        }
367
368        // The real check: this is what rejects an over-length payload instead of truncating it.
369        dlc_for_len(self.data.len(), self.fd)?;
370        Ok(())
371    }
372
373    /// The data length code this frame will carry on the wire.
374    pub fn dlc(&self) -> u8 {
375        if self.rtr {
376            self.rtr_dlc
377        } else {
378            dlc_for_len(self.data.len(), self.fd).unwrap_or(0)
379        }
380    }
381
382    /// The composite SocketCAN identifier word: the identifier plus the EFF/RTR/ERR flag bits.
383    pub fn id_word(&self) -> u32 {
384        let mut word = if self.error {
385            self.id
386        } else if self.extended {
387            self.id & CAN_EFF_MASK
388        } else {
389            self.id & CAN_SFF_MASK
390        };
391        if self.extended {
392            word |= CAN_EFF_FLAG;
393        }
394        if self.rtr {
395            word |= CAN_RTR_FLAG;
396        }
397        if self.error {
398            word |= CAN_ERR_FLAG;
399        }
400        word
401    }
402
403    /// Rebuild a frame from a composite identifier word and a payload.
404    ///
405    /// This is the receive-side entry point for both transports: the Linux one gets the word from
406    /// `socketcan`'s `Frame::id_word()`, and the UDP one from the first four octets of the
407    /// datagram. Keeping the flag decoding here — rather than once per transport — is what lets
408    /// `tests/server/can/frame_test.rs` prove it on a machine with no CAN stack.
409    pub fn from_id_word(id_word: u32, data: Vec<u8>, fd: bool, brs: bool, esi: bool) -> Self {
410        let extended = id_word & CAN_EFF_FLAG != 0;
411        let rtr = id_word & CAN_RTR_FLAG != 0;
412        let error = id_word & CAN_ERR_FLAG != 0;
413
414        let id = if error {
415            // The class bitmask occupies the same 29 bits.
416            id_word & CAN_EFF_MASK
417        } else if extended {
418            id_word & CAN_EFF_MASK
419        } else {
420            id_word & CAN_SFF_MASK
421        };
422
423        Self {
424            id,
425            extended,
426            rtr,
427            error,
428            fd,
429            brs,
430            esi,
431            // A remote frame's payload octets are undefined; the DLC is the message.
432            data: if rtr { Vec::new() } else { data },
433            rtr_dlc: 0,
434        }
435    }
436
437    // ---------------------------------------------------------------------------------------------
438    // The SocketCAN wire layout
439    // ---------------------------------------------------------------------------------------------
440
441    /// Encode as `struct can_frame` (16 octets) or `struct canfd_frame` (72 octets).
442    ///
443    /// Little-endian, matching `<linux/can.h>` on every architecture Linux supports CAN on. The
444    /// two layouts differ in more than length, so the tests pin both:
445    ///
446    /// ```text
447    /// struct can_frame {          struct canfd_frame {
448    ///   canid_t can_id;   // 4      canid_t can_id;   // 4
449    ///   __u8    len;      // 1      __u8    len;      // 1
450    ///   __u8    __pad;    // 1      __u8    flags;    // 1   <- BRS/ESI/FDF live here
451    ///   __u8    __res0;   // 1      __u8    __res0;   // 1
452    ///   __u8    len8_dlc; // 1      __u8    __res1;   // 1
453    ///   __u8    data[8];  // 8      __u8    data[64]; // 64
454    /// };                          };
455    /// ```
456    ///
457    /// Note `len` is a **byte count** in both structs — the kernel does the DLC encoding itself on
458    /// the way to the controller. The DLC still matters and is still reported to the model,
459    /// because it is what appears on the physical bus and what a DBC file is written against; for
460    /// a remote frame it is the only content there is, so it is written into `len`, which is where
461    /// the kernel reads an RTR request length from.
462    pub fn to_wire_bytes(&self) -> Result<Vec<u8>> {
463        self.validate()?;
464
465        let id_word = self.id_word().to_le_bytes();
466
467        if self.fd {
468            let mut out = vec![0u8; CANFD_MTU];
469            out[0..4].copy_from_slice(&id_word);
470            out[4] = self.data.len() as u8;
471            let mut flags = CANFD_FDF;
472            if self.brs {
473                flags |= CANFD_BRS;
474            }
475            if self.esi {
476                flags |= CANFD_ESI;
477            }
478            out[5] = flags;
479            out[8..8 + self.data.len()].copy_from_slice(&self.data);
480            Ok(out)
481        } else {
482            let mut out = vec![0u8; CAN_MTU];
483            out[0..4].copy_from_slice(&id_word);
484            out[4] = if self.rtr {
485                self.rtr_dlc
486            } else {
487                self.data.len() as u8
488            };
489            // `len8_dlc` carries a raw DLC above 8 for classic frames on controllers that allow
490            // it; NetGet never produces one, so it stays zero.
491            out[8..8 + self.data.len()].copy_from_slice(&self.data);
492            Ok(out)
493        }
494    }
495
496    /// Decode a `struct can_frame` or `struct canfd_frame`.
497    ///
498    /// The length picks the layout, which is exactly how `read()` on an `AF_CAN` socket
499    /// distinguishes them: an FD-enabled socket returns 72 octets for an FD frame and 16 for a
500    /// classic one.
501    pub fn from_wire_bytes(bytes: &[u8]) -> Result<Self> {
502        match bytes.len() {
503            CAN_MTU => {
504                let id_word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
505                let rtr = id_word & CAN_RTR_FLAG != 0;
506                let len = (bytes[4] as usize).min(CAN_MAX_DLEN);
507                let mut frame =
508                    Self::from_id_word(id_word, bytes[8..8 + len].to_vec(), false, false, false);
509                if rtr {
510                    // For an RTR frame `len` is the requested byte count, not a payload length.
511                    frame.rtr_dlc = bytes[4] & 0x0F;
512                }
513                Ok(frame)
514            }
515            CANFD_MTU => {
516                let id_word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
517                let len = (bytes[4] as usize).min(CANFD_MAX_DLEN);
518                let flags = bytes[5];
519                Ok(Self::from_id_word(
520                    id_word,
521                    bytes[8..8 + len].to_vec(),
522                    true,
523                    flags & CANFD_BRS != 0,
524                    flags & CANFD_ESI != 0,
525                ))
526            }
527            other => Err(anyhow!(
528                "a SocketCAN frame is {CAN_MTU} octets (struct can_frame) or {CANFD_MTU} octets \
529                 (struct canfd_frame); got {other}"
530            )),
531        }
532    }
533
534    // ---------------------------------------------------------------------------------------------
535    // Error frames
536    // ---------------------------------------------------------------------------------------------
537
538    /// The error classes this frame reports, as names, or `None` if it is not an error frame.
539    ///
540    /// The model is given names rather than the bitmask because a mask is exactly the kind of
541    /// thing it cannot reliably decode, and because these names are what `candump` prints and
542    /// what every CAN document calls them.
543    pub fn error_classes(&self) -> Option<Vec<&'static str>> {
544        if !self.error {
545            return None;
546        }
547        let mut classes = Vec::new();
548        for (bit, name) in [
549            (CAN_ERR_TX_TIMEOUT, "tx_timeout"),
550            (CAN_ERR_LOSTARB, "arbitration_lost"),
551            (CAN_ERR_CRTL, "controller_problem"),
552            (CAN_ERR_PROT, "protocol_violation"),
553            (CAN_ERR_TRX, "transceiver_status"),
554            (CAN_ERR_ACK, "no_acknowledgement"),
555            (CAN_ERR_BUSOFF, "bus_off"),
556            (CAN_ERR_BUSERROR, "bus_error"),
557            (CAN_ERR_RESTARTED, "controller_restarted"),
558            (CAN_ERR_CNT, "error_counters"),
559        ] {
560            if self.id & bit != 0 {
561                classes.push(name);
562            }
563        }
564        if classes.is_empty() {
565            classes.push("unspecified");
566        }
567        Some(classes)
568    }
569
570    /// The controller state this error frame reports, if it reports one.
571    ///
572    /// `None` for a data frame, and for an error frame that says nothing about confinement — a
573    /// lost arbitration is not a state change. This is the emit condition for
574    /// `can_bus_state_changed`, so getting it wrong would either flood the model with
575    /// non-transitions or hide a bus-off.
576    pub fn bus_state(&self) -> Option<BusState> {
577        if !self.error {
578            return None;
579        }
580        if self.id & CAN_ERR_BUSOFF != 0 {
581            return Some(BusState::BusOff);
582        }
583        if self.id & CAN_ERR_RESTARTED != 0 {
584            return Some(BusState::ErrorActive);
585        }
586        if self.id & CAN_ERR_CRTL == 0 {
587            return None;
588        }
589        // data[1] carries the controller status for CAN_ERR_CRTL. A frame too short to hold it
590        // is malformed; report nothing rather than guessing.
591        let status = *self.data.get(1)?;
592        if status & (CAN_ERR_CRTL_RX_PASSIVE | CAN_ERR_CRTL_TX_PASSIVE) != 0 {
593            Some(BusState::ErrorPassive)
594        } else if status & (CAN_ERR_CRTL_RX_WARNING | CAN_ERR_CRTL_TX_WARNING) != 0 {
595            Some(BusState::ErrorWarning)
596        } else if status & CAN_ERR_CRTL_ACTIVE != 0 {
597            Some(BusState::ErrorActive)
598        } else {
599            // Overflows are real problems but not confinement states.
600            None
601        }
602    }
603
604    // ---------------------------------------------------------------------------------------------
605    // JSON
606    // ---------------------------------------------------------------------------------------------
607
608    /// The identifier written the way automotive documentation writes it.
609    pub fn id_hex(&self) -> String {
610        if self.extended {
611            format!("0x{:08X}", self.id)
612        } else {
613            format!("0x{:03X}", self.id)
614        }
615    }
616
617    /// A one-line description for the log.
618    pub fn describe(&self) -> String {
619        if self.error {
620            return format!(
621                "error frame [{}]",
622                self.error_classes().unwrap_or_default().join(",")
623            );
624        }
625        let kind = match (self.fd, self.rtr) {
626            (true, _) => {
627                if self.brs {
628                    "FD/BRS"
629                } else {
630                    "FD"
631                }
632            }
633            (false, true) => "RTR",
634            (false, false) => "CAN",
635        };
636        format!(
637            "{} {} dlc={} {}",
638            kind,
639            self.id_hex(),
640            self.dlc(),
641            if self.rtr {
642                "(no data)".to_string()
643            } else {
644                hex::encode(&self.data)
645            }
646        )
647    }
648
649    /// Event data for `can_frame_received` and friends.
650    ///
651    /// `data` is hex and `data_encoding` says so, every time. See [`Self::from_action`] for why
652    /// hex is the default here and not in most of this codebase.
653    pub fn to_event_data(&self) -> serde_json::Map<String, Value> {
654        let mut map = serde_json::Map::new();
655        map.insert("id".into(), json!(self.id_hex()));
656        map.insert("id_decimal".into(), json!(self.id));
657        map.insert("extended".into(), json!(self.extended));
658        map.insert("rtr".into(), json!(self.rtr));
659        map.insert("error".into(), json!(self.error));
660        map.insert("fd".into(), json!(self.fd));
661        map.insert("brs".into(), json!(self.brs));
662        map.insert("esi".into(), json!(self.esi));
663        map.insert("dlc".into(), json!(self.dlc()));
664        map.insert("data_length".into(), json!(self.data.len()));
665        map.insert("data".into(), json!(hex::encode(&self.data)));
666        map.insert("data_encoding".into(), json!("hex"));
667        if let Some(classes) = self.error_classes() {
668            map.insert("error_classes".into(), json!(classes));
669        }
670        if let Some(state) = self.bus_state() {
671            map.insert("bus_state".into(), json!(state.as_str()));
672        }
673        map
674    }
675
676    /// Build a frame from a `send_can_frame` action object.
677    ///
678    /// Pure, so the whole action vocabulary is tested on a machine with no CAN stack.
679    ///
680    /// # `encoding` is read, never sniffed
681    ///
682    /// `"hex"` is the default, which is unusual in this codebase and is right here: a CAN payload
683    /// is eight bytes whose meaning is defined by a DBC file NetGet does not have. There is no
684    /// text to default to. `"text"` is offered because an ASCII payload does occur (diagnostic
685    /// VIN responses, some telematics gateways) and writing `"484921"` for `"HI!"` helps nobody.
686    ///
687    /// The executor **really decodes** what the field says. It never guesses: `"48656c6c6f"` is
688    /// simultaneously valid text and valid hex, and only the sender knows which it meant. This is
689    /// the `send_tcp_data` defect the root `CLAUDE.md` records, where the documentation promised
690    /// hex and the executor called `as_bytes()`, putting literal ASCII on the wire.
691    pub fn from_action(action: &Value) -> Result<Self> {
692        let extended = action
693            .get("extended")
694            .and_then(Value::as_bool)
695            .unwrap_or(false);
696        let rtr = action.get("rtr").and_then(Value::as_bool).unwrap_or(false);
697        let fd = action.get("fd").and_then(Value::as_bool).unwrap_or(false);
698        let brs = action.get("brs").and_then(Value::as_bool).unwrap_or(false);
699
700        let id = parse_id(
701            action
702                .get("id")
703                .context("send_can_frame requires 'id' (e.g. \"0x7DF\" or 2015)")?,
704        )?;
705
706        if rtr {
707            let dlc = action
708                .get("dlc")
709                .and_then(Value::as_u64)
710                .or_else(|| action.get("data_length").and_then(Value::as_u64))
711                .unwrap_or(0);
712            if dlc > CAN_MAX_DLEN as u64 {
713                bail!("a remote frame can request at most 8 bytes; dlc {dlc} given");
714            }
715            let mut frame = Self::remote(id, extended, dlc as u8)?;
716            frame.fd = fd;
717            frame.brs = brs;
718            // Re-validated so an `"rtr": true, "fd": true` combination is rejected by name rather
719            // than encoded into something the bus cannot carry.
720            frame.validate()?;
721            return Ok(frame);
722        }
723
724        let data = decode_payload(action)?;
725        let frame = Self {
726            id,
727            extended,
728            rtr: false,
729            error: false,
730            fd,
731            brs,
732            esi: false,
733            data,
734            rtr_dlc: 0,
735        };
736        frame.validate()?;
737        Ok(frame)
738    }
739}
740
741/// Decode a `send_can_frame` payload according to its declared `encoding`.
742///
743/// Absent `data` is an empty payload — a zero-length frame is legal CAN and is how a node
744/// announces presence without content.
745fn decode_payload(action: &Value) -> Result<Vec<u8>> {
746    let Some(data) = action.get("data") else {
747        return Ok(Vec::new());
748    };
749
750    // A JSON array of byte values is accepted because a script handler naturally produces one,
751    // and refusing it would push script authors into hex-encoding by hand.
752    if let Some(array) = data.as_array() {
753        return array
754            .iter()
755            .map(|v| {
756                v.as_u64()
757                    .filter(|n| *n <= 0xFF)
758                    .map(|n| n as u8)
759                    .ok_or_else(|| {
760                        anyhow!("every element of a 'data' array must be 0-255, got {v}")
761                    })
762            })
763            .collect();
764    }
765
766    let text = data
767        .as_str()
768        .ok_or_else(|| anyhow!("'data' must be a string (see 'encoding') or an array of bytes"))?;
769
770    match action.get("encoding").and_then(Value::as_str) {
771        None | Some("hex") => {
772            let cleaned: String = text
773                .chars()
774                .filter(|c| !c.is_whitespace() && *c != ':' && *c != '-')
775                .collect();
776            let cleaned = cleaned
777                .strip_prefix("0x")
778                .or_else(|| cleaned.strip_prefix("0X"))
779                .unwrap_or(&cleaned)
780                .to_string();
781            hex::decode(&cleaned).with_context(|| {
782                format!(
783                    "'data' is hex-encoded (the default for CAN) but {text:?} is not valid hex. \
784                     Pass \"encoding\": \"text\" if you meant these characters literally."
785                )
786            })
787        }
788        Some("text") => Ok(text.as_bytes().to_vec()),
789        Some(other) => Err(anyhow!(
790            "unknown encoding {other:?}: expected \"hex\" (the default) or \"text\""
791        )),
792    }
793}
794
795/// Parse an identifier written as a JSON number or as a hex string.
796///
797/// `"0x7DF"`, `"7DF"` and `2015` are the same identifier. Hex is accepted — and is how every CAN
798/// document, DBC file and `candump` line writes an identifier — but it is a fixed-width numeric
799/// identifier in its published notation, not a byte blob, which is the same reasoning that lets
800/// `bluetooth_ble_beacon` take an Eddystone namespace as hex.
801pub fn parse_id(value: &Value) -> Result<u32> {
802    if let Some(n) = value.as_u64() {
803        return u32::try_from(n)
804            .map_err(|_| anyhow!("identifier {n} does not fit in 32 bits, let alone 29"));
805    }
806    let text = value
807        .as_str()
808        .ok_or_else(|| anyhow!("'id' must be a number or a hex string such as \"0x7DF\""))?
809        .trim();
810    let (body, radix) = match text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
811        Some(body) => (body, 16),
812        // A bare string is hex: that is the universal notation for a CAN identifier, and reading
813        // "0700" as seven hundred decimal would silently address a different ECU.
814        None => (text, 16),
815    };
816    u32::from_str_radix(body, radix)
817        .with_context(|| format!("'id' {text:?} is not a valid CAN identifier"))
818}