dbc_rs/dbc/
decode.rs

1use crate::{Dbc, Error, MAX_EXTENDED_MULTIPLEXING, MAX_SIGNALS_PER_MESSAGE, Result, compat::Vec};
2
3/// Decoding functionality for DBC structures
4impl Dbc {
5    /// Decode a CAN message payload using the message ID to find the corresponding message definition.
6    ///
7    /// This is a high-performance method for decoding CAN messages in `no_std` environments.
8    /// It finds the message by ID, then decodes all signals in the message from the payload bytes.
9    ///
10    /// # Arguments
11    ///
12    /// * `id` - The CAN message ID to look up
13    /// * `payload` - The CAN message payload bytes (up to 64 bytes for CAN FD)
14    ///
15    /// # Returns
16    ///
17    /// * `Ok(Vec<...>)` - A vector of (signal_name, physical_value) pairs
18    /// * `Err(Error)` - If the message ID is not found, payload length doesn't match DLC, or signal decoding fails
19    ///
20    /// # Examples
21    ///
22    /// ```rust,no_run
23    /// use dbc_rs::Dbc;
24    ///
25    /// let dbc = Dbc::parse(r#"VERSION "1.0"
26    ///
27    /// BU_: ECM
28    ///
29    /// BO_ 256 Engine : 8 ECM
30    ///  SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
31    /// "#)?;
32    ///
33    /// // Decode a CAN message with RPM value of 2000 (raw: 8000 = 0x1F40)
34    /// let payload = [0x40, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
35    /// let decoded = dbc.decode(256, &payload)?;
36    /// assert_eq!(decoded.len(), 1);
37    /// assert_eq!(decoded[0].0, "RPM");
38    /// assert_eq!(decoded[0].1, 2000.0);
39    /// assert_eq!(decoded[0].2, Some("rpm"));
40    /// # Ok::<(), dbc_rs::Error>(())
41    /// ```
42    /// High-performance CAN message decoding optimized for throughput.
43    ///
44    /// Performance optimizations:
45    /// - O(1) or O(log n) message lookup via feature-flagged index (heapless/alloc)
46    /// - Inlined hot paths
47    /// - Direct error construction (no closure allocation)
48    /// - Early validation to avoid unnecessary work
49    /// - Optimized signal decoding loop
50    #[inline]
51    pub fn decode(
52        &self,
53        id: u32,
54        payload: &[u8],
55    ) -> Result<Vec<(&str, f64, Option<&str>), { MAX_SIGNALS_PER_MESSAGE }>> {
56        // Find message by ID (performance-critical lookup)
57        // Uses optimized index when available (O(1) with heapless, O(log n) with alloc)
58        let message = self
59            .messages()
60            .find_by_id(id)
61            .ok_or(Error::Decoding(Error::MESSAGE_NOT_FOUND))?;
62
63        // Cache DLC conversion to avoid repeated casts
64        let dlc = message.dlc() as usize;
65
66        // Validate payload length matches message DLC (early return before any decoding)
67        if payload.len() < dlc {
68            return Err(Error::Decoding(Error::PAYLOAD_LENGTH_MISMATCH));
69        }
70
71        // Allocate Vec for decoded signals (name, value, unit)
72        // Note: heapless Vec grows as needed; alloc Vec allocates dynamically
73        let mut decoded_signals: Vec<(&str, f64, Option<&str>), { MAX_SIGNALS_PER_MESSAGE }> =
74            Vec::new();
75
76        let signals = message.signals();
77
78        // Step 1: Decode all multiplexer switch signals first
79        // Map switch signal names to their decoded values
80        let mut switch_values: Vec<(&str, u64), 16> = Vec::new();
81        for signal in signals.iter() {
82            if signal.is_multiplexer_switch() {
83                let value = signal.decode(payload)?;
84                // Store the raw integer value (before factor/offset) for switch matching
85                // We need to decode again to get the raw value for comparison
86                let raw_value = {
87                    let start_bit = signal.start_bit() as usize;
88                    let length = signal.length() as usize;
89                    let raw_bits = signal.byte_order().extract_bits(payload, start_bit, length);
90                    if signal.is_unsigned() {
91                        raw_bits as i64
92                    } else {
93                        let sign_bit_mask = 1u64 << (length - 1);
94                        if (raw_bits & sign_bit_mask) != 0 {
95                            let mask = !((1u64 << length) - 1);
96                            (raw_bits | mask) as i64
97                        } else {
98                            raw_bits as i64
99                        }
100                    }
101                };
102                // Multiplexer switch values must be non-negative (u64)
103                // If the raw value is negative, it cannot be used as a multiplexer switch value
104                if raw_value < 0 {
105                    return Err(Error::Decoding(Error::MULTIPLEXER_SWITCH_NEGATIVE));
106                }
107                switch_values
108                    .push((signal.name(), raw_value as u64))
109                    .map_err(|_| Error::Decoding(Error::MESSAGE_TOO_MANY_SIGNALS))?;
110                // Also add to decoded signals
111                decoded_signals
112                    .push((signal.name(), value, signal.unit()))
113                    .map_err(|_| Error::Decoding(Error::MESSAGE_TOO_MANY_SIGNALS))?;
114            }
115        }
116
117        // Step 2: Get extended multiplexing entries for this message
118        let extended_mux_entries = self.extended_multiplexing_for_message(id);
119
120        // Step 3: Decode all non-switch signals based on multiplexing rules
121        for signal in signals.iter() {
122            // Skip multiplexer switches (already decoded)
123            if signal.is_multiplexer_switch() {
124                continue;
125            }
126
127            // Check if signal should be decoded based on multiplexing
128            let should_decode = if let Some(mux_value) = signal.multiplexer_switch_value() {
129                // This is a multiplexed signal (m0, m1, etc.)
130                // Extended multiplexing: Check SG_MUL_VAL_ ranges first
131                // If extended multiplexing entries exist, they take precedence over basic m0/m1 values
132                let extended_entries_for_signal: Vec<_, { MAX_EXTENDED_MULTIPLEXING }> =
133                    extended_mux_entries
134                        .iter()
135                        .filter(|ext_mux| ext_mux.signal_name() == signal.name())
136                        .cloned()
137                        .collect();
138
139                if !extended_entries_for_signal.is_empty() {
140                    // Extended multiplexing: Check ALL switches referenced in extended entries (AND logic)
141                    // Collect unique switch names (no_std compatible, using Vec)
142                    let mut unique_switches: Vec<&str, 16> = Vec::new();
143                    for ext_mux in extended_entries_for_signal.iter() {
144                        let switch_name = ext_mux.multiplexer_switch();
145                        if !unique_switches.iter().any(|&s| s == switch_name) {
146                            unique_switches
147                                .push(switch_name)
148                                .map_err(|_| Error::Decoding(Error::MESSAGE_TOO_MANY_SIGNALS))?;
149                        }
150                    }
151
152                    // ALL switches referenced by this signal must have matching values
153                    unique_switches.iter().all(|switch_name| {
154                        // Find the switch value by name
155                        let switch_val = switch_values
156                            .iter()
157                            .find(|(name, _)| *name == *switch_name)
158                            .map(|(_, val)| *val);
159
160                        if let Some(val) = switch_val {
161                            // Check if any extended entry for this switch has a matching value range
162                            extended_entries_for_signal
163                                .iter()
164                                .filter(|e| e.multiplexer_switch() == *switch_name)
165                                .any(|ext_mux| {
166                                    ext_mux
167                                        .value_ranges()
168                                        .iter()
169                                        .any(|(min, max)| val >= *min && val <= *max)
170                                })
171                        } else {
172                            // Switch not found, cannot match
173                            false
174                        }
175                    })
176                } else {
177                    // Use basic multiplexing: Check if any switch value equals mux_value
178                    // m0 means decode when any switch value is 0, m1 means decode when any switch value is 1, etc.
179                    switch_values.iter().any(|(_, switch_val)| *switch_val == mux_value)
180                }
181            } else {
182                // Normal signal (not multiplexed) - always decode
183                true
184            };
185
186            if should_decode {
187                let value = signal.decode(payload)?;
188                decoded_signals
189                    .push((signal.name(), value, signal.unit()))
190                    .map_err(|_| Error::Decoding(Error::MESSAGE_TOO_MANY_SIGNALS))?;
191            }
192        }
193
194        Ok(decoded_signals)
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::Dbc;
201
202    #[test]
203    fn test_decode_basic() {
204        let dbc = Dbc::parse(
205            r#"VERSION "1.0"
206
207BU_: ECM
208
209BO_ 256 Engine : 8 ECM
210 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
211"#,
212        )
213        .unwrap();
214
215        // Decode a CAN message with RPM value of 2000 (raw: 8000 = 0x1F40)
216        let payload = [0x40, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
217        let decoded = dbc.decode(256, &payload).unwrap();
218        assert_eq!(decoded.len(), 1);
219        assert_eq!(decoded[0].0, "RPM");
220        assert_eq!(decoded[0].1, 2000.0);
221        assert_eq!(decoded[0].2, Some("rpm"));
222    }
223
224    #[test]
225    fn test_decode_message_not_found() {
226        let dbc = Dbc::parse(
227            r#"VERSION "1.0"
228
229BU_: ECM
230
231BO_ 256 Engine : 8 ECM
232"#,
233        )
234        .unwrap();
235
236        let payload = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
237        let result = dbc.decode(512, &payload);
238        assert!(result.is_err());
239    }
240
241    #[test]
242    fn test_decode_message() {
243        let data = r#"VERSION "1.0"
244
245BU_: ECM
246
247BO_ 256 Engine : 8 ECM
248 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
249 SG_ Temp : 16|8@1- (1,-40) [-40|215] "°C" *
250"#;
251
252        let dbc = Dbc::parse(data).unwrap();
253
254        // Decode a CAN message with RPM = 2000 (raw: 8000 = 0x1F40) and Temp = 50°C (raw: 90)
255        // Little-endian: RPM at bits 0-15, Temp at bits 16-23
256        let payload = [0x40, 0x1F, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00];
257        let decoded = dbc.decode(256, &payload).unwrap();
258
259        assert_eq!(decoded.len(), 2);
260        assert_eq!(decoded[0].0, "RPM");
261        assert_eq!(decoded[0].1, 2000.0);
262        assert_eq!(decoded[0].2, Some("rpm"));
263        assert_eq!(decoded[1].0, "Temp");
264        assert_eq!(decoded[1].1, 50.0);
265        assert_eq!(decoded[1].2, Some("°C"));
266    }
267
268    #[test]
269    fn test_decode_payload_length_mismatch() {
270        use crate::Error;
271        let data = r#"VERSION "1.0"
272
273BU_: ECM
274
275BO_ 256 Engine : 8 ECM
276 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
277"#;
278
279        let dbc = Dbc::parse(data).unwrap();
280
281        // Try to decode with payload shorter than DLC (DLC is 8, payload is 4)
282        let payload = [0x40, 0x1F, 0x00, 0x00];
283        let result = dbc.decode(256, &payload);
284        assert!(result.is_err());
285        match result.unwrap_err() {
286            Error::Decoding(msg) => {
287                assert!(msg.contains(Error::PAYLOAD_LENGTH_MISMATCH));
288            }
289            _ => panic!("Expected Error::Decoding"),
290        }
291    }
292
293    #[test]
294    fn test_decode_big_endian_signal() {
295        let data = r#"VERSION "1.0"
296
297BU_: ECM
298
299BO_ 256 Engine : 8 ECM
300 SG_ RPM : 0|16@0+ (1.0,0) [0|65535] "rpm" *
301"#;
302
303        let dbc = Dbc::parse(data).unwrap();
304
305        // Decode a big-endian signal: RPM = 256 (raw: 256 = 0x0100)
306        // For big-endian at bit 0-15, the bytes are arranged as [0x01, 0x00]
307        // Testing with a simple value that's easier to verify
308        let payload = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
309        let decoded = dbc.decode(256, &payload).unwrap();
310
311        assert_eq!(decoded.len(), 1);
312        assert_eq!(decoded[0].0, "RPM");
313        // The exact value depends on big-endian bit extraction implementation
314        // We just verify that decoding doesn't crash and returns a value
315        assert!(decoded[0].1 >= 0.0);
316        assert_eq!(decoded[0].2, Some("rpm"));
317    }
318
319    #[test]
320    fn test_decode_multiplexed_signal() {
321        let dbc = Dbc::parse(
322            r#"VERSION "1.0"
323
324BU_: ECM
325
326BO_ 256 Engine : 8 ECM
327 SG_ MuxId M : 0|8@1+ (1,0) [0|255] ""
328 SG_ Signal0 m0 : 8|16@1+ (0.1,0) [0|6553.5] "unit" *
329 SG_ Signal1 m1 : 24|16@1+ (0.01,0) [0|655.35] "unit" *
330 SG_ NormalSignal : 40|8@1+ (1,0) [0|255] ""
331"#,
332        )
333        .unwrap();
334
335        // Test with MuxId = 0: Should decode Signal0 and NormalSignal, but not Signal1
336        let payload = [0x00, 0x64, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00];
337        // MuxId=0, Signal0=100 (raw: 1000 = 0x03E8, but little-endian: 0xE8, 0x03), NormalSignal=50
338        // Payload: [MuxId=0, Signal0_low=0x64, Signal0_high=0x00, padding, NormalSignal=0x32, ...]
339        let decoded = dbc.decode(256, &payload).unwrap();
340
341        // Helper to find value by signal name
342        let find_signal =
343            |name: &str| decoded.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
344
345        // MuxId should always be decoded
346        assert!(find_signal("MuxId").is_some());
347        // Signal0 should be decoded (MuxId == 0)
348        assert!(find_signal("Signal0").is_some());
349        // Signal1 should NOT be decoded (MuxId != 1)
350        assert!(find_signal("Signal1").is_none());
351        // NormalSignal should always be decoded (not multiplexed)
352        assert!(find_signal("NormalSignal").is_some());
353    }
354
355    #[test]
356    fn test_decode_multiplexed_signal_switch_one() {
357        let dbc = Dbc::parse(
358            r#"VERSION "1.0"
359
360BU_: ECM
361
362BO_ 256 Engine : 8 ECM
363 SG_ MuxId M : 0|8@1+ (1,0) [0|255] ""
364 SG_ Signal0 m0 : 8|16@1+ (0.1,0) [0|6553.5] "unit" *
365 SG_ Signal1 m1 : 24|16@1+ (0.01,0) [0|655.35] "unit" *
366"#,
367        )
368        .unwrap();
369
370        // Test with MuxId = 1: Should decode Signal1, but not Signal0
371        let payload = [0x01, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00];
372        // MuxId=1 (at byte 0), Signal1 at bits 24-39 (bytes 3-4, little-endian)
373        // Signal1 value: 100 (raw: 100, little-endian bytes: 0x64, 0x00)
374        let decoded = dbc.decode(256, &payload).unwrap();
375
376        // Helper to find value by signal name
377        let find_signal =
378            |name: &str| decoded.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
379
380        // MuxId should always be decoded
381        assert_eq!(find_signal("MuxId"), Some(1.0));
382        // Signal0 should NOT be decoded (MuxId != 0)
383        assert!(find_signal("Signal0").is_none());
384        // Signal1 should be decoded (MuxId == 1)
385        assert!(find_signal("Signal1").is_some());
386    }
387
388    #[test]
389    fn test_decode_mixed_byte_order() {
390        let dbc = Dbc::parse(
391            r#"VERSION "1.0"
392
393BU_: ECM
394
395BO_ 256 MixedByteOrder : 8 ECM
396 SG_ LittleEndianSignal : 0|16@1+ (1.0,0) [0|65535] ""
397 SG_ BigEndianSignal : 16|16@0+ (1.0,0) [0|65535] ""
398 SG_ AnotherLittleEndian : 32|8@1+ (1.0,0) [0|255] ""
399 SG_ AnotherBigEndian : 40|8@0+ (1.0,0) [0|255] ""
400"#,
401        )
402        .unwrap();
403
404        // Test payload with both big-endian and little-endian signals:
405        // - LittleEndianSignal at bits 0-15 (bytes 0-1): [0x34, 0x12] = 0x1234 = 4660
406        // - BigEndianSignal at bits 16-31 (bytes 2-3): [0x00, 0x01] = decoded based on BE bit mapping
407        // - AnotherLittleEndian at bits 32-39 (byte 4): 0xAB = 171
408        // - AnotherBigEndian at bits 40-47 (byte 5): 0xCD = decoded based on BE bit mapping
409        let payload = [
410            0x34, 0x12, // Bytes 0-1: LittleEndianSignal
411            0x00, 0x01, // Bytes 2-3: BigEndianSignal
412            0xAB, // Byte 4: AnotherLittleEndian
413            0xCD, // Byte 5: AnotherBigEndian
414            0x00, 0x00, // Padding
415        ];
416        let decoded = dbc.decode(256, &payload).unwrap();
417
418        // Helper to find value by signal name
419        let find_signal =
420            |name: &str| decoded.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
421
422        // Verify little-endian 16-bit signal: bytes [0x34, 0x12] = 0x1234 = 4660
423        assert_eq!(find_signal("LittleEndianSignal"), Some(4660.0)); // 0x1234
424
425        // For big-endian, verify it decodes correctly (exact value depends on BE bit mapping)
426        let big_endian_value = find_signal("BigEndianSignal").unwrap();
427        // Big-endian signal should decode to a reasonable value
428        assert!((0.0..=65535.0).contains(&big_endian_value));
429
430        // Verify little-endian 8-bit signal at byte 4
431        assert_eq!(find_signal("AnotherLittleEndian"), Some(171.0)); // 0xAB
432
433        // For big-endian 8-bit signal, verify it decoded (exact value depends on BE bit mapping)
434        let big_endian_8bit = find_signal("AnotherBigEndian");
435        assert!(big_endian_8bit.is_some());
436        assert!(big_endian_8bit.unwrap() >= 0.0 && big_endian_8bit.unwrap() <= 255.0);
437
438        // All signals should be decoded
439        assert_eq!(decoded.len(), 4);
440
441        // Verify both 16-bit signals decoded successfully (proves both byte orders work)
442        assert!(find_signal("LittleEndianSignal").is_some());
443        assert!(find_signal("BigEndianSignal").is_some());
444    }
445
446    #[test]
447    fn test_decode_extended_multiplexing_simple() {
448        let dbc = Dbc::parse(
449            r#"VERSION "1.0"
450
451BU_: ECM
452
453BO_ 500 ComplexMux : 8 ECM
454 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
455 SG_ Signal_A m0 : 16|16@1+ (0.1,0) [0|100] "unit" *
456
457SG_MUL_VAL_ 500 Signal_A Mux1 5-10 ;
458"#,
459        )
460        .unwrap();
461
462        // Test with Mux1 = 5: Should decode Signal_A (within range 5-10)
463        // Mux1=5 (byte 0), Signal_A at bits 16-31 (bytes 2-3, little-endian)
464        // Signal_A=100 (raw: 1000 = 0x03E8, little-endian bytes: 0xE8, 0x03)
465        let payload = [0x05, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00];
466        let decoded = dbc.decode(500, &payload).unwrap();
467
468        let find_signal =
469            |name: &str| decoded.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
470
471        assert_eq!(find_signal("Mux1"), Some(5.0));
472        // Extended multiplexing: Signal_A should decode when Mux1 is in range 5-10
473        let ext_entries = dbc.extended_multiplexing_for_message(500);
474        assert_eq!(
475            ext_entries.len(),
476            1,
477            "Extended multiplexing entries should be parsed"
478        );
479        assert!(
480            find_signal("Signal_A").is_some(),
481            "Signal_A should be decoded when Mux1=5 (within range 5-10)"
482        );
483        assert_eq!(find_signal("Signal_A").unwrap(), 100.0);
484
485        // Test with Mux1 = 15: Should NOT decode Signal_A (outside range 5-10)
486        let payload2 = [0x0F, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00];
487        let decoded2 = dbc.decode(500, &payload2).unwrap();
488        let find_signal2 =
489            |name: &str| decoded2.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
490
491        assert_eq!(find_signal2("Mux1"), Some(15.0));
492        assert!(find_signal2("Signal_A").is_none());
493    }
494
495    #[test]
496    fn test_decode_extended_multiplexing_multiple_ranges() {
497        let dbc = Dbc::parse(
498            r#"VERSION "1.0"
499
500BU_: ECM
501
502BO_ 501 MultiRangeMux : 8 ECM
503 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
504 SG_ Signal_B m0 : 16|16@1+ (1,0) [0|65535] "unit" *
505
506SG_MUL_VAL_ 501 Signal_B Mux1 0-5,10-15,20-25 ;
507"#,
508        )
509        .unwrap();
510
511        // Test with Mux1 = 3: Should decode (within range 0-5)
512        // Signal_B at bits 16-31, value 4096 (raw, little-endian: 0x00, 0x10)
513        let payload1 = [0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00];
514        let decoded1 = dbc.decode(501, &payload1).unwrap();
515        let find1 = |name: &str| decoded1.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
516        assert_eq!(find1("Mux1"), Some(3.0));
517        assert!(find1("Signal_B").is_some());
518
519        // Test with Mux1 = 12: Should decode (within range 10-15)
520        // Signal_B at bits 16-31, value 8192 (raw, little-endian: 0x00, 0x20)
521        let payload2 = [0x0C, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00];
522        let decoded2 = dbc.decode(501, &payload2).unwrap();
523        let find2 = |name: &str| decoded2.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
524        assert_eq!(find2("Mux1"), Some(12.0));
525        assert!(find2("Signal_B").is_some());
526
527        // Test with Mux1 = 22: Should decode (within range 20-25)
528        // Signal_B at bits 16-31, value 12288 (raw, little-endian: 0x00, 0x30)
529        let payload3 = [0x16, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00];
530        let decoded3 = dbc.decode(501, &payload3).unwrap();
531        let find3 = |name: &str| decoded3.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
532        assert_eq!(find3("Mux1"), Some(22.0));
533        assert!(find3("Signal_B").is_some());
534
535        // Test with Mux1 = 8: Should NOT decode (not in any range)
536        // Signal_B at bits 16-31, value 16384 (raw, little-endian: 0x00, 0x40)
537        let payload4 = [0x08, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00];
538        let decoded4 = dbc.decode(501, &payload4).unwrap();
539        let find4 = |name: &str| decoded4.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
540        assert_eq!(find4("Mux1"), Some(8.0));
541        assert!(find4("Signal_B").is_none());
542    }
543
544    /// Test extended multiplexing with multiple switches (AND logic - all must match).
545    /// Note: Depends on SG_MUL_VAL_ parsing working correctly.
546    #[test]
547    fn test_decode_extended_multiplexing_multiple_switches() {
548        let dbc = Dbc::parse(
549            r#"VERSION "1.0"
550
551BU_: ECM
552
553BO_ 502 MultiSwitchMux : 8 ECM
554 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
555 SG_ Mux2 M : 8|8@1+ (1,0) [0|255] ""
556 SG_ Signal_C m0 : 16|16@1+ (1,0) [0|65535] "unit" *
557
558SG_MUL_VAL_ 502 Signal_C Mux1 5-10 ;
559SG_MUL_VAL_ 502 Signal_C Mux2 20-25 ;
560"#,
561        )
562        .unwrap();
563
564        // Test with Mux1=7 and Mux2=22: Should decode (both switches match their ranges)
565        // Mux1=7 (byte 0), Mux2=22 (byte 1), Signal_C at bits 16-31 (bytes 2-3, little-endian)
566        let payload1 = [0x07, 0x16, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00];
567        let decoded1 = dbc.decode(502, &payload1).unwrap();
568        let find1 = |name: &str| decoded1.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
569        assert_eq!(find1("Mux1"), Some(7.0));
570        assert_eq!(find1("Mux2"), Some(22.0));
571        assert!(find1("Signal_C").is_some());
572
573        // Test with Mux1=7 and Mux2=30: Should NOT decode (Mux2 outside range)
574        let payload2 = [0x07, 0x1E, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00];
575        let decoded2 = dbc.decode(502, &payload2).unwrap();
576        let find2 = |name: &str| decoded2.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
577        assert_eq!(find2("Mux1"), Some(7.0));
578        assert_eq!(find2("Mux2"), Some(30.0));
579        assert!(find2("Signal_C").is_none());
580
581        // Test with Mux1=15 and Mux2=22: Should NOT decode (Mux1 outside range)
582        let payload3 = [0x0F, 0x16, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00];
583        let decoded3 = dbc.decode(502, &payload3).unwrap();
584        let find3 = |name: &str| decoded3.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
585        assert_eq!(find3("Mux1"), Some(15.0));
586        assert_eq!(find3("Mux2"), Some(22.0));
587        assert!(find3("Signal_C").is_none());
588    }
589
590    /// Test that extended multiplexing takes precedence over basic m0/m1 values.
591    /// Note: Depends on SG_MUL_VAL_ parsing working correctly.
592    #[test]
593    fn test_decode_extended_multiplexing_takes_precedence() {
594        let dbc = Dbc::parse(
595            r#"VERSION "1.0"
596
597BU_: ECM
598
599BO_ 503 PrecedenceTest : 8 ECM
600 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
601 SG_ Signal_D m0 : 16|16@1+ (1,0) [0|65535] "unit" *
602
603SG_MUL_VAL_ 503 Signal_D Mux1 10-15 ;
604"#,
605        )
606        .unwrap();
607
608        // Test with Mux1 = 0: Should NOT decode
609        // Even though basic multiplexing would match (m0 means decode when switch=0),
610        // extended multiplexing takes precedence and requires Mux1 to be 10-15
611        let payload1 = [0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00];
612        let decoded1 = dbc.decode(503, &payload1).unwrap();
613        let find1 = |name: &str| decoded1.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
614        assert_eq!(find1("Mux1"), Some(0.0));
615        assert!(find1("Signal_D").is_none());
616
617        // Test with Mux1 = 12: Should decode (within extended range 10-15)
618        let payload2 = [0x0C, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00];
619        let decoded2 = dbc.decode(503, &payload2).unwrap();
620        let find2 = |name: &str| decoded2.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
621        assert_eq!(find2("Mux1"), Some(12.0));
622        assert!(find2("Signal_D").is_some());
623    }
624
625    /// Test extended multiplexing with signals that are both multiplexed and multiplexer switches (m65M pattern).
626    /// Note: Depends on SG_MUL_VAL_ parsing working correctly.
627    #[test]
628    fn test_decode_extended_multiplexing_with_extended_mux_signal() {
629        // Test extended multiplexing where the signal itself is also a multiplexer (m65M pattern)
630        let dbc = Dbc::parse(
631            r#"VERSION "1.0"
632
633BU_: ECM
634
635BO_ 504 ExtendedMuxSignal : 8 ECM
636 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
637 SG_ Mux2 m65M : 8|8@1+ (1,0) [0|255] ""
638 SG_ Signal_E m0 : 16|16@1+ (1,0) [0|65535] "unit" *
639
640SG_MUL_VAL_ 504 Signal_E Mux1 65-65 ;
641SG_MUL_VAL_ 504 Signal_E Mux2 10-15 ;
642"#,
643        )
644        .unwrap();
645
646        // Test with Mux1=65 and Mux2=12: Should decode Signal_E
647        // Mux2 is both multiplexed (m65 - active when Mux1=65) and a multiplexer (M)
648        let payload = [0x41, 0x0C, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00];
649        // Mux1=65 (0x41), Mux2=12 (0x0C), Signal_E at bits 16-31
650        let decoded = dbc.decode(504, &payload).unwrap();
651        let find = |name: &str| decoded.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
652
653        assert_eq!(find("Mux1"), Some(65.0));
654        assert_eq!(find("Mux2"), Some(12.0));
655        assert!(find("Signal_E").is_some());
656
657        // Test with Mux1=64 and Mux2=12: Should NOT decode (Mux1 not 65)
658        let payload2 = [0x40, 0x0C, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00];
659        let decoded2 = dbc.decode(504, &payload2).unwrap();
660        let find2 = |name: &str| decoded2.iter().find(|(n, _, _)| *n == name).map(|(_, v, _)| *v);
661        assert_eq!(find2("Mux1"), Some(64.0));
662        assert_eq!(find2("Mux2"), Some(12.0));
663        assert!(find2("Signal_E").is_none());
664    }
665
666    #[test]
667    fn test_decode_negative_multiplexer_switch() {
668        use crate::Error;
669        // Create a DBC with a signed multiplexer switch signal
670        // 8-bit signed signal: values -128 to 127
671        let dbc = Dbc::parse(
672            r#"VERSION "1.0"
673
674BU_: ECM
675
676BO_ 256 MuxMessage : 8 ECM
677 SG_ MuxSwitch M : 0|8@1- (1,0) [-128|127] ""
678 SG_ SignalA m0 : 8|8@1+ (1,0) [0|255] ""
679"#,
680        )
681        .unwrap();
682
683        // Try to decode with a negative raw value for the multiplexer switch
684        // -5 in 8-bit two's complement is 0xFB
685        // Little-endian: MuxSwitch at bits 0-7, SignalA at bits 8-15
686        let payload = [0xFB, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
687        let result = dbc.decode(256, &payload);
688        assert!(result.is_err());
689        match result.unwrap_err() {
690            Error::Decoding(msg) => {
691                assert_eq!(msg, Error::MULTIPLEXER_SWITCH_NEGATIVE);
692            }
693            _ => panic!("Expected Error::Decoding with MULTIPLEXER_SWITCH_NEGATIVE"),
694        }
695    }
696
697    #[test]
698    fn test_decode_too_many_unique_switches() {
699        use crate::{Error, MAX_SIGNALS_PER_MESSAGE};
700        // Skip this test if MAX_SIGNALS_PER_MESSAGE is too low to create 17 signals
701        // (16 multiplexer switches + 1 signal = 17 total signals needed)
702        if MAX_SIGNALS_PER_MESSAGE < 17 {
703            return;
704        }
705
706        // Create a DBC with more than 16 unique switches in extended multiplexing
707        // This should trigger an error when trying to decode
708        // Using string literal concatenation to avoid std features
709        let dbc_str = r#"VERSION "1.0"
710
711BU_: ECM
712
713BO_ 600 TooManySwitches : 18 ECM
714 SG_ Mux1 M : 0|8@1+ (1,0) [0|255] ""
715 SG_ Mux2 M : 8|8@1+ (1,0) [0|255] ""
716 SG_ Mux3 M : 16|8@1+ (1,0) [0|255] ""
717 SG_ Mux4 M : 24|8@1+ (1,0) [0|255] ""
718 SG_ Mux5 M : 32|8@1+ (1,0) [0|255] ""
719 SG_ Mux6 M : 40|8@1+ (1,0) [0|255] ""
720 SG_ Mux7 M : 48|8@1+ (1,0) [0|255] ""
721 SG_ Mux8 M : 56|8@1+ (1,0) [0|255] ""
722 SG_ Mux9 M : 64|8@1+ (1,0) [0|255] ""
723 SG_ Mux10 M : 72|8@1+ (1,0) [0|255] ""
724 SG_ Mux11 M : 80|8@1+ (1,0) [0|255] ""
725 SG_ Mux12 M : 88|8@1+ (1,0) [0|255] ""
726 SG_ Mux13 M : 96|8@1+ (1,0) [0|255] ""
727 SG_ Mux14 M : 104|8@1+ (1,0) [0|255] ""
728 SG_ Mux15 M : 112|8@1+ (1,0) [0|255] ""
729 SG_ Mux16 M : 120|8@1+ (1,0) [0|255] ""
730 SG_ Mux17 M : 128|8@1+ (1,0) [0|255] ""
731 SG_ Signal_X m0 : 136|8@1+ (1,0) [0|255] "unit" *
732
733SG_MUL_VAL_ 600 Signal_X Mux1 0-255 ;
734SG_MUL_VAL_ 600 Signal_X Mux2 0-255 ;
735SG_MUL_VAL_ 600 Signal_X Mux3 0-255 ;
736SG_MUL_VAL_ 600 Signal_X Mux4 0-255 ;
737SG_MUL_VAL_ 600 Signal_X Mux5 0-255 ;
738SG_MUL_VAL_ 600 Signal_X Mux6 0-255 ;
739SG_MUL_VAL_ 600 Signal_X Mux7 0-255 ;
740SG_MUL_VAL_ 600 Signal_X Mux8 0-255 ;
741SG_MUL_VAL_ 600 Signal_X Mux9 0-255 ;
742SG_MUL_VAL_ 600 Signal_X Mux10 0-255 ;
743SG_MUL_VAL_ 600 Signal_X Mux11 0-255 ;
744SG_MUL_VAL_ 600 Signal_X Mux12 0-255 ;
745SG_MUL_VAL_ 600 Signal_X Mux13 0-255 ;
746SG_MUL_VAL_ 600 Signal_X Mux14 0-255 ;
747SG_MUL_VAL_ 600 Signal_X Mux15 0-255 ;
748SG_MUL_VAL_ 600 Signal_X Mux16 0-255 ;
749SG_MUL_VAL_ 600 Signal_X Mux17 0-255 ;
750"#;
751
752        let dbc = Dbc::parse(dbc_str).unwrap();
753
754        // Try to decode - should fail with MESSAGE_TOO_MANY_SIGNALS error
755        // because we have 17 unique switches (exceeding the limit of 16)
756        let payload = [0x00; 18];
757        let result = dbc.decode(600, &payload);
758        assert!(
759            result.is_err(),
760            "Decode should fail when there are more than 16 unique switches"
761        );
762        match result.unwrap_err() {
763            Error::Decoding(msg) => {
764                assert_eq!(
765                    msg,
766                    Error::MESSAGE_TOO_MANY_SIGNALS,
767                    "Expected MESSAGE_TOO_MANY_SIGNALS error, got: {}",
768                    msg
769                );
770            }
771            e => panic!(
772                "Expected Error::Decoding with MESSAGE_TOO_MANY_SIGNALS, got: {:?}",
773                e
774            ),
775        }
776    }
777}