dbc_rs/dbc/
parse.rs

1use crate::{
2    BitTiming, Dbc, Error, ExtendedMultiplexing, MAX_EXTENDED_MULTIPLEXING, MAX_MESSAGES,
3    MAX_NODES, MAX_SIGNALS_PER_MESSAGE, Message, Nodes, Parser, Result, Signal, ValueDescriptions,
4    Version,
5    compat::{Comment, Name, ValueDescEntries, Vec},
6    dbc::{Messages, Validate, ValueDescriptionsMap},
7};
8
9impl Dbc {
10    /// Parse a DBC file from a string slice
11    ///
12    /// # Examples
13    ///
14    /// ```rust,no_run
15    /// use dbc_rs::Dbc;
16    ///
17    /// let dbc_content = r#"VERSION "1.0"
18    ///
19    /// BU_: ECM
20    ///
21    /// BO_ 256 EngineData : 8 ECM
22    ///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm""#;
23    ///
24    /// let dbc = Dbc::parse(dbc_content)?;
25    /// assert_eq!(dbc.messages().len(), 1);
26    /// # Ok::<(), dbc_rs::Error>(())
27    /// ```
28    pub fn parse(data: &str) -> Result<Self> {
29        let mut parser = Parser::new(data.as_bytes())?;
30
31        let mut messages_buffer: Vec<Message, { MAX_MESSAGES }> = Vec::new();
32
33        let mut message_count_actual = 0;
34
35        // Parse version, nodes, and messages
36        use crate::{
37            BA_, BA_DEF_, BA_DEF_DEF_, BO_, BO_TX_BU_, BS_, BU_, CM_, EV_, NS_, SG_, SG_MUL_VAL_,
38            SIG_GROUP_, SIG_VALTYPE_, VAL_, VAL_TABLE_, VERSION,
39        };
40
41        let mut version: Option<Version> = None;
42        let mut bit_timing: Option<BitTiming> = None;
43        let mut nodes: Option<Nodes> = None;
44
45        // Type aliases for parsing buffers
46        type ValueDescBufferEntry = (Option<u32>, Name, ValueDescEntries);
47        type ValueDescBuffer = Vec<ValueDescBufferEntry, { MAX_MESSAGES }>;
48        type ExtMuxBuffer = Vec<ExtendedMultiplexing, { MAX_EXTENDED_MULTIPLEXING }>;
49
50        // Comment buffers - CM_ entries can appear anywhere in the file
51        // so we collect them first and apply after parsing messages
52        type MessageCommentBuffer = Vec<(u32, Comment), { MAX_MESSAGES }>;
53        // Signal comments: (message_id, signal_name, comment)
54        type SignalCommentBuffer = Vec<(u32, Name, Comment), { MAX_MESSAGES * 4 }>;
55
56        let mut value_descriptions_buffer: ValueDescBuffer = ValueDescBuffer::new();
57        let mut extended_multiplexing_buffer: ExtMuxBuffer = ExtMuxBuffer::new();
58
59        // Comment buffers
60        let mut db_comment: Option<Comment> = None;
61        // Node comments: (node_name, comment)
62        type NodeCommentBuffer = Vec<(Name, Comment), { MAX_NODES }>;
63        let mut node_comments_buffer: NodeCommentBuffer = NodeCommentBuffer::new();
64        let mut message_comments_buffer: MessageCommentBuffer = MessageCommentBuffer::new();
65        let mut signal_comments_buffer: SignalCommentBuffer = SignalCommentBuffer::new();
66
67        loop {
68            // Skip comments (lines starting with //)
69            parser.skip_newlines_and_spaces();
70            if parser.starts_with(b"//") {
71                parser.skip_to_end_of_line();
72                continue;
73            }
74
75            let keyword_result = parser.peek_next_keyword();
76            let keyword = match keyword_result {
77                Ok(kw) => kw,
78                Err(Error::UnexpectedEof { .. }) => break,
79                Err(Error::Expected { .. }) => {
80                    if parser.starts_with(b"//") {
81                        parser.skip_to_end_of_line();
82                        continue;
83                    }
84                    return Err(keyword_result.unwrap_err());
85                }
86                Err(e) => return Err(e),
87            };
88
89            // Save position after peek_next_keyword (which skips whitespace, so we're at the keyword)
90            let pos_at_keyword = parser.pos();
91
92            match keyword {
93                NS_ => {
94                    // Consume NS_ keyword
95                    let line = parser.line();
96                    parser
97                        .expect(crate::NS_.as_bytes())
98                        .map_err(|_| Error::expected_at("Failed to consume NS_ keyword", line))?;
99                    parser.skip_newlines_and_spaces();
100                    let _ = parser.expect(b":").ok();
101                    loop {
102                        parser.skip_newlines_and_spaces();
103                        if parser.is_empty() {
104                            break;
105                        }
106                        if parser.starts_with(b" ") || parser.starts_with(b"\t") {
107                            parser.skip_to_end_of_line();
108                            continue;
109                        }
110                        if parser.starts_with(b"//") {
111                            parser.skip_to_end_of_line();
112                            continue;
113                        }
114                        if parser.starts_with(BS_.as_bytes())
115                            || parser.starts_with(BU_.as_bytes())
116                            || parser.starts_with(BO_.as_bytes())
117                            || parser.starts_with(SG_.as_bytes())
118                            || parser.starts_with(VERSION.as_bytes())
119                        {
120                            break;
121                        }
122                        parser.skip_to_end_of_line();
123                    }
124                    continue;
125                }
126                BS_ => {
127                    // Parse bit timing section (usually empty)
128                    let parsed = BitTiming::parse(&mut parser)?;
129                    // Only store if not empty (has actual values)
130                    if !parsed.is_empty() {
131                        bit_timing = Some(parsed);
132                    }
133                    parser.skip_to_end_of_line();
134                    continue;
135                }
136                VAL_TABLE_ | BA_DEF_ | BA_DEF_DEF_ | BA_ | SIG_GROUP_ | SIG_VALTYPE_ | EV_
137                | BO_TX_BU_ => {
138                    // TODO: These DBC sections are recognized but not parsed:
139                    //   VAL_TABLE_   - Global value tables (rarely used)
140                    //   BA_DEF_      - Attribute definitions (common, high priority)
141                    //   BA_DEF_DEF_  - Attribute defaults (common, high priority)
142                    //   BA_          - Attribute values (common, high priority)
143                    //   SIG_GROUP_   - Signal groups (rarely used)
144                    //   SIG_VALTYPE_ - Signal extended value types: float/double (medium priority)
145                    //   EV_          - Environment variables (rarely used)
146                    //   BO_TX_BU_    - Multiple message transmitters (rarely used)
147                    //
148                    // Not yet recognized (rarely used):
149                    //   ENVVAR_DATA_, SGTYPE_, BA_DEF_SGTYPE_, BA_SGTYPE_, SIG_TYPE_REF_,
150                    //   BA_DEF_REL_, BA_REL_, BA_DEF_DEF_REL_, BU_SG_REL_, BU_EV_REL_, BU_BO_REL_
151                    //
152                    // Consume keyword then skip to end of line
153                    let _ = parser.expect(keyword.as_bytes()).ok();
154                    parser.skip_to_end_of_line();
155                    continue;
156                }
157                CM_ => {
158                    // Parse CM_ comment entry
159                    // Formats:
160                    //   CM_ "general comment";
161                    //   CM_ BU_ node_name "comment";
162                    //   CM_ BO_ message_id "comment";
163                    //   CM_ SG_ message_id signal_name "comment";
164                    let _ = parser.expect(crate::CM_.as_bytes()).ok();
165                    parser.skip_newlines_and_spaces();
166
167                    // Determine comment type by peeking next token
168                    if parser.starts_with(b"\"") {
169                        // General database comment: CM_ "string";
170                        if parser.expect(b"\"").is_ok() {
171                            if let Ok(comment_bytes) = parser.take_until_quote(false, 1024) {
172                                if let Ok(comment_str) = core::str::from_utf8(comment_bytes) {
173                                    if let Ok(comment) = Comment::try_from(comment_str) {
174                                        db_comment = Some(comment);
175                                    }
176                                }
177                            }
178                        }
179                        parser.skip_to_end_of_line();
180                    } else if parser.starts_with(BU_.as_bytes()) {
181                        // Node comment: CM_ BU_ node_name "string";
182                        let _ = parser.expect(BU_.as_bytes()).ok();
183                        parser.skip_newlines_and_spaces();
184                        if let Ok(node_name_bytes) = parser.parse_identifier() {
185                            if let Ok(node_name) = Name::try_from(node_name_bytes) {
186                                parser.skip_newlines_and_spaces();
187                                if parser.expect(b"\"").is_ok() {
188                                    if let Ok(comment_bytes) = parser.take_until_quote(false, 1024)
189                                    {
190                                        if let Ok(comment_str) = core::str::from_utf8(comment_bytes)
191                                        {
192                                            if let Ok(comment) = Comment::try_from(comment_str) {
193                                                let _ =
194                                                    node_comments_buffer.push((node_name, comment));
195                                            }
196                                        }
197                                    }
198                                }
199                            }
200                        }
201                        parser.skip_to_end_of_line();
202                    } else if parser.starts_with(BO_.as_bytes()) {
203                        // Message comment: CM_ BO_ message_id "string";
204                        let _ = parser.expect(BO_.as_bytes()).ok();
205                        parser.skip_newlines_and_spaces();
206                        if let Ok(message_id) = parser.parse_u32() {
207                            parser.skip_newlines_and_spaces();
208                            if parser.expect(b"\"").is_ok() {
209                                if let Ok(comment_bytes) = parser.take_until_quote(false, 1024) {
210                                    if let Ok(comment_str) = core::str::from_utf8(comment_bytes) {
211                                        if let Ok(comment) = Comment::try_from(comment_str) {
212                                            let _ =
213                                                message_comments_buffer.push((message_id, comment));
214                                        }
215                                    }
216                                }
217                            }
218                        }
219                        parser.skip_to_end_of_line();
220                    } else if parser.starts_with(SG_.as_bytes()) {
221                        // Signal comment: CM_ SG_ message_id signal_name "string";
222                        let _ = parser.expect(SG_.as_bytes()).ok();
223                        parser.skip_newlines_and_spaces();
224                        if let Ok(message_id) = parser.parse_u32() {
225                            parser.skip_newlines_and_spaces();
226                            if let Ok(signal_name_bytes) = parser.parse_identifier() {
227                                if let Ok(signal_name) = Name::try_from(signal_name_bytes) {
228                                    parser.skip_newlines_and_spaces();
229                                    if parser.expect(b"\"").is_ok() {
230                                        if let Ok(comment_bytes) =
231                                            parser.take_until_quote(false, 1024)
232                                        {
233                                            if let Ok(comment_str) =
234                                                core::str::from_utf8(comment_bytes)
235                                            {
236                                                if let Ok(comment) = Comment::try_from(comment_str)
237                                                {
238                                                    let _ = signal_comments_buffer.push((
239                                                        message_id,
240                                                        signal_name,
241                                                        comment,
242                                                    ));
243                                                }
244                                            }
245                                        }
246                                    }
247                                }
248                            }
249                        }
250                        parser.skip_to_end_of_line();
251                    } else {
252                        // Unknown comment type, skip
253                        parser.skip_to_end_of_line();
254                    }
255                    continue;
256                }
257                SG_MUL_VAL_ => {
258                    // Consume SG_MUL_VAL_ keyword
259                    let line = parser.line();
260                    parser.expect(SG_MUL_VAL_.as_bytes()).map_err(|_| {
261                        Error::expected_at("Failed to consume SG_MUL_VAL_ keyword", line)
262                    })?;
263
264                    // Parse the extended multiplexing entry
265                    if let Some(ext_mux) = ExtendedMultiplexing::parse(&mut parser) {
266                        if extended_multiplexing_buffer.push(ext_mux).is_err() {
267                            // Buffer full - return error instead of silently dropping entries
268                            return Err(Error::Validation(Error::EXTENDED_MULTIPLEXING_TOO_MANY));
269                        }
270                    } else {
271                        // Parsing failed, skip to end of line
272                        parser.skip_to_end_of_line();
273                    }
274                    continue;
275                }
276                VAL_ => {
277                    // Consume VAL_ keyword
278                    let _ = parser.expect(crate::VAL_.as_bytes()).ok();
279                    // Parse VAL_ statement: VAL_ message_id signal_name value1 "desc1" value2 "desc2" ... ;
280                    // Note: message_id of -1 (0xFFFFFFFF) means the value descriptions apply to
281                    // all signals with this name in ANY message (global value descriptions)
282                    parser.skip_newlines_and_spaces();
283                    let message_id = match parser.parse_i64() {
284                        Ok(id) => {
285                            // -1 (0xFFFFFFFF) is the magic number for global value descriptions
286                            if id == -1 {
287                                None
288                            } else if id >= 0 && id <= u32::MAX as i64 {
289                                Some(id as u32)
290                            } else {
291                                parser.skip_to_end_of_line();
292                                continue;
293                            }
294                        }
295                        Err(_) => {
296                            parser.skip_to_end_of_line();
297                            continue;
298                        }
299                    };
300                    parser.skip_newlines_and_spaces();
301                    let signal_name = match parser.parse_identifier() {
302                        Ok(name) => match Name::try_from(name) {
303                            Ok(s) => s,
304                            Err(_) => {
305                                parser.skip_to_end_of_line();
306                                continue;
307                            }
308                        },
309                        Err(_) => {
310                            parser.skip_to_end_of_line();
311                            continue;
312                        }
313                    };
314                    // Parse value-description pairs
315                    let mut entries: ValueDescEntries = ValueDescEntries::new();
316                    loop {
317                        parser.skip_newlines_and_spaces();
318                        // Check for semicolon (end of VAL_ statement)
319                        if parser.starts_with(b";") {
320                            parser.expect(b";").ok();
321                            break;
322                        }
323                        // Parse value (as i64 first to handle negative values like -1, then convert to u64)
324                        // Note: -1 (0xFFFFFFFF) is the magic number for global value descriptions in message_id,
325                        // but values in VAL_ can also be negative
326                        let value = match parser.parse_i64() {
327                            Ok(v) => {
328                                // Handle -1 specially: convert to 0xFFFFFFFF (u32::MAX) instead of large u64
329                                if v == -1 { 0xFFFF_FFFFu64 } else { v as u64 }
330                            }
331                            Err(_) => {
332                                parser.skip_to_end_of_line();
333                                break;
334                            }
335                        };
336                        parser.skip_newlines_and_spaces();
337                        // Parse description string (expect quote, then take until quote)
338                        if parser.expect(b"\"").is_err() {
339                            parser.skip_to_end_of_line();
340                            break;
341                        }
342                        let description_bytes = match parser.take_until_quote(false, 1024) {
343                            Ok(bytes) => bytes,
344                            Err(_) => {
345                                parser.skip_to_end_of_line();
346                                break;
347                            }
348                        };
349                        let description = match core::str::from_utf8(description_bytes)
350                            .ok()
351                            .and_then(|s| Name::try_from(s).ok())
352                        {
353                            Some(desc) => desc,
354                            None => {
355                                parser.skip_to_end_of_line();
356                                break;
357                            }
358                        };
359                        let _ = entries.push((value, description));
360                    }
361                    if !entries.is_empty() {
362                        let _ = value_descriptions_buffer.push((message_id, signal_name, entries));
363                    }
364                    continue;
365                }
366                VERSION => {
367                    // Version::parse expects VERSION keyword, don't consume it here
368                    version = Some(Version::parse(&mut parser)?);
369                    continue;
370                }
371                BU_ => {
372                    // Nodes::parse expects BU_ keyword, create parser from original input including it
373                    parser.skip_to_end_of_line();
374                    let bu_input = &data.as_bytes()[pos_at_keyword..parser.pos()];
375                    let mut bu_parser = Parser::new(bu_input)?;
376                    nodes = Some(Nodes::parse(&mut bu_parser)?);
377                    continue;
378                }
379                BO_ => {
380                    // Check limit using MAX_MESSAGES constant
381                    if message_count_actual >= MAX_MESSAGES {
382                        return Err(parser.err_nodes(Error::NODES_TOO_MANY));
383                    }
384
385                    // Save parser position (at BO_ keyword, so Message::parse can consume it)
386                    let message_start_pos = pos_at_keyword;
387
388                    // Don't manually parse - just find where the header ends by looking for the colon and sender
389                    // We need to find the end of the header line to separate it from signals
390                    let header_line_end = {
391                        // Skip to end of line to find where header ends
392                        let mut temp_parser = Parser::new(&data.as_bytes()[pos_at_keyword..])?;
393                        // Skip BO_ keyword
394                        temp_parser.expect(crate::BO_.as_bytes()).ok();
395                        temp_parser.skip_whitespace().ok();
396                        temp_parser.parse_u32().ok(); // ID
397                        temp_parser.skip_whitespace().ok();
398                        temp_parser.parse_identifier().ok(); // name
399                        temp_parser.skip_whitespace().ok();
400                        temp_parser.expect(b":").ok(); // colon
401                        temp_parser.skip_whitespace().ok();
402                        temp_parser.parse_u8().ok(); // DLC
403                        temp_parser.skip_whitespace().ok();
404                        temp_parser.parse_identifier().ok(); // sender
405                        pos_at_keyword + temp_parser.pos()
406                    };
407
408                    // Now parse signals from the original parser
409                    parser.skip_to_end_of_line(); // Skip past header line
410
411                    let mut signals_array: Vec<Signal, { MAX_SIGNALS_PER_MESSAGE }> = Vec::new();
412
413                    // Parse signals until we find a non-signal line
414                    loop {
415                        parser.skip_newlines_and_spaces();
416
417                        // Use peek_next_keyword to check for SG_ keyword
418                        // peek_next_keyword correctly distinguishes SG_ from SG_MUL_VAL_ (checks longer keywords first)
419                        let keyword_result = parser.peek_next_keyword();
420                        let keyword = match keyword_result {
421                            Ok(kw) => kw,
422                            Err(Error::UnexpectedEof { .. }) => break,
423                            Err(_) => break, // Not a keyword, no more signals
424                        };
425
426                        // Only process SG_ signals here (SG_MUL_VAL_ is handled in main loop)
427                        if keyword != SG_ {
428                            break; // Not a signal, exit signal parsing loop
429                        }
430
431                        // Check limit before parsing
432                        if signals_array.len() >= MAX_SIGNALS_PER_MESSAGE {
433                            return Err(parser.err_message(Error::MESSAGE_TOO_MANY_SIGNALS));
434                        }
435
436                        // Parse signal - Signal::parse consumes SG_ itself
437                        match Signal::parse(&mut parser) {
438                            Ok(signal) => {
439                                signals_array.push(signal).map_err(|_| {
440                                    parser.err_receivers(Error::SIGNAL_RECEIVERS_TOO_MANY)
441                                })?;
442                                // Receivers::parse stops at newline but doesn't consume it
443                                // Consume it so next iteration starts at the next line
444                                if parser.at_newline() {
445                                    parser.skip_to_end_of_line();
446                                }
447                            }
448                            Err(_) => {
449                                // Parsing failed, skip to end of line and stop
450                                parser.skip_to_end_of_line();
451                                break;
452                            }
453                        }
454                    }
455
456                    // Restore parser to start of message line and use Message::parse
457                    // Create a new parser from the original input, but only up to the end of the header
458                    // (not including signals, so Message::parse doesn't complain about extra content)
459                    let message_input = &data.as_bytes()[message_start_pos..header_line_end];
460                    let mut message_parser = Parser::new(message_input)?;
461
462                    // Use Message::parse which will parse the header and use our signals
463                    let message = Message::parse(&mut message_parser, signals_array.as_slice())?;
464
465                    messages_buffer
466                        .push(message)
467                        .map_err(|_| parser.err_message(Error::NODES_TOO_MANY))?;
468                    message_count_actual += 1;
469                    continue;
470                }
471                SG_ => {
472                    // Orphaned signal (not inside a message) - skip it
473                    parser.skip_to_end_of_line();
474                    continue;
475                }
476                _ => {
477                    parser.skip_to_end_of_line();
478                    continue;
479                }
480            }
481        }
482
483        // Allow empty nodes (DBC spec allows empty BU_: line)
484        let mut nodes = nodes.unwrap_or_default();
485
486        // Apply node comments to nodes
487        for (node_name, comment) in node_comments_buffer.iter() {
488            nodes.set_node_comment(node_name.as_str(), comment.clone());
489        }
490
491        // If no version was parsed, default to empty version
492        let version = version.or_else(|| {
493            static EMPTY_VERSION: &[u8] = b"VERSION \"\"";
494            let mut parser = Parser::new(EMPTY_VERSION).ok()?;
495            Version::parse(&mut parser).ok()
496        });
497
498        // Build value descriptions map for storage in Dbc
499        let value_descriptions_map = {
500            let mut map: crate::compat::BTreeMap<
501                (Option<u32>, Name),
502                ValueDescriptions,
503                { MAX_MESSAGES },
504            > = crate::compat::BTreeMap::new();
505            for (message_id, signal_name, entries) in value_descriptions_buffer.iter() {
506                let key = (*message_id, signal_name.clone());
507                let value_descriptions = ValueDescriptions::new(entries.clone());
508                let _ = map.insert(key, value_descriptions);
509            }
510            ValueDescriptionsMap::new(map)
511        };
512
513        // Apply comments to messages and signals
514        // Message comments are applied by matching message_id
515        for (message_id, comment) in message_comments_buffer.iter() {
516            for msg in messages_buffer.iter_mut() {
517                if msg.id() == *message_id || msg.id_with_flag() == *message_id {
518                    msg.set_comment(comment.clone());
519                    break;
520                }
521            }
522        }
523
524        // Signal comments are applied by matching (message_id, signal_name)
525        for (message_id, signal_name, comment) in signal_comments_buffer.iter() {
526            for msg in messages_buffer.iter_mut() {
527                if msg.id() == *message_id || msg.id_with_flag() == *message_id {
528                    if let Some(signal) = msg.signals_mut().find_mut(signal_name.as_str()) {
529                        signal.set_comment(comment.clone());
530                    }
531                    break;
532                }
533            }
534        }
535
536        // Convert messages buffer to slice for validation and construction
537        let messages_slice: &[Message] = messages_buffer.as_slice();
538        let extended_multiplexing_slice: &[ExtendedMultiplexing] =
539            extended_multiplexing_buffer.as_slice();
540
541        // Validate messages (duplicate IDs, sender in nodes, etc.)
542        Validate::validate(
543            &nodes,
544            messages_slice,
545            Some(&value_descriptions_map),
546            Some(extended_multiplexing_slice),
547        )
548        .map_err(|e| {
549            crate::error::map_val_error(e, Error::message, || {
550                Error::message(Error::MESSAGE_ERROR_PREFIX)
551            })
552        })?;
553
554        // Construct directly (validation already done)
555        let messages = Messages::new(messages_slice)?;
556
557        Ok(Dbc::new(
558            version,
559            bit_timing,
560            nodes,
561            messages,
562            value_descriptions_map,
563            extended_multiplexing_buffer,
564            db_comment,
565        ))
566    }
567
568    /// Parse a DBC file from a byte slice
569    ///
570    /// # Examples
571    ///
572    /// ```rust,no_run
573    /// use dbc_rs::Dbc;
574    ///
575    /// let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
576    /// let dbc = Dbc::parse_bytes(dbc_bytes)?;
577    /// println!("Parsed {} messages", dbc.messages().len());
578    /// # Ok::<(), dbc_rs::Error>(())
579    /// ```
580    pub fn parse_bytes(data: &[u8]) -> Result<Self> {
581        let content =
582            core::str::from_utf8(data).map_err(|_e| Error::expected(Error::INVALID_UTF8))?;
583        Dbc::parse(content)
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use crate::Dbc;
590
591    #[test]
592    fn test_parse_basic() {
593        let dbc_content = r#"VERSION "1.0"
594
595BU_: ECM
596
597BO_ 256 Engine : 8 ECM
598 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
599"#;
600        let dbc = Dbc::parse(dbc_content).unwrap();
601        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
602        assert!(dbc.nodes().contains("ECM"));
603        assert_eq!(dbc.messages().len(), 1);
604    }
605
606    #[test]
607    fn test_parse_bytes() {
608        let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
609        let dbc = Dbc::parse_bytes(dbc_bytes).unwrap();
610        assert_eq!(dbc.version().map(|v| v.as_str()), Some("1.0"));
611        assert!(dbc.nodes().contains("ECM"));
612        assert_eq!(dbc.messages().len(), 1);
613    }
614
615    #[test]
616    fn test_parse_empty_nodes() {
617        let dbc_content = r#"VERSION "1.0"
618
619BU_:
620
621BO_ 256 Engine : 8 ECM
622"#;
623        let dbc = Dbc::parse(dbc_content).unwrap();
624        assert!(dbc.nodes().is_empty());
625    }
626
627    #[test]
628    fn test_parse_no_version() {
629        let dbc_content = r#"BU_: ECM
630
631BO_ 256 Engine : 8 ECM
632"#;
633        let dbc = Dbc::parse(dbc_content).unwrap();
634        // Should default to empty version
635        assert!(dbc.version().is_some());
636    }
637
638    #[test]
639    fn parses_real_dbc() {
640        let data = r#"VERSION "1.0"
641
642BU_: ECM TCM
643
644BO_ 256 Engine : 8 ECM
645 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
646 SG_ Temp : 16|8@1- (1,-40) [-40|215] "°C"
647
648BO_ 512 Brake : 4 TCM
649 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar""#;
650
651        let dbc = Dbc::parse(data).unwrap();
652        assert_eq!(dbc.messages().len(), 2);
653        let mut messages_iter = dbc.messages().iter();
654        let msg0 = messages_iter.next().unwrap();
655        assert_eq!(msg0.signals().len(), 2);
656        let mut signals_iter = msg0.signals().iter();
657        assert_eq!(signals_iter.next().unwrap().name(), "RPM");
658        assert_eq!(signals_iter.next().unwrap().name(), "Temp");
659        let msg1 = messages_iter.next().unwrap();
660        assert_eq!(msg1.signals().len(), 1);
661        assert_eq!(msg1.signals().iter().next().unwrap().name(), "Pressure");
662    }
663
664    #[test]
665    fn test_parse_duplicate_message_id() {
666        use crate::Error;
667        // Test that parse also validates duplicate message IDs
668        let data = r#"VERSION "1.0"
669
670BU_: ECM
671
672BO_ 256 EngineData1 : 8 ECM
673 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
674
675BO_ 256 EngineData2 : 8 ECM
676 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
677"#;
678
679        let result = Dbc::parse(data);
680        assert!(result.is_err());
681        match result.unwrap_err() {
682            Error::Message { msg, .. } => {
683                assert!(msg.contains(Error::DUPLICATE_MESSAGE_ID));
684            }
685            _ => panic!("Expected Error::Message"),
686        }
687    }
688
689    #[test]
690    fn test_parse_sender_not_in_nodes() {
691        use crate::Error;
692        // Test that parse also validates message senders are in nodes list
693        let data = r#"VERSION "1.0"
694
695BU_: ECM
696
697BO_ 256 EngineData : 8 TCM
698 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
699"#;
700
701        let result = Dbc::parse(data);
702        assert!(result.is_err());
703        match result.unwrap_err() {
704            Error::Message { msg, .. } => {
705                assert!(msg.contains(Error::SENDER_NOT_IN_NODES));
706            }
707            _ => panic!("Expected Error::Message"),
708        }
709    }
710
711    #[test]
712    fn test_parse_empty_file() {
713        use crate::Error;
714        // Test parsing an empty file
715        let result = Dbc::parse("");
716        assert!(result.is_err());
717        match result.unwrap_err() {
718            Error::UnexpectedEof { .. } => {
719                // Empty file should result in unexpected EOF
720            }
721            _ => panic!("Expected Error::UnexpectedEof"),
722        }
723    }
724
725    #[test]
726    fn test_parse_bytes_invalid_utf8() {
727        use crate::Error;
728        // Invalid UTF-8 sequence
729        let invalid_bytes = &[0xFF, 0xFE, 0xFD];
730        let result = Dbc::parse_bytes(invalid_bytes);
731        assert!(result.is_err());
732        match result.unwrap_err() {
733            Error::Expected { msg, .. } => {
734                assert_eq!(msg, Error::INVALID_UTF8);
735            }
736            _ => panic!("Expected Error::Expected with INVALID_UTF8"),
737        }
738    }
739
740    #[test]
741    fn test_parse_without_version_with_comment() {
742        // DBC file with comment and no VERSION line
743        let data = r#"// This is a comment
744BU_: ECM
745
746BO_ 256 Engine : 8 ECM
747 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
748"#;
749        let dbc = Dbc::parse(data).unwrap();
750        assert_eq!(dbc.version().map(|v| v.as_str()), Some(""));
751    }
752
753    #[test]
754    fn test_parse_with_strict_boundary_check() {
755        // Test that strict mode (default) rejects signals that extend beyond boundaries
756        let data = r#"VERSION "1.0"
757
758BU_: ECM
759
760BO_ 256 Test : 8 ECM
761 SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] ""
762"#;
763
764        // Default (strict) mode should fail
765        let result = Dbc::parse(data);
766        assert!(result.is_err());
767    }
768
769    #[cfg(feature = "std")]
770    #[test]
771    fn test_parse_val_value_descriptions() {
772        let data = r#"VERSION ""
773
774NS_ :
775
776BS_:
777
778BU_: Node1 Node2
779
780BO_ 100 Message1 : 8 Node1
781 SG_ Signal : 32|8@1- (1,0) [-1|4] "Gear" Node2
782
783VAL_ 100 Signal -1 "Reverse" 0 "Neutral" 1 "First" 2 "Second" 3 "Third" 4 "Fourth" ;
784"#;
785
786        let dbc = match Dbc::parse(data) {
787            Ok(dbc) => dbc,
788            Err(e) => panic!("Failed to parse DBC: {:?}", e),
789        };
790
791        // Verify basic structure
792        assert_eq!(dbc.messages().len(), 1);
793        let message = dbc.messages().iter().find(|m| m.id() == 100).unwrap();
794        assert_eq!(message.name(), "Message1");
795        assert_eq!(message.sender(), "Node1");
796
797        // Verify value descriptions
798        let value_descriptions = dbc
799            .value_descriptions_for_signal(100, "Signal")
800            .expect("Value descriptions should exist");
801        assert_eq!(value_descriptions.get(0xFFFFFFFF), Some("Reverse")); // -1 as u64
802        assert_eq!(value_descriptions.get(0), Some("Neutral"));
803        assert_eq!(value_descriptions.get(1), Some("First"));
804        assert_eq!(value_descriptions.get(2), Some("Second"));
805        assert_eq!(value_descriptions.get(3), Some("Third"));
806        assert_eq!(value_descriptions.get(4), Some("Fourth"));
807    }
808
809    #[cfg(feature = "std")]
810    #[test]
811    fn test_parse_val_global_value_descriptions() {
812        // Test global value descriptions (VAL_ -1) that apply to all signals with the same name
813        let data = r#"VERSION "1.0"
814
815NS_ :
816
817    VAL_
818
819BS_:
820
821BU_: ECU DASH
822
823BO_ 256 EngineData: 8 ECU
824 SG_ EngineRPM : 0|16@1+ (0.125,0) [0|8000] "rpm" Vector__XXX
825 SG_ DI_gear : 24|3@1+ (1,0) [0|7] "" Vector__XXX
826
827BO_ 512 DashboardDisplay: 8 DASH
828 SG_ DI_gear : 0|3@1+ (1,0) [0|7] "" Vector__XXX
829 SG_ SpeedDisplay : 8|16@1+ (0.01,0) [0|300] "km/h" Vector__XXX
830
831VAL_ -1 DI_gear 0 "INVALID" 1 "P" 2 "R" 3 "N" 4 "D" 5 "S" 6 "L" 7 "SNA" ;
832"#;
833
834        let dbc = match Dbc::parse(data) {
835            Ok(dbc) => dbc,
836            Err(e) => panic!("Failed to parse DBC: {:?}", e),
837        };
838
839        // Verify basic structure
840        assert_eq!(dbc.messages().len(), 2);
841
842        // Verify first message (EngineData)
843        let engine_msg = dbc.messages().iter().find(|m| m.id() == 256).unwrap();
844        assert_eq!(engine_msg.name(), "EngineData");
845        assert_eq!(engine_msg.sender(), "ECU");
846        let di_gear_signal1 = engine_msg.signals().find("DI_gear").unwrap();
847        assert_eq!(di_gear_signal1.name(), "DI_gear");
848        assert_eq!(di_gear_signal1.start_bit(), 24);
849
850        // Verify second message (DashboardDisplay)
851        let dash_msg = dbc.messages().iter().find(|m| m.id() == 512).unwrap();
852        assert_eq!(dash_msg.name(), "DashboardDisplay");
853        assert_eq!(dash_msg.sender(), "DASH");
854        let di_gear_signal2 = dash_msg.signals().find("DI_gear").unwrap();
855        assert_eq!(di_gear_signal2.name(), "DI_gear");
856        assert_eq!(di_gear_signal2.start_bit(), 0);
857
858        // Verify global value descriptions apply to DI_gear in message 256
859        let value_descriptions1 = dbc
860            .value_descriptions_for_signal(256, "DI_gear")
861            .expect("Global value descriptions should exist for DI_gear in message 256");
862
863        assert_eq!(value_descriptions1.get(0), Some("INVALID"));
864        assert_eq!(value_descriptions1.get(1), Some("P"));
865        assert_eq!(value_descriptions1.get(2), Some("R"));
866        assert_eq!(value_descriptions1.get(3), Some("N"));
867        assert_eq!(value_descriptions1.get(4), Some("D"));
868        assert_eq!(value_descriptions1.get(5), Some("S"));
869        assert_eq!(value_descriptions1.get(6), Some("L"));
870        assert_eq!(value_descriptions1.get(7), Some("SNA"));
871
872        // Verify global value descriptions also apply to DI_gear in message 512
873        let value_descriptions2 = dbc
874            .value_descriptions_for_signal(512, "DI_gear")
875            .expect("Global value descriptions should exist for DI_gear in message 512");
876
877        // Both should return the same value descriptions (same reference or same content)
878        assert_eq!(value_descriptions2.get(0), Some("INVALID"));
879        assert_eq!(value_descriptions2.get(1), Some("P"));
880        assert_eq!(value_descriptions2.get(2), Some("R"));
881        assert_eq!(value_descriptions2.get(3), Some("N"));
882        assert_eq!(value_descriptions2.get(4), Some("D"));
883        assert_eq!(value_descriptions2.get(5), Some("S"));
884        assert_eq!(value_descriptions2.get(6), Some("L"));
885        assert_eq!(value_descriptions2.get(7), Some("SNA"));
886
887        // Verify they should be the same instance (both reference the global entry)
888        // Since we store by (Option<u32>, &str), both should return the same entry
889        assert_eq!(value_descriptions1.len(), value_descriptions2.len());
890        assert_eq!(value_descriptions1.len(), 8);
891
892        // Verify other signals don't have value descriptions
893        assert_eq!(dbc.value_descriptions_for_signal(256, "EngineRPM"), None);
894        assert_eq!(dbc.value_descriptions_for_signal(512, "SpeedDisplay"), None);
895    }
896
897    // ============================================================================
898    // Specification Compliance Tests
899    // These tests verify against exact requirements from dbc/SPECIFICATIONS.md
900    // ============================================================================
901
902    /// Verify Section 8.3: DLC = 0 is valid
903    /// "CAN 2.0: 0 to 8 bytes"
904    /// "CAN FD: 0 to 64 bytes"
905    #[test]
906    fn test_spec_section_8_3_dlc_zero_is_valid() {
907        // DLC = 0 is valid per spec (e.g., for control messages without data payload)
908        let data = r#"VERSION "1.0"
909
910BU_: ECM
911
912BO_ 256 ControlMessage : 0 ECM
913"#;
914        let dbc = Dbc::parse(data).unwrap();
915        assert_eq!(dbc.messages().len(), 1);
916        let msg = dbc.messages().iter().next().unwrap();
917        assert_eq!(msg.dlc(), 0);
918    }
919
920    /// Verify Section 8.1: Extended CAN ID format
921    /// "Extended ID in DBC = 0x80000000 | actual_extended_id"
922    /// "Example: 0x80001234 represents extended ID 0x1234"
923    #[test]
924    fn test_spec_section_8_1_extended_can_id_format() {
925        // Extended ID 0x494 is stored as 0x80000000 | 0x494 = 0x80000494 = 2147484820
926        // 0x80000000 = 2147483648, 0x494 = 1172, 2147483648 + 1172 = 2147484820
927        let data = r#"VERSION "1.0"
928
929BU_: ECM
930
931BO_ 2147484820 ExtendedMessage : 8 ECM
932"#;
933        let dbc = Dbc::parse(data).unwrap();
934        assert_eq!(dbc.messages().len(), 1);
935        let msg = dbc.messages().iter().next().unwrap();
936        // id() returns the raw CAN ID without the extended flag
937        assert_eq!(msg.id(), 0x494); // Raw extended ID
938        assert!(msg.is_extended()); // is_extended() tells if it's a 29-bit ID
939    }
940
941    /// Verify Section 8.3: Maximum extended ID (0x1FFFFFFF) with bit 31 flag
942    #[test]
943    fn test_spec_section_8_1_max_extended_id() {
944        // Maximum extended ID: 0x80000000 | 0x1FFFFFFF = 0x9FFFFFFF = 2684354559
945        let data = r#"VERSION "1.0"
946
947BU_: ECM
948
949BO_ 2684354559 MaxExtendedId : 8 ECM
950"#;
951        let dbc = Dbc::parse(data).unwrap();
952        assert_eq!(dbc.messages().len(), 1);
953        let msg = dbc.messages().iter().next().unwrap();
954        // id() returns the raw 29-bit CAN ID without the extended flag
955        assert_eq!(msg.id(), 0x1FFFFFFF);
956        assert!(msg.is_extended());
957    }
958
959    /// Verify Section 8.4: Vector__XXX as transmitter
960    /// "Vector__XXX - No sender / unknown sender"
961    #[test]
962    fn test_spec_section_8_4_vector_xxx_transmitter() {
963        let data = r#"VERSION "1.0"
964
965BU_: Gateway
966
967BO_ 256 UnknownSender : 8 Vector__XXX
968 SG_ Signal1 : 0|8@1+ (1,0) [0|255] "" Gateway
969"#;
970        let dbc = Dbc::parse(data).unwrap();
971        assert_eq!(dbc.messages().len(), 1);
972        let msg = dbc.messages().iter().next().unwrap();
973        assert_eq!(msg.sender(), "Vector__XXX");
974    }
975
976    /// Verify Section 9.5: Receivers format
977    /// Parser accepts both comma-separated (per spec) and space-separated (tool extension)
978    #[test]
979    fn test_spec_section_9_5_receivers_comma_separated() {
980        // Comma-separated receivers (per spec)
981        // Note: The parser identifier function stops at commas, so we test that comma-separated
982        // receiver parsing works correctly
983        use crate::{Parser, Signal};
984
985        // Test comma-separated receivers directly via Signal::parse
986        let signal = Signal::parse(
987            &mut Parser::new(b"SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\" Gateway,Dashboard")
988                .unwrap(),
989        )
990        .unwrap();
991        assert_eq!(signal.receivers().len(), 2);
992        let mut receivers = signal.receivers().iter();
993        assert_eq!(receivers.next(), Some("Gateway"));
994        assert_eq!(receivers.next(), Some("Dashboard"));
995    }
996
997    /// Verify Section 9.4: Multiplexer indicator patterns
998    /// "M" for multiplexer switch, "m0", "m1", etc. for multiplexed signals
999    #[test]
1000    fn test_spec_section_9_4_multiplexer_indicators() {
1001        let data = r#"VERSION "1.0"
1002
1003BU_: ECM Gateway
1004
1005BO_ 400 MultiplexedMsg : 8 ECM
1006 SG_ MuxSwitch M : 0|8@1+ (1,0) [0|255] "" Gateway
1007 SG_ Signal_0 m0 : 8|16@1+ (0.1,0) [0|1000] "kPa" Gateway
1008 SG_ Signal_1 m1 : 8|16@1+ (0.01,0) [0|100] "degC" Gateway
1009"#;
1010        let dbc = Dbc::parse(data).unwrap();
1011        let msg = dbc.messages().iter().next().unwrap();
1012
1013        // Find signals by name
1014        let mux_switch = msg.signals().find("MuxSwitch").unwrap();
1015        let signal_0 = msg.signals().find("Signal_0").unwrap();
1016        let signal_1 = msg.signals().find("Signal_1").unwrap();
1017
1018        // Verify multiplexer switch
1019        assert!(mux_switch.is_multiplexer_switch());
1020        assert_eq!(mux_switch.multiplexer_switch_value(), None);
1021
1022        // Verify multiplexed signals
1023        assert!(!signal_0.is_multiplexer_switch());
1024        assert_eq!(signal_0.multiplexer_switch_value(), Some(0));
1025
1026        assert!(!signal_1.is_multiplexer_switch());
1027        assert_eq!(signal_1.multiplexer_switch_value(), Some(1));
1028    }
1029
1030    #[test]
1031    fn test_error_includes_line_number() {
1032        // Test that parsing errors include line numbers
1033        let data = r#"VERSION "1.0"
1034
1035BU_: ECM
1036
1037BO_ invalid EngineData : 8 ECM
1038"#;
1039
1040        let result = Dbc::parse(data);
1041        assert!(result.is_err());
1042        let err = result.unwrap_err();
1043        // The error should have line information
1044        assert!(err.line().is_some(), "Error should include line number");
1045    }
1046
1047    // ============================================================================
1048    // CM_ Comment Parsing Tests (Section 14 of SPECIFICATIONS.md)
1049    // ============================================================================
1050
1051    /// Test parsing general database comment: CM_ "string";
1052    #[test]
1053    fn test_parse_cm_database_comment() {
1054        let data = r#"VERSION "1.0"
1055
1056BU_: ECM
1057
1058BO_ 256 Engine : 8 ECM
1059
1060CM_ "This is the database comment";
1061"#;
1062        let dbc = Dbc::parse(data).unwrap();
1063        assert_eq!(dbc.comment(), Some("This is the database comment"));
1064    }
1065
1066    /// Test parsing node comment: CM_ BU_ node_name "string";
1067    #[test]
1068    fn test_parse_cm_node_comment() {
1069        let data = r#"VERSION "1.0"
1070
1071BU_: ECM
1072
1073BO_ 256 Engine : 8 ECM
1074
1075CM_ BU_ ECM "Engine Control Module";
1076"#;
1077        let dbc = Dbc::parse(data).unwrap();
1078        assert_eq!(dbc.node_comment("ECM"), Some("Engine Control Module"));
1079    }
1080
1081    /// Test parsing message comment: CM_ BO_ message_id "string";
1082    #[test]
1083    fn test_parse_cm_message_comment() {
1084        let data = r#"VERSION "1.0"
1085
1086BU_: ECM
1087
1088BO_ 256 Engine : 8 ECM
1089
1090CM_ BO_ 256 "Engine status message";
1091"#;
1092        let dbc = Dbc::parse(data).unwrap();
1093        let msg = dbc.messages().iter().next().unwrap();
1094        assert_eq!(msg.comment(), Some("Engine status message"));
1095    }
1096
1097    /// Test parsing signal comment: CM_ SG_ message_id signal_name "string";
1098    #[test]
1099    fn test_parse_cm_signal_comment() {
1100        let data = r#"VERSION "1.0"
1101
1102BU_: ECM
1103
1104BO_ 256 Engine : 8 ECM
1105 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1106
1107CM_ SG_ 256 RPM "Engine rotations per minute";
1108"#;
1109        let dbc = Dbc::parse(data).unwrap();
1110        let msg = dbc.messages().iter().next().unwrap();
1111        let signal = msg.signals().find("RPM").unwrap();
1112        assert_eq!(signal.comment(), Some("Engine rotations per minute"));
1113    }
1114
1115    /// Test multiple comments in one file
1116    #[test]
1117    fn test_parse_cm_multiple_comments() {
1118        let data = r#"VERSION "1.0"
1119
1120BU_: ECM TCM
1121
1122BO_ 256 Engine : 8 ECM
1123 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1124
1125BO_ 512 Trans : 8 TCM
1126 SG_ Gear : 0|8@1+ (1,0) [0|6] ""
1127
1128CM_ "Vehicle CAN database";
1129CM_ BU_ ECM "Engine Control Module";
1130CM_ BU_ TCM "Transmission Control Module";
1131CM_ BO_ 256 "Engine status message";
1132CM_ BO_ 512 "Transmission status";
1133CM_ SG_ 256 RPM "Engine rotations per minute";
1134CM_ SG_ 512 Gear "Current gear position";
1135"#;
1136        let dbc = Dbc::parse(data).unwrap();
1137
1138        // Database comment
1139        assert_eq!(dbc.comment(), Some("Vehicle CAN database"));
1140
1141        // Node comments
1142        assert_eq!(dbc.node_comment("ECM"), Some("Engine Control Module"));
1143        assert_eq!(dbc.node_comment("TCM"), Some("Transmission Control Module"));
1144
1145        // Message comments
1146        let engine = dbc.messages().iter().find(|m| m.id() == 256).unwrap();
1147        let trans = dbc.messages().iter().find(|m| m.id() == 512).unwrap();
1148        assert_eq!(engine.comment(), Some("Engine status message"));
1149        assert_eq!(trans.comment(), Some("Transmission status"));
1150
1151        // Signal comments
1152        let rpm = engine.signals().find("RPM").unwrap();
1153        let gear = trans.signals().find("Gear").unwrap();
1154        assert_eq!(rpm.comment(), Some("Engine rotations per minute"));
1155        assert_eq!(gear.comment(), Some("Current gear position"));
1156    }
1157
1158    /// Test CM_ appearing before the entities they describe
1159    #[test]
1160    fn test_parse_cm_before_entity() {
1161        let data = r#"VERSION "1.0"
1162
1163BU_: ECM
1164
1165CM_ BO_ 256 "Engine status message";
1166CM_ SG_ 256 RPM "Engine RPM";
1167
1168BO_ 256 Engine : 8 ECM
1169 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1170"#;
1171        let dbc = Dbc::parse(data).unwrap();
1172        let msg = dbc.messages().iter().next().unwrap();
1173        assert_eq!(msg.comment(), Some("Engine status message"));
1174        let signal = msg.signals().find("RPM").unwrap();
1175        assert_eq!(signal.comment(), Some("Engine RPM"));
1176    }
1177
1178    /// Test multiple CM_ entries for same entity - last wins
1179    #[test]
1180    fn test_parse_cm_last_wins() {
1181        let data = r#"VERSION "1.0"
1182
1183BU_: ECM
1184
1185BO_ 256 Engine : 8 ECM
1186 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1187
1188CM_ BO_ 256 "First message comment";
1189CM_ BO_ 256 "Second message comment";
1190CM_ SG_ 256 RPM "First signal comment";
1191CM_ SG_ 256 RPM "Second signal comment";
1192CM_ BU_ ECM "First node comment";
1193CM_ BU_ ECM "Second node comment";
1194"#;
1195        let dbc = Dbc::parse(data).unwrap();
1196
1197        // Last comment wins for each entity
1198        let msg = dbc.messages().iter().next().unwrap();
1199        assert_eq!(msg.comment(), Some("Second message comment"));
1200        let signal = msg.signals().find("RPM").unwrap();
1201        assert_eq!(signal.comment(), Some("Second signal comment"));
1202        assert_eq!(dbc.node_comment("ECM"), Some("Second node comment"));
1203    }
1204
1205    /// Test comment round-trip (parse -> serialize -> parse)
1206    #[test]
1207    #[cfg(feature = "std")]
1208    fn test_parse_cm_round_trip() {
1209        let data = r#"VERSION "1.0"
1210
1211BU_: ECM TCM
1212
1213BO_ 256 Engine : 8 ECM
1214 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1215
1216CM_ "Database comment";
1217CM_ BU_ ECM "Engine Control Module";
1218CM_ BO_ 256 "Engine status message";
1219CM_ SG_ 256 RPM "Engine rotations per minute";
1220"#;
1221        let dbc = Dbc::parse(data).unwrap();
1222
1223        // Serialize and re-parse
1224        let serialized = dbc.to_dbc_string();
1225        let dbc2 = Dbc::parse(&serialized).unwrap();
1226
1227        // Verify comments are preserved
1228        assert_eq!(dbc2.comment(), Some("Database comment"));
1229        assert_eq!(dbc2.node_comment("ECM"), Some("Engine Control Module"));
1230        let msg = dbc2.messages().iter().next().unwrap();
1231        assert_eq!(msg.comment(), Some("Engine status message"));
1232        let signal = msg.signals().find("RPM").unwrap();
1233        assert_eq!(signal.comment(), Some("Engine rotations per minute"));
1234    }
1235
1236    /// Test CM_ serialization in output
1237    #[test]
1238    #[cfg(feature = "std")]
1239    fn test_serialize_cm_comments() {
1240        let data = r#"VERSION "1.0"
1241
1242BU_: ECM
1243
1244BO_ 256 Engine : 8 ECM
1245 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1246
1247CM_ "Database comment";
1248CM_ BU_ ECM "Engine Control Module";
1249CM_ BO_ 256 "Engine status";
1250CM_ SG_ 256 RPM "RPM signal";
1251"#;
1252        let dbc = Dbc::parse(data).unwrap();
1253        let serialized = dbc.to_dbc_string();
1254
1255        // Verify CM_ lines are present in output
1256        assert!(serialized.contains("CM_ \"Database comment\";"));
1257        assert!(serialized.contains("CM_ BU_ ECM \"Engine Control Module\";"));
1258        assert!(serialized.contains("CM_ BO_ 256 \"Engine status\";"));
1259        assert!(serialized.contains("CM_ SG_ 256 RPM \"RPM signal\";"));
1260    }
1261}