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}