1use bitflags::bitflags;
4use thiserror::Error;
5
6bitflags! {
7 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
12 pub struct FrameFlags: u8 {
13 const EXTENDED_ID = 0b0000_0001;
15 const FD_FORMAT = 0b0000_0010;
17 const BIT_RATE_SWITCH = 0b0000_0100;
19 const ERROR_STATE = 0b0000_1000;
21 const REMOTE_REQUEST = 0b0001_0000;
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Error)]
29#[non_exhaustive]
30pub enum FrameError {
31 #[error("classical CAN payload too long: got {got} bytes, max is 8")]
33 PayloadTooLong {
34 got: usize,
36 },
37
38 #[error("invalid CAN-FD payload length: got {got} bytes")]
41 InvalidFdLength {
42 got: usize,
44 },
45
46 #[error("incompatible frame flags: {reason}")]
48 IncompatibleFlags {
49 reason: &'static str,
51 },
52}
53
54#[derive(Copy, Clone, Debug, PartialEq, Eq)]
62pub struct CanFrame {
63 pub id: u32,
65 pub flags: FrameFlags,
67 pub len: u8,
69 data: [u8; 64],
70}
71
72impl CanFrame {
73 pub fn classical(id: u32, payload: &[u8]) -> Result<Self, FrameError> {
75 if payload.len() > 8 {
76 return Err(FrameError::PayloadTooLong { got: payload.len() });
77 }
78 let mut data = [0u8; 64];
79 data[..payload.len()].copy_from_slice(payload);
80 Ok(Self {
81 id,
82 flags: FrameFlags::empty(),
83 len: payload.len() as u8,
84 data,
85 })
86 }
87
88 pub fn classical_extended(id: u32, payload: &[u8]) -> Result<Self, FrameError> {
90 let mut f = Self::classical(id, payload)?;
91 f.flags |= FrameFlags::EXTENDED_ID;
92 Ok(f)
93 }
94
95 pub fn fd(id: u32, payload: &[u8]) -> Result<Self, FrameError> {
97 if !is_valid_fd_dlc(payload.len()) {
98 return Err(FrameError::InvalidFdLength { got: payload.len() });
99 }
100 let mut data = [0u8; 64];
101 data[..payload.len()].copy_from_slice(payload);
102 Ok(Self {
103 id,
104 flags: FrameFlags::FD_FORMAT | FrameFlags::BIT_RATE_SWITCH,
105 len: payload.len() as u8,
106 data,
107 })
108 }
109
110 pub fn fd_extended(id: u32, payload: &[u8]) -> Result<Self, FrameError> {
112 let mut f = Self::fd(id, payload)?;
113 f.flags |= FrameFlags::EXTENDED_ID;
114 Ok(f)
115 }
116
117 pub fn from_parts(id: u32, flags: FrameFlags, payload: &[u8]) -> Result<Self, FrameError> {
121 check_flag_compat(flags)?;
122 if flags.contains(FrameFlags::FD_FORMAT) {
123 if !is_valid_fd_dlc(payload.len()) {
124 return Err(FrameError::InvalidFdLength { got: payload.len() });
125 }
126 } else if payload.len() > 8 {
127 return Err(FrameError::PayloadTooLong { got: payload.len() });
128 }
129 let mut data = [0u8; 64];
130 data[..payload.len()].copy_from_slice(payload);
131 Ok(Self {
132 id,
133 flags,
134 len: payload.len() as u8,
135 data,
136 })
137 }
138
139 pub fn payload(&self) -> &[u8] {
141 &self.data[..self.len as usize]
142 }
143
144 pub fn is_fd(&self) -> bool {
146 self.flags.contains(FrameFlags::FD_FORMAT)
147 }
148
149 pub fn is_extended(&self) -> bool {
151 self.flags.contains(FrameFlags::EXTENDED_ID)
152 }
153}
154
155pub fn is_valid_fd_dlc(len: usize) -> bool {
157 matches!(len, 0..=8 | 12 | 16 | 20 | 24 | 32 | 48 | 64)
158}
159
160fn check_flag_compat(flags: FrameFlags) -> Result<(), FrameError> {
161 if flags.contains(FrameFlags::FD_FORMAT) && flags.contains(FrameFlags::REMOTE_REQUEST) {
162 return Err(FrameError::IncompatibleFlags {
163 reason: "FD_FORMAT and REMOTE_REQUEST are mutually exclusive",
164 });
165 }
166 Ok(())
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn classical_8byte_ok() {
175 let f = CanFrame::classical(0x101, &[0xFF; 8]).unwrap();
176 assert_eq!(f.id, 0x101);
177 assert_eq!(f.len, 8);
178 assert!(!f.is_fd());
179 assert!(!f.is_extended());
180 assert_eq!(f.payload(), &[0xFF; 8]);
181 }
182
183 #[test]
184 fn classical_9byte_rejected() {
185 let err = CanFrame::classical(0x101, &[0; 9]).unwrap_err();
186 assert_eq!(err, FrameError::PayloadTooLong { got: 9 });
187 }
188
189 #[test]
190 fn fd_16byte_ok() {
191 let f = CanFrame::fd(0x101, &[0xAA; 16]).unwrap();
192 assert_eq!(f.id, 0x101);
193 assert_eq!(f.len, 16);
194 assert!(f.is_fd());
195 assert!(f.flags.contains(FrameFlags::BIT_RATE_SWITCH));
196 assert_eq!(f.payload(), &[0xAA; 16]);
197 }
198
199 #[test]
200 fn fd_invalid_dlc_rejected() {
201 let err = CanFrame::fd(0x101, &[0; 9]).unwrap_err();
202 assert_eq!(err, FrameError::InvalidFdLength { got: 9 });
203 }
204
205 #[test]
206 fn fd_dlc_table() {
207 for n in [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] {
208 let payload = alloc::vec![0u8; n];
209 assert!(CanFrame::fd(0x100, &payload).is_ok(), "len={n}");
210 }
211 for n in [9, 10, 11, 13, 17, 33, 65] {
212 let payload = alloc::vec![0u8; n];
213 assert!(CanFrame::fd(0x100, &payload).is_err(), "len={n}");
214 }
215 }
216
217 #[test]
218 fn is_fd_reflects_flag() {
219 let f = CanFrame::classical(0x100, &[]).unwrap();
220 assert_eq!(f.is_fd(), f.flags.contains(FrameFlags::FD_FORMAT));
221 let f = CanFrame::fd(0x100, &[]).unwrap();
222 assert_eq!(f.is_fd(), f.flags.contains(FrameFlags::FD_FORMAT));
223 }
224
225 #[test]
226 fn fd_and_rtr_incompatible() {
227 let bad = FrameFlags::FD_FORMAT | FrameFlags::REMOTE_REQUEST;
228 let err = CanFrame::from_parts(0x100, bad, &[]).unwrap_err();
229 matches!(err, FrameError::IncompatibleFlags { .. });
230 }
231
232 #[test]
233 fn flag_bits_compose() {
234 let f = FrameFlags::FD_FORMAT | FrameFlags::BIT_RATE_SWITCH;
235 assert!(f.contains(FrameFlags::FD_FORMAT));
236 assert!(f.contains(FrameFlags::BIT_RATE_SWITCH));
237 assert!(!f.contains(FrameFlags::REMOTE_REQUEST));
238 }
239
240 #[test]
241 fn error_display_is_informative() {
242 let s = alloc::format!("{}", FrameError::PayloadTooLong { got: 9 });
243 assert!(s.contains("classical"));
244 assert!(s.contains("9"));
245 let s = alloc::format!("{}", FrameError::InvalidFdLength { got: 13 });
246 assert!(s.contains("CAN-FD"));
247 }
248}