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}