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/// let frame = Frame::new(CanId::standard(0x100), &[0x01, 0x02, 0x03]).unwrap();
92/// assert_eq!(frame.data(), &[0x01, 0x02, 0x03]);
93/// assert_eq!(frame.dlc(), 3);
94/// assert!(!frame.is_fd());
95/// ```
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct Frame {
98    id: CanId,
99    data: [u8; MAX_FD_DATA],
100    len: usize,
101    fd: bool,
102    remote: bool,
103}
104
105impl Frame {
106    /// Builds a classic CAN 2.0 data frame.
107    ///
108    /// # Arguments
109    ///
110    /// * `id` - the arbitration identifier.
111    /// * `data` - the payload, at most 8 bytes.
112    ///
113    /// # Returns
114    ///
115    /// The frame.
116    ///
117    /// # Errors
118    ///
119    /// Returns [`CanError::DataTooLong`] if `data` is longer than 8 bytes.
120    pub fn new(id: CanId, data: &[u8]) -> Result<Frame, CanError> {
121        if data.len() > 8 {
122            return Err(CanError::DataTooLong);
123        }
124        Ok(Self::store(id, data, false, false))
125    }
126
127    /// Builds a CAN-FD data frame.
128    ///
129    /// # Arguments
130    ///
131    /// * `id` - the arbitration identifier.
132    /// * `data` - the payload, at one of the discrete CAN-FD lengths up to 64 bytes.
133    ///
134    /// # Returns
135    ///
136    /// The frame.
137    ///
138    /// # Errors
139    ///
140    /// Returns [`CanError::DataTooLong`] if `data` is longer than 64 bytes, or
141    /// [`CanError::InvalidFdLength`] if its length is not one CAN-FD can carry.
142    pub fn fd(id: CanId, data: &[u8]) -> Result<Frame, CanError> {
143        if data.len() > MAX_FD_DATA {
144            return Err(CanError::DataTooLong);
145        }
146        if !is_fd_length(data.len()) {
147            return Err(CanError::InvalidFdLength);
148        }
149        Ok(Self::store(id, data, true, false))
150    }
151
152    /// Builds a classic remote frame, which requests data of a given length and carries
153    /// none.
154    ///
155    /// # Arguments
156    ///
157    /// * `id` - the arbitration identifier.
158    /// * `len` - the data length being requested, clamped to 8 bytes.
159    ///
160    /// # Returns
161    ///
162    /// The remote frame.
163    pub fn remote(id: CanId, len: usize) -> Frame {
164        Frame {
165            id,
166            data: [0; MAX_FD_DATA],
167            len: len.min(8),
168            fd: false,
169            remote: true,
170        }
171    }
172
173    fn store(id: CanId, data: &[u8], fd: bool, remote: bool) -> Frame {
174        let mut buf = [0; MAX_FD_DATA];
175        buf[..data.len()].copy_from_slice(data);
176        Frame {
177            id,
178            data: buf,
179            len: data.len(),
180            fd,
181            remote,
182        }
183    }
184
185    /// Returns the arbitration identifier.
186    ///
187    /// # Returns
188    ///
189    /// The identifier.
190    pub fn id(&self) -> CanId {
191        self.id
192    }
193
194    /// Returns the frame's data.
195    ///
196    /// # Returns
197    ///
198    /// The payload bytes, or an empty slice for a remote frame.
199    pub fn data(&self) -> &[u8] {
200        if self.remote {
201            &[]
202        } else {
203            &self.data[..self.len]
204        }
205    }
206
207    /// Reads the frame's data as J1939 signals.
208    ///
209    /// A J1939 message always carries exactly eight bytes, addressed by the offsets its
210    /// parameter group publishes, so this hands back a view that reads them by offset
211    /// rather than a slice a caller has to index.
212    ///
213    /// # Returns
214    ///
215    /// The eight data bytes as [`Signals`](crate::Signals), or `None` if this frame does
216    /// not carry exactly eight, which a J1939 message always does.
217    pub fn signals(&self) -> Option<crate::Signals> {
218        let bytes: [u8; 8] = self.data().try_into().ok()?;
219        Some(crate::Signals::from_bytes(bytes))
220    }
221
222    /// Returns the data length: the payload length, or the requested length for a remote
223    /// frame.
224    ///
225    /// # Returns
226    ///
227    /// The length in bytes.
228    pub fn len(&self) -> usize {
229        self.len
230    }
231
232    /// Reports whether the frame carries no data.
233    ///
234    /// # Returns
235    ///
236    /// `true` if the length is zero.
237    pub fn is_empty(&self) -> bool {
238        self.len == 0
239    }
240
241    /// Returns the data-length code for this frame's length.
242    ///
243    /// # Returns
244    ///
245    /// The 4-bit data-length code.
246    pub fn dlc(&self) -> u8 {
247        len_to_dlc(self.len)
248    }
249
250    /// Reports whether this is a CAN-FD frame.
251    ///
252    /// # Returns
253    ///
254    /// `true` for a CAN-FD frame.
255    pub fn is_fd(&self) -> bool {
256        self.fd
257    }
258
259    /// Reports whether this is a remote frame.
260    ///
261    /// # Returns
262    ///
263    /// `true` for a remote frame.
264    pub fn is_remote(&self) -> bool {
265        self.remote
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn a_classic_frame_holds_its_data() {
275        let frame = Frame::new(CanId::standard(0x100), &[1, 2, 3, 4]).unwrap();
276        assert_eq!(frame.data(), &[1, 2, 3, 4]);
277        assert_eq!(frame.len(), 4);
278        assert_eq!(frame.dlc(), 4);
279        assert!(!frame.is_fd());
280        assert!(!frame.is_remote());
281    }
282
283    #[test]
284    fn a_classic_frame_rejects_more_than_eight_bytes() {
285        assert_eq!(
286            Frame::new(CanId::standard(0x100), &[0; 9]),
287            Err(CanError::DataTooLong)
288        );
289    }
290
291    #[test]
292    fn an_fd_frame_carries_up_to_sixty_four_bytes() {
293        let frame = Frame::fd(CanId::extended(0x1234), &[0xAB; 64]).unwrap();
294        assert_eq!(frame.len(), 64);
295        assert_eq!(frame.dlc(), 15);
296        assert!(frame.is_fd());
297    }
298
299    #[test]
300    fn an_fd_frame_rejects_a_length_it_cannot_carry() {
301        // 9 bytes is not a valid CAN-FD length.
302        assert_eq!(
303            Frame::fd(CanId::standard(0x1), &[0; 9]),
304            Err(CanError::InvalidFdLength)
305        );
306    }
307
308    #[test]
309    fn an_fd_frame_rejects_more_than_sixty_four_bytes() {
310        assert_eq!(
311            Frame::fd(CanId::standard(0x1), &[0; 65]),
312            Err(CanError::DataTooLong)
313        );
314    }
315
316    #[test]
317    fn a_remote_frame_requests_a_length_and_carries_no_data() {
318        let frame = Frame::remote(CanId::standard(0x200), 8);
319        assert!(frame.is_remote());
320        assert_eq!(frame.data(), &[]);
321        assert_eq!(frame.len(), 8);
322        assert_eq!(frame.dlc(), 8);
323    }
324
325    #[test]
326    fn the_dlc_encoding_round_trips_at_each_step() {
327        for &len in &[0usize, 1, 8, 12, 16, 20, 24, 32, 48, 64] {
328            assert_eq!(dlc_to_len(len_to_dlc(len)), len);
329        }
330    }
331
332    #[test]
333    fn a_length_between_steps_rounds_up() {
334        assert_eq!(len_to_dlc(9), 9); // the code for 12 bytes
335        assert_eq!(dlc_to_len(9), 12);
336        assert_eq!(len_to_dlc(33), 14); // the code for 48 bytes
337        assert_eq!(dlc_to_len(14), 48);
338    }
339
340    #[test]
341    fn a_zero_length_fd_frame_is_valid() {
342        let frame = Frame::fd(CanId::standard(0x1), &[]).unwrap();
343        assert!(frame.is_fd());
344        assert!(frame.is_empty());
345        assert_eq!(frame.len(), 0);
346        assert_eq!(frame.dlc(), 0);
347        assert_eq!(frame.data(), &[]);
348    }
349
350    #[test]
351    fn a_classic_frame_can_be_empty() {
352        let frame = Frame::new(CanId::standard(0x1), &[]).unwrap();
353        assert!(frame.is_empty());
354        assert_eq!(frame.dlc(), 0);
355    }
356}