dbc_rs/signal/
decode.rs

1use super::Signal;
2use crate::{ByteOrder, 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 = match self.byte_order {
56            ByteOrder::LittleEndian => Self::extract_bits_little_endian(data, start_bit, length),
57            ByteOrder::BigEndian => Self::extract_bits_big_endian(data, start_bit, length),
58        };
59
60        // Convert to signed/unsigned with optimized sign extension
61        let value = if self.unsigned {
62            raw_value as i64
63        } else {
64            // Sign extend for signed values
65            // Optimized: compute sign bit mask only once
66            let sign_bit_mask = 1u64 << (length - 1);
67            if (raw_value & sign_bit_mask) != 0 {
68                // Negative value - sign extend using bitwise mask
69                let mask = !((1u64 << length) - 1);
70                (raw_value | mask) as i64
71            } else {
72                raw_value as i64
73            }
74        };
75
76        // Apply factor and offset to get physical value (single mul-add operation)
77        Ok((value as f64) * self.factor + self.offset)
78    }
79
80    /// Extract bits from data using little-endian byte order.
81    /// Inlined for hot path optimization.
82    #[inline]
83    fn extract_bits_little_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
84        let mut value: u64 = 0;
85        let mut bits_remaining = length;
86        let mut current_bit = start_bit;
87
88        while bits_remaining > 0 {
89            let byte_idx = current_bit / 8;
90            let bit_in_byte = current_bit % 8;
91            let bits_to_take = bits_remaining.min(8 - bit_in_byte);
92
93            let byte = data[byte_idx] as u64;
94            let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
95            let extracted = (byte & mask) >> bit_in_byte;
96
97            value |= extracted << (length - bits_remaining);
98
99            bits_remaining -= bits_to_take;
100            current_bit += bits_to_take;
101        }
102
103        value
104    }
105
106    /// Extract bits from data using big-endian byte order.
107    /// Inlined for hot path optimization.
108    #[inline]
109    fn extract_bits_big_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
110        let mut value: u64 = 0;
111        let mut bits_remaining = length;
112        let mut current_bit = start_bit;
113
114        while bits_remaining > 0 {
115            let byte_idx = current_bit / 8;
116            let bit_in_byte = current_bit % 8;
117            let bits_to_take = bits_remaining.min(8 - bit_in_byte);
118
119            let byte = data[byte_idx] as u64;
120            let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
121            let extracted = (byte & mask) >> bit_in_byte;
122
123            // For big-endian, we need to reverse the bit order within bytes
124            // and place bits in the correct position
125            value |= extracted << (length - bits_remaining);
126
127            bits_remaining -= bits_to_take;
128            current_bit += bits_to_take;
129        }
130
131        value
132    }
133}