dbc_rs/dbc/
parse.rs

1use crate::{
2    Dbc, Error, MAX_MESSAGES, MAX_SIGNALS_PER_MESSAGE, Message, Nodes, Parser, Result, Signal,
3    Version,
4    compat::Vec,
5    dbc::{Messages, Validate},
6};
7#[cfg(feature = "std")]
8use crate::{ValueDescriptions, dbc::ValueDescriptionsMap};
9#[cfg(feature = "std")]
10use std::collections::BTreeMap;
11
12impl Dbc {
13    /// Parse a DBC file from a string slice
14    ///
15    /// # Examples
16    ///
17    /// ```rust,no_run
18    /// use dbc_rs::Dbc;
19    ///
20    /// let dbc_content = r#"VERSION "1.0"
21    ///
22    /// BU_: ECM
23    ///
24    /// BO_ 256 EngineData : 8 ECM
25    ///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm""#;
26    ///
27    /// let dbc = Dbc::parse(dbc_content)?;
28    /// assert_eq!(dbc.messages().len(), 1);
29    /// # Ok::<(), dbc_rs::Error>(())
30    /// ```
31    pub fn parse(data: &str) -> Result<Self> {
32        let mut parser = Parser::new(data.as_bytes())?;
33
34        let mut messages_buffer: Vec<Message, { MAX_MESSAGES }> = Vec::new();
35
36        let mut message_count_actual = 0;
37
38        // Parse version, nodes, and messages
39        use crate::{
40            BA_, BA_DEF_, BA_DEF_DEF_, BO_, BO_TX_BU_, BS_, BU_, CM_, EV_, NS_, SG_, SIG_GROUP_,
41            SIG_VALTYPE_, VAL_, VAL_TABLE_, VERSION,
42        };
43
44        let mut version: Option<Version> = None;
45        let mut nodes: Option<Nodes> = None;
46
47        // Store value descriptions during parsing: (message_id, signal_name, value, description)
48        #[cfg(feature = "std")]
49        type ValueDescriptionsBufferEntry = (
50            Option<u32>,
51            std::string::String,
52            std::vec::Vec<(u64, std::string::String)>,
53        );
54        #[cfg(feature = "std")]
55        let mut value_descriptions_buffer: std::vec::Vec<ValueDescriptionsBufferEntry> =
56            std::vec::Vec::new();
57
58        loop {
59            // Skip comments (lines starting with //)
60            parser.skip_newlines_and_spaces();
61            if parser.starts_with(b"//") {
62                parser.skip_to_end_of_line();
63                continue;
64            }
65
66            let keyword_result = parser.peek_next_keyword();
67            let keyword = match keyword_result {
68                Ok(kw) => kw,
69                Err(Error::UnexpectedEof) => break,
70                Err(Error::Expected(_)) => {
71                    if parser.starts_with(b"//") {
72                        parser.skip_to_end_of_line();
73                        continue;
74                    }
75                    return Err(keyword_result.unwrap_err());
76                }
77                Err(e) => return Err(e),
78            };
79
80            // Save position after peek_next_keyword (which skips whitespace, so we're at the keyword)
81            let pos_at_keyword = parser.pos();
82
83            match keyword {
84                NS_ => {
85                    // Consume NS_ keyword
86                    parser
87                        .expect(crate::NS_.as_bytes())
88                        .map_err(|_| Error::Expected("Failed to consume NS_ keyword"))?;
89                    parser.skip_newlines_and_spaces();
90                    let _ = parser.expect(b":").ok();
91                    loop {
92                        parser.skip_newlines_and_spaces();
93                        if parser.is_empty() {
94                            break;
95                        }
96                        if parser.starts_with(b" ") || parser.starts_with(b"\t") {
97                            parser.skip_to_end_of_line();
98                            continue;
99                        }
100                        if parser.starts_with(b"//") {
101                            parser.skip_to_end_of_line();
102                            continue;
103                        }
104                        if parser.starts_with(BS_.as_bytes())
105                            || parser.starts_with(BU_.as_bytes())
106                            || parser.starts_with(BO_.as_bytes())
107                            || parser.starts_with(SG_.as_bytes())
108                            || parser.starts_with(VERSION.as_bytes())
109                        {
110                            break;
111                        }
112                        parser.skip_to_end_of_line();
113                    }
114                    continue;
115                }
116                CM_ | BS_ | VAL_TABLE_ | BA_DEF_ | BA_DEF_DEF_ | BA_ | SIG_GROUP_
117                | SIG_VALTYPE_ | EV_ | BO_TX_BU_ => {
118                    // Consume keyword then skip to end of line
119                    let _ = parser.expect(keyword.as_bytes()).ok();
120                    parser.skip_to_end_of_line();
121                    continue;
122                }
123                VAL_ => {
124                    #[cfg(feature = "std")]
125                    {
126                        // Consume VAL_ keyword
127                        let _ = parser.expect(crate::VAL_.as_bytes()).ok();
128                        // Parse VAL_ statement: VAL_ message_id signal_name value1 "desc1" value2 "desc2" ... ;
129                        // Note: message_id of -1 (0xFFFFFFFF) means the value descriptions apply to
130                        // all signals with this name in ANY message (global value descriptions)
131                        parser.skip_newlines_and_spaces();
132                        let message_id = match parser.parse_i64() {
133                            Ok(id) => {
134                                // -1 (0xFFFFFFFF) is the magic number for global value descriptions
135                                if id == -1 {
136                                    None
137                                } else if id >= 0 && id <= u32::MAX as i64 {
138                                    Some(id as u32)
139                                } else {
140                                    parser.skip_to_end_of_line();
141                                    continue;
142                                }
143                            }
144                            Err(_) => {
145                                parser.skip_to_end_of_line();
146                                continue;
147                            }
148                        };
149                        parser.skip_newlines_and_spaces();
150                        let signal_name = match parser.parse_identifier() {
151                            Ok(name) => name.to_string(),
152                            Err(_) => {
153                                parser.skip_to_end_of_line();
154                                continue;
155                            }
156                        };
157                        // Parse value-description pairs
158                        let mut entries: std::vec::Vec<(u64, std::string::String)> =
159                            std::vec::Vec::new();
160                        loop {
161                            parser.skip_newlines_and_spaces();
162                            // Check for semicolon (end of VAL_ statement)
163                            if parser.starts_with(b";") {
164                                parser.expect(b";").ok();
165                                break;
166                            }
167                            // Parse value (as i64 first to handle negative values like -1, then convert to u64)
168                            // Note: -1 (0xFFFFFFFF) is the magic number for global value descriptions in message_id,
169                            // but values in VAL_ can also be negative
170                            let value = match parser.parse_i64() {
171                                Ok(v) => {
172                                    // Handle -1 specially: convert to 0xFFFFFFFF (u32::MAX) instead of large u64
173                                    if v == -1 { 0xFFFF_FFFFu64 } else { v as u64 }
174                                }
175                                Err(_) => {
176                                    parser.skip_to_end_of_line();
177                                    break;
178                                }
179                            };
180                            parser.skip_newlines_and_spaces();
181                            // Parse description string (expect quote, then take until quote)
182                            if parser.expect(b"\"").is_err() {
183                                parser.skip_to_end_of_line();
184                                break;
185                            }
186                            let description_bytes = match parser.take_until_quote(false, 1024) {
187                                Ok(bytes) => bytes,
188                                Err(_) => {
189                                    parser.skip_to_end_of_line();
190                                    break;
191                                }
192                            };
193                            let description = match core::str::from_utf8(description_bytes) {
194                                Ok(s) => s.to_string(),
195                                Err(_) => {
196                                    parser.skip_to_end_of_line();
197                                    break;
198                                }
199                            };
200                            entries.push((value, description));
201                        }
202                        if !entries.is_empty() {
203                            value_descriptions_buffer.push((message_id, signal_name, entries));
204                        }
205                    }
206                    #[cfg(not(feature = "std"))]
207                    {
208                        // In no_std mode, consume VAL_ keyword and skip the rest
209                        let _ = parser.expect(crate::VAL_.as_bytes()).ok();
210                        parser.skip_to_end_of_line();
211                    }
212                    continue;
213                }
214                VERSION => {
215                    // Version::parse expects VERSION keyword, don't consume it here
216                    version = Some(Version::parse(&mut parser)?);
217                    continue;
218                }
219                BU_ => {
220                    // Nodes::parse expects BU_ keyword, create parser from original input including it
221                    parser.skip_to_end_of_line();
222                    let bu_input = &data.as_bytes()[pos_at_keyword..parser.pos()];
223                    let mut bu_parser = Parser::new(bu_input)?;
224                    nodes = Some(Nodes::parse(&mut bu_parser)?);
225                    continue;
226                }
227                BO_ => {
228                    // Check limit using MAX_MESSAGES constant
229                    if message_count_actual >= MAX_MESSAGES {
230                        return Err(Error::Nodes(Error::NODES_TOO_MANY));
231                    }
232
233                    // Save parser position (at BO_ keyword, so Message::parse can consume it)
234                    let message_start_pos = pos_at_keyword;
235
236                    // Don't manually parse - just find where the header ends by looking for the colon and sender
237                    // We need to find the end of the header line to separate it from signals
238                    let header_line_end = {
239                        // Skip to end of line to find where header ends
240                        let mut temp_parser = Parser::new(&data.as_bytes()[pos_at_keyword..])?;
241                        // Skip BO_ keyword
242                        temp_parser.expect(crate::BO_.as_bytes()).ok();
243                        temp_parser.skip_whitespace().ok();
244                        temp_parser.parse_u32().ok(); // ID
245                        temp_parser.skip_whitespace().ok();
246                        temp_parser.parse_identifier().ok(); // name
247                        temp_parser.skip_whitespace().ok();
248                        temp_parser.expect(b":").ok(); // colon
249                        temp_parser.skip_whitespace().ok();
250                        temp_parser.parse_u8().ok(); // DLC
251                        temp_parser.skip_whitespace().ok();
252                        temp_parser.parse_identifier().ok(); // sender
253                        pos_at_keyword + temp_parser.pos()
254                    };
255
256                    // Now parse signals from the original parser
257                    parser.skip_to_end_of_line(); // Skip past header line
258
259                    let mut signals_array: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }> = Vec::new();
260
261                    loop {
262                        parser.skip_newlines_and_spaces();
263                        if parser.starts_with(crate::SG_.as_bytes()) {
264                            if let Some(next_byte) = parser.peek_byte_at(3) {
265                                if matches!(next_byte, b' ' | b'\n' | b'\r' | b'\t') {
266                                    if signals_array.len() >= MAX_SIGNALS_PER_MESSAGE {
267                                        return Err(Error::Receivers(
268                                            Error::SIGNAL_RECEIVERS_TOO_MANY,
269                                        ));
270                                    }
271                                    // Signal::parse expects SG_ keyword, which we've already verified with starts_with
272                                    let signal = Signal::parse(&mut parser)?;
273                                    signals_array.push(signal).map_err(|_| {
274                                        Error::Receivers(Error::SIGNAL_RECEIVERS_TOO_MANY)
275                                    })?;
276                                    continue;
277                                }
278                            }
279                        }
280                        break;
281                    }
282
283                    // Restore parser to start of message line and use Message::parse
284                    // Create a new parser from the original input, but only up to the end of the header
285                    // (not including signals, so Message::parse doesn't complain about extra content)
286                    let message_input = &data.as_bytes()[message_start_pos..header_line_end];
287                    let mut message_parser = Parser::new(message_input)?;
288
289                    // Use Message::parse which will parse the header and use our signals
290                    let message = Message::parse(&mut message_parser, signals_array.as_slice())?;
291
292                    messages_buffer
293                        .push(message)
294                        .map_err(|_| Error::Message(Error::NODES_TOO_MANY))?;
295                    message_count_actual += 1;
296                    continue;
297                }
298                SG_ => {
299                    // Orphaned signal (not inside a message) - skip it
300                    parser.skip_to_end_of_line();
301                    continue;
302                }
303                _ => {
304                    parser.skip_to_end_of_line();
305                    continue;
306                }
307            }
308        }
309
310        // Allow empty nodes (DBC spec allows empty BU_: line)
311        let nodes = nodes.unwrap_or_default();
312
313        // If no version was parsed, default to empty version
314        let version = version.or_else(|| {
315            static EMPTY_VERSION: &[u8] = b"VERSION \"\"";
316            let mut parser = Parser::new(EMPTY_VERSION).ok()?;
317            Version::parse(&mut parser).ok()
318        });
319
320        // Build value descriptions map for storage in Dbc
321        #[cfg(feature = "std")]
322        let value_descriptions_map = {
323            let mut map: BTreeMap<(Option<u32>, std::string::String), ValueDescriptions> =
324                BTreeMap::new();
325            for (message_id, signal_name, entries) in value_descriptions_buffer {
326                let key = (message_id, signal_name);
327                let value_descriptions = ValueDescriptions::from_slice(&entries);
328                map.insert(key, value_descriptions);
329            }
330            ValueDescriptionsMap::from_map(map)
331        };
332
333        // Convert messages buffer to slice for validation and construction
334        let messages_slice: &[Message] = messages_buffer.as_slice();
335
336        // Validate messages (duplicate IDs, sender in nodes, etc.)
337        #[cfg(feature = "std")]
338        Validate::validate(&nodes, messages_slice, Some(&value_descriptions_map)).map_err(|e| {
339            crate::error::map_val_error(e, Error::Message, || {
340                Error::Message(Error::MESSAGE_ERROR_PREFIX)
341            })
342        })?;
343        #[cfg(not(feature = "std"))]
344        Validate::validate(&nodes, messages_slice).map_err(|e| {
345            crate::error::map_val_error(e, Error::Message, || {
346                Error::Message(Error::MESSAGE_ERROR_PREFIX)
347            })
348        })?;
349
350        // Construct directly (validation already done)
351        let messages = Messages::new(messages_slice)?;
352        #[cfg(feature = "std")]
353        let dbc = Dbc::new(version, nodes, messages, value_descriptions_map);
354        #[cfg(not(feature = "std"))]
355        let dbc = Dbc::new(version, nodes, messages);
356        Ok(dbc)
357    }
358
359    /// Parse a DBC file from a byte slice
360    ///
361    /// # Examples
362    ///
363    /// ```rust,no_run
364    /// use dbc_rs::Dbc;
365    ///
366    /// let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
367    /// let dbc = Dbc::parse_bytes(dbc_bytes)?;
368    /// println!("Parsed {} messages", dbc.messages().len());
369    /// # Ok::<(), dbc_rs::Error>(())
370    /// ```
371    pub fn parse_bytes(data: &[u8]) -> Result<Dbc> {
372        let content =
373            core::str::from_utf8(data).map_err(|_e| Error::Expected(Error::INVALID_UTF8))?;
374        Dbc::parse(content)
375    }
376}
377
378#[cfg(test)]
379mod tests {
380    use crate::Dbc;
381
382    #[test]
383    fn test_parse_basic() {
384        let dbc_content = r#"VERSION "1.0"
385
386BU_: ECM
387
388BO_ 256 Engine : 8 ECM
389 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
390"#;
391        let dbc = Dbc::parse(dbc_content).unwrap();
392        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
393        assert!(dbc.nodes().contains("ECM"));
394        assert_eq!(dbc.messages().len(), 1);
395    }
396
397    #[test]
398    fn test_parse_bytes() {
399        let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
400        let dbc = Dbc::parse_bytes(dbc_bytes).unwrap();
401        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
402        assert!(dbc.nodes().contains("ECM"));
403        assert_eq!(dbc.messages().len(), 1);
404    }
405
406    #[test]
407    fn test_parse_empty_nodes() {
408        let dbc_content = r#"VERSION "1.0"
409
410BU_:
411
412BO_ 256 Engine : 8 ECM
413"#;
414        let dbc = Dbc::parse(dbc_content).unwrap();
415        assert!(dbc.nodes().is_empty());
416    }
417
418    #[test]
419    fn test_parse_no_version() {
420        let dbc_content = r#"BU_: ECM
421
422BO_ 256 Engine : 8 ECM
423"#;
424        let dbc = Dbc::parse(dbc_content).unwrap();
425        // Should default to empty version
426        assert!(dbc.version().is_some());
427    }
428
429    #[test]
430    fn parses_real_dbc() {
431        let data = r#"VERSION "1.0"
432
433BU_: ECM TCM
434
435BO_ 256 Engine : 8 ECM
436 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
437 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
438
439BO_ 512 Brake : 4 TCM
440 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar""#;
441
442        let dbc = Dbc::parse(data).unwrap();
443        assert_eq!(dbc.messages().len(), 2);
444        let mut messages_iter = dbc.messages().iter();
445        let msg0 = messages_iter.next().unwrap();
446        assert_eq!(msg0.signals().len(), 2);
447        let mut signals_iter = msg0.signals().iter();
448        assert_eq!(signals_iter.next().unwrap().name(), "RPM");
449        assert_eq!(signals_iter.next().unwrap().name(), "Temp");
450        let msg1 = messages_iter.next().unwrap();
451        assert_eq!(msg1.signals().len(), 1);
452        assert_eq!(msg1.signals().iter().next().unwrap().name(), "Pressure");
453    }
454
455    #[test]
456    fn test_parse_duplicate_message_id() {
457        use crate::Error;
458        // Test that parse also validates duplicate message IDs
459        let data = r#"VERSION "1.0"
460
461BU_: ECM
462
463BO_ 256 EngineData1 : 8 ECM
464 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
465
466BO_ 256 EngineData2 : 8 ECM
467 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
468"#;
469
470        let result = Dbc::parse(data);
471        assert!(result.is_err());
472        match result.unwrap_err() {
473            Error::Message(msg) => {
474                assert!(msg.contains(Error::DUPLICATE_MESSAGE_ID));
475            }
476            _ => panic!("Expected Error::Message"),
477        }
478    }
479
480    #[test]
481    fn test_parse_sender_not_in_nodes() {
482        use crate::Error;
483        // Test that parse also validates message senders are in nodes list
484        let data = r#"VERSION "1.0"
485
486BU_: ECM
487
488BO_ 256 EngineData : 8 TCM
489 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
490"#;
491
492        let result = Dbc::parse(data);
493        assert!(result.is_err());
494        match result.unwrap_err() {
495            Error::Message(msg) => {
496                assert!(msg.contains(Error::SENDER_NOT_IN_NODES));
497            }
498            _ => panic!("Expected Error::Message"),
499        }
500    }
501
502    #[test]
503    fn test_parse_empty_file() {
504        use crate::Error;
505        // Test parsing an empty file
506        let result = Dbc::parse("");
507        assert!(result.is_err());
508        match result.unwrap_err() {
509            Error::UnexpectedEof => {
510                // Empty file should result in unexpected EOF
511            }
512            _ => panic!("Expected Error::UnexpectedEof"),
513        }
514    }
515
516    #[test]
517    fn test_parse_bytes_invalid_utf8() {
518        use crate::Error;
519        // Invalid UTF-8 sequence
520        let invalid_bytes = &[0xFF, 0xFE, 0xFD];
521        let result = Dbc::parse_bytes(invalid_bytes);
522        assert!(result.is_err());
523        match result.unwrap_err() {
524            Error::Expected(msg) => {
525                assert_eq!(msg, Error::INVALID_UTF8);
526            }
527            _ => panic!("Expected Error::Expected with INVALID_UTF8"),
528        }
529    }
530
531    #[test]
532    fn test_parse_without_version_with_comment() {
533        // DBC file with comment and no VERSION line
534        let data = r#"// This is a comment
535BU_: ECM
536
537BO_ 256 Engine : 8 ECM
538 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
539"#;
540        let dbc = Dbc::parse(data).unwrap();
541        assert_eq!(dbc.version().map(|v| v.as_str()), Some(""));
542    }
543
544    #[test]
545    fn test_parse_with_strict_boundary_check() {
546        // Test that strict mode (default) rejects signals that extend beyond boundaries
547        let data = r#"VERSION "1.0"
548
549BU_: ECM
550
551BO_ 256 Test : 8 ECM
552 SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] ""
553"#;
554
555        // Default (strict) mode should fail
556        let result = Dbc::parse(data);
557        assert!(result.is_err());
558    }
559
560    #[cfg(feature = "std")]
561    #[test]
562    fn test_parse_val_value_descriptions() {
563        let data = r#"VERSION ""
564
565NS_ :
566
567BS_:
568
569BU_: Node1 Node2
570
571BO_ 100 Message1 : 8 Node1
572 SG_ Signal : 32|8@1- (1,0) [-1|4] "Gear" Node2
573
574VAL_ 100 Signal -1 "Reverse" 0 "Neutral" 1 "First" 2 "Second" 3 "Third" 4 "Fourth" ;
575"#;
576
577        let dbc = match Dbc::parse(data) {
578            Ok(dbc) => dbc,
579            Err(e) => panic!("Failed to parse DBC: {:?}", e),
580        };
581
582        // Verify basic structure
583        assert_eq!(dbc.messages().len(), 1);
584        let message = dbc.messages().iter().find(|m| m.id() == 100).unwrap();
585        assert_eq!(message.name(), "Message1");
586        assert_eq!(message.sender(), "Node1");
587
588        // Verify value descriptions
589        let value_descriptions = dbc
590            .value_descriptions_for_signal(100, "Signal")
591            .expect("Value descriptions should exist");
592        assert_eq!(value_descriptions.get(0xFFFFFFFF), Some("Reverse")); // -1 as u64
593        assert_eq!(value_descriptions.get(0), Some("Neutral"));
594        assert_eq!(value_descriptions.get(1), Some("First"));
595        assert_eq!(value_descriptions.get(2), Some("Second"));
596        assert_eq!(value_descriptions.get(3), Some("Third"));
597        assert_eq!(value_descriptions.get(4), Some("Fourth"));
598    }
599
600    #[cfg(feature = "std")]
601    #[test]
602    fn test_parse_val_global_value_descriptions() {
603        // Test global value descriptions (VAL_ -1) that apply to all signals with the same name
604        let data = r#"VERSION "1.0"
605
606NS_ :
607
608    VAL_
609
610BS_:
611
612BU_: ECU DASH
613
614BO_ 256 EngineData: 8 ECU
615 SG_ EngineRPM : 0|16@1+ (0.125,0) [0|8000] "rpm" Vector__XXX
616 SG_ DI_gear : 24|3@1+ (1,0) [0|7] "" Vector__XXX
617
618BO_ 512 DashboardDisplay: 8 DASH
619 SG_ DI_gear : 0|3@1+ (1,0) [0|7] "" Vector__XXX
620 SG_ SpeedDisplay : 8|16@1+ (0.01,0) [0|300] "km/h" Vector__XXX
621
622VAL_ -1 DI_gear 0 "INVALID" 1 "P" 2 "R" 3 "N" 4 "D" 5 "S" 6 "L" 7 "SNA" ;
623"#;
624
625        let dbc = match Dbc::parse(data) {
626            Ok(dbc) => dbc,
627            Err(e) => panic!("Failed to parse DBC: {:?}", e),
628        };
629
630        // Verify basic structure
631        assert_eq!(dbc.messages().len(), 2);
632
633        // Verify first message (EngineData)
634        let engine_msg = dbc.messages().iter().find(|m| m.id() == 256).unwrap();
635        assert_eq!(engine_msg.name(), "EngineData");
636        assert_eq!(engine_msg.sender(), "ECU");
637        let di_gear_signal1 = engine_msg.signals().find("DI_gear").unwrap();
638        assert_eq!(di_gear_signal1.name(), "DI_gear");
639        assert_eq!(di_gear_signal1.start_bit(), 24);
640
641        // Verify second message (DashboardDisplay)
642        let dash_msg = dbc.messages().iter().find(|m| m.id() == 512).unwrap();
643        assert_eq!(dash_msg.name(), "DashboardDisplay");
644        assert_eq!(dash_msg.sender(), "DASH");
645        let di_gear_signal2 = dash_msg.signals().find("DI_gear").unwrap();
646        assert_eq!(di_gear_signal2.name(), "DI_gear");
647        assert_eq!(di_gear_signal2.start_bit(), 0);
648
649        // Verify global value descriptions apply to DI_gear in message 256
650        let value_descriptions1 = dbc
651            .value_descriptions_for_signal(256, "DI_gear")
652            .expect("Global value descriptions should exist for DI_gear in message 256");
653
654        assert_eq!(value_descriptions1.get(0), Some("INVALID"));
655        assert_eq!(value_descriptions1.get(1), Some("P"));
656        assert_eq!(value_descriptions1.get(2), Some("R"));
657        assert_eq!(value_descriptions1.get(3), Some("N"));
658        assert_eq!(value_descriptions1.get(4), Some("D"));
659        assert_eq!(value_descriptions1.get(5), Some("S"));
660        assert_eq!(value_descriptions1.get(6), Some("L"));
661        assert_eq!(value_descriptions1.get(7), Some("SNA"));
662
663        // Verify global value descriptions also apply to DI_gear in message 512
664        let value_descriptions2 = dbc
665            .value_descriptions_for_signal(512, "DI_gear")
666            .expect("Global value descriptions should exist for DI_gear in message 512");
667
668        // Both should return the same value descriptions (same reference or same content)
669        assert_eq!(value_descriptions2.get(0), Some("INVALID"));
670        assert_eq!(value_descriptions2.get(1), Some("P"));
671        assert_eq!(value_descriptions2.get(2), Some("R"));
672        assert_eq!(value_descriptions2.get(3), Some("N"));
673        assert_eq!(value_descriptions2.get(4), Some("D"));
674        assert_eq!(value_descriptions2.get(5), Some("S"));
675        assert_eq!(value_descriptions2.get(6), Some("L"));
676        assert_eq!(value_descriptions2.get(7), Some("SNA"));
677
678        // Verify they should be the same instance (both reference the global entry)
679        // Since we store by (Option<u32>, &str), both should return the same entry
680        assert_eq!(value_descriptions1.len(), value_descriptions2.len());
681        assert_eq!(value_descriptions1.len(), 8);
682
683        // Verify other signals don't have value descriptions
684        assert_eq!(dbc.value_descriptions_for_signal(256, "EngineRPM"), None);
685        assert_eq!(dbc.value_descriptions_for_signal(512, "SpeedDisplay"), None);
686    }
687}