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}