1use crate::{Error, Result};
21
22pub const MAX_STRING_LEN: usize = 32;
30
31#[derive(Clone, Copy)]
34pub struct ByteString {
35 data: [u8; MAX_STRING_LEN],
36 len: u8,
37}
38
39impl ByteString {
40 pub const fn new() -> Self {
42 Self {
43 data: [0; MAX_STRING_LEN],
44 len: 0,
45 }
46 }
47
48 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
51 if bytes.len() > MAX_STRING_LEN {
52 return Err(Error::BadLength);
53 }
54 let mut data = [0u8; MAX_STRING_LEN];
55 data[..bytes.len()].copy_from_slice(bytes);
56 Ok(Self {
57 data,
58 len: bytes.len() as u8,
59 })
60 }
61
62 #[allow(clippy::should_implement_trait)]
66 pub fn from_str(s: &str) -> Result<Self> {
67 Self::from_bytes(s.as_bytes())
68 }
69
70 pub fn as_bytes(&self) -> &[u8] {
72 &self.data[..self.len as usize]
73 }
74
75 pub fn as_str(&self) -> Option<&str> {
77 core::str::from_utf8(self.as_bytes()).ok()
78 }
79
80 pub const fn len(&self) -> usize {
82 self.len as usize
83 }
84
85 pub const fn is_empty(&self) -> bool {
87 self.len == 0
88 }
89}
90
91impl Default for ByteString {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl PartialEq for ByteString {
100 fn eq(&self, other: &Self) -> bool {
101 self.as_bytes() == other.as_bytes()
102 }
103}
104
105impl Eq for ByteString {}
106
107impl core::fmt::Debug for ByteString {
108 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109 match self.as_str() {
110 Some(s) => write!(f, "ByteString({s:?})"),
111 None => write!(f, "ByteString({:?})", self.as_bytes()),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum DataType {
123 Boolean = 0x01,
125 Integer8 = 0x02,
127 Integer16 = 0x03,
129 Integer32 = 0x04,
131 Unsigned8 = 0x05,
133 Unsigned16 = 0x06,
135 Unsigned32 = 0x07,
137 Real32 = 0x08,
139 Real64 = 0x11,
141 Integer64 = 0x15,
143 Unsigned64 = 0x1B,
145 VisibleString = 0x09,
147 OctetString = 0x0A,
149 Domain = 0x0F,
151}
152
153impl DataType {
154 pub const fn index(self) -> u16 {
156 self as u16
157 }
158
159 pub const fn from_index(index: u16) -> Option<Self> {
162 Some(match index {
163 0x01 => DataType::Boolean,
164 0x02 => DataType::Integer8,
165 0x03 => DataType::Integer16,
166 0x04 => DataType::Integer32,
167 0x05 => DataType::Unsigned8,
168 0x06 => DataType::Unsigned16,
169 0x07 => DataType::Unsigned32,
170 0x08 => DataType::Real32,
171 0x09 => DataType::VisibleString,
172 0x0A => DataType::OctetString,
173 0x0F => DataType::Domain,
174 0x11 => DataType::Real64,
175 0x15 => DataType::Integer64,
176 0x1B => DataType::Unsigned64,
177 _ => return None,
178 })
179 }
180
181 pub const fn fixed_size(self) -> Option<usize> {
184 Some(match self {
185 DataType::Boolean | DataType::Integer8 | DataType::Unsigned8 => 1,
186 DataType::Integer16 | DataType::Unsigned16 => 2,
187 DataType::Integer32 | DataType::Unsigned32 | DataType::Real32 => 4,
188 DataType::Integer64 | DataType::Unsigned64 | DataType::Real64 => 8,
189 DataType::VisibleString | DataType::OctetString | DataType::Domain => return None,
190 })
191 }
192
193 pub const fn is_variable(self) -> bool {
195 self.fixed_size().is_none()
196 }
197
198 pub const fn size(self) -> usize {
202 match self.fixed_size() {
203 Some(n) => n,
204 None => 0,
205 }
206 }
207}
208
209#[derive(Debug, Clone, Copy, PartialEq)]
211#[non_exhaustive]
212pub enum Value {
213 Boolean(bool),
215 Integer8(i8),
217 Integer16(i16),
219 Integer32(i32),
221 Unsigned8(u8),
223 Unsigned16(u16),
225 Unsigned32(u32),
227 Real32(f32),
229 Real64(f64),
231 Integer64(i64),
233 Unsigned64(u64),
235 VisibleString(ByteString),
237 OctetString(ByteString),
239 Domain(ByteString),
241}
242
243impl Value {
244 pub const fn data_type(&self) -> DataType {
246 match self {
247 Value::Boolean(_) => DataType::Boolean,
248 Value::Integer8(_) => DataType::Integer8,
249 Value::Integer16(_) => DataType::Integer16,
250 Value::Integer32(_) => DataType::Integer32,
251 Value::Unsigned8(_) => DataType::Unsigned8,
252 Value::Unsigned16(_) => DataType::Unsigned16,
253 Value::Unsigned32(_) => DataType::Unsigned32,
254 Value::Real32(_) => DataType::Real32,
255 Value::Real64(_) => DataType::Real64,
256 Value::Integer64(_) => DataType::Integer64,
257 Value::Unsigned64(_) => DataType::Unsigned64,
258 Value::VisibleString(_) => DataType::VisibleString,
259 Value::OctetString(_) => DataType::OctetString,
260 Value::Domain(_) => DataType::Domain,
261 }
262 }
263
264 pub const fn size(&self) -> usize {
267 match self {
268 Value::VisibleString(s) | Value::OctetString(s) | Value::Domain(s) => s.len(),
269 _ => self.data_type().size(),
270 }
271 }
272
273 pub fn encode_le(&self, buf: &mut [u8]) -> Result<usize> {
278 let n = self.size();
279 if buf.len() < n {
280 return Err(Error::BadLength);
281 }
282 match self {
283 Value::Boolean(v) => buf[0] = *v as u8,
284 Value::Integer8(v) => buf[0] = *v as u8,
285 Value::Unsigned8(v) => buf[0] = *v,
286 Value::Integer16(v) => buf[..2].copy_from_slice(&v.to_le_bytes()),
287 Value::Unsigned16(v) => buf[..2].copy_from_slice(&v.to_le_bytes()),
288 Value::Integer32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
289 Value::Unsigned32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
290 Value::Real32(v) => buf[..4].copy_from_slice(&v.to_le_bytes()),
291 Value::Integer64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
292 Value::Unsigned64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
293 Value::Real64(v) => buf[..8].copy_from_slice(&v.to_le_bytes()),
294 Value::VisibleString(s) | Value::OctetString(s) | Value::Domain(s) => {
295 buf[..n].copy_from_slice(s.as_bytes())
296 }
297 }
298 Ok(n)
299 }
300
301 pub fn decode_le(data_type: DataType, bytes: &[u8]) -> Result<Value> {
307 match data_type.fixed_size() {
308 Some(fixed) if bytes.len() != fixed => return Err(Error::BadLength),
309 None if bytes.len() > MAX_STRING_LEN => return Err(Error::BadLength),
310 _ => {}
311 }
312 let v = match data_type {
313 DataType::Boolean => Value::Boolean(bytes[0] != 0),
314 DataType::Integer8 => Value::Integer8(bytes[0] as i8),
315 DataType::Unsigned8 => Value::Unsigned8(bytes[0]),
316 DataType::Integer16 => Value::Integer16(i16::from_le_bytes([bytes[0], bytes[1]])),
317 DataType::Unsigned16 => Value::Unsigned16(u16::from_le_bytes([bytes[0], bytes[1]])),
318 DataType::Integer32 => {
319 Value::Integer32(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
320 }
321 DataType::Unsigned32 => {
322 Value::Unsigned32(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
323 }
324 DataType::Real32 => {
325 Value::Real32(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
326 }
327 DataType::Integer64 => Value::Integer64(i64::from_le_bytes([
328 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
329 ])),
330 DataType::Unsigned64 => Value::Unsigned64(u64::from_le_bytes([
331 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
332 ])),
333 DataType::Real64 => Value::Real64(f64::from_le_bytes([
334 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
335 ])),
336 DataType::VisibleString => Value::VisibleString(ByteString::from_bytes(bytes)?),
337 DataType::OctetString => Value::OctetString(ByteString::from_bytes(bytes)?),
338 DataType::Domain => Value::Domain(ByteString::from_bytes(bytes)?),
339 };
340 Ok(v)
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn sizes_match_spec() {
350 assert_eq!(DataType::Boolean.size(), 1);
351 assert_eq!(DataType::Unsigned16.size(), 2);
352 assert_eq!(DataType::Unsigned32.size(), 4);
353 assert_eq!(DataType::Real64.size(), 8);
354 }
355
356 #[test]
357 fn data_type_index_roundtrips() {
358 for dt in [
359 DataType::Boolean,
360 DataType::Unsigned32,
361 DataType::Real64,
362 DataType::Integer64,
363 DataType::Unsigned64,
364 DataType::VisibleString,
365 DataType::OctetString,
366 DataType::Domain,
367 ] {
368 assert_eq!(DataType::from_index(dt.index()), Some(dt));
369 }
370 assert_eq!(DataType::Unsigned32.index(), 0x0007);
371 assert_eq!(DataType::VisibleString.index(), 0x0009);
372 assert_eq!(DataType::from_index(0x0C), None);
374 }
375
376 #[test]
377 fn variable_length_types_have_no_fixed_size() {
378 for dt in [
379 DataType::VisibleString,
380 DataType::OctetString,
381 DataType::Domain,
382 ] {
383 assert!(dt.is_variable());
384 assert_eq!(dt.fixed_size(), None);
385 assert_eq!(dt.size(), 0);
386 }
387 assert!(!DataType::Unsigned32.is_variable());
388 assert_eq!(DataType::Unsigned32.fixed_size(), Some(4));
389 }
390
391 #[test]
392 fn string_values_round_trip() {
393 let mut buf = [0u8; MAX_STRING_LEN];
394 for v in [
395 Value::VisibleString(ByteString::from_str("canopen-rs").unwrap()),
396 Value::OctetString(ByteString::from_bytes(&[0, 1, 2, 0xFF]).unwrap()),
397 Value::Domain(ByteString::from_bytes(&[]).unwrap()), ] {
399 let n = v.encode_le(&mut buf).unwrap();
400 assert_eq!(n, v.size());
401 let back = Value::decode_le(v.data_type(), &buf[..n]).unwrap();
402 assert_eq!(v, back);
403 }
404 }
405
406 #[test]
407 fn bytestring_rejects_oversized_input() {
408 let too_long = [0u8; MAX_STRING_LEN + 1];
409 assert_eq!(ByteString::from_bytes(&too_long), Err(Error::BadLength));
410 assert_eq!(
411 Value::decode_le(DataType::VisibleString, &too_long),
412 Err(Error::BadLength)
413 );
414 }
415
416 #[test]
417 fn bytestring_equality_ignores_unused_tail() {
418 let a = ByteString::from_str("hi").unwrap();
419 let b = ByteString::from_bytes(b"hi").unwrap();
420 assert_eq!(a, b);
421 assert_eq!(a.as_str(), Some("hi"));
422 assert_eq!(a.len(), 2);
423 }
424
425 #[test]
426 fn u32_is_little_endian() {
427 let mut buf = [0u8; 8];
428 let n = Value::Unsigned32(0x1234_5678).encode_le(&mut buf).unwrap();
429 assert_eq!(n, 4);
430 assert_eq!(&buf[..4], &[0x78, 0x56, 0x34, 0x12]);
431 }
432
433 #[test]
434 fn decode_roundtrips() {
435 let cases = [
436 Value::Boolean(true),
437 Value::Integer8(-5),
438 Value::Unsigned16(0xBEEF),
439 Value::Integer32(-123456),
440 Value::Unsigned32(0xDEAD_BEEF),
441 Value::Unsigned64(0x0102_0304_0506_0708),
442 ];
443 let mut buf = [0u8; 8];
444 for v in cases {
445 let n = v.encode_le(&mut buf).unwrap();
446 let back = Value::decode_le(v.data_type(), &buf[..n]).unwrap();
447 assert_eq!(v, back);
448 }
449 }
450
451 #[test]
452 fn encode_rejects_short_buffer() {
453 let mut buf = [0u8; 2];
454 assert_eq!(
455 Value::Unsigned32(0).encode_le(&mut buf),
456 Err(Error::BadLength)
457 );
458 }
459
460 #[test]
461 fn decode_rejects_wrong_length() {
462 assert_eq!(
463 Value::decode_le(DataType::Unsigned32, &[0, 0]),
464 Err(Error::BadLength)
465 );
466 }
467}