dbc_rs/message/
decode.rs

1//! High-performance decoding for Message.
2
3use super::Message;
4
5impl Message {
6    /// Decode all signals into the output buffer (physical values).
7    ///
8    /// This is a zero-allocation decode path for high-speed CAN processing.
9    /// Signals are decoded in order and written to the output buffer.
10    ///
11    /// # Arguments
12    /// * `data` - Raw CAN payload bytes
13    /// * `out` - Output buffer for physical values (must be at least `signals().len()` long)
14    ///
15    /// # Returns
16    /// Number of signals decoded, or 0 if payload is too short.
17    ///
18    /// # Example
19    /// ```rust,ignore
20    /// let msg = dbc.messages().find_by_id(256).unwrap();
21    /// let mut values = [0.0f64; 64];
22    /// let count = msg.decode_into(&payload, &mut values);
23    /// for i in 0..count {
24    ///     let signal = msg.signals().at(i).unwrap();
25    ///     println!("{}: {}", signal.name(), values[i]);
26    /// }
27    /// ```
28    #[inline]
29    pub fn decode_into(&self, data: &[u8], out: &mut [f64]) -> usize {
30        // Check minimum payload length
31        let min_bytes = self.min_bytes_required() as usize;
32        if data.len() < min_bytes {
33            return 0;
34        }
35
36        // Use zip to avoid redundant bounds checks - iterates min(signals, out) times
37        let mut count = 0;
38        for (out_val, signal) in out.iter_mut().zip(self.signals().iter()) {
39            // decode_raw returns (raw_value, physical_value)
40            *out_val = signal.decode_raw(data).map(|(_, p)| p).unwrap_or(0.0);
41            count += 1;
42        }
43
44        count
45    }
46
47    /// Decode all signals into the output buffer (raw integer values).
48    ///
49    /// Returns raw values before factor/offset conversion.
50    /// Useful for encoding or debugging.
51    ///
52    /// # Arguments
53    /// * `data` - Raw CAN payload bytes
54    /// * `out` - Output buffer for raw values (must be at least `signals().len()` long)
55    ///
56    /// # Returns
57    /// Number of signals decoded, or 0 if payload is too short.
58    #[inline]
59    pub fn decode_raw_into(&self, data: &[u8], out: &mut [i64]) -> usize {
60        let min_bytes = self.min_bytes_required() as usize;
61        if data.len() < min_bytes {
62            return 0;
63        }
64
65        // Use zip to avoid redundant bounds checks
66        let mut count = 0;
67        for (out_val, signal) in out.iter_mut().zip(self.signals().iter()) {
68            *out_val = signal.decode_raw(data).map(|(r, _)| r).unwrap_or(0);
69            count += 1;
70        }
71
72        count
73    }
74
75    /// Decode a single signal by index.
76    ///
77    /// Returns the physical value or `None` if index is out of bounds or decode fails.
78    #[inline]
79    pub fn decode_signal(&self, index: usize, data: &[u8]) -> Option<f64> {
80        let signal = self.signals().at(index)?;
81        signal.decode_raw(data).ok().map(|(_, physical)| physical)
82    }
83
84    /// Decode a single signal by index (raw value).
85    ///
86    /// Returns the raw integer value or `None` if index is out of bounds or decode fails.
87    #[inline]
88    pub fn decode_signal_raw(&self, index: usize, data: &[u8]) -> Option<i64> {
89        let signal = self.signals().at(index)?;
90        signal.decode_raw(data).ok().map(|(raw, _)| raw)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::Dbc;
97
98    #[test]
99    fn test_decode_into_basic() {
100        let dbc = Dbc::parse(
101            r#"VERSION "1.0"
102
103BU_: ECM
104
105BO_ 256 Engine : 8 ECM
106 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
107 SG_ Temp : 16|8@1- (1,-40) [-40|215] "C" *
108"#,
109        )
110        .unwrap();
111
112        let msg = dbc.messages().find_by_id(256).unwrap();
113
114        // RPM = 2000 (raw 8000 = 0x1F40), Temp = 50°C (raw 90 = 0x5A)
115        let payload = [0x40, 0x1F, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00];
116        let mut values = [0.0f64; 8];
117
118        let count = msg.decode_into(&payload, &mut values);
119
120        assert_eq!(count, 2);
121        assert_eq!(values[0], 2000.0); // RPM
122        assert_eq!(values[1], 50.0); // Temp
123    }
124
125    #[test]
126    fn test_decode_raw_into() {
127        let dbc = Dbc::parse(
128            r#"VERSION "1.0"
129
130BU_: ECM
131
132BO_ 256 Engine : 8 ECM
133 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
134"#,
135        )
136        .unwrap();
137
138        let msg = dbc.messages().find_by_id(256).unwrap();
139
140        let payload = [0x40, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
141        let mut raw_values = [0i64; 8];
142
143        let count = msg.decode_raw_into(&payload, &mut raw_values);
144
145        assert_eq!(count, 1);
146        assert_eq!(raw_values[0], 8000); // Raw before factor
147    }
148
149    #[test]
150    fn test_decode_into_payload_too_short() {
151        let dbc = Dbc::parse(
152            r#"VERSION "1.0"
153
154BU_: ECM
155
156BO_ 256 Engine : 8 ECM
157 SG_ RPM : 0|16@1+ (1,0) [0|65535] "rpm" *
158"#,
159        )
160        .unwrap();
161
162        let msg = dbc.messages().find_by_id(256).unwrap();
163
164        // Payload too short (need 2 bytes for 16-bit signal)
165        let payload = [0x40];
166        let mut values = [0.0f64; 8];
167
168        let count = msg.decode_into(&payload, &mut values);
169
170        assert_eq!(count, 0);
171    }
172
173    #[test]
174    fn test_decode_signal_by_index() {
175        let dbc = Dbc::parse(
176            r#"VERSION "1.0"
177
178BU_: ECM
179
180BO_ 256 Engine : 8 ECM
181 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
182 SG_ Temp : 16|8@1- (1,-40) [-40|215] "C" *
183"#,
184        )
185        .unwrap();
186
187        let msg = dbc.messages().find_by_id(256).unwrap();
188        let payload = [0x40, 0x1F, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00];
189
190        assert_eq!(msg.decode_signal(0, &payload), Some(2000.0));
191        assert_eq!(msg.decode_signal(1, &payload), Some(50.0));
192        assert_eq!(msg.decode_signal(2, &payload), None); // Out of bounds
193    }
194
195    #[test]
196    fn test_decode_into_buffer_smaller_than_signals() {
197        let dbc = Dbc::parse(
198            r#"VERSION "1.0"
199
200BU_: ECM
201
202BO_ 256 Engine : 8 ECM
203 SG_ Sig1 : 0|8@1+ (1,0) [0|255] "" *
204 SG_ Sig2 : 8|8@1+ (1,0) [0|255] "" *
205 SG_ Sig3 : 16|8@1+ (1,0) [0|255] "" *
206"#,
207        )
208        .unwrap();
209
210        let msg = dbc.messages().find_by_id(256).unwrap();
211        let payload = [0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00];
212
213        // Buffer only has space for 2 values, but message has 3 signals
214        let mut values = [0.0f64; 2];
215        let count = msg.decode_into(&payload, &mut values);
216
217        assert_eq!(count, 2);
218        assert_eq!(values[0], 1.0);
219        assert_eq!(values[1], 2.0);
220    }
221}