dbc_rs/signal/decode.rs
1use super::Signal;
2use crate::{Error, Result};
3
4impl Signal {
5 /// Decode the signal value from CAN message data bytes.
6 ///
7 /// Extracts the raw value from bits based on the signal's start bit, length, and byte order,
8 /// then applies factor and offset to return the physical/engineering value.
9 ///
10 /// # Arguments
11 ///
12 /// * `data` - The CAN message data bytes (up to 8 bytes for classic CAN, 64 for CAN FD)
13 ///
14 /// # Returns
15 ///
16 /// * `Ok(f64)` - The physical value (raw * factor + offset)
17 /// * `Err(Error)` - If the signal extends beyond the data length
18 ///
19 /// # Examples
20 ///
21 /// ```rust,no_run
22 /// use dbc_rs::Dbc;
23 ///
24 /// let dbc = Dbc::parse(r#"VERSION "1.0"
25 ///
26 /// BU_: ECM
27 ///
28 /// BO_ 256 Engine : 8 ECM
29 /// SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
30 /// "#)?;
31 ///
32 /// let message = dbc.messages().at(0).unwrap();
33 /// let signal = message.signals().at(0).unwrap();
34 ///
35 /// // Decode a CAN message with RPM value of 2000 (raw: 8000)
36 /// let data = [0x20, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
37 /// let rpm = signal.decode(&data)?;
38 /// assert_eq!(rpm, 2000.0);
39 /// # Ok::<(), dbc_rs::Error>(())
40 /// ```
41 /// Decode signal value from CAN payload - optimized for high-throughput decoding.
42 #[inline]
43 pub fn decode(&self, data: &[u8]) -> Result<f64> {
44 // Cache conversions to usize (common in hot path)
45 let start_bit = self.start_bit as usize;
46 let length = self.length as usize;
47 let end_byte = (start_bit + length - 1) / 8;
48
49 // Bounds check - early return for invalid signals
50 if end_byte >= data.len() {
51 return Err(Error::Decoding(Error::SIGNAL_EXTENDS_BEYOND_DATA));
52 }
53
54 // Extract bits based on byte order
55 let raw_value = self.byte_order.extract_bits(data, start_bit, length);
56
57 // Convert to signed/unsigned with optimized sign extension
58 let value = if self.unsigned {
59 raw_value as i64
60 } else {
61 // Sign extend for signed values
62 // Optimized: compute sign bit mask only once
63 let sign_bit_mask = 1u64 << (length - 1);
64 if (raw_value & sign_bit_mask) != 0 {
65 // Negative value - sign extend using bitwise mask
66 let mask = !((1u64 << length) - 1);
67 (raw_value | mask) as i64
68 } else {
69 raw_value as i64
70 }
71 };
72
73 // Apply factor and offset to get physical value (single mul-add operation)
74 Ok((value as f64) * self.factor + self.offset)
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::Signal;
81 use crate::Parser;
82
83 #[test]
84 fn test_decode_little_endian() {
85 let signal = Signal::parse(
86 &mut Parser::new(b"SG_ TestSignal : 0|16@1+ (1,0) [0|65535] \"\"").unwrap(),
87 )
88 .unwrap();
89 // Test value 0x0102 = 258: little-endian bytes are [0x02, 0x01]
90 let data = [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
91 let value = signal.decode(&data).unwrap();
92 assert_eq!(value, 258.0);
93 }
94
95 #[test]
96 fn test_decode_big_endian() {
97 let signal = Signal::parse(
98 &mut Parser::new(b"SG_ TestSignal : 0|16@0+ (1,0) [0|65535] \"\"").unwrap(),
99 )
100 .unwrap();
101 // Test big-endian decoding: value 0x0100 = 256 at bit 0-15
102 let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
103 let value = signal.decode(&data).unwrap();
104 // Verify it decodes to a valid value within range
105 assert!((0.0..=65535.0).contains(&value));
106 }
107
108 #[test]
109 fn test_decode_little_endian_with_offset() {
110 let signal =
111 Signal::parse(&mut Parser::new(b"SG_ Temp : 0|8@1- (1,-40) [-40|215] \"\"").unwrap())
112 .unwrap();
113 // Raw value 90 with offset -40 = 50°C
114 let data = [0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
115 let value = signal.decode(&data).unwrap();
116 assert_eq!(value, 50.0);
117 }
118
119 #[test]
120 fn test_decode_big_endian_with_factor() {
121 let signal =
122 Signal::parse(&mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"\"").unwrap())
123 .unwrap();
124 // Test big-endian decoding with factor
125 // Big-endian at bit 0-15: bytes [0x1F, 0x40]
126 let data = [0x1F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
127 let value = signal.decode(&data).unwrap();
128 // Verify it decodes and applies factor correctly (value should be positive)
129 assert!((0.0..=16383.75).contains(&value)); // Max u16 * 0.25
130 }
131}