dbc_rs/dbc/
dbc.rs

1#[cfg(feature = "alloc")]
2use crate::{Error, Result, error::messages as error_messages};
3use crate::{
4    Message, Messages, Nodes, ParseOptions, Parser, Signal, Signals, Version,
5    error::{ParseError, ParseResult},
6};
7
8#[cfg(feature = "alloc")]
9use alloc::string::String;
10
11/// Represents a complete DBC (CAN database) file.
12///
13/// A `Dbc` contains:
14/// - An optional version string
15/// - A list of nodes (ECUs)
16/// - A collection of messages with their signals
17///
18/// # Examples
19///
20/// ```rust,no_run
21/// use dbc_rs::Dbc;
22///
23/// let dbc_content = r#"VERSION "1.0"
24///
25/// BU_: ECM TCM
26///
27/// BO_ 256 EngineData : 8 ECM
28///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM
29/// "#;
30///
31/// let dbc = Dbc::parse(dbc_content)?;
32/// println!("Parsed {} messages", dbc.messages().len());
33/// # Ok::<(), dbc_rs::Error>(())
34/// ```
35#[derive(Debug)]
36pub struct Dbc<'a> {
37    version: Option<Version<'a>>,
38    nodes: Nodes<'a>,
39    messages: Messages<'a>,
40}
41
42impl<'a> Dbc<'a> {
43    pub(crate) fn validate(
44        _version: Option<&Version<'_>>,
45        nodes: &Nodes<'_>,
46        messages: &[Option<Message<'_>>],
47        message_count: usize,
48    ) -> ParseResult<()> {
49        #[cfg(feature = "alloc")]
50        use crate::error::messages as error_messages;
51
52        // Check for duplicate message IDs
53        let messages_slice = &messages[..message_count];
54        for (i, msg1_opt) in messages_slice.iter().enumerate() {
55            let msg1 = match msg1_opt {
56                Some(m) => m,
57                None => continue, // Should not happen, but be safe
58            };
59            for msg2_opt in messages_slice.iter().skip(i + 1) {
60                let msg2 = match msg2_opt {
61                    Some(m) => m,
62                    None => continue, // Should not happen, but be safe
63                };
64                if msg1.id() == msg2.id() {
65                    #[cfg(feature = "alloc")]
66                    {
67                        let msg = error_messages::duplicate_message_id(
68                            msg1.id(),
69                            msg1.name(),
70                            msg2.name(),
71                        );
72                        return Err(ParseError::Version(msg.leak()));
73                    }
74                    #[cfg(not(feature = "alloc"))]
75                    {
76                        return Err(ParseError::Version(
77                            crate::error::lang::FORMAT_DUPLICATE_MESSAGE_ID,
78                        ));
79                    }
80                }
81            }
82        }
83
84        // Validate that all message senders are in the nodes list
85        // Skip validation if nodes list is empty (empty nodes allowed per DBC spec)
86        if !nodes.is_empty() {
87            for msg_opt in messages_slice {
88                let msg = match msg_opt {
89                    Some(m) => m,
90                    None => continue, // Should not happen, but be safe
91                };
92                if !nodes.contains(msg.sender()) {
93                    #[cfg(feature = "alloc")]
94                    {
95                        let msg_str = error_messages::sender_not_in_nodes(msg.name(), msg.sender());
96                        return Err(ParseError::Version(msg_str.leak()));
97                    }
98                    #[cfg(not(feature = "alloc"))]
99                    {
100                        return Err(ParseError::Version(
101                            crate::error::lang::FORMAT_SENDER_NOT_IN_NODES,
102                        ));
103                    }
104                }
105            }
106        }
107
108        Ok(())
109    }
110
111    #[allow(dead_code)] // Only used by builders (std-only)
112    pub(crate) fn new(
113        version: Option<Version<'a>>,
114        nodes: Nodes<'a>,
115        messages: &'a [Message<'a>],
116    ) -> Self {
117        // Validation should have been done prior (by builder)
118        Self {
119            version,
120            nodes,
121            messages: Messages::from_messages_slice(messages),
122        }
123    }
124
125    fn new_from_options(
126        version: Option<Version<'a>>,
127        nodes: Nodes<'a>,
128        messages: &[Option<Message<'a>>],
129        message_count: usize,
130    ) -> Self {
131        // Validation should have been done prior (by parse)
132        Self {
133            version,
134            nodes,
135            messages: Messages::from_options_slice(messages, message_count),
136        }
137    }
138
139    /// Get the version of the DBC file
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use dbc_rs::Dbc;
145    ///
146    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
147    /// if let Some(version) = dbc.version() {
148    ///     println!("Version: {}", version.to_string());
149    /// }
150    /// # Ok::<(), dbc_rs::Error>(())
151    /// ```
152    #[inline]
153    #[must_use]
154    pub fn version(&self) -> Option<&Version<'a>> {
155        self.version.as_ref()
156    }
157
158    /// Get a reference to the nodes collection
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use dbc_rs::Dbc;
164    ///
165    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM\n\nBO_ 256 Engine : 8 ECM")?;
166    /// let nodes = dbc.nodes();
167    /// println!("Nodes: {}", nodes.to_string());
168    /// println!("Node count: {}", nodes.len());
169    /// # Ok::<(), dbc_rs::Error>(())
170    /// ```
171    #[inline]
172    #[must_use]
173    pub fn nodes(&self) -> &Nodes<'a> {
174        &self.nodes
175    }
176
177    /// Get a reference to the messages collection
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use dbc_rs::Dbc;
183    ///
184    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
185    /// let messages = dbc.messages();
186    /// println!("Message count: {}", messages.len());
187    /// for message in messages.iter() {
188    ///     println!("Message: {} (ID: {})", message.name(), message.id());
189    /// }
190    /// # Ok::<(), dbc_rs::Error>(())
191    /// ```
192    #[inline]
193    #[must_use]
194    pub fn messages(&self) -> &Messages<'a> {
195        &self.messages
196    }
197
198    /// Parse a DBC file from a string slice
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use dbc_rs::Dbc;
204    ///
205    /// let dbc_content = r#"VERSION "1.0"
206    ///
207    /// BU_: ECM
208    ///
209    /// BO_ 256 EngineData : 8 ECM
210    ///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm""#;
211    ///
212    /// let dbc = Dbc::parse(dbc_content)?;
213    /// println!("Parsed {} messages", dbc.messages().len());
214    /// # Ok::<(), dbc_rs::Error>(())
215    /// ```
216    pub fn parse(data: &'a str) -> ParseResult<Self> {
217        Self::parse_with_options(data, ParseOptions::default())
218    }
219
220    /// Parses a DBC file from a string with custom parsing options.
221    ///
222    /// # Arguments
223    ///
224    /// * `data` - The DBC file content as a string
225    /// * `options` - Parsing options to control validation behavior
226    ///
227    /// # Examples
228    ///
229    /// ```
230    /// use dbc_rs::{Dbc, ParseOptions};
231    ///
232    /// let dbc_content = r#"VERSION "1.0"
233    ///
234    /// BU_: ECM
235    ///
236    /// BO_ 256 Test : 8 ECM
237    ///  SG_ Signal1 : 0|8@1+ (1,0) [0|255] ""
238    /// "#;
239    ///
240    /// // Use lenient mode to allow signals that extend beyond message boundaries
241    /// let options = ParseOptions::lenient();
242    /// let dbc = Dbc::parse_with_options(dbc_content, options)?;
243    /// # Ok::<(), dbc_rs::Error>(())
244    /// ```
245    pub fn parse_with_options(data: &'a str, options: ParseOptions) -> ParseResult<Self> {
246        // FIRST PASS: Count messages (two-pass parsing to allocate correct sizes)
247        let mut parser1 = Parser::new(data.as_bytes())?;
248        let _ = Messages::count_messages_and_signals(&mut parser1)?;
249
250        // SECOND PASS: Parse into messages array
251        let mut parser2 = Parser::new(data.as_bytes())?;
252
253        // Allocate messages buffer - Messages will handle the size internally
254        // We use a temporary buffer that Messages can work with (no alloc in no_std)
255        // Messages handles capacity internally, we just need a buffer
256        #[cfg(not(feature = "alloc"))]
257        let mut messages_buffer = Messages::new_parse_buffer();
258
259        #[cfg(feature = "alloc")]
260        let mut messages_buffer: alloc::vec::Vec<Option<Message<'a>>> =
261            alloc::vec::Vec::with_capacity(Messages::max_capacity());
262
263        let mut message_count_actual = 0;
264
265        // Parse version, nodes, and messages
266        use crate::{
267            BA_, BA_DEF_, BA_DEF_DEF_, BO_, BO_TX_BU_, BS_, BU_, CM_, EV_, NS_, SG_, SIG_GROUP_,
268            SIG_VALTYPE_, VAL_, VAL_TABLE_, VERSION,
269        };
270
271        let mut version: Option<Version<'a>> = None;
272        let mut nodes: Option<Nodes<'a>> = None;
273
274        loop {
275            // Skip comments (lines starting with //)
276            parser2.skip_newlines_and_spaces();
277            if parser2.starts_with(b"//") {
278                parser2.skip_to_end_of_line();
279                continue;
280            }
281
282            let keyword_result = parser2.find_next_keyword();
283            let keyword = match keyword_result {
284                Ok(kw) => kw,
285                Err(ParseError::UnexpectedEof) => break,
286                Err(ParseError::Expected(_)) => {
287                    if parser2.starts_with(b"//") {
288                        parser2.skip_to_end_of_line();
289                        continue;
290                    }
291                    return Err(keyword_result.unwrap_err());
292                }
293                Err(e) => return Err(e),
294            };
295
296            match keyword {
297                NS_ => {
298                    parser2.skip_newlines_and_spaces();
299                    let _ = parser2.expect(b":").ok();
300                    loop {
301                        parser2.skip_newlines_and_spaces();
302                        if parser2.is_empty() {
303                            break;
304                        }
305                        if parser2.starts_with(b" ") || parser2.starts_with(b"\t") {
306                            parser2.skip_to_end_of_line();
307                            continue;
308                        }
309                        if parser2.starts_with(b"//") {
310                            parser2.skip_to_end_of_line();
311                            continue;
312                        }
313                        if parser2.starts_with(BS_.as_bytes())
314                            || parser2.starts_with(BU_.as_bytes())
315                            || parser2.starts_with(BO_.as_bytes())
316                            || parser2.starts_with(SG_.as_bytes())
317                            || parser2.starts_with(VERSION.as_bytes())
318                        {
319                            break;
320                        }
321                        parser2.skip_to_end_of_line();
322                    }
323                    continue;
324                }
325                CM_ | BS_ | VAL_TABLE_ | BA_DEF_ | BA_DEF_DEF_ | BA_ | VAL_ | SIG_GROUP_
326                | SIG_VALTYPE_ | EV_ | BO_TX_BU_ => {
327                    parser2.skip_to_end_of_line();
328                    continue;
329                }
330                VERSION => {
331                    version = Some(Version::parse(&mut parser2)?);
332                    continue;
333                }
334                BU_ => {
335                    nodes = Some(Nodes::parse(&mut parser2)?);
336                    continue;
337                }
338                BO_ => {
339                    // Check limit using Messages (which knows about the capacity)
340                    if message_count_actual >= Messages::max_capacity() {
341                        return Err(ParseError::Version(crate::error::lang::NODES_TOO_MANY));
342                    }
343
344                    // Save parser position (after BO_ keyword, before message header)
345                    let message_start_pos = parser2.pos();
346
347                    // Parse message header to get past it, then parse signals
348                    parser2.skip_newlines_and_spaces();
349                    let _id = parser2.parse_u32().ok();
350                    parser2.skip_newlines_and_spaces();
351                    let _name = parser2.parse_identifier().ok();
352                    parser2.skip_newlines_and_spaces();
353                    let _ = parser2.expect(b":").ok();
354                    parser2.skip_newlines_and_spaces();
355                    let _dlc = parser2.parse_u8().ok();
356                    parser2.skip_newlines_and_spaces();
357                    let _sender = parser2.parse_identifier().ok();
358                    let message_header_end_pos = parser2.pos();
359                    parser2.skip_to_end_of_line();
360
361                    // Parse signals into fixed array
362                    #[cfg(not(feature = "alloc"))]
363                    let mut signals_array = Signals::new_parse_buffer();
364
365                    #[cfg(feature = "alloc")]
366                    let mut signals_array: alloc::vec::Vec<Option<Signal<'a>>> =
367                        alloc::vec::Vec::with_capacity(Signals::max_capacity());
368
369                    let mut signal_count = 0;
370                    loop {
371                        parser2.skip_newlines_and_spaces();
372                        if parser2.starts_with(crate::SG_.as_bytes()) {
373                            if let Some(next_byte) = parser2.peek_byte_at(3) {
374                                if matches!(next_byte, b' ' | b'\n' | b'\r' | b'\t') {
375                                    if signal_count >= Signals::max_capacity() {
376                                        return Err(ParseError::Version(
377                                            crate::error::messages::SIGNAL_RECEIVERS_TOO_MANY,
378                                        ));
379                                    }
380                                    let _kw = parser2.find_next_keyword().map_err(|e| match e {
381                                        ParseError::Expected(_) => {
382                                            ParseError::Expected("Expected SG_ keyword")
383                                        }
384                                        _ => e,
385                                    })?;
386                                    let signal = Signal::parse(&mut parser2)?;
387                                    #[cfg(not(feature = "alloc"))]
388                                    {
389                                        signals_array[signal_count] = Some(signal);
390                                    }
391                                    #[cfg(feature = "alloc")]
392                                    {
393                                        signals_array.push(Some(signal));
394                                    }
395                                    signal_count += 1;
396                                    continue;
397                                }
398                            }
399                        }
400                        break;
401                    }
402
403                    // Restore parser to start of message line and use Message::parse
404                    // Create a new parser from the original input, but only up to the end of the header
405                    // (not including signals, so Message::parse doesn't complain about extra content)
406                    let message_input = &data.as_bytes()[message_start_pos..message_header_end_pos];
407                    let mut message_parser = Parser::new(message_input)?;
408
409                    // Use Message::parse which will parse the header and use our signals
410                    let signals_slice: &[Option<Signal<'a>>] = {
411                        #[cfg(not(feature = "alloc"))]
412                        {
413                            &signals_array[..signal_count]
414                        }
415                        #[cfg(feature = "alloc")]
416                        {
417                            &signals_array[..]
418                        }
419                    };
420                    let message =
421                        Message::parse(&mut message_parser, signals_slice, signal_count, options)?;
422
423                    #[cfg(not(feature = "alloc"))]
424                    {
425                        messages_buffer[message_count_actual] = Some(message);
426                    }
427                    #[cfg(feature = "alloc")]
428                    {
429                        messages_buffer.push(Some(message));
430                    }
431                    message_count_actual += 1;
432                    continue;
433                }
434                SG_ => {
435                    let _ = Signal::parse(&mut parser2)?;
436                    continue;
437                }
438                _ => {
439                    parser2.skip_to_end_of_line();
440                    continue;
441                }
442            }
443        }
444
445        // Allow empty nodes (DBC spec allows empty BU_: line)
446        let nodes = nodes.unwrap_or_default();
447
448        // If no version was parsed, default to empty version
449        let version = version.or_else(|| {
450            static EMPTY_VERSION: &[u8] = b"VERSION \"\"";
451            let mut parser = Parser::new(EMPTY_VERSION).ok()?;
452            Version::parse(&mut parser).ok()
453        });
454
455        // Convert messages buffer to slice for validation and construction
456        let messages_slice: &[Option<Message<'a>>] = {
457            #[cfg(not(feature = "alloc"))]
458            {
459                &messages_buffer[..message_count_actual]
460            }
461            #[cfg(feature = "alloc")]
462            {
463                &messages_buffer[..]
464            }
465        };
466
467        // Validate messages (duplicate IDs, sender in nodes, etc.)
468        Self::validate(
469            version.as_ref(),
470            &nodes,
471            messages_slice,
472            message_count_actual,
473        )?;
474
475        // Construct directly (validation already done)
476        Ok(Self::new_from_options(
477            version,
478            nodes,
479            messages_slice,
480            message_count_actual,
481        ))
482    }
483
484    /// Parse a DBC file from a byte slice
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// use dbc_rs::Dbc;
490    ///
491    /// let dbc_bytes = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
492    /// let dbc = Dbc::parse_bytes(dbc_bytes)?;
493    /// println!("Parsed {} messages", dbc.messages().len());
494    /// # Ok::<(), dbc_rs::Error>(())
495    /// ```
496    #[cfg(feature = "alloc")]
497    pub fn parse_bytes(data: &[u8]) -> Result<Dbc<'static>> {
498        let content =
499            core::str::from_utf8(data).map_err(|e| Error::Dbc(error_messages::invalid_utf8(e)))?;
500        // Convert to owned string, box it, and leak to get 'static lifetime
501        let owned = String::from(content);
502        let boxed = owned.into_boxed_str();
503        let content_ref: &'static str = Box::leak(boxed);
504        Dbc::parse(content_ref).map_err(Error::ParseError)
505    }
506
507    /// Parse a DBC file from a file path
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// use dbc_rs::Dbc;
513    ///
514    /// // Create a temporary file for the example
515    /// let dbc_content = r#"VERSION "1.0"
516    ///
517    /// BU_: ECM
518    ///
519    /// BO_ 256 Engine : 8 ECM
520    ///  SG_ Signal1 : 0|8@1+ (1,0) [0|255] ""
521    /// "#;
522    /// std::fs::write("/tmp/example.dbc", dbc_content)?;
523    ///
524    /// let dbc = Dbc::from_file("/tmp/example.dbc")?;
525    /// println!("Parsed {} messages", dbc.messages().len());
526    /// # Ok::<(), Box<dyn std::error::Error>>(())
527    /// ```
528    #[cfg(feature = "std")]
529    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Dbc<'static>> {
530        let file =
531            std::fs::File::open(path).map_err(|e| Error::Dbc(error_messages::read_failed(e)))?;
532        Self::from_reader(file)
533    }
534
535    /// Parse a DBC file from a reader
536    ///
537    /// # Examples
538    ///
539    /// ```
540    /// use dbc_rs::Dbc;
541    /// use std::io::Cursor;
542    ///
543    /// let data = b"VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM";
544    /// let reader = Cursor::new(data);
545    /// let dbc = Dbc::from_reader(reader)?;
546    /// println!("Parsed {} messages", dbc.messages().len());
547    /// # Ok::<(), dbc_rs::Error>(())
548    /// ```
549    #[cfg(feature = "std")]
550    pub fn from_reader<R: std::io::Read>(mut reader: R) -> Result<Dbc<'static>> {
551        let mut buffer = String::new();
552        std::io::Read::read_to_string(&mut reader, &mut buffer)
553            .map_err(|e| Error::Dbc(error_messages::read_failed(e)))?;
554        // Convert to boxed str and leak to get 'static lifetime
555        // The leaked memory will live for the duration of the program
556        let boxed = buffer.into_boxed_str();
557        let content_ref: &'static str = Box::leak(boxed);
558        Dbc::parse(content_ref).map_err(Error::ParseError)
559    }
560
561    /// Serialize this DBC to a DBC format string
562    ///
563    /// # Examples
564    ///
565    /// ```
566    /// use dbc_rs::Dbc;
567    ///
568    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM\n\nBO_ 256 Engine : 8 ECM")?;
569    /// let dbc_string = dbc.to_dbc_string();
570    /// // The string can be written to a file or used elsewhere
571    /// assert!(dbc_string.contains("VERSION"));
572    /// # Ok::<(), dbc_rs::Error>(())
573    /// ```
574    #[cfg(feature = "alloc")]
575    #[must_use]
576    pub fn to_dbc_string(&self) -> String {
577        // Pre-allocate with estimated capacity
578        // Estimate: ~50 chars per message + ~100 chars per signal
579        let signal_count: usize = self.messages().iter().map(|m| m.signals().len()).sum();
580        let estimated_capacity = 200 + (self.messages.len() * 50) + (signal_count * 100);
581        let mut result = String::with_capacity(estimated_capacity);
582
583        // VERSION line
584        if let Some(version) = &self.version {
585            result.push_str(&version.to_dbc_string());
586            result.push_str("\n\n");
587        }
588
589        // BU_ line
590        result.push_str(&self.nodes.to_dbc_string());
591        result.push('\n');
592
593        // BO_ and SG_ lines for each message
594        for message in self.messages().iter() {
595            result.push('\n');
596            result.push_str(&message.to_dbc_string_with_signals());
597        }
598
599        result
600    }
601}
602
603#[cfg(feature = "alloc")]
604impl<'a> core::fmt::Display for Dbc<'a> {
605    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
606        write!(f, "{}", self.to_dbc_string())
607    }
608}
609
610#[cfg(all(test, feature = "std"))]
611mod tests {
612    #![allow(clippy::float_cmp)]
613    use super::*;
614    use crate::{
615        ByteOrder, Error, Parser, Receivers, Version,
616        error::{ParseError, lang},
617        nodes::NodesBuilder,
618    };
619    use crate::{DbcBuilder, MessageBuilder, ReceiversBuilder, SignalBuilder};
620
621    #[test]
622    fn test_dbc_new_valid() {
623        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
624        let version = Version::parse(&mut parser).unwrap();
625        let nodes = NodesBuilder::new().add_node("ECM").add_node("TCM").build().unwrap();
626
627        let signal1 = SignalBuilder::new()
628            .name("RPM")
629            .start_bit(0)
630            .length(16)
631            .byte_order(ByteOrder::BigEndian)
632            .unsigned(true)
633            .factor(0.25)
634            .offset(0.0)
635            .min(0.0)
636            .max(8000.0)
637            .unit("rpm")
638            .receivers(Receivers::Broadcast)
639            .build()
640            .unwrap();
641
642        let signal2 = SignalBuilder::new()
643            .name("Temperature")
644            .start_bit(16)
645            .length(8)
646            .byte_order(ByteOrder::BigEndian)
647            .unsigned(true)
648            .factor(1.0)
649            .offset(-40.0)
650            .min(-40.0)
651            .max(215.0)
652            .unit("°C")
653            .receivers(Receivers::Broadcast)
654            .build()
655            .unwrap();
656
657        let message1 = MessageBuilder::new()
658            .id(256)
659            .name("EngineData")
660            .dlc(8)
661            .sender("ECM")
662            .add_signal(signal1)
663            .add_signal(signal2)
664            .build()
665            .unwrap();
666        let message2 = MessageBuilder::new()
667            .id(512)
668            .name("BrakeData")
669            .dlc(4)
670            .sender("TCM")
671            .build()
672            .unwrap();
673
674        let dbc = DbcBuilder::new()
675            .version(version)
676            .nodes(nodes)
677            .add_message(message1)
678            .add_message(message2)
679            .build()
680            .unwrap();
681        assert_eq!(dbc.messages().len(), 2);
682        let mut messages_iter = dbc.messages().iter();
683        assert_eq!(messages_iter.next().unwrap().id(), 256);
684        assert_eq!(messages_iter.next().unwrap().id(), 512);
685    }
686
687    #[test]
688    fn test_dbc_new_duplicate_message_id() {
689        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
690        let version = Version::parse(&mut parser).unwrap();
691        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
692
693        let signal = SignalBuilder::new()
694            .name("RPM")
695            .start_bit(0)
696            .length(16)
697            .byte_order(ByteOrder::BigEndian)
698            .unsigned(true)
699            .factor(1.0)
700            .offset(0.0)
701            .min(0.0)
702            .max(100.0)
703            .receivers(Receivers::None)
704            .build()
705            .unwrap();
706
707        let message1 = MessageBuilder::new()
708            .id(256)
709            .name("EngineData1")
710            .dlc(8)
711            .sender("ECM")
712            .add_signal(signal.clone())
713            .build()
714            .unwrap();
715        let message2 = MessageBuilder::new()
716            .id(256)
717            .name("EngineData2")
718            .dlc(8)
719            .sender("ECM")
720            .add_signal(signal)
721            .build()
722            .unwrap();
723
724        let result = DbcBuilder::new()
725            .version(version)
726            .nodes(nodes)
727            .add_message(message1)
728            .add_message(message2)
729            .build();
730        assert!(result.is_err());
731        match result.unwrap_err() {
732            Error::Dbc(msg) => {
733                // Check for format template text (language-agnostic) - extract text before first placeholder
734                let template_text = lang::FORMAT_DUPLICATE_MESSAGE_ID.split("{}").next().unwrap();
735                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
736            }
737            _ => panic!("Expected Error::Dbc"),
738        }
739    }
740
741    #[test]
742    fn test_dbc_new_sender_not_in_nodes() {
743        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
744        let version = Version::parse(&mut parser).unwrap();
745        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap(); // Only ECM, but message uses TCM
746
747        let signal = SignalBuilder::new()
748            .name("RPM")
749            .start_bit(0)
750            .length(16)
751            .byte_order(ByteOrder::BigEndian)
752            .unsigned(true)
753            .factor(1.0)
754            .offset(0.0)
755            .min(0.0)
756            .max(100.0)
757            .receivers(Receivers::None)
758            .build()
759            .unwrap();
760
761        let message = MessageBuilder::new()
762            .id(256)
763            .name("EngineData")
764            .dlc(8)
765            .sender("TCM")
766            .add_signal(signal)
767            .build()
768            .unwrap();
769
770        let result = DbcBuilder::new().version(version).nodes(nodes).add_message(message).build();
771        assert!(result.is_err());
772        match result.unwrap_err() {
773            Error::Dbc(msg) => {
774                // Check for format template text (language-agnostic) - extract text before first placeholder
775                let template_text = lang::FORMAT_SENDER_NOT_IN_NODES.split("{}").next().unwrap();
776                assert!(msg.contains(template_text.trim_end()));
777            }
778            _ => panic!("Expected Error::Dbc"),
779        }
780    }
781
782    #[test]
783    fn parses_real_dbc() {
784        let data = r#"VERSION "1.0"
785
786BU_: ECM TCM
787
788BO_ 256 Engine : 8 ECM
789 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
790 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
791
792BO_ 512 Brake : 4 TCM
793 SG_ Pressure : 0|16@1+ (0.1,0) [0|1000] "bar""#;
794
795        let dbc = Dbc::parse(data).unwrap();
796        assert_eq!(dbc.messages().len(), 2);
797        let mut messages_iter = dbc.messages().iter();
798        let msg0 = messages_iter.next().unwrap();
799        assert_eq!(msg0.signals().len(), 2);
800        let mut signals_iter = msg0.signals().iter();
801        assert_eq!(signals_iter.next().unwrap().name(), "RPM");
802        assert_eq!(signals_iter.next().unwrap().name(), "Temp");
803        let msg1 = messages_iter.next().unwrap();
804        assert_eq!(msg1.signals().len(), 1);
805        assert_eq!(msg1.signals().iter().next().unwrap().name(), "Pressure");
806    }
807
808    #[test]
809    fn test_parse_duplicate_message_id() {
810        // Test that parse also validates duplicate message IDs
811        let data = r#"VERSION "1.0"
812
813BU_: ECM
814
815BO_ 256 EngineData1 : 8 ECM
816 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
817
818BO_ 256 EngineData2 : 8 ECM
819 SG_ Temp : 16|8@0- (1,-40) [-40|215] "°C"
820"#;
821
822        let result = Dbc::parse(data);
823        assert!(result.is_err());
824        match result.unwrap_err() {
825            ParseError::Version(msg) => {
826                // Check for format template text (language-agnostic) - extract text before first placeholder
827                let template_text = lang::FORMAT_DUPLICATE_MESSAGE_ID.split("{}").next().unwrap();
828                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
829            }
830            _ => panic!("Expected ParseError::Version"),
831        }
832    }
833
834    #[test]
835    fn test_parse_sender_not_in_nodes() {
836        // Test that parse also validates message senders are in nodes list
837        let data = r#"VERSION "1.0"
838
839BU_: ECM
840
841BO_ 256 EngineData : 8 TCM
842 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
843"#;
844
845        let result = Dbc::parse(data);
846        assert!(result.is_err());
847        match result.unwrap_err() {
848            ParseError::Version(msg) => {
849                // Check for format template text (language-agnostic) - extract text before first placeholder
850                let template_text = lang::FORMAT_SENDER_NOT_IN_NODES.split("{}").next().unwrap();
851                assert!(msg.contains(template_text.trim_end()));
852            }
853            _ => panic!("Expected ParseError::Version"),
854        }
855    }
856
857    #[test]
858    fn test_parse_empty_file() {
859        // Test parsing an empty file
860        let result = Dbc::parse("");
861        assert!(result.is_err());
862        match result.unwrap_err() {
863            ParseError::UnexpectedEof => {
864                // Empty file should result in unexpected EOF
865            }
866            _ => panic!("Expected ParseError::UnexpectedEof"),
867        }
868    }
869
870    #[test]
871    fn test_parse_missing_nodes() {
872        // Test parsing without BU_ statement
873        // Note: The parser allows missing BU_ line and treats it as empty nodes
874        // This is consistent with allowing empty nodes per DBC spec
875        let data = r#"VERSION "1.0"
876
877BO_ 256 EngineData : 8 ECM
878 SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm"
879"#;
880
881        let result = Dbc::parse(data);
882        // Parser should succeed with empty nodes (missing BU_ is treated as empty nodes)
883        assert!(result.is_ok());
884        let dbc = result.unwrap();
885        assert!(dbc.nodes().is_empty());
886    }
887
888    #[test]
889    fn test_parse_bytes() {
890        let data = r#"VERSION "1.0"
891
892BU_: ECM
893
894BO_ 256 Engine : 8 ECM
895 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
896"#;
897
898        let bytes = data.as_bytes();
899        let dbc = Dbc::parse_bytes(bytes).unwrap();
900        assert_eq!(
901            dbc.version().map(|v| v.to_string()),
902            Some("1.0".to_string())
903        );
904        assert_eq!(dbc.messages().len(), 1);
905    }
906
907    #[test]
908    #[cfg(feature = "alloc")]
909    fn test_parse_from_string() {
910        let data = String::from(
911            r#"VERSION "1.0"
912
913BU_: ECM
914
915BO_ 256 Engine : 8 ECM
916 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
917"#,
918        );
919
920        let dbc = Dbc::parse(&data).unwrap();
921        assert_eq!(
922            dbc.version().map(|v| v.to_string()),
923            Some("1.0".to_string())
924        );
925        assert_eq!(dbc.messages().len(), 1);
926    }
927
928    #[test]
929    fn test_parse_bytes_invalid_utf8() {
930        // Invalid UTF-8 sequence
931        let invalid_bytes = &[0xFF, 0xFE, 0xFD];
932        let result = Dbc::parse_bytes(invalid_bytes);
933        assert!(result.is_err());
934        match result.unwrap_err() {
935            Error::Dbc(msg) => {
936                // Check for format template text (language-agnostic) - extract text before first placeholder
937                let template_text = lang::FORMAT_INVALID_UTF8.split("{}").next().unwrap();
938                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
939            }
940            _ => panic!("Expected Dbc error"),
941        }
942    }
943
944    #[test]
945    #[cfg(feature = "alloc")]
946    fn test_save_basic() {
947        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
948        let version = Version::parse(&mut parser).unwrap();
949        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
950
951        let signal = SignalBuilder::new()
952            .name("RPM")
953            .start_bit(0)
954            .length(16)
955            .byte_order(ByteOrder::BigEndian)
956            .unsigned(true)
957            .factor(0.25)
958            .offset(0.0)
959            .min(0.0)
960            .max(8000.0)
961            .unit("rpm")
962            .receivers(ReceiversBuilder::new().broadcast().build().unwrap())
963            .build()
964            .unwrap();
965
966        let message = MessageBuilder::new()
967            .id(256)
968            .name("EngineData")
969            .dlc(8)
970            .sender("ECM")
971            .add_signal(signal)
972            .build()
973            .unwrap();
974        let messages_array = [message];
975        // For validation, we need Option array but Message doesn't implement Clone
976        // Since this is a simple test with one message, we'll just skip validation
977        // In real usage, builders handle this conversion properly
978        let dbc = Dbc::new(Some(version), nodes, &messages_array);
979
980        let saved = dbc.to_dbc_string();
981        assert!(saved.contains("VERSION \"1.0\""));
982        assert!(saved.contains("BU_: ECM"));
983        assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
984        assert!(saved.contains("SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *")); // BigEndian = @0
985    }
986
987    #[test]
988    fn test_save_round_trip() {
989        let original = r#"VERSION "1.0"
990
991BU_: ECM TCM
992
993BO_ 256 EngineData : 8 ECM
994 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
995 SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM
996
997BO_ 512 BrakeData : 4 TCM
998 SG_ Pressure : 0|16@0+ (0.1,0) [0|1000] "bar"
999"#;
1000
1001        let dbc = Dbc::parse(original).unwrap();
1002        let saved = dbc.to_dbc_string();
1003        let dbc2 = Dbc::parse(&saved).unwrap();
1004
1005        // Verify round-trip: parsed data should match
1006        assert_eq!(
1007            dbc.version().map(|v| v.to_string()),
1008            dbc2.version().map(|v| v.to_string())
1009        );
1010        assert_eq!(dbc.messages().len(), dbc2.messages().len());
1011
1012        for (msg1, msg2) in dbc.messages().iter().zip(dbc2.messages().iter()) {
1013            assert_eq!(msg1.id(), msg2.id());
1014            assert_eq!(msg1.name(), msg2.name());
1015            assert_eq!(msg1.dlc(), msg2.dlc());
1016            assert_eq!(msg1.sender(), msg2.sender());
1017            assert_eq!(msg1.signals().len(), msg2.signals().len());
1018
1019            for (sig1, sig2) in msg1.signals().iter().zip(msg2.signals().iter()) {
1020                assert_eq!(sig1.name(), sig2.name());
1021                assert_eq!(sig1.start_bit(), sig2.start_bit());
1022                assert_eq!(sig1.length(), sig2.length());
1023                assert_eq!(sig1.byte_order(), sig2.byte_order());
1024                assert_eq!(sig1.is_unsigned(), sig2.is_unsigned());
1025                assert_eq!(sig1.factor(), sig2.factor());
1026                assert_eq!(sig1.offset(), sig2.offset());
1027                assert_eq!(sig1.min(), sig2.min());
1028                assert_eq!(sig1.max(), sig2.max());
1029                assert_eq!(sig1.unit(), sig2.unit());
1030                assert_eq!(sig1.receivers(), sig2.receivers());
1031            }
1032        }
1033    }
1034
1035    #[test]
1036    #[cfg(feature = "alloc")]
1037    fn test_save_multiple_messages() {
1038        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
1039        let version = Version::parse(&mut parser).unwrap();
1040        let nodes = NodesBuilder::new().add_node("ECM").add_node("TCM").build().unwrap();
1041
1042        let signal1 = SignalBuilder::new()
1043            .name("RPM")
1044            .start_bit(0)
1045            .length(16)
1046            .byte_order(ByteOrder::BigEndian)
1047            .unsigned(true)
1048            .factor(0.25)
1049            .offset(0.0)
1050            .min(0.0)
1051            .max(8000.0)
1052            .unit("rpm")
1053            .receivers(Receivers::Broadcast)
1054            .build()
1055            .unwrap();
1056
1057        let signal2 = SignalBuilder::new()
1058            .name("Pressure")
1059            .start_bit(0)
1060            .length(16)
1061            .byte_order(ByteOrder::LittleEndian)
1062            .unsigned(true)
1063            .factor(0.1)
1064            .offset(0.0)
1065            .min(0.0)
1066            .max(1000.0)
1067            .unit("bar")
1068            .receivers(Receivers::None)
1069            .build()
1070            .unwrap();
1071
1072        let message1 = MessageBuilder::new()
1073            .id(256)
1074            .name("EngineData")
1075            .dlc(8)
1076            .sender("ECM")
1077            .add_signal(signal1)
1078            .build()
1079            .unwrap();
1080        let message2 = MessageBuilder::new()
1081            .id(512)
1082            .name("BrakeData")
1083            .dlc(4)
1084            .sender("TCM")
1085            .add_signal(signal2)
1086            .build()
1087            .unwrap();
1088
1089        let dbc = DbcBuilder::new()
1090            .version(version)
1091            .nodes(nodes)
1092            .add_message(message1)
1093            .add_message(message2)
1094            .build()
1095            .unwrap();
1096        let saved = dbc.to_dbc_string();
1097
1098        // Verify both messages are present
1099        assert!(saved.contains("BO_ 256 EngineData : 8 ECM"));
1100        assert!(saved.contains("BO_ 512 BrakeData : 4 TCM"));
1101        assert!(saved.contains("SG_ RPM"));
1102        assert!(saved.contains("SG_ Pressure"));
1103    }
1104
1105    #[test]
1106    #[cfg(feature = "alloc")]
1107    fn test_dbc_too_many_messages() {
1108        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
1109        let version = Version::parse(&mut parser).unwrap();
1110        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
1111        let signal = SignalBuilder::new()
1112            .name("RPM")
1113            .start_bit(0)
1114            .length(16)
1115            .byte_order(ByteOrder::BigEndian)
1116            .unsigned(true)
1117            .factor(1.0)
1118            .offset(0.0)
1119            .min(0.0)
1120            .max(100.0)
1121            .receivers(ReceiversBuilder::new().none().build().unwrap())
1122            .build()
1123            .unwrap();
1124
1125        // Create 10,001 messages (exceeds limit of 10,000)
1126        let mut messages = Vec::new();
1127        let message_names: Vec<String> = (0..10_001).map(|i| format!("Message{i}")).collect();
1128        for i in 0..10_001 {
1129            let message = MessageBuilder::new()
1130                .id(i)
1131                .name(&message_names[i as usize])
1132                .dlc(8)
1133                .sender("ECM")
1134                .add_signal(signal.clone())
1135                .build()
1136                .unwrap();
1137            messages.push(message);
1138        }
1139
1140        // Should cap at max capacity even if more messages are provided
1141        let dbc = Dbc::new(Some(version), nodes, &messages);
1142        // Use the actual count from the DBC (which will be capped by Messages)
1143        assert_eq!(dbc.messages().len(), 10_000); // MAX_MESSAGES default
1144    }
1145
1146    #[test]
1147    #[cfg(feature = "alloc")]
1148    fn test_dbc_at_message_limit() {
1149        use crate::nodes::NodesBuilder;
1150
1151        let mut parser = Parser::new(b"VERSION \"1.0\"").unwrap();
1152        let version = Version::parse(&mut parser).unwrap();
1153        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
1154        let signal = SignalBuilder::new()
1155            .name("RPM")
1156            .start_bit(0)
1157            .length(16)
1158            .byte_order(ByteOrder::BigEndian)
1159            .unsigned(true)
1160            .factor(1.0)
1161            .offset(0.0)
1162            .min(0.0)
1163            .max(100.0)
1164            .receivers(ReceiversBuilder::new().none().build().unwrap())
1165            .build()
1166            .unwrap();
1167
1168        // Create exactly 10,000 messages (at the limit)
1169        let mut messages = Vec::new();
1170        let message_names: Vec<String> = (0..10_000).map(|i| format!("Message{i}")).collect();
1171        for i in 0..10_000 {
1172            let message = MessageBuilder::new()
1173                .id(i)
1174                .name(&message_names[i as usize])
1175                .dlc(8)
1176                .sender("ECM")
1177                .add_signal(signal.clone())
1178                .build()
1179                .unwrap();
1180            messages.push(message);
1181        }
1182
1183        // Note: Message doesn't implement Clone, so we can't easily convert to Option array for validation
1184        // Since this test is about testing limits (not validation), we'll skip validation
1185        // In real usage, builders handle this conversion
1186        let dbc = Dbc::new(Some(version), nodes, &messages);
1187        assert_eq!(dbc.messages().len(), 10_000);
1188    }
1189
1190    #[test]
1191    fn test_parse_without_version() {
1192        // DBC file without VERSION line should default to empty version
1193        let data = r#"
1194BU_: ECM
1195
1196BO_ 256 Engine : 8 ECM
1197 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1198"#;
1199        let dbc = Dbc::parse(data).unwrap();
1200        assert_eq!(dbc.version().map(|v| v.to_string()), Some("".to_string()));
1201    }
1202
1203    #[test]
1204    fn test_parse_without_version_with_comment() {
1205        // DBC file with comment and no VERSION line
1206        let data = r#"// This is a comment
1207BU_: ECM
1208
1209BO_ 256 Engine : 8 ECM
1210 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1211"#;
1212        let dbc = Dbc::parse(data).unwrap();
1213        assert_eq!(dbc.version().map(|v| v.to_string()), Some("".to_string()));
1214    }
1215
1216    #[test]
1217    fn test_parse_error_with_line_number() {
1218        // Test that errors include line numbers (or at least that errors are returned)
1219        let data = r#"VERSION "1.0"
1220
1221BU_: ECM
1222
1223BO_ 256 Engine : 8 ECM
1224 SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm"
1225BO_ 257 Invalid : 8 ECM
1226 SG_ InvalidSignal : invalid|16@1+ (0.25,0) [0|8000] "rpm"
1227"#;
1228        let result = Dbc::parse(data);
1229        assert!(result.is_err());
1230        let err = result.unwrap_err();
1231        // Accept any ParseError - line number tracking is not yet implemented
1232        match err {
1233            ParseError::Version(_)
1234            | ParseError::UnexpectedEof
1235            | ParseError::Expected(_)
1236            | ParseError::InvalidChar(_) => {
1237                // Accept various parse errors
1238            }
1239            _ => panic!("Expected ParseError"),
1240        };
1241        // Note: Line number tracking is not yet implemented, so we just verify an error is returned
1242    }
1243
1244    #[test]
1245    fn test_parse_error_version_with_line_number() {
1246        // Test that version parsing errors are returned (line number tracking not yet implemented)
1247        let data = r#"VERSION invalid
1248
1249BU_: ECM
1250"#;
1251        let result = Dbc::parse(data);
1252        assert!(result.is_err());
1253        let err = result.unwrap_err();
1254        // Accept any ParseError - line number tracking is not yet implemented
1255        match err {
1256            ParseError::Version(_) | ParseError::UnexpectedEof | ParseError::Expected(_) => {
1257                // Accept various parse errors
1258            }
1259            _ => panic!("Expected ParseError"),
1260        };
1261        // Note: Line number tracking is not yet implemented, so we just verify an error is returned
1262    }
1263
1264    #[test]
1265    fn test_parse_with_lenient_boundary_check() {
1266        // Test that lenient mode allows signals that extend beyond message boundaries
1267        let data = r#"VERSION "1.0"
1268
1269BU_: ECM
1270
1271BO_ 256 Test : 8 ECM
1272 SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] ""
1273"#;
1274
1275        // Strict mode should fail
1276        let result = Dbc::parse(data);
1277        assert!(result.is_err());
1278
1279        // Lenient mode should succeed
1280        let options = ParseOptions::lenient();
1281        let dbc = Dbc::parse_with_options(data, options).unwrap();
1282        assert_eq!(dbc.messages().len(), 1);
1283        let message = dbc.messages().at(0).unwrap();
1284        assert_eq!(message.signals().len(), 1);
1285        assert_eq!(message.signals().at(0).unwrap().name(), "CHECKSUM");
1286    }
1287
1288    #[test]
1289    fn test_parse_with_strict_boundary_check() {
1290        // Test that strict mode (default) rejects signals that extend beyond boundaries
1291        let data = r#"VERSION "1.0"
1292
1293BU_: ECM
1294
1295BO_ 256 Test : 8 ECM
1296 SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] ""
1297"#;
1298
1299        // Default (strict) mode should fail
1300        let result = Dbc::parse(data);
1301        assert!(result.is_err());
1302
1303        // Explicit strict mode should also fail
1304        let options = ParseOptions::new();
1305        let result = Dbc::parse_with_options(data, options);
1306        assert!(result.is_err());
1307    }
1308}