dbc_rs/signal/
decode.rs

1use super::Signal;
2use crate::{Error, Result};
3
4impl Signal {
5    /// Decode the signal and return both raw and physical values in a single pass.
6    ///
7    /// This is an optimized method for multiplexer switch decoding where both the
8    /// raw integer value (for switch matching) and the physical value are needed.
9    /// Avoids the overhead of extracting bits twice.
10    ///
11    /// # Arguments
12    ///
13    /// * `data` - The CAN message data bytes (up to 8 bytes for classic CAN, 64 for CAN FD)
14    ///
15    /// # Returns
16    ///
17    /// * `Ok((raw_value, physical_value))` - The raw signed integer and physical (factor+offset) value
18    /// * `Err(Error)` - If the signal extends beyond the data length
19    #[inline]
20    pub(crate) fn decode_raw(&self, data: &[u8]) -> Result<(i64, f64)> {
21        let start_bit = self.start_bit as usize;
22        let length = self.length as usize;
23        let end_byte = (start_bit + length - 1) / 8;
24
25        if end_byte >= data.len() {
26            return Err(Error::Decoding(Error::SIGNAL_EXTENDS_BEYOND_DATA));
27        }
28
29        let raw_bits = self.byte_order.extract_bits(data, start_bit, length);
30
31        let raw_value = if self.unsigned {
32            raw_bits as i64
33        } else {
34            let sign_bit_mask = 1u64 << (length - 1);
35            if (raw_bits & sign_bit_mask) != 0 {
36                let mask = !((1u64 << length) - 1);
37                (raw_bits | mask) as i64
38            } else {
39                raw_bits as i64
40            }
41        };
42
43        let physical_value = (raw_value as f64) * self.factor + self.offset;
44        Ok((raw_value, physical_value))
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::Signal;
51    use crate::Parser;
52
53    #[test]
54    fn test_decode_little_endian() {
55        let signal = Signal::parse(
56            &mut Parser::new(b"SG_ TestSignal : 0|16@1+ (1,0) [0|65535] \"\"").unwrap(),
57        )
58        .unwrap();
59        // Test value 0x0102 = 258: little-endian bytes are [0x02, 0x01]
60        let data = [0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
61        let (_raw, value) = signal.decode_raw(&data).unwrap();
62        assert_eq!(value, 258.0);
63    }
64
65    #[test]
66    fn test_decode_big_endian() {
67        let signal = Signal::parse(
68            &mut Parser::new(b"SG_ TestSignal : 0|16@0+ (1,0) [0|65535] \"\"").unwrap(),
69        )
70        .unwrap();
71        // Test big-endian decoding: value 0x0100 = 256 at bit 0-15
72        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
73        let (_raw, value) = signal.decode_raw(&data).unwrap();
74        // Verify it decodes to a valid value within range
75        assert!((0.0..=65535.0).contains(&value));
76    }
77
78    #[test]
79    fn test_decode_little_endian_with_offset() {
80        let signal =
81            Signal::parse(&mut Parser::new(b"SG_ Temp : 0|8@1- (1,-40) [-40|215] \"\"").unwrap())
82                .unwrap();
83        // Raw value 90 with offset -40 = 50°C
84        let data = [0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
85        let (_raw, value) = signal.decode_raw(&data).unwrap();
86        assert_eq!(value, 50.0);
87    }
88
89    #[test]
90    fn test_decode_big_endian_with_factor() {
91        let signal =
92            Signal::parse(&mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"\"").unwrap())
93                .unwrap();
94        // Test big-endian decoding with factor
95        // Big-endian at bit 0-15: bytes [0x1F, 0x40]
96        let data = [0x1F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
97        let (_raw, value) = signal.decode_raw(&data).unwrap();
98        // Verify it decodes and applies factor correctly (value should be positive)
99        assert!((0.0..=16383.75).contains(&value)); // Max u16 * 0.25
100    }
101
102    // ============================================================================
103    // Specification Verification Tests
104    // These tests verify against exact examples from dbc/SPECIFICATIONS.md
105    // ============================================================================
106
107    /// Verify Section 10.3: Little-Endian Signals
108    /// Example from spec:
109    /// Signal: SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] "km/h"
110    /// Message bytes: [0x64, 0x00, ...]
111    /// Raw value = 0x0064 = 100 decimal
112    /// Physical = 100 × 0.1 = 10.0 km/h
113    #[test]
114    fn test_spec_section_10_3_little_endian_example() {
115        let signal = Signal::parse(
116            &mut Parser::new(b"SG_ Speed : 0|16@1+ (0.1,0) [0|6553.5] \"km/h\"").unwrap(),
117        )
118        .unwrap();
119
120        // Spec example: bytes [0x64, 0x00]
121        let data = [0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
122        let (_raw, value) = signal.decode_raw(&data).unwrap();
123
124        // Expected: raw=100, physical=100*0.1=10.0
125        assert_eq!(
126            value, 10.0,
127            "Spec Section 10.3: Little-endian 0x64 should decode to 10.0 km/h"
128        );
129    }
130
131    /// Verify Section 10.4: Big-Endian Signals
132    /// Example from spec:
133    /// Signal: SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] "kPa"
134    /// Message bytes: [0x03, 0xE8, ...]
135    /// Raw value = 0x03E8 = 1000 decimal
136    /// Physical = 1000 × 0.01 = 10.0 kPa
137    #[test]
138    fn test_spec_section_10_4_big_endian_example() {
139        let signal = Signal::parse(
140            &mut Parser::new(b"SG_ Pressure : 7|16@0+ (0.01,0) [0|655.35] \"kPa\"").unwrap(),
141        )
142        .unwrap();
143
144        // Spec example: bytes [0x03, 0xE8]
145        let data = [0x03, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
146        let (_raw, value) = signal.decode_raw(&data).unwrap();
147
148        // Expected: raw=1000 (0x03E8), physical=1000*0.01=10.0
149        assert_eq!(
150            value, 10.0,
151            "Spec Section 10.4: Big-endian 0x03E8 should decode to 10.0 kPa"
152        );
153    }
154
155    /// Verify Section 10.5: Value Conversion with Offset
156    /// Example from spec:
157    /// Signal: SG_ Temperature : 16|8@1- (1,-40) [-40|87] "°C"
158    /// Raw value = 127 (0x7F) → Physical = 127 × 1 + (-40) = 87°C
159    /// Raw value = 0 (0x00) → Physical = 0 × 1 + (-40) = -40°C
160    #[test]
161    fn test_spec_section_10_5_temperature_offset_example() {
162        let signal = Signal::parse(
163            &mut Parser::new(b"SG_ Temperature : 16|8@1- (1,-40) [-40|87] \"\"").unwrap(),
164        )
165        .unwrap();
166
167        // Test 1: raw=127 → physical=87°C
168        // Little-endian: signal at bit 16 means byte 2
169        let data1 = [0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00];
170        let (_raw, value1) = signal.decode_raw(&data1).unwrap();
171        assert_eq!(
172            value1, 87.0,
173            "Spec Section 10.5: raw=127 should decode to 87°C"
174        );
175
176        // Test 2: raw=0 → physical=-40°C
177        let data2 = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
178        let (_raw, value2) = signal.decode_raw(&data2).unwrap();
179        assert_eq!(
180            value2, -40.0,
181            "Spec Section 10.5: raw=0 should decode to -40°C"
182        );
183    }
184
185    /// Verify Section 10.2: Byte Order Values
186    /// @0 = Big-Endian (Motorola)
187    /// @1 = Little-Endian (Intel)
188    #[test]
189    fn test_spec_section_10_2_byte_order_values() {
190        use crate::ByteOrder;
191
192        // Verify enum values match spec
193        assert_eq!(
194            ByteOrder::BigEndian as u8,
195            0,
196            "Spec Section 10.2: @0 = Big-Endian"
197        );
198        assert_eq!(
199            ByteOrder::LittleEndian as u8,
200            1,
201            "Spec Section 10.2: @1 = Little-Endian"
202        );
203    }
204
205    /// Verify Section 10.5: Value Conversion Formula
206    /// physical_value = raw_value × factor + offset
207    #[test]
208    fn test_spec_section_10_5_value_conversion_formula() {
209        // Test with factor=0.25 and offset=100
210        let signal = Signal::parse(
211            &mut Parser::new(b"SG_ Test : 0|16@1+ (0.25,100) [0|1000] \"\"").unwrap(),
212        )
213        .unwrap();
214
215        // raw=400 → physical = 400 * 0.25 + 100 = 200
216        // Little-endian: 400 = 0x0190 → bytes [0x90, 0x01]
217        let data = [0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
218        let (_raw, value) = signal.decode_raw(&data).unwrap();
219        assert_eq!(value, 200.0, "Spec Section 10.5: 400 * 0.25 + 100 = 200");
220    }
221}