Skip to main content

pamoja_can/
frame.rs

1//! The CAN data frame and the length-to-DLC encoding it uses.
2
3use crate::error::CanError;
4use crate::id::CanId;
5
6// The largest payload a CAN-FD frame can carry.
7const MAX_FD_DATA: usize = 64;
8
9/// Maps a data length to the data-length code that represents it.
10///
11/// Classic CAN and the first nine CAN-FD codes are the length itself, 0 through 8. Above
12/// that CAN-FD jumps in steps, so a length between two steps rounds up to the next code.
13///
14/// # Arguments
15///
16/// * `len` - the data length in bytes.
17///
18/// # Returns
19///
20/// The 4-bit data-length code.
21///
22/// # Examples
23///
24/// ```
25/// use pamoja_can::len_to_dlc;
26///
27/// assert_eq!(len_to_dlc(8), 8);
28/// assert_eq!(len_to_dlc(12), 9);
29/// assert_eq!(len_to_dlc(64), 15);
30/// ```
31pub fn len_to_dlc(len: usize) -> u8 {
32    match len {
33        0..=8 => len as u8,
34        9..=12 => 9,
35        13..=16 => 10,
36        17..=20 => 11,
37        21..=24 => 12,
38        25..=32 => 13,
39        33..=48 => 14,
40        _ => 15,
41    }
42}
43
44/// Maps a data-length code to the number of bytes it represents.
45///
46/// # Arguments
47///
48/// * `dlc` - the data-length code; only its low four bits are used.
49///
50/// # Returns
51///
52/// The data length in bytes.
53///
54/// # Examples
55///
56/// ```
57/// use pamoja_can::dlc_to_len;
58///
59/// assert_eq!(dlc_to_len(8), 8);
60/// assert_eq!(dlc_to_len(15), 64);
61/// ```
62pub fn dlc_to_len(dlc: u8) -> usize {
63    match dlc & 0x0F {
64        small @ 0..=8 => small as usize,
65        9 => 12,
66        10 => 16,
67        11 => 20,
68        12 => 24,
69        13 => 32,
70        14 => 48,
71        _ => 64,
72    }
73}
74
75// Reports whether a length is one a CAN-FD frame can carry exactly.
76fn is_fd_length(len: usize) -> bool {
77    matches!(len, 0..=8 | 12 | 16 | 20 | 24 | 32 | 48 | 64)
78}
79
80/// A CAN frame: an identifier and its data.
81///
82/// Holds a classic CAN 2.0 frame (up to 8 bytes), a CAN-FD frame (up to 64 bytes at the
83/// discrete CAN-FD lengths), or a classic remote frame, which requests data and carries
84/// none. The data lives in a fixed buffer, so building a frame never allocates.
85///
86/// # Examples
87///
88/// ```
89/// use pamoja_can::{CanId, Frame};
90///
91/// // A cabin sensor reports 21.5 degrees as a count of tenths, big-endian, and a humidity
92/// // of 48 percent in the byte after it.
93/// let [high, low] = 215u16.to_be_bytes();
94/// let frame = Frame::new(CanId::standard(0x100), &[high, low, 48]).unwrap();
95/// assert_eq!(frame.data(), [high, low, 48]);
96/// assert_eq!(frame.dlc(), 3);
97/// assert!(!frame.is_fd());
98/// ```
99#[derive(Clone, Copy, Debug, PartialEq, Eq)]
100pub struct Frame {
101    id: CanId,
102    data: [u8; MAX_FD_DATA],
103    len: usize,
104    fd: bool,
105    remote: bool,
106}
107
108impl Frame {
109    /// Builds a classic CAN 2.0 data frame.
110    ///
111    /// # Arguments
112    ///
113    /// * `id` - the arbitration identifier.
114    /// * `data` - the payload, at most 8 bytes.
115    ///
116    /// # Returns
117    ///
118    /// The frame.
119    ///
120    /// # Errors
121    ///
122    /// Returns [`CanError::DataTooLong`] if `data` is longer than 8 bytes.
123    pub fn new(id: CanId, data: &[u8]) -> Result<Frame, CanError> {
124        if data.len() > 8 {
125            return Err(CanError::DataTooLong);
126        }
127        Ok(Self::store(id, data, false, false))
128    }
129
130    /// Builds a CAN-FD data frame.
131    ///
132    /// # Arguments
133    ///
134    /// * `id` - the arbitration identifier.
135    /// * `data` - the payload, at one of the discrete CAN-FD lengths up to 64 bytes.
136    ///
137    /// # Returns
138    ///
139    /// The frame.
140    ///
141    /// # Errors
142    ///
143    /// Returns [`CanError::DataTooLong`] if `data` is longer than 64 bytes, or
144    /// [`CanError::InvalidFdLength`] if its length is not one CAN-FD can carry.
145    pub fn fd(id: CanId, data: &[u8]) -> Result<Frame, CanError> {
146        if data.len() > MAX_FD_DATA {
147            return Err(CanError::DataTooLong);
148        }
149        if !is_fd_length(data.len()) {
150            return Err(CanError::InvalidFdLength);
151        }
152        Ok(Self::store(id, data, true, false))
153    }
154
155    /// Builds a classic remote frame, which requests data of a given length and carries
156    /// none.
157    ///
158    /// # Arguments
159    ///
160    /// * `id` - the arbitration identifier.
161    /// * `len` - the data length being requested, clamped to 8 bytes.
162    ///
163    /// # Returns
164    ///
165    /// The remote frame.
166    pub fn remote(id: CanId, len: usize) -> Frame {
167        Frame {
168            id,
169            data: [0; MAX_FD_DATA],
170            len: len.min(8),
171            fd: false,
172            remote: true,
173        }
174    }
175
176    fn store(id: CanId, data: &[u8], fd: bool, remote: bool) -> Frame {
177        let mut buf = [0; MAX_FD_DATA];
178        buf[..data.len()].copy_from_slice(data);
179        Frame {
180            id,
181            data: buf,
182            len: data.len(),
183            fd,
184            remote,
185        }
186    }
187
188    /// Returns the arbitration identifier.
189    ///
190    /// # Returns
191    ///
192    /// The identifier.
193    pub fn id(&self) -> CanId {
194        self.id
195    }
196
197    /// Returns the frame's data.
198    ///
199    /// # Returns
200    ///
201    /// The payload bytes, or an empty slice for a remote frame.
202    pub fn data(&self) -> &[u8] {
203        if self.remote {
204            &[]
205        } else {
206            &self.data[..self.len]
207        }
208    }
209
210    /// Reads the frame's data as J1939 signals.
211    ///
212    /// A J1939 message always carries exactly eight bytes, addressed by the offsets its
213    /// parameter group publishes, so this hands back a view that reads them by offset
214    /// rather than a slice a caller has to index.
215    ///
216    /// # Returns
217    ///
218    /// The eight data bytes as [`Signals`](crate::Signals), or `None` if this frame does
219    /// not carry exactly eight, which a J1939 message always does.
220    pub fn signals(&self) -> Option<crate::Signals> {
221        let bytes: [u8; 8] = self.data().try_into().ok()?;
222        Some(crate::Signals::from_bytes(bytes))
223    }
224
225    /// Returns the data length: the payload length, or the requested length for a remote
226    /// frame.
227    ///
228    /// # Returns
229    ///
230    /// The length in bytes.
231    pub fn len(&self) -> usize {
232        self.len
233    }
234
235    /// Reports whether the frame carries no data.
236    ///
237    /// # Returns
238    ///
239    /// `true` if the length is zero.
240    pub fn is_empty(&self) -> bool {
241        self.len == 0
242    }
243
244    /// Returns the data-length code for this frame's length.
245    ///
246    /// # Returns
247    ///
248    /// The 4-bit data-length code.
249    pub fn dlc(&self) -> u8 {
250        len_to_dlc(self.len)
251    }
252
253    /// Reports whether this is a CAN-FD frame.
254    ///
255    /// # Returns
256    ///
257    /// `true` for a CAN-FD frame.
258    pub fn is_fd(&self) -> bool {
259        self.fd
260    }
261
262    /// Reports whether this is a remote frame.
263    ///
264    /// # Returns
265    ///
266    /// `true` for a remote frame.
267    pub fn is_remote(&self) -> bool {
268        self.remote
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn a_classic_frame_holds_its_data() {
278        let frame = Frame::new(CanId::standard(0x100), &[1, 2, 3, 4]).unwrap();
279        assert_eq!(frame.data(), &[1, 2, 3, 4]);
280        assert_eq!(frame.len(), 4);
281        assert_eq!(frame.dlc(), 4);
282        assert!(!frame.is_fd());
283        assert!(!frame.is_remote());
284    }
285
286    #[test]
287    fn a_classic_frame_rejects_more_than_eight_bytes() {
288        assert_eq!(
289            Frame::new(CanId::standard(0x100), &[0; 9]),
290            Err(CanError::DataTooLong)
291        );
292    }
293
294    #[test]
295    fn an_fd_frame_carries_up_to_sixty_four_bytes() {
296        let frame = Frame::fd(CanId::extended(0x1234), &[0xAB; 64]).unwrap();
297        assert_eq!(frame.len(), 64);
298        assert_eq!(frame.dlc(), 15);
299        assert!(frame.is_fd());
300    }
301
302    #[test]
303    fn an_fd_frame_rejects_a_length_it_cannot_carry() {
304        // 9 bytes is not a valid CAN-FD length.
305        assert_eq!(
306            Frame::fd(CanId::standard(0x1), &[0; 9]),
307            Err(CanError::InvalidFdLength)
308        );
309    }
310
311    #[test]
312    fn an_fd_frame_rejects_more_than_sixty_four_bytes() {
313        assert_eq!(
314            Frame::fd(CanId::standard(0x1), &[0; 65]),
315            Err(CanError::DataTooLong)
316        );
317    }
318
319    #[test]
320    fn a_remote_frame_requests_a_length_and_carries_no_data() {
321        let frame = Frame::remote(CanId::standard(0x200), 8);
322        assert!(frame.is_remote());
323        assert_eq!(frame.data(), &[]);
324        assert_eq!(frame.len(), 8);
325        assert_eq!(frame.dlc(), 8);
326    }
327
328    #[test]
329    fn the_dlc_encoding_round_trips_at_each_step() {
330        for &len in &[0usize, 1, 8, 12, 16, 20, 24, 32, 48, 64] {
331            assert_eq!(dlc_to_len(len_to_dlc(len)), len);
332        }
333    }
334
335    #[test]
336    fn a_length_between_steps_rounds_up() {
337        assert_eq!(len_to_dlc(9), 9); // the code for 12 bytes
338        assert_eq!(dlc_to_len(9), 12);
339        assert_eq!(len_to_dlc(33), 14); // the code for 48 bytes
340        assert_eq!(dlc_to_len(14), 48);
341    }
342
343    #[test]
344    fn a_zero_length_fd_frame_is_valid() {
345        let frame = Frame::fd(CanId::standard(0x1), &[]).unwrap();
346        assert!(frame.is_fd());
347        assert!(frame.is_empty());
348        assert_eq!(frame.len(), 0);
349        assert_eq!(frame.dlc(), 0);
350        assert_eq!(frame.data(), &[]);
351    }
352
353    #[test]
354    fn a_classic_frame_can_be_empty() {
355        let frame = Frame::new(CanId::standard(0x1), &[]).unwrap();
356        assert!(frame.is_empty());
357        assert_eq!(frame.dlc(), 0);
358    }
359}