dbc_rs/dbc/
dbc.rs

1use crate::{
2    Error, MAX_MESSAGES, MAX_SIGNALS_PER_MESSAGE, Message, MessageList, Nodes, Parser, Result,
3    Signal, Version, compat::Vec, error::lang,
4};
5#[cfg(feature = "std")]
6use crate::{ValueDescriptions, ValueDescriptionsList};
7#[cfg(feature = "std")]
8use std::collections::BTreeMap;
9
10/// Represents a complete DBC (CAN database) file.
11///
12/// A `Dbc` contains:
13/// - An optional version string
14/// - A list of nodes (ECUs)
15/// - A collection of messages with their signals
16///
17/// # Examples
18///
19/// ```rust,no_run
20/// use dbc_rs::Dbc;
21///
22/// let dbc_content = r#"VERSION "1.0"
23///
24/// BU_: ECM TCM
25///
26/// BO_ 256 EngineData : 8 ECM
27///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM
28/// "#;
29///
30/// let dbc = Dbc::parse(dbc_content)?;
31/// println!("Parsed {} messages", dbc.messages().len());
32/// # Ok::<(), dbc_rs::Error>(())
33/// ```
34#[derive(Debug)]
35pub struct Dbc {
36    version: Option<Version>,
37    nodes: Nodes,
38    messages: MessageList,
39    #[cfg(feature = "std")]
40    value_descriptions: ValueDescriptionsList,
41}
42
43impl Dbc {
44    // Validate function for std feature (with value_descriptions)
45    #[cfg(feature = "std")]
46    pub(crate) fn validate(
47        nodes: &Nodes,
48        messages: &[Message],
49        value_descriptions: Option<&ValueDescriptionsList>,
50    ) -> Result<()> {
51        Self::validate_common(nodes, messages)?;
52
53        // Validate value descriptions if provided
54        if let Some(value_descriptions) = value_descriptions {
55            // Validate that all value descriptions reference existing messages and signals
56            for ((message_id_opt, signal_name), _) in value_descriptions.iter() {
57                // Check if message exists (for message-specific value descriptions)
58                if let Some(message_id) = message_id_opt {
59                    let message_exists = messages.iter().any(|msg| msg.id() == message_id);
60                    if !message_exists {
61                        return Err(Error::Validation(lang::VALUE_DESCRIPTION_MESSAGE_NOT_FOUND));
62                    }
63
64                    // Check if signal exists in the message
65                    let signal_exists = messages.iter().any(|msg| {
66                        msg.id() == message_id && msg.signals().find(signal_name).is_some()
67                    });
68                    if !signal_exists {
69                        return Err(Error::Validation(lang::VALUE_DESCRIPTION_SIGNAL_NOT_FOUND));
70                    }
71                } else {
72                    // For global value descriptions (message_id is None), check if signal exists in any message
73                    let signal_exists =
74                        messages.iter().any(|msg| msg.signals().find(signal_name).is_some());
75                    if !signal_exists {
76                        return Err(Error::Validation(lang::VALUE_DESCRIPTION_SIGNAL_NOT_FOUND));
77                    }
78                }
79            }
80        }
81
82        Ok(())
83    }
84
85    // Validate function for no_std mode (without value_descriptions)
86    #[cfg(not(feature = "std"))]
87    pub(crate) fn validate(nodes: &Nodes, messages: &[Message]) -> Result<()> {
88        Self::validate_common(nodes, messages)
89    }
90
91    // Common validation logic shared by both versions
92    fn validate_common(nodes: &Nodes, messages: &[Message]) -> Result<()> {
93        // Check for duplicate message IDs
94        for (i, msg1) in messages.iter().enumerate() {
95            for msg2 in messages.iter().skip(i + 1) {
96                if msg1.id() == msg2.id() {
97                    return Err(Error::Validation(lang::DUPLICATE_MESSAGE_ID));
98                }
99            }
100        }
101
102        // Validate that all message senders are in the nodes list
103        // Skip validation if nodes list is empty (empty nodes allowed per DBC spec)
104        if !nodes.is_empty() {
105            for msg in messages {
106                if !nodes.contains(msg.sender()) {
107                    return Err(Error::Validation(lang::SENDER_NOT_IN_NODES));
108                }
109            }
110        }
111
112        Ok(())
113    }
114
115    #[cfg(feature = "std")]
116    pub(crate) fn new(
117        version: Option<Version>,
118        nodes: Nodes,
119        messages: MessageList,
120        value_descriptions: ValueDescriptionsList,
121    ) -> Self {
122        // Validation should have been done prior (by builder)
123        Self {
124            version,
125            nodes,
126            messages,
127            value_descriptions,
128        }
129    }
130
131    /// Get the version of the DBC file
132    ///
133    /// # Examples
134    ///
135    /// ```rust,no_run
136    /// use dbc_rs::Dbc;
137    ///
138    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
139    /// if let Some(version) = dbc.version() {
140    ///     // Version is available
141    ///     let _ = version.as_str();
142    /// }
143    /// # Ok::<(), dbc_rs::Error>(())
144    /// ```
145    #[inline]
146    #[must_use]
147    pub fn version(&self) -> Option<&Version> {
148        self.version.as_ref()
149    }
150
151    /// Get a reference to the nodes collection
152    ///
153    /// # Examples
154    ///
155    /// ```rust,no_run
156    /// use dbc_rs::Dbc;
157    ///
158    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM\n\nBO_ 256 Engine : 8 ECM")?;
159    /// let nodes = dbc.nodes();
160    /// assert_eq!(nodes.len(), 2);
161    /// // Iterate over nodes
162    /// let mut iter = nodes.iter();
163    /// assert_eq!(iter.next(), Some("ECM"));
164    /// assert_eq!(iter.next(), Some("TCM"));
165    /// assert_eq!(iter.next(), None);
166    /// # Ok::<(), dbc_rs::Error>(())
167    /// ```
168    #[inline]
169    #[must_use]
170    pub fn nodes(&self) -> &Nodes {
171        &self.nodes
172    }
173
174    /// Get a reference to the messages collection
175    ///
176    /// # Examples
177    ///
178    /// ```rust,no_run
179    /// use dbc_rs::Dbc;
180    ///
181    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
182    /// let messages = dbc.messages();
183    /// assert_eq!(messages.len(), 1);
184    /// let message = messages.at(0).unwrap();
185    /// assert_eq!(message.name(), "Engine");
186    /// assert_eq!(message.id(), 256);
187    /// # Ok::<(), dbc_rs::Error>(())
188    /// ```
189    #[inline]
190    #[must_use]
191    pub fn messages(&self) -> &MessageList {
192        &self.messages
193    }
194
195    /// Get value descriptions for a specific signal
196    ///
197    /// Value descriptions map numeric signal values to human-readable text.
198    /// Returns `None` if the signal has no value descriptions.
199    ///
200    /// **Global Value Descriptions**: According to the Vector DBC specification,
201    /// a message_id of `-1` (0xFFFFFFFF) in a `VAL_` statement means the value
202    /// descriptions apply to all signals with that name in ANY message. This
203    /// method will first check for a message-specific entry, then fall back to
204    /// a global entry if one exists.
205    ///
206    /// # Examples
207    ///
208    /// ```rust,no_run
209    /// # use dbc_rs::Dbc;
210    /// # let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 100 Engine : 8 ECM\n SG_ Gear : 0|8@1+ (1,0) [0|5] "" *\n\nVAL_ 100 Gear 0 "Park" 1 "Reverse" ;"#)?;
211    /// if let Some(value_descriptions) = dbc.value_descriptions_for_signal(100, "Gear") {
212    ///     if let Some(desc) = value_descriptions.get(0) {
213    ///         println!("Value 0 means: {}", desc);
214    ///     }
215    /// }
216    /// # Ok::<(), dbc_rs::Error>(())
217    /// ```
218    /// Get a reference to the value descriptions list
219    ///
220    /// # Examples
221    ///
222    /// ```rust,no_run
223    /// use dbc_rs::Dbc;
224    ///
225    /// let dbc = Dbc::parse(r#"VERSION "1.0"
226    ///
227    /// BU_: ECM
228    ///
229    /// BO_ 100 Engine : 8 ECM
230    ///  SG_ Gear : 0|8@1+ (1,0) [0|5] "" *
231    ///
232    /// VAL_ 100 Gear 0 "Park" 1 "Drive" ;"#)?;
233    /// let value_descriptions_list = dbc.value_descriptions();
234    /// assert_eq!(value_descriptions_list.len(), 1);
235    /// # Ok::<(), dbc_rs::Error>(())
236    /// ```
237    #[cfg(feature = "std")]
238    #[inline]
239    #[must_use]
240    pub fn value_descriptions(&self) -> &ValueDescriptionsList {
241        &self.value_descriptions
242    }
243
244    #[cfg(feature = "std")]
245    #[must_use]
246    pub fn value_descriptions_for_signal(
247        &self,
248        message_id: u32,
249        signal_name: &str,
250    ) -> Option<&ValueDescriptions> {
251        self.value_descriptions.for_signal(message_id, signal_name)
252    }
253
254    /// Parse a DBC file from a string slice
255    ///
256    /// # Examples
257    ///
258    /// ```rust,no_run
259    /// use dbc_rs::Dbc;
260    ///
261    /// let dbc_content = r#"VERSION "1.0"
262    ///
263    /// BU_: ECM
264    ///
265    /// BO_ 256 EngineData : 8 ECM
266    ///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm""#;
267    ///
268    /// let dbc = Dbc::parse(dbc_content)?;
269    /// assert_eq!(dbc.messages().len(), 1);
270    /// # Ok::<(), dbc_rs::Error>(())
271    /// ```
272    pub fn parse(data: &str) -> Result<Self> {
273        let mut parser = Parser::new(data.as_bytes())?;
274
275        let mut messages_buffer: Vec<Message, { MAX_MESSAGES }> = Vec::new();
276
277        let mut message_count_actual = 0;
278
279        // Parse version, nodes, and messages
280        use crate::{
281            BA_, BA_DEF_, BA_DEF_DEF_, BO_, BO_TX_BU_, BS_, BU_, CM_, EV_, NS_, SG_, SIG_GROUP_,
282            SIG_VALTYPE_, VAL_, VAL_TABLE_, VERSION,
283        };
284
285        let mut version: Option<Version> = None;
286        let mut nodes: Option<Nodes> = None;
287
288        // Store value descriptions during parsing: (message_id, signal_name, value, description)
289        #[cfg(feature = "std")]
290        type ValueDescriptionsBufferEntry = (
291            Option<u32>,
292            std::string::String,
293            std::vec::Vec<(u64, std::string::String)>,
294        );
295        #[cfg(feature = "std")]
296        let mut value_descriptions_buffer: std::vec::Vec<ValueDescriptionsBufferEntry> =
297            std::vec::Vec::new();
298
299        loop {
300            // Skip comments (lines starting with //)
301            parser.skip_newlines_and_spaces();
302            if parser.starts_with(b"//") {
303                parser.skip_to_end_of_line();
304                continue;
305            }
306
307            let keyword_result = parser.peek_next_keyword();
308            let keyword = match keyword_result {
309                Ok(kw) => kw,
310                Err(Error::UnexpectedEof) => break,
311                Err(Error::Expected(_)) => {
312                    if parser.starts_with(b"//") {
313                        parser.skip_to_end_of_line();
314                        continue;
315                    }
316                    return Err(keyword_result.unwrap_err());
317                }
318                Err(e) => return Err(e),
319            };
320
321            // Save position after peek_next_keyword (which skips whitespace, so we're at the keyword)
322            let pos_at_keyword = parser.pos();
323
324            match keyword {
325                NS_ => {
326                    // Consume NS_ keyword
327                    parser
328                        .expect(crate::NS_.as_bytes())
329                        .map_err(|_| Error::Expected("Failed to consume NS_ keyword"))?;
330                    parser.skip_newlines_and_spaces();
331                    let _ = parser.expect(b":").ok();
332                    loop {
333                        parser.skip_newlines_and_spaces();
334                        if parser.is_empty() {
335                            break;
336                        }
337                        if parser.starts_with(b" ") || parser.starts_with(b"\t") {
338                            parser.skip_to_end_of_line();
339                            continue;
340                        }
341                        if parser.starts_with(b"//") {
342                            parser.skip_to_end_of_line();
343                            continue;
344                        }
345                        if parser.starts_with(BS_.as_bytes())
346                            || parser.starts_with(BU_.as_bytes())
347                            || parser.starts_with(BO_.as_bytes())
348                            || parser.starts_with(SG_.as_bytes())
349                            || parser.starts_with(VERSION.as_bytes())
350                        {
351                            break;
352                        }
353                        parser.skip_to_end_of_line();
354                    }
355                    continue;
356                }
357                CM_ | BS_ | VAL_TABLE_ | BA_DEF_ | BA_DEF_DEF_ | BA_ | SIG_GROUP_
358                | SIG_VALTYPE_ | EV_ | BO_TX_BU_ => {
359                    // Consume keyword then skip to end of line
360                    let _ = parser.expect(keyword.as_bytes()).ok();
361                    parser.skip_to_end_of_line();
362                    continue;
363                }
364                VAL_ => {
365                    #[cfg(feature = "std")]
366                    {
367                        // Consume VAL_ keyword
368                        let _ = parser.expect(crate::VAL_.as_bytes()).ok();
369                        // Parse VAL_ statement: VAL_ message_id signal_name value1 "desc1" value2 "desc2" ... ;
370                        // Note: message_id of -1 (0xFFFFFFFF) means the value descriptions apply to
371                        // all signals with this name in ANY message (global value descriptions)
372                        parser.skip_newlines_and_spaces();
373                        let message_id = match parser.parse_i64() {
374                            Ok(id) => {
375                                // -1 (0xFFFFFFFF) is the magic number for global value descriptions
376                                if id == -1 {
377                                    None
378                                } else if id >= 0 && id <= u32::MAX as i64 {
379                                    Some(id as u32)
380                                } else {
381                                    parser.skip_to_end_of_line();
382                                    continue;
383                                }
384                            }
385                            Err(_) => {
386                                parser.skip_to_end_of_line();
387                                continue;
388                            }
389                        };
390                        parser.skip_newlines_and_spaces();
391                        let signal_name = match parser.parse_identifier() {
392                            Ok(name) => name.to_string(),
393                            Err(_) => {
394                                parser.skip_to_end_of_line();
395                                continue;
396                            }
397                        };
398                        // Parse value-description pairs
399                        let mut entries: std::vec::Vec<(u64, std::string::String)> =
400                            std::vec::Vec::new();
401                        loop {
402                            parser.skip_newlines_and_spaces();
403                            // Check for semicolon (end of VAL_ statement)
404                            if parser.starts_with(b";") {
405                                parser.expect(b";").ok();
406                                break;
407                            }
408                            // Parse value (as i64 first to handle negative values like -1, then convert to u64)
409                            // Note: -1 (0xFFFFFFFF) is the magic number for global value descriptions in message_id,
410                            // but values in VAL_ can also be negative
411                            let value = match parser.parse_i64() {
412                                Ok(v) => {
413                                    // Handle -1 specially: convert to 0xFFFFFFFF (u32::MAX) instead of large u64
414                                    if v == -1 { 0xFFFF_FFFFu64 } else { v as u64 }
415                                }
416                                Err(_) => {
417                                    parser.skip_to_end_of_line();
418                                    break;
419                                }
420                            };
421                            parser.skip_newlines_and_spaces();
422                            // Parse description string (expect quote, then take until quote)
423                            if parser.expect(b"\"").is_err() {
424                                parser.skip_to_end_of_line();
425                                break;
426                            }
427                            let description_bytes = match parser.take_until_quote(false, 1024) {
428                                Ok(bytes) => bytes,
429                                Err(_) => {
430                                    parser.skip_to_end_of_line();
431                                    break;
432                                }
433                            };
434                            let description = match core::str::from_utf8(description_bytes) {
435                                Ok(s) => s.to_string(),
436                                Err(_) => {
437                                    parser.skip_to_end_of_line();
438                                    break;
439                                }
440                            };
441                            entries.push((value, description));
442                        }
443                        if !entries.is_empty() {
444                            value_descriptions_buffer.push((message_id, signal_name, entries));
445                        }
446                    }
447                    #[cfg(not(feature = "std"))]
448                    {
449                        // In no_std mode, consume VAL_ keyword and skip the rest
450                        let _ = parser.expect(crate::VAL_.as_bytes()).ok();
451                        parser.skip_to_end_of_line();
452                    }
453                    continue;
454                }
455                VERSION => {
456                    // Version::parse expects VERSION keyword, don't consume it here
457                    version = Some(Version::parse(&mut parser)?);
458                    continue;
459                }
460                BU_ => {
461                    // Nodes::parse expects BU_ keyword, create parser from original input including it
462                    parser.skip_to_end_of_line();
463                    let bu_input = &data.as_bytes()[pos_at_keyword..parser.pos()];
464                    let mut bu_parser = Parser::new(bu_input)?;
465                    nodes = Some(Nodes::parse(&mut bu_parser)?);
466                    continue;
467                }
468                BO_ => {
469                    // Check limit using MAX_MESSAGES constant
470                    if message_count_actual >= MAX_MESSAGES {
471                        return Err(Error::Nodes(lang::NODES_TOO_MANY));
472                    }
473
474                    // Save parser position (at BO_ keyword, so Message::parse can consume it)
475                    let message_start_pos = pos_at_keyword;
476
477                    // Don't manually parse - just find where the header ends by looking for the colon and sender
478                    // We need to find the end of the header line to separate it from signals
479                    let header_line_end = {
480                        // Skip to end of line to find where header ends
481                        let mut temp_parser = Parser::new(&data.as_bytes()[pos_at_keyword..])?;
482                        // Skip BO_ keyword
483                        temp_parser.expect(crate::BO_.as_bytes()).ok();
484                        temp_parser.skip_whitespace().ok();
485                        temp_parser.parse_u32().ok(); // ID
486                        temp_parser.skip_whitespace().ok();
487                        temp_parser.parse_identifier().ok(); // name
488                        temp_parser.skip_whitespace().ok();
489                        temp_parser.expect(b":").ok(); // colon
490                        temp_parser.skip_whitespace().ok();
491                        temp_parser.parse_u8().ok(); // DLC
492                        temp_parser.skip_whitespace().ok();
493                        temp_parser.parse_identifier().ok(); // sender
494                        pos_at_keyword + temp_parser.pos()
495                    };
496
497                    // Now parse signals from the original parser
498                    parser.skip_to_end_of_line(); // Skip past header line
499
500                    let mut signals_array: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }> = Vec::new();
501
502                    loop {
503                        parser.skip_newlines_and_spaces();
504                        if parser.starts_with(crate::SG_.as_bytes()) {
505                            if let Some(next_byte) = parser.peek_byte_at(3) {
506                                if matches!(next_byte, b' ' | b'\n' | b'\r' | b'\t') {
507                                    if signals_array.len() >= MAX_SIGNALS_PER_MESSAGE {
508                                        return Err(Error::Receivers(
509                                            lang::SIGNAL_RECEIVERS_TOO_MANY,
510                                        ));
511                                    }
512                                    // Signal::parse expects SG_ keyword, which we've already verified with starts_with
513                                    let signal = Signal::parse(&mut parser)?;
514                                    signals_array.push(signal).map_err(|_| {
515                                        Error::Receivers(lang::SIGNAL_RECEIVERS_TOO_MANY)
516                                    })?;
517                                    continue;
518                                }
519                            }
520                        }
521                        break;
522                    }
523
524                    // Restore parser to start of message line and use Message::parse
525                    // Create a new parser from the original input, but only up to the end of the header
526                    // (not including signals, so Message::parse doesn't complain about extra content)
527                    let message_input = &data.as_bytes()[message_start_pos..header_line_end];
528                    let mut message_parser = Parser::new(message_input)?;
529
530                    // Use Message::parse which will parse the header and use our signals
531                    let message = Message::parse(&mut message_parser, signals_array.as_slice())?;
532
533                    messages_buffer
534                        .push(message)
535                        .map_err(|_| Error::Message(lang::NODES_TOO_MANY))?;
536                    message_count_actual += 1;
537                    continue;
538                }
539                SG_ => {
540                    // Orphaned signal (not inside a message) - skip it
541                    parser.skip_to_end_of_line();
542                    continue;
543                }
544                _ => {
545                    parser.skip_to_end_of_line();
546                    continue;
547                }
548            }
549        }
550
551        // Allow empty nodes (DBC spec allows empty BU_: line)
552        let nodes = nodes.unwrap_or_default();
553
554        // If no version was parsed, default to empty version
555        let version = version.or_else(|| {
556            static EMPTY_VERSION: &[u8] = b"VERSION \"\"";
557            let mut parser = Parser::new(EMPTY_VERSION).ok()?;
558            Version::parse(&mut parser).ok()
559        });
560
561        // Build value descriptions map for storage in Dbc
562        #[cfg(feature = "std")]
563        let value_descriptions_list = {
564            let mut map: BTreeMap<(Option<u32>, std::string::String), ValueDescriptions> =
565                BTreeMap::new();
566            for (message_id, signal_name, entries) in value_descriptions_buffer {
567                let key = (message_id, signal_name);
568                let value_descriptions = ValueDescriptions::from_slice(&entries);
569                map.insert(key, value_descriptions);
570            }
571            ValueDescriptionsList::from_map(map)
572        };
573
574        // Convert messages buffer to slice for validation and construction
575        let messages_slice: &[Message] = messages_buffer.as_slice();
576
577        // Validate messages (duplicate IDs, sender in nodes, etc.)
578        #[cfg(feature = "std")]
579        Self::validate(&nodes, messages_slice, Some(&value_descriptions_list)).map_err(|e| {
580            crate::error::map_val_error(e, Error::Message, || {
581                Error::Message(crate::error::lang::MESSAGE_ERROR_PREFIX)
582            })
583        })?;
584        #[cfg(not(feature = "std"))]
585        Self::validate(&nodes, messages_slice).map_err(|e| {
586            crate::error::map_val_error(e, Error::Message, || {
587                Error::Message(crate::error::lang::MESSAGE_ERROR_PREFIX)
588            })
589        })?;
590
591        // Construct directly (validation already done)
592        let messages = MessageList::new(messages_slice)?;
593        Ok(Self {
594            version,
595            nodes,
596            messages,
597            #[cfg(feature = "std")]
598            value_descriptions: value_descriptions_list,
599        })
600    }
601
602    /// Parse a DBC file from a byte slice
603    ///
604    /// # Examples
605    ///
606    /// ```rust,no_run
607    /// use dbc_rs::Dbc;
608    ///
609    /// let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
610    /// let dbc = Dbc::parse_bytes(dbc_bytes)?;
611    /// println!("Parsed {} messages", dbc.messages().len());
612    /// # Ok::<(), dbc_rs::Error>(())
613    /// ```
614    pub fn parse_bytes(data: &[u8]) -> Result<Dbc> {
615        let content =
616            core::str::from_utf8(data).map_err(|_e| Error::Expected(lang::INVALID_UTF8))?;
617        Dbc::parse(content)
618    }
619
620    /// Serialize this DBC to a DBC format string
621    ///
622    /// # Examples
623    ///
624    /// ```rust,no_run
625    /// use dbc_rs::Dbc;
626    ///
627    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
628    /// let dbc_string = dbc.to_dbc_string();
629    /// // The string can be written to a file or used elsewhere
630    /// assert!(dbc_string.contains("VERSION"));
631    /// # Ok::<(), dbc_rs::Error>(())
632    /// ```
633    #[cfg(feature = "std")]
634    #[must_use]
635    pub fn to_dbc_string(&self) -> String {
636        // Pre-allocate with estimated capacity
637        // Estimate: ~50 chars per message + ~100 chars per signal
638        let signal_count: usize = self.messages().iter().map(|m| m.signals().len()).sum();
639        let estimated_capacity = 200 + (self.messages.len() * 50) + (signal_count * 100);
640        let mut result = String::with_capacity(estimated_capacity);
641
642        // VERSION line
643        if let Some(version) = &self.version {
644            result.push_str(&version.to_dbc_string());
645            result.push_str("\n\n");
646        }
647
648        // BU_ line
649        result.push_str(&self.nodes.to_dbc_string());
650        result.push('\n');
651
652        // BO_ and SG_ lines for each message
653        for message in self.messages().iter() {
654            result.push('\n');
655            result.push_str(&message.to_string_full());
656        }
657
658        result
659    }
660}
661
662#[cfg(feature = "std")]
663impl core::fmt::Display for Dbc {
664    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
665        write!(f, "{}", self.to_dbc_string())
666    }
667}
668
669#[cfg(all(test, feature = "std"))]
670mod tests {
671    #![allow(clippy::float_cmp)]
672    use super::*;
673    use crate::{Error, error::lang};
674
675    #[test]
676    fn parses_real_dbc() {
677        let data = r#"VERSION "1.0"
678
679BU_: ECM TCM
680
681BO_ 256 Engine : 8 ECM
682 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
683 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
684
685BO_ 512 Brake : 4 TCM
686 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar""#;
687
688        let dbc = Dbc::parse(data).unwrap();
689        assert_eq!(dbc.messages().len(), 2);
690        let mut messages_iter = dbc.messages().iter();
691        let msg0 = messages_iter.next().unwrap();
692        assert_eq!(msg0.signals().len(), 2);
693        let mut signals_iter = msg0.signals().iter();
694        assert_eq!(signals_iter.next().unwrap().name(), "RPM");
695        assert_eq!(signals_iter.next().unwrap().name(), "Temp");
696        let msg1 = messages_iter.next().unwrap();
697        assert_eq!(msg1.signals().len(), 1);
698        assert_eq!(msg1.signals().iter().next().unwrap().name(), "Pressure");
699    }
700
701    #[test]
702    fn test_parse_duplicate_message_id() {
703        // Test that parse also validates duplicate message IDs
704        let data = r#"VERSION "1.0"
705
706BU_: ECM
707
708BO_ 256 EngineData1 : 8 ECM
709 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
710
711BO_ 256 EngineData2 : 8 ECM
712 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
713"#;
714
715        let result = Dbc::parse(data);
716        assert!(result.is_err());
717        match result.unwrap_err() {
718            Error::Message(msg) => {
719                assert!(msg.contains(lang::DUPLICATE_MESSAGE_ID));
720            }
721            _ => panic!("Expected Error::Message"),
722        }
723    }
724
725    #[test]
726    fn test_parse_sender_not_in_nodes() {
727        // Test that parse also validates message senders are in nodes list
728        let data = r#"VERSION "1.0"
729
730BU_: ECM
731
732BO_ 256 EngineData : 8 TCM
733 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
734"#;
735
736        let result = Dbc::parse(data);
737        assert!(result.is_err());
738        match result.unwrap_err() {
739            Error::Message(msg) => {
740                assert!(msg.contains(lang::SENDER_NOT_IN_NODES));
741            }
742            _ => panic!("Expected Error::Message"),
743        }
744    }
745
746    #[test]
747    fn test_parse_empty_file() {
748        // Test parsing an empty file
749        let result = Dbc::parse("");
750        assert!(result.is_err());
751        match result.unwrap_err() {
752            Error::UnexpectedEof => {
753                // Empty file should result in unexpected EOF
754            }
755            _ => panic!("Expected Error::UnexpectedEof"),
756        }
757    }
758
759    #[test]
760    fn test_parse_missing_nodes() {
761        // Test parsing without BU_ statement
762        // Note: The parser allows missing BU_ line and treats it as empty nodes
763        // This is consistent with allowing empty nodes per DBC spec
764        let data = r#"VERSION "1.0"
765
766BO_ 256 EngineData : 8 ECM
767 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
768"#;
769
770        let result = Dbc::parse(data);
771        // Parser should succeed with empty nodes (missing BU_ is treated as empty nodes)
772        assert!(result.is_ok());
773        let dbc = result.unwrap();
774        assert!(dbc.nodes().is_empty());
775    }
776
777    #[test]
778    fn test_parse_bytes() {
779        let data = r#"VERSION "1.0"
780
781BU_: ECM
782
783BO_ 256 Engine : 8 ECM
784 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
785"#;
786
787        let bytes = data.as_bytes();
788        let dbc = Dbc::parse_bytes(bytes).unwrap();
789        assert_eq!(
790            dbc.version().map(|v| v.to_string()),
791            Some("1.0".to_string())
792        );
793        assert_eq!(dbc.messages().len(), 1);
794    }
795
796    #[test]
797    fn test_parse_bytes_invalid_utf8() {
798        // Invalid UTF-8 sequence
799        let invalid_bytes = &[0xFF, 0xFE, 0xFD];
800        let result = Dbc::parse_bytes(invalid_bytes);
801        assert!(result.is_err());
802        match result.unwrap_err() {
803            Error::Expected(msg) => {
804                assert_eq!(msg, lang::INVALID_UTF8);
805            }
806            _ => panic!("Expected Error::Expected with INVALID_UTF8"),
807        }
808    }
809
810    #[test]
811    fn test_save_round_trip() {
812        let original = r#"VERSION "1.0"
813
814BU_: ECM TCM
815
816BO_ 256 EngineData : 8 ECM
817 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
818 SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM
819
820BO_ 512 BrakeData : 4 TCM
821 SG_ Pressure : 0|16@0+ (0.1,0) [0|1000] "bar"
822"#;
823
824        let dbc = Dbc::parse(original).unwrap();
825        let saved = dbc.to_dbc_string();
826        let dbc2 = Dbc::parse(&saved).unwrap();
827
828        // Verify round-trip: parsed data should match
829        assert_eq!(
830            dbc.version().map(|v| v.to_string()),
831            dbc2.version().map(|v| v.to_string())
832        );
833        assert_eq!(dbc.messages().len(), dbc2.messages().len());
834
835        for (msg1, msg2) in dbc.messages().iter().zip(dbc2.messages().iter()) {
836            assert_eq!(msg1.id(), msg2.id());
837            assert_eq!(msg1.name(), msg2.name());
838            assert_eq!(msg1.dlc(), msg2.dlc());
839            assert_eq!(msg1.sender(), msg2.sender());
840            assert_eq!(msg1.signals().len(), msg2.signals().len());
841
842            for (sig1, sig2) in msg1.signals().iter().zip(msg2.signals().iter()) {
843                assert_eq!(sig1.name(), sig2.name());
844                assert_eq!(sig1.start_bit(), sig2.start_bit());
845                assert_eq!(sig1.length(), sig2.length());
846                assert_eq!(sig1.byte_order(), sig2.byte_order());
847                assert_eq!(sig1.is_unsigned(), sig2.is_unsigned());
848                assert_eq!(sig1.factor(), sig2.factor());
849                assert_eq!(sig1.offset(), sig2.offset());
850                assert_eq!(sig1.min(), sig2.min());
851                assert_eq!(sig1.max(), sig2.max());
852                assert_eq!(sig1.unit(), sig2.unit());
853                assert_eq!(sig1.receivers(), sig2.receivers());
854            }
855        }
856    }
857
858    #[test]
859    #[cfg(feature = "std")]
860    fn test_save_multiple_messages() {
861        // Use parsing instead of builders
862        let dbc_content = r#"VERSION "1.0"
863
864BU_: ECM TCM
865
866BO_ 256 EngineData : 8 ECM
867 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *
868
869BO_ 512 BrakeData : 4 TCM
870 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar"
871"#;
872        let dbc = Dbc::parse(dbc_content).unwrap();
873        let saved = dbc.to_dbc_string();
874
875        // Verify both messages are present
876        assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
877        assert!(saved.contains("BO_ 512 BrakeData : 4 TCM"));
878        assert!(saved.contains("SG_ RPM"));
879        assert!(saved.contains("SG_ Pressure"));
880    }
881
882    // Note: Builder limit tests have been moved to dbc_builder.rs
883    // These tests require building many messages programmatically, which is builder functionality
884
885    #[test]
886    fn test_parse_without_version() {
887        // DBC file without VERSION line should default to empty version
888        let data = r#"
889BU_: ECM
890
891BO_ 256 Engine : 8 ECM
892 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
893"#;
894        let dbc = Dbc::parse(data).unwrap();
895        assert_eq!(dbc.version().map(|v| v.to_string()), Some("".to_string()));
896    }
897
898    #[test]
899    fn test_parse_without_version_with_comment() {
900        // DBC file with comment and no VERSION line
901        let data = r#"// This is a comment
902BU_: ECM
903
904BO_ 256 Engine : 8 ECM
905 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
906"#;
907        let dbc = Dbc::parse(data).unwrap();
908        assert_eq!(dbc.version().map(|v| v.to_string()), Some("".to_string()));
909    }
910
911    #[test]
912    fn test_parse_with_strict_boundary_check() {
913        // Test that strict mode (default) rejects signals that extend beyond boundaries
914        let data = r#"VERSION "1.0"
915
916BU_: ECM
917
918BO_ 256 Test : 8 ECM
919 SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] ""
920"#;
921
922        // Default (strict) mode should fail
923        let result = Dbc::parse(data);
924        assert!(result.is_err());
925
926        // Explicit strict mode should also fail
927        let result = Dbc::parse(data);
928        assert!(result.is_err());
929    }
930
931    // Tests that require std (for to_dbc_string, value_descriptions, etc.)
932    #[cfg(feature = "std")]
933    mod tests_std {
934        use super::*;
935
936        #[test]
937        fn test_parse_from_string() {
938            let data = String::from(
939                r#"VERSION "1.0"
940
941BU_: ECM
942
943BO_ 256 Engine : 8 ECM
944 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
945"#,
946            );
947
948            let dbc = Dbc::parse(&data).unwrap();
949            assert_eq!(
950                dbc.version().map(|v| v.to_string()),
951                Some("1.0".to_string())
952            );
953            assert_eq!(dbc.messages().len(), 1);
954        }
955
956        #[test]
957        fn test_save_basic() {
958            // Use parsing instead of builders
959            let dbc_content = r#"VERSION "1.0"
960
961BU_: ECM
962
963BO_ 256 EngineData : 8 ECM
964 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *
965"#;
966            let dbc = Dbc::parse(dbc_content).unwrap();
967
968            let saved = dbc.to_dbc_string();
969            assert!(saved.contains("VERSION \"1.0\""));
970            assert!(saved.contains("BU_: ECM"));
971            assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
972            assert!(saved.contains("SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *")); // BigEndian = @0
973        }
974
975        #[test]
976        fn test_save_round_trip() {
977            let original = r#"VERSION "1.0"
978
979BU_: ECM TCM
980
981BO_ 256 EngineData : 8 ECM
982 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
983 SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM
984
985BO_ 512 BrakeData : 4 TCM
986 SG_ Pressure : 0|16@0+ (0.1,0) [0|1000] "bar"
987"#;
988
989            let dbc = Dbc::parse(original).unwrap();
990            let saved = dbc.to_dbc_string();
991            let dbc2 = Dbc::parse(&saved).unwrap();
992
993            // Verify round-trip: parsed data should match
994            assert_eq!(
995                dbc.version().map(|v| v.to_string()),
996                dbc2.version().map(|v| v.to_string())
997            );
998            assert_eq!(dbc.messages().len(), dbc2.messages().len());
999
1000            for (msg1, msg2) in dbc.messages().iter().zip(dbc2.messages().iter()) {
1001                assert_eq!(msg1.id(), msg2.id());
1002                assert_eq!(msg1.name(), msg2.name());
1003                assert_eq!(msg1.dlc(), msg2.dlc());
1004                assert_eq!(msg1.sender(), msg2.sender());
1005                assert_eq!(msg1.signals().len(), msg2.signals().len());
1006
1007                for (sig1, sig2) in msg1.signals().iter().zip(msg2.signals().iter()) {
1008                    assert_eq!(sig1.name(), sig2.name());
1009                    assert_eq!(sig1.start_bit(), sig2.start_bit());
1010                    assert_eq!(sig1.length(), sig2.length());
1011                    assert_eq!(sig1.byte_order(), sig2.byte_order());
1012                    assert_eq!(sig1.is_unsigned(), sig2.is_unsigned());
1013                    assert_eq!(sig1.factor(), sig2.factor());
1014                    assert_eq!(sig1.offset(), sig2.offset());
1015                    assert_eq!(sig1.min(), sig2.min());
1016                    assert_eq!(sig1.max(), sig2.max());
1017                    assert_eq!(sig1.unit(), sig2.unit());
1018                    assert_eq!(sig1.receivers(), sig2.receivers());
1019                }
1020            }
1021        }
1022
1023        #[test]
1024        fn test_save_multiple_messages() {
1025            // Use parsing instead of builders
1026            let dbc_content = r#"VERSION "1.0"
1027
1028BU_: ECM TCM
1029
1030BO_ 256 EngineData : 8 ECM
1031 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
1032
1033BO_ 512 BrakeData : 4 TCM
1034 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar"
1035"#;
1036            let dbc = Dbc::parse(dbc_content).unwrap();
1037            let saved = dbc.to_dbc_string();
1038
1039            // Verify both messages are present
1040            assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
1041            assert!(saved.contains("BO_ 512 BrakeData : 4 TCM"));
1042            assert!(saved.contains("SG_ RPM"));
1043            assert!(saved.contains("SG_ Pressure"));
1044        }
1045
1046        #[test]
1047        fn test_parse_val_value_descriptions() {
1048            let data = r#"VERSION ""
1049
1050NS_ :
1051
1052BS_:
1053
1054BU_: Node1 Node2
1055
1056BO_ 100 Message1 : 8 Node1
1057 SG_ Signal : 32|8@1- (1,0) [-1|4] "Gear" Node2
1058
1059VAL_ 100 Signal -1 "Reverse" 0 "Neutral" 1 "First" 2 "Second" 3 "Third" 4 "Fourth" ;
1060"#;
1061
1062            let dbc = match Dbc::parse(data) {
1063                Ok(dbc) => dbc,
1064                Err(e) => panic!("Failed to parse DBC: {:?}", e),
1065            };
1066
1067            // Verify basic structure
1068            assert_eq!(dbc.messages().len(), 1);
1069            let message = dbc.messages().iter().find(|m| m.id() == 100).unwrap();
1070            assert_eq!(message.name(), "Message1");
1071            assert_eq!(message.sender(), "Node1");
1072
1073            // Verify signal exists
1074            let signal = message.signals().find("Signal").unwrap();
1075            assert_eq!(signal.name(), "Signal");
1076            assert_eq!(signal.start_bit(), 32);
1077            assert_eq!(signal.length(), 8);
1078            assert_eq!(signal.unit(), Some("Gear"));
1079
1080            // Verify value descriptions are parsed and accessible
1081            let value_descriptions = dbc
1082                .value_descriptions_for_signal(100, "Signal")
1083                .expect("Value descriptions should exist for signal");
1084
1085            // Verify all value mappings
1086            assert_eq!(value_descriptions.get(0xFFFFFFFF), Some("Reverse")); // -1 as u32
1087            assert_eq!(value_descriptions.get(0), Some("Neutral"));
1088            assert_eq!(value_descriptions.get(1), Some("First"));
1089            assert_eq!(value_descriptions.get(2), Some("Second"));
1090            assert_eq!(value_descriptions.get(3), Some("Third"));
1091            assert_eq!(value_descriptions.get(4), Some("Fourth"));
1092
1093            // Verify non-existent values return None
1094            assert_eq!(value_descriptions.get(5), None);
1095            assert_eq!(value_descriptions.get(99), None);
1096
1097            // Verify we can iterate over all value descriptions
1098            let iter = value_descriptions.iter();
1099            let mut entries: std::vec::Vec<_> = iter.collect();
1100            entries.sort_by_key(|(v, _)| *v);
1101
1102            assert_eq!(entries.len(), 6);
1103            // After sorting by key (u64), 0xFFFFFFFF (4294967295) comes after smaller values
1104            assert_eq!(entries[0], (0, "Neutral".to_string()));
1105            assert_eq!(entries[1], (1, "First".to_string()));
1106            assert_eq!(entries[2], (2, "Second".to_string()));
1107            assert_eq!(entries[3], (3, "Third".to_string()));
1108            assert_eq!(entries[4], (4, "Fourth".to_string()));
1109            assert_eq!(entries[5], (0xFFFFFFFF, "Reverse".to_string()));
1110        }
1111
1112        #[test]
1113        fn test_parse_val_global_value_descriptions() {
1114            // Test global value descriptions (VAL_ -1) that apply to all signals with the same name
1115            let data = r#"VERSION "1.0"
1116
1117NS_ :
1118
1119    VAL_
1120
1121BS_:
1122
1123BU_: ECU DASH
1124
1125BO_ 256 EngineData: 8 ECU
1126 SG_ EngineRPM : 0|16@1+ (0.125,0) [0|8000] "rpm" Vector__XXX
1127 SG_ DI_gear : 24|3@1+ (1,0) [0|7] "" Vector__XXX
1128
1129BO_ 512 DashboardDisplay: 8 DASH
1130 SG_ DI_gear : 0|3@1+ (1,0) [0|7] "" Vector__XXX
1131 SG_ SpeedDisplay : 8|16@1+ (0.01,0) [0|300] "km/h" Vector__XXX
1132
1133VAL_ -1 DI_gear 0 "INVALID" 1 "P" 2 "R" 3 "N" 4 "D" 5 "S" 6 "L" 7 "SNA" ;
1134"#;
1135
1136            let dbc = match Dbc::parse(data) {
1137                Ok(dbc) => dbc,
1138                Err(e) => panic!("Failed to parse DBC: {:?}", e),
1139            };
1140
1141            // Verify basic structure
1142            assert_eq!(dbc.messages().len(), 2);
1143
1144            // Verify first message (EngineData)
1145            let engine_msg = dbc.messages().iter().find(|m| m.id() == 256).unwrap();
1146            assert_eq!(engine_msg.name(), "EngineData");
1147            assert_eq!(engine_msg.sender(), "ECU");
1148            let di_gear_signal1 = engine_msg.signals().find("DI_gear").unwrap();
1149            assert_eq!(di_gear_signal1.name(), "DI_gear");
1150            assert_eq!(di_gear_signal1.start_bit(), 24);
1151
1152            // Verify second message (DashboardDisplay)
1153            let dash_msg = dbc.messages().iter().find(|m| m.id() == 512).unwrap();
1154            assert_eq!(dash_msg.name(), "DashboardDisplay");
1155            assert_eq!(dash_msg.sender(), "DASH");
1156            let di_gear_signal2 = dash_msg.signals().find("DI_gear").unwrap();
1157            assert_eq!(di_gear_signal2.name(), "DI_gear");
1158            assert_eq!(di_gear_signal2.start_bit(), 0);
1159
1160            // Verify global value descriptions apply to DI_gear in message 256
1161            let value_descriptions1 = dbc
1162                .value_descriptions_for_signal(256, "DI_gear")
1163                .expect("Global value descriptions should exist for DI_gear in message 256");
1164
1165            assert_eq!(value_descriptions1.get(0), Some("INVALID"));
1166            assert_eq!(value_descriptions1.get(1), Some("P"));
1167            assert_eq!(value_descriptions1.get(2), Some("R"));
1168            assert_eq!(value_descriptions1.get(3), Some("N"));
1169            assert_eq!(value_descriptions1.get(4), Some("D"));
1170            assert_eq!(value_descriptions1.get(5), Some("S"));
1171            assert_eq!(value_descriptions1.get(6), Some("L"));
1172            assert_eq!(value_descriptions1.get(7), Some("SNA"));
1173
1174            // Verify global value descriptions also apply to DI_gear in message 512
1175            let value_descriptions2 = dbc
1176                .value_descriptions_for_signal(512, "DI_gear")
1177                .expect("Global value descriptions should exist for DI_gear in message 512");
1178
1179            // Both should return the same value descriptions (same reference or same content)
1180            assert_eq!(value_descriptions2.get(0), Some("INVALID"));
1181            assert_eq!(value_descriptions2.get(1), Some("P"));
1182            assert_eq!(value_descriptions2.get(2), Some("R"));
1183            assert_eq!(value_descriptions2.get(3), Some("N"));
1184            assert_eq!(value_descriptions2.get(4), Some("D"));
1185            assert_eq!(value_descriptions2.get(5), Some("S"));
1186            assert_eq!(value_descriptions2.get(6), Some("L"));
1187            assert_eq!(value_descriptions2.get(7), Some("SNA"));
1188
1189            // Verify they should be the same instance (both reference the global entry)
1190            // Since we store by (Option<u32>, &str), both should return the same entry
1191            assert_eq!(value_descriptions1.len(), value_descriptions2.len());
1192            assert_eq!(value_descriptions1.len(), 8);
1193
1194            // Verify other signals don't have value descriptions
1195            assert_eq!(dbc.value_descriptions_for_signal(256, "EngineRPM"), None);
1196            assert_eq!(dbc.value_descriptions_for_signal(512, "SpeedDisplay"), None);
1197        }
1198    }
1199}