dbc_rs/message/
message.rs

1use crate::{
2    ByteOrder, Parser, Signal,
3    error::{ParseError, ParseResult},
4};
5
6use super::Signals;
7
8/// Represents a CAN message in a DBC file.
9///
10/// A `Message` contains:
11/// - A unique ID (CAN identifier)
12/// - A name
13/// - A DLC (Data Length Code) specifying the message size in bytes
14/// - A sender node (ECU that transmits this message)
15/// - A collection of signals
16///
17/// # Examples
18///
19/// ```rust,no_run
20/// use dbc_rs::Dbc;
21///
22/// let dbc_content = r#"VERSION "1.0"
23///
24/// BU_: ECM
25///
26/// BO_ 256 EngineData : 8 ECM
27///  SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *
28/// "#;
29///
30/// let dbc = Dbc::parse(dbc_content)?;
31/// let message = dbc.messages().at(0).unwrap();
32/// println!("Message: {} (ID: {})", message.name(), message.id());
33/// # Ok::<(), dbc_rs::Error>(())
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Message<'a> {
37    id: u32,
38    name: &'a str,
39    dlc: u8,
40    sender: &'a str,
41    signals: Signals<'a>,
42}
43
44impl<'a> Message<'a> {
45    #[allow(clippy::similar_names)] // physical_lsb and physical_msb are intentionally similar
46    fn calculate_bit_range(start_bit: u16, length: u16, byte_order: ByteOrder) -> (u16, u16) {
47        let start = start_bit;
48        let len = length;
49
50        match byte_order {
51            ByteOrder::LittleEndian => {
52                // Little-endian: start_bit is LSB, signal extends forward
53                // Range: [start_bit, start_bit + length - 1]
54                (start, start + len - 1)
55            }
56            ByteOrder::BigEndian => {
57                // Big-endian: start_bit is MSB in big-endian numbering, signal extends backward
58                // The big-endian bit numbering follows Vector convention:
59                // be_bits = [7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, ...]
60                // This means: BE bit 0 -> physical bit 7, BE bit 7 -> physical bit 0
61                //            BE bit 8 -> physical bit 15, BE bit 15 -> physical bit 8
62                // To find the physical bit range:
63                // 1. Find the index of start_bit in the be_bits sequence
64                // 2. MSB (physical) = be_bits[idx]
65                // 3. LSB (physical) = be_bits[idx + length - 1]
66                // We can calculate this directly:
67                // For BE bit N: byte_num = N / 8, bit_in_byte = N % 8
68                // Physical bit = byte_num * 8 + (7 - bit_in_byte)
69                let byte_num = start / 8;
70                let bit_in_byte = start % 8;
71                let physical_msb = byte_num * 8 + (7 - bit_in_byte);
72
73                // Calculate LSB: move forward (length - 1) positions in the BE sequence
74                // BE bit (start + length - 1) maps to physical bit
75                let lsb_be_bit = start + len - 1;
76                let lsb_byte_num = lsb_be_bit / 8;
77                let lsb_bit_in_byte = lsb_be_bit % 8;
78                let physical_lsb = lsb_byte_num * 8 + (7 - lsb_bit_in_byte);
79
80                // Ensure lsb <= msb (they should be in that order for big-endian)
81                if physical_lsb <= physical_msb {
82                    (physical_lsb, physical_msb)
83                } else {
84                    (physical_msb, physical_lsb)
85                }
86            }
87        }
88    }
89
90    #[allow(clippy::similar_names)] // Overlap detection uses intentionally similar variable names (sig1_lsb/sig1_msb, sig2_lsb/sig2_msb)
91    pub(crate) fn validate_internal(
92        id: u32,
93        name: &str,
94        dlc: u8,
95        sender: &str,
96        signals: &[Option<Signal<'a>>],
97        signal_count: usize,
98        options: crate::ParseOptions,
99    ) -> ParseResult<()> {
100        // Validate CAN ID range
101        // CAN specification allows:
102        // - Standard 11-bit IDs: 0x000 to 0x7FF (0-2047)
103        // - Extended 29-bit IDs: 0x00000000 to 0x1FFFFFFF (0-536870911)
104        // Note: Extended IDs can technically be 0-536870911, but DBC files typically
105        // use the convention where IDs 0-2047 are treated as standard and 2048+ as extended.
106        // We only validate the maximum range here; the distinction between standard/extended
107        // is determined by the ID value in practice.
108        const MAX_EXTENDED_ID: u32 = 0x1FFF_FFFF; // 536870911
109
110        // Check signal count limit per message (DoS protection)
111        const MAX_SIGNALS_PER_MESSAGE: usize = crate::Signals::max_capacity();
112        if signal_count > MAX_SIGNALS_PER_MESSAGE {
113            return Err(ParseError::Version(
114                crate::error::lang::MESSAGE_TOO_MANY_SIGNALS,
115            ));
116        }
117
118        if name.trim().is_empty() {
119            return Err(ParseError::Version(crate::error::lang::MESSAGE_NAME_EMPTY));
120        }
121
122        if sender.trim().is_empty() {
123            return Err(ParseError::Version(
124                crate::error::lang::MESSAGE_SENDER_EMPTY,
125            ));
126        }
127
128        // Validate DLC (Data Length Code): must be between 1 and 64 bytes
129        // - Classic CAN Standard (CAN 2.0A): DLC <= 8 bytes (64 bits) maximum payload
130        // - Classic CAN Extended (CAN 2.0B): DLC <= 8 bytes (64 bits) maximum payload
131        // - CAN FD (Flexible Data Rate, ISO/Bosch): DLC <= 64 bytes (512 bits) maximum payload
132        if dlc == 0 {
133            #[cfg(feature = "alloc")]
134            {
135                use crate::error::messages;
136                let msg = messages::message_dlc_too_small(name, id, dlc);
137                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
138            }
139            #[cfg(not(feature = "alloc"))]
140            {
141                return Err(ParseError::Version(
142                    crate::error::lang::MESSAGE_DLC_TOO_SMALL,
143                ));
144            }
145        }
146        if dlc > 64 {
147            #[cfg(feature = "alloc")]
148            {
149                use crate::error::messages;
150                let msg = messages::message_dlc_too_large(name, id, dlc);
151                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
152            }
153            #[cfg(not(feature = "alloc"))]
154            {
155                return Err(ParseError::Version(
156                    crate::error::lang::MESSAGE_DLC_TOO_LARGE,
157                ));
158            }
159        }
160
161        // Validate that ID is within valid CAN ID range
162        // Extended CAN IDs can be 0x00000000 to 0x1FFFFFFF (0 to 536870911)
163        // IDs exceeding 0x1FFFFFFF are invalid
164        if id > MAX_EXTENDED_ID {
165            #[cfg(feature = "alloc")]
166            {
167                use crate::error::messages;
168                let msg = messages::message_id_out_of_range(id);
169                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
170            }
171            #[cfg(not(feature = "alloc"))]
172            {
173                return Err(ParseError::Version(
174                    crate::error::lang::MESSAGE_ID_OUT_OF_RANGE,
175                ));
176            }
177        }
178
179        // Validate that all signals fit within the message boundary
180        // Each signal must fit within: DLC * 8 bits
181        // - Classic CAN (2.0A/2.0B): DLC * 8 <= 64 bits (8 bytes)
182        // - CAN FD: DLC * 8 <= 512 bits (64 bytes)
183        // This ensures no signal extends beyond the message payload capacity
184        let max_bits = u16::from(dlc) * 8;
185        for signal in signals.iter().take(signal_count).filter_map(|opt| opt.as_ref()) {
186            // Calculate the actual bit range for this signal (accounting for byte order)
187            let (lsb, msb) =
188                Self::calculate_bit_range(signal.start_bit(), signal.length(), signal.byte_order());
189            // Check if the signal extends beyond the message boundary
190            // The signal's highest bit position must be less than max_bits
191            let signal_max_bit = lsb.max(msb);
192            if signal_max_bit >= max_bits {
193                // Only fail if strict boundary checking is enabled
194                if options.strict_boundary_check {
195                    #[cfg(feature = "alloc")]
196                    {
197                        use crate::error::messages;
198                        let msg = messages::signal_extends_beyond_message(
199                            signal.name(),
200                            signal.start_bit(),
201                            signal.length(),
202                            signal_max_bit,
203                            max_bits,
204                            dlc,
205                        );
206                        return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
207                    }
208                    #[cfg(not(feature = "alloc"))]
209                    {
210                        return Err(ParseError::Version(
211                            crate::error::lang::SIGNAL_LENGTH_TOO_LARGE,
212                        ));
213                    }
214                }
215                // In lenient mode, we allow signals that extend beyond boundaries
216            }
217        }
218
219        // Validate signal overlap detection
220        // Check if any two signals overlap in the same message
221        // Must account for byte order: little-endian signals extend forward,
222        // big-endian signals extend backward from start_bit
223        // We iterate over pairs without collecting to avoid alloc
224        let signals_slice = &signals[..signal_count];
225        for (i, sig1_opt) in signals_slice.iter().enumerate() {
226            let sig1 = match sig1_opt {
227                Some(s) => s,
228                None => continue, // Should not happen, but be safe
229            };
230            let (sig1_lsb, sig1_msb) =
231                Self::calculate_bit_range(sig1.start_bit(), sig1.length(), sig1.byte_order());
232
233            for sig2_opt in signals_slice.iter().skip(i + 1) {
234                let sig2 = match sig2_opt {
235                    Some(s) => s,
236                    None => continue, // Should not happen, but be safe
237                };
238                let (sig2_lsb, sig2_msb) =
239                    Self::calculate_bit_range(sig2.start_bit(), sig2.length(), sig2.byte_order());
240
241                // Check if ranges overlap
242                // Two ranges [lsb1, msb1] and [lsb2, msb2] overlap if:
243                // lsb1 <= msb2 && lsb2 <= msb1
244                if sig1_lsb <= sig2_msb && sig2_lsb <= sig1_msb {
245                    #[cfg(feature = "alloc")]
246                    {
247                        use crate::error::messages;
248                        let msg = messages::signal_overlap(sig1.name(), sig2.name(), name);
249                        return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
250                    }
251                    #[cfg(not(feature = "alloc"))]
252                    {
253                        return Err(ParseError::Version(crate::error::lang::SIGNAL_OVERLAP));
254                    }
255                }
256            }
257        }
258
259        Ok(())
260    }
261
262    #[allow(dead_code)] // Only used by builders (std-only)
263    pub(crate) fn new(
264        id: u32,
265        name: &'a str,
266        dlc: u8,
267        sender: &'a str,
268        signals: &'a [Signal<'a>],
269    ) -> Self {
270        // Validation should have been done prior (by builder or parse)
271        Self {
272            id,
273            name,
274            dlc,
275            sender,
276            signals: Signals::from_signals_slice(signals),
277        }
278    }
279
280    fn new_from_options(
281        id: u32,
282        name: &'a str,
283        dlc: u8,
284        sender: &'a str,
285        signals: &[Option<Signal<'a>>],
286        signal_count: usize,
287    ) -> Self {
288        // Validation should have been done prior (by builder or parse)
289        Self {
290            id,
291            name,
292            dlc,
293            sender,
294            signals: Signals::from_options_slice(signals, signal_count),
295        }
296    }
297
298    pub(crate) fn parse<'b: 'a>(
299        parser: &mut Parser<'b>,
300        signals: &[Option<Signal<'a>>],
301        signal_count: usize,
302        options: crate::ParseOptions,
303    ) -> ParseResult<Self> {
304        // Expect "BO_" keyword (should already be consumed by find_next_keyword, but handle both cases)
305        if parser.expect(crate::BO_.as_bytes()).is_err() {
306            // Already past "BO_" from find_next_keyword, continue
307        }
308
309        // Skip whitespace
310        let _ = parser.skip_whitespace();
311
312        // Parse message ID
313        let id = parser
314            .parse_u32()
315            .map_err(|_| ParseError::Version(crate::error::lang::MESSAGE_INVALID_ID))?;
316
317        // Skip whitespace
318        parser
319            .skip_whitespace()
320            .map_err(|_| ParseError::Expected("Expected whitespace"))?;
321
322        // Parse message name (identifier)
323        let name = parser
324            .parse_identifier()
325            .map_err(|_| ParseError::Version(crate::error::lang::MESSAGE_NAME_EMPTY))?;
326
327        // Skip whitespace (optional before colon)
328        let _ = parser.skip_whitespace();
329
330        // Expect colon
331        parser.expect(b":").map_err(|_| ParseError::Expected("Expected colon"))?;
332
333        // Skip whitespace after colon
334        let _ = parser.skip_whitespace();
335
336        // Parse DLC
337        let dlc = parser
338            .parse_u8()
339            .map_err(|_| ParseError::Version(crate::error::lang::MESSAGE_INVALID_DLC))?;
340
341        // Skip whitespace
342        parser
343            .skip_whitespace()
344            .map_err(|_| ParseError::Expected("Expected whitespace"))?;
345
346        // Parse sender (identifier, until end of line or whitespace)
347        let sender = parser
348            .parse_identifier()
349            .map_err(|_| ParseError::Version(crate::error::lang::MESSAGE_SENDER_EMPTY))?;
350
351        // Check for extra content after sender (invalid format)
352        parser.skip_newlines_and_spaces();
353        if !parser.is_empty() {
354            return Err(ParseError::Expected(
355                "Unexpected content after message sender",
356            ));
357        }
358
359        // Validate before construction
360        Self::validate_internal(
361            id,
362            name,
363            dlc,
364            sender,
365            &signals[..signal_count],
366            signal_count,
367            options,
368        )?;
369        // Construct directly (validation already done)
370        Ok(Self::new_from_options(
371            id,
372            name,
373            dlc,
374            sender,
375            signals,
376            signal_count,
377        ))
378    }
379
380    /// Returns the CAN message ID.
381    ///
382    /// # Examples
383    ///
384    /// ```rust,no_run
385    /// use dbc_rs::Dbc;
386    ///
387    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
388    /// let message = dbc.messages().at(0).unwrap();
389    /// assert_eq!(message.id(), 256);
390    /// # Ok::<(), dbc_rs::Error>(())
391    /// ```
392    #[inline]
393    #[must_use]
394    pub fn id(&self) -> u32 {
395        self.id
396    }
397
398    /// Returns the message name.
399    ///
400    /// # Examples
401    ///
402    /// ```rust,no_run
403    /// use dbc_rs::Dbc;
404    ///
405    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
406    /// let message = dbc.messages().at(0).unwrap();
407    /// assert_eq!(message.name(), "EngineData");
408    /// # Ok::<(), dbc_rs::Error>(())
409    /// ```
410    #[inline]
411    #[must_use]
412    pub fn name(&self) -> &'a str {
413        self.name
414    }
415
416    /// Returns the Data Length Code (DLC) in bytes.
417    ///
418    /// DLC specifies the size of the message payload. For classic CAN, this is 1-8 bytes.
419    /// For CAN FD, this can be up to 64 bytes.
420    ///
421    /// # Examples
422    ///
423    /// ```rust,no_run
424    /// use dbc_rs::Dbc;
425    ///
426    /// let dbc = Dbc::parse(r#"VERSION "1.0"\n\nBU_: ECM\n\nBO_ 256 EngineData : 8 ECM"#)?;
427    /// let message = dbc.messages().at(0).unwrap();
428    /// assert_eq!(message.dlc(), 8);
429    /// # Ok::<(), dbc_rs::Error>(())
430    /// ```
431    #[inline]
432    #[must_use]
433    pub fn dlc(&self) -> u8 {
434        self.dlc
435    }
436
437    #[inline]
438    #[must_use]
439    pub fn sender(&self) -> &'a str {
440        self.sender
441    }
442
443    /// Get a reference to the signals collection
444    #[inline]
445    #[must_use]
446    pub fn signals(&self) -> &Signals<'a> {
447        &self.signals
448    }
449
450    #[cfg(feature = "alloc")]
451    #[must_use]
452    pub fn to_dbc_string(&self) -> String {
453        format!(
454            "BO_ {} {} : {} {}",
455            self.id(),
456            self.name(),
457            self.dlc(),
458            self.sender()
459        )
460    }
461
462    #[cfg(feature = "alloc")]
463    #[must_use]
464    pub fn to_dbc_string_with_signals(&self) -> String {
465        let mut result = String::with_capacity(200 + (self.signals.len() * 100));
466        result.push_str(&self.to_dbc_string());
467        result.push('\n');
468
469        for signal in self.signals().iter() {
470            result.push_str(&signal.to_dbc_string());
471            result.push('\n');
472        }
473
474        result
475    }
476}
477
478#[cfg(feature = "alloc")]
479impl<'a> core::fmt::Display for Message<'a> {
480    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
481        write!(f, "{}", self.to_dbc_string_with_signals())
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    #![allow(clippy::float_cmp)]
488    use super::*;
489    use crate::{
490        ByteOrder, Error, Parser, Receivers,
491        error::{ParseError, lang},
492    };
493    #[cfg(feature = "alloc")]
494    use crate::{MessageBuilder, SignalBuilder};
495
496    #[test]
497    #[cfg(feature = "alloc")]
498    fn test_message_new_valid() {
499        let signal = SignalBuilder::new()
500            .name("RPM")
501            .start_bit(0)
502            .length(16)
503            .byte_order(ByteOrder::BigEndian)
504            .unsigned(true)
505            .factor(0.25)
506            .offset(0.0)
507            .min(0.0)
508            .max(8000.0)
509            .unit("rpm")
510            .receivers(Receivers::Broadcast)
511            .build()
512            .unwrap();
513
514        let message = MessageBuilder::new()
515            .id(256)
516            .name("EngineData")
517            .dlc(8)
518            .sender("ECM")
519            .add_signal(signal)
520            .build()
521            .unwrap();
522        assert_eq!(message.id(), 256);
523        assert_eq!(message.name(), "EngineData");
524        assert_eq!(message.dlc(), 8);
525        assert_eq!(message.sender(), "ECM");
526        assert_eq!(message.signals().len(), 1);
527    }
528
529    #[test]
530    #[cfg(feature = "alloc")]
531    fn test_message_new_empty_name() {
532        let signal = SignalBuilder::new()
533            .name("RPM")
534            .start_bit(0)
535            .length(16)
536            .byte_order(ByteOrder::BigEndian)
537            .unsigned(true)
538            .factor(1.0)
539            .offset(0.0)
540            .min(0.0)
541            .max(100.0)
542            .receivers(Receivers::None)
543            .build()
544            .unwrap();
545
546        let result = MessageBuilder::new()
547            .id(256)
548            .name("")
549            .dlc(8)
550            .sender("ECM")
551            .add_signal(signal)
552            .build();
553        assert!(result.is_err());
554        match result.unwrap_err() {
555            Error::Dbc(msg) => {
556                assert!(msg.contains(lang::MESSAGE_NAME_EMPTY));
557            }
558            _ => panic!("Expected Error::Dbc"),
559        }
560    }
561
562    #[test]
563    #[cfg(feature = "alloc")]
564    fn test_message_new_empty_sender() {
565        let signal = SignalBuilder::new()
566            .name("RPM")
567            .start_bit(0)
568            .length(16)
569            .byte_order(ByteOrder::BigEndian)
570            .unsigned(true)
571            .factor(1.0)
572            .offset(0.0)
573            .min(0.0)
574            .max(100.0)
575            .receivers(Receivers::None)
576            .build()
577            .unwrap();
578
579        let result = MessageBuilder::new()
580            .id(256)
581            .name("EngineData")
582            .dlc(8)
583            .sender("")
584            .add_signal(signal)
585            .build();
586        assert!(result.is_err());
587        match result.unwrap_err() {
588            Error::Dbc(msg) => {
589                assert!(msg.contains(lang::MESSAGE_SENDER_EMPTY));
590            }
591            _ => panic!("Expected Error::Dbc"),
592        }
593    }
594
595    #[test]
596    #[cfg(feature = "alloc")]
597    fn test_message_new_dlc_too_large() {
598        let signal = SignalBuilder::new()
599            .name("RPM")
600            .start_bit(0)
601            .length(16)
602            .byte_order(ByteOrder::BigEndian)
603            .unsigned(true)
604            .factor(1.0)
605            .offset(0.0)
606            .min(0.0)
607            .max(100.0)
608            .receivers(Receivers::None)
609            .build()
610            .unwrap();
611
612        let result = MessageBuilder::new()
613            .id(256)
614            .name("EngineData")
615            .dlc(65) // DLC > 64 is invalid
616            .sender("ECM")
617            .add_signal(signal)
618            .build();
619        assert!(result.is_err());
620        match result.unwrap_err() {
621            Error::Dbc(msg) => {
622                // Check for either the old constant or the new formatted message
623                assert!(
624                    msg.contains(lang::MESSAGE_DLC_TOO_LARGE)
625                        || msg.contains("Message 'EngineData'")
626                        || msg.contains("DLC 65")
627                );
628            }
629            _ => panic!("Expected Error::Dbc"),
630        }
631    }
632
633    #[test]
634    #[cfg(feature = "alloc")]
635    fn test_message_new_dlc_zero() {
636        let signal = SignalBuilder::new()
637            .name("RPM")
638            .start_bit(0)
639            .length(16)
640            .byte_order(ByteOrder::BigEndian)
641            .unsigned(true)
642            .factor(1.0)
643            .offset(0.0)
644            .min(0.0)
645            .max(100.0)
646            .receivers(Receivers::None)
647            .build()
648            .unwrap();
649
650        let result = MessageBuilder::new()
651            .id(256)
652            .name("EngineData")
653            .dlc(0) // DLC == 0 is invalid
654            .sender("ECM")
655            .add_signal(signal)
656            .build();
657        assert!(result.is_err());
658        match result.unwrap_err() {
659            Error::Dbc(msg) => {
660                // Check for either the old constant or the new formatted message
661                assert!(
662                    msg.contains(lang::MESSAGE_DLC_TOO_SMALL)
663                        || msg.contains("Message 'EngineData'")
664                        || msg.contains("DLC 0")
665                );
666            }
667            _ => panic!("Expected Error::Dbc"),
668        }
669    }
670
671    #[test]
672    #[cfg(feature = "alloc")]
673    fn test_message_new_id_out_of_range() {
674        let signal = SignalBuilder::new()
675            .name("RPM")
676            .start_bit(0)
677            .length(16)
678            .byte_order(ByteOrder::BigEndian)
679            .unsigned(true)
680            .factor(1.0)
681            .offset(0.0)
682            .min(0.0)
683            .max(100.0)
684            .receivers(Receivers::None)
685            .build()
686            .unwrap();
687
688        // Extended CAN ID max is 0x1FFFFFFF (536870911)
689        let result = MessageBuilder::new()
690            .id(0x2000_0000) // Exceeds max extended ID
691            .name("EngineData")
692            .dlc(8)
693            .sender("ECM")
694            .add_signal(signal)
695            .build();
696        assert!(result.is_err());
697        match result.unwrap_err() {
698            Error::Dbc(msg) => {
699                // Check for format template text (language-agnostic) - extract text before first placeholder
700                let template_text =
701                    lang::FORMAT_MESSAGE_ID_OUT_OF_RANGE.split("{}").next().unwrap();
702                assert!(msg.contains(template_text.trim_end()));
703            }
704            _ => panic!("Expected Error::Dbc"),
705        }
706    }
707
708    #[test]
709    #[cfg(feature = "alloc")]
710    fn test_message_new_signal_overlap() {
711        let signal1 = SignalBuilder::new()
712            .name("Signal1")
713            .start_bit(0)
714            .length(8)
715            .byte_order(ByteOrder::LittleEndian)
716            .unsigned(true)
717            .factor(1.0)
718            .offset(0.0)
719            .min(0.0)
720            .max(255.0)
721            .receivers(Receivers::None)
722            .build()
723            .unwrap();
724
725        let signal2 = SignalBuilder::new()
726            .name("Signal2")
727            .start_bit(4) // Overlaps with Signal1 (bits 0-7)
728            .length(8)
729            .byte_order(ByteOrder::LittleEndian)
730            .unsigned(true)
731            .factor(1.0)
732            .offset(0.0)
733            .min(0.0)
734            .max(255.0)
735            .receivers(Receivers::None)
736            .build()
737            .unwrap();
738
739        let result = MessageBuilder::new()
740            .id(256)
741            .name("EngineData")
742            .dlc(8)
743            .sender("ECM")
744            .add_signal(signal1)
745            .add_signal(signal2)
746            .build();
747        assert!(result.is_err());
748        match result.unwrap_err() {
749            Error::Dbc(msg) => {
750                // Check for format template text (language-agnostic) - extract text before first placeholder
751                let template_text = lang::FORMAT_SIGNAL_OVERLAP.split("{}").next().unwrap();
752                assert!(msg.contains(template_text.trim_end()));
753            }
754            _ => panic!("Expected Error::Dbc"),
755        }
756    }
757
758    #[test]
759    #[cfg(feature = "alloc")]
760    fn test_message_new_signal_extends_beyond_message() {
761        let signal = SignalBuilder::new()
762            .name("Signal1")
763            .start_bit(0)
764            .length(72) // Exceeds 8-byte DLC (64 bits)
765            .byte_order(ByteOrder::LittleEndian)
766            .unsigned(true)
767            .factor(1.0)
768            .offset(0.0)
769            .min(0.0)
770            .max(255.0)
771            .receivers(Receivers::None)
772            .build()
773            .unwrap();
774
775        let result = MessageBuilder::new()
776            .id(256)
777            .name("EngineData")
778            .dlc(8) // 8 bytes = 64 bits max
779            .sender("ECM")
780            .add_signal(signal)
781            .build();
782        assert!(result.is_err());
783        match result.unwrap_err() {
784            Error::Dbc(msg) => {
785                // Check for format template text (language-agnostic) - extract text before first placeholder
786                let template_text =
787                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
788                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
789            }
790            _ => panic!("Expected Error::Dbc"),
791        }
792    }
793
794    #[test]
795    #[cfg(feature = "alloc")]
796    fn test_message_new_too_many_signals() {
797        let mut builder = MessageBuilder::new().id(256).name("EngineData").dlc(8).sender("ECM");
798
799        // Add 65 signals (exceeds limit of 64)
800        for i in 0..65 {
801            // Create unique signal names to avoid conflicts
802            let sig_name = format!("Signal{i}");
803            builder = builder.add_signal(
804                SignalBuilder::new()
805                    .name(&sig_name)
806                    .start_bit(i as u16)
807                    .length(1)
808                    .byte_order(ByteOrder::LittleEndian)
809                    .unsigned(true)
810                    .factor(1.0)
811                    .offset(0.0)
812                    .min(0.0)
813                    .max(1.0)
814                    .receivers(Receivers::None)
815                    .build()
816                    .unwrap(),
817            );
818        }
819
820        let result = builder.build();
821        assert!(result.is_err());
822        match result.unwrap_err() {
823            Error::Dbc(msg) => {
824                assert!(msg.contains(lang::MESSAGE_TOO_MANY_SIGNALS));
825            }
826            _ => panic!("Expected Error::Dbc"),
827        }
828    }
829
830    #[test]
831    fn test_message_parse_valid() {
832        let data = b"BO_ 256 EngineData : 8 ECM";
833        let mut parser = Parser::new(data).unwrap();
834        const MAX_CAP: usize = crate::Signals::max_capacity();
835        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
836        let message = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
837        assert_eq!(message.id(), 256);
838        assert_eq!(message.name(), "EngineData");
839        assert_eq!(message.dlc(), 8);
840        assert_eq!(message.sender(), "ECM");
841        assert_eq!(message.signals().len(), 0);
842    }
843
844    #[test]
845    fn test_message_parse_invalid_id() {
846        let data = b"BO_ invalid EngineData : 8 ECM";
847        let mut parser = Parser::new(data).unwrap();
848        const MAX_CAP: usize = crate::Signals::max_capacity();
849        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
850        let result = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new());
851        assert!(result.is_err());
852        match result.unwrap_err() {
853            ParseError::Version(_) => {
854                // Expected
855            }
856            _ => panic!("Expected ParseError::Version"),
857        }
858    }
859
860    #[test]
861    fn test_message_parse_empty_name() {
862        let data = b"BO_ 256  : 8 ECM";
863        let mut parser = Parser::new(data).unwrap();
864        const MAX_CAP: usize = crate::Signals::max_capacity();
865        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
866        let result = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new());
867        assert!(result.is_err());
868        match result.unwrap_err() {
869            ParseError::Version(_) => {
870                // Expected
871            }
872            _ => panic!("Expected ParseError::Version"),
873        }
874    }
875
876    #[test]
877    fn test_message_parse_invalid_dlc() {
878        let data = b"BO_ 256 EngineData : invalid ECM";
879        let mut parser = Parser::new(data).unwrap();
880        const MAX_CAP: usize = crate::Signals::max_capacity();
881        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
882        let result = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new());
883        assert!(result.is_err());
884        match result.unwrap_err() {
885            ParseError::Version(_) => {
886                // Expected
887            }
888            _ => panic!("Expected ParseError::Version"),
889        }
890    }
891
892    #[test]
893    fn test_message_parse_empty_sender() {
894        let data = b"BO_ 256 EngineData : 8 ";
895        let mut parser = Parser::new(data).unwrap();
896        const MAX_CAP: usize = crate::Signals::max_capacity();
897        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
898        let result = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new());
899        assert!(result.is_err());
900        match result.unwrap_err() {
901            ParseError::Version(_) => {
902                // Expected
903            }
904            _ => panic!("Expected ParseError::Version"),
905        }
906    }
907
908    #[test]
909    fn test_message_parse_with_signals() {
910        let data = b"BO_ 256 EngineData : 8 ECM";
911        let mut parser = Parser::new(data).unwrap();
912
913        // Create test signals
914        let signal1 = Signal::parse(
915            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
916        )
917        .unwrap();
918        let signal2 = Signal::parse(
919            &mut Parser::new(b"SG_ Temp : 16|8@0- (1,-40) [-40|215] \"\xC2\xB0C\"").unwrap(),
920        )
921        .unwrap();
922
923        const MAX_CAP: usize = crate::Signals::max_capacity();
924        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
925        signals[0] = Some(signal1);
926        signals[1] = Some(signal2);
927
928        let message = Message::parse(&mut parser, &signals, 2, crate::ParseOptions::new()).unwrap();
929        assert_eq!(message.id(), 256);
930        assert_eq!(message.name(), "EngineData");
931        assert_eq!(message.dlc(), 8);
932        assert_eq!(message.sender(), "ECM");
933        assert_eq!(message.signals().len(), 2);
934    }
935
936    #[test]
937    fn test_message_signals_iterator() {
938        let data = b"BO_ 256 EngineData : 8 ECM";
939        let mut parser = Parser::new(data).unwrap();
940
941        // Create test signals
942        let signal1 = Signal::parse(
943            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
944        )
945        .unwrap();
946        let signal2 = Signal::parse(
947            &mut Parser::new(b"SG_ Temp : 16|8@0- (1,-40) [-40|215] \"\xC2\xB0C\"").unwrap(),
948        )
949        .unwrap();
950
951        const MAX_CAP: usize = crate::Signals::max_capacity();
952        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
953        signals[0] = Some(signal1);
954        signals[1] = Some(signal2);
955
956        let message = Message::parse(&mut parser, &signals, 2, crate::ParseOptions::new()).unwrap();
957        let mut signals_iter = message.signals().iter();
958        assert_eq!(signals_iter.next().unwrap().name(), "RPM");
959        assert_eq!(signals_iter.next().unwrap().name(), "Temp");
960        assert!(signals_iter.next().is_none());
961    }
962
963    #[test]
964    fn test_message_signal_count() {
965        let data = b"BO_ 256 EngineData : 8 ECM";
966        let mut parser = Parser::new(data).unwrap();
967
968        let signals: [Option<Signal>; crate::Signals::max_capacity()] =
969            [const { None }; crate::Signals::max_capacity()];
970        let message = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
971        assert_eq!(message.signals().len(), 0);
972
973        // Create a new parser for the second parse since the first one consumed the input
974        let data2 = b"BO_ 256 EngineData : 8 ECM";
975        let mut parser2 = Parser::new(data2).unwrap();
976        let signal1 = Signal::parse(
977            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
978        )
979        .unwrap();
980        const MAX_CAP: usize = crate::Signals::max_capacity();
981        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
982        signals[0] = Some(signal1);
983        let message =
984            Message::parse(&mut parser2, &signals, 1, crate::ParseOptions::new()).unwrap();
985        assert_eq!(message.signals().len(), 1);
986    }
987
988    #[test]
989    fn test_message_signal_at() {
990        let data = b"BO_ 256 EngineData : 8 ECM";
991        let mut parser = Parser::new(data).unwrap();
992
993        let signal1 = Signal::parse(
994            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
995        )
996        .unwrap();
997        let signal2 = Signal::parse(
998            &mut Parser::new(b"SG_ Temp : 16|8@0- (1,-40) [-40|215] \"\xC2\xB0C\"").unwrap(),
999        )
1000        .unwrap();
1001
1002        const MAX_CAP: usize = crate::Signals::max_capacity();
1003        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1004        signals[0] = Some(signal1);
1005        signals[1] = Some(signal2);
1006
1007        let message = Message::parse(&mut parser, &signals, 2, crate::ParseOptions::new()).unwrap();
1008        assert_eq!(message.signals().at(0).unwrap().name(), "RPM");
1009        assert_eq!(message.signals().at(1).unwrap().name(), "Temp");
1010        assert!(message.signals().at(2).is_none());
1011    }
1012
1013    #[test]
1014    fn test_message_find_signal() {
1015        let data = b"BO_ 256 EngineData : 8 ECM";
1016        let mut parser = Parser::new(data).unwrap();
1017
1018        let signal1 = Signal::parse(
1019            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
1020        )
1021        .unwrap();
1022        let signal2 = Signal::parse(
1023            &mut Parser::new(b"SG_ Temp : 16|8@0- (1,-40) [-40|215] \"\xC2\xB0C\"").unwrap(),
1024        )
1025        .unwrap();
1026
1027        const MAX_CAP: usize = crate::Signals::max_capacity();
1028        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1029        signals[0] = Some(signal1);
1030        signals[1] = Some(signal2);
1031
1032        let message = Message::parse(&mut parser, &signals, 2, crate::ParseOptions::new()).unwrap();
1033        assert_eq!(message.signals().find("RPM").unwrap().name(), "RPM");
1034        assert_eq!(message.signals().find("Temp").unwrap().name(), "Temp");
1035        assert!(message.signals().find("Nonexistent").is_none());
1036    }
1037
1038    #[test]
1039    #[cfg(feature = "alloc")]
1040    fn test_message_to_dbc_string() {
1041        let data = b"BO_ 256 EngineData : 8 ECM";
1042        let mut parser = Parser::new(data).unwrap();
1043        const MAX_CAP: usize = crate::Signals::max_capacity();
1044        let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1045        let message = Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
1046        let dbc_string = message.to_dbc_string();
1047        assert_eq!(dbc_string, "BO_ 256 EngineData : 8 ECM");
1048    }
1049
1050    #[test]
1051    #[cfg(feature = "alloc")]
1052    fn test_message_to_dbc_string_with_signals() {
1053        let data = b"BO_ 256 EngineData : 8 ECM";
1054        let mut parser = Parser::new(data).unwrap();
1055
1056        let signal1 = Signal::parse(
1057            &mut Parser::new(b"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\"").unwrap(),
1058        )
1059        .unwrap();
1060        let signal2 = Signal::parse(
1061            &mut Parser::new(b"SG_ Temp : 16|8@0- (1,-40) [-40|215] \"\xC2\xB0C\"").unwrap(),
1062        )
1063        .unwrap();
1064
1065        const MAX_CAP: usize = crate::Signals::max_capacity();
1066        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1067        signals[0] = Some(signal1);
1068        signals[1] = Some(signal2);
1069
1070        let message = Message::parse(&mut parser, &signals, 2, crate::ParseOptions::new()).unwrap();
1071        let dbc_string = message.to_dbc_string_with_signals();
1072        assert!(dbc_string.contains("BO_ 256 EngineData : 8 ECM"));
1073        assert!(dbc_string.contains("SG_ RPM"));
1074        assert!(dbc_string.contains("SG_ Temp"));
1075    }
1076
1077    #[test]
1078    fn test_message_can_2_0a_dlc_limits() {
1079        // CAN 2.0A: DLC can be 1-8 bytes (8-64 bits)
1080        // Test valid DLC values
1081        for dlc in 1..=8 {
1082            let data = format!("BO_ 256 EngineData : {} ECM", dlc);
1083            let mut parser = Parser::new(data.as_bytes()).unwrap();
1084            const MAX_CAP: usize = crate::Signals::max_capacity();
1085            let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1086            let message =
1087                Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
1088            assert_eq!(message.dlc(), dlc);
1089        }
1090    }
1091
1092    #[test]
1093    fn test_message_can_2_0b_dlc_limits() {
1094        // CAN 2.0B: DLC can be 1-8 bytes (8-64 bits)
1095        // Test valid DLC values
1096        for dlc in 1..=8 {
1097            let data = format!("BO_ 256 EngineData : {} ECM", dlc);
1098            let mut parser = Parser::new(data.as_bytes()).unwrap();
1099            const MAX_CAP: usize = crate::Signals::max_capacity();
1100            let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1101            let message =
1102                Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
1103            assert_eq!(message.dlc(), dlc);
1104        }
1105    }
1106
1107    #[test]
1108    fn test_message_can_fd_dlc_limits() {
1109        // CAN FD: DLC can be 1-64 bytes (8-512 bits)
1110        // Test valid DLC values up to 64
1111        for dlc in [1, 8, 12, 16, 20, 24, 32, 48, 64] {
1112            let data = format!("BO_ 256 EngineData : {} ECM", dlc);
1113            let mut parser = Parser::new(data.as_bytes()).unwrap();
1114            const MAX_CAP: usize = crate::Signals::max_capacity();
1115            let signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1116            let message =
1117                Message::parse(&mut parser, &signals, 0, crate::ParseOptions::new()).unwrap();
1118            assert_eq!(message.dlc(), dlc);
1119        }
1120    }
1121
1122    #[test]
1123    fn test_message_multiple_signals_boundary_validation() {
1124        // Test that signals at message boundaries are validated correctly
1125        let data = b"BO_ 256 EngineData : 8 ECM";
1126        let mut parser = Parser::new(data).unwrap();
1127
1128        // Create signals that exactly fill the message (8 bytes = 64 bits)
1129        // Signal 1: bits 0-15 (16 bits)
1130        let signal1 =
1131            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 0|16@0+ (1,0) [0|65535] \"\"").unwrap())
1132                .unwrap();
1133        // Signal 2: bits 16-31 (16 bits)
1134        let signal2 = Signal::parse(
1135            &mut Parser::new(b"SG_ Signal2 : 16|16@0+ (1,0) [0|65535] \"\"").unwrap(),
1136        )
1137        .unwrap();
1138        // Signal 3: bits 32-47 (16 bits)
1139        let signal3 = Signal::parse(
1140            &mut Parser::new(b"SG_ Signal3 : 32|16@0+ (1,0) [0|65535] \"\"").unwrap(),
1141        )
1142        .unwrap();
1143        // Signal 4: bits 48-63 (16 bits) - exactly at boundary
1144        let signal4 = Signal::parse(
1145            &mut Parser::new(b"SG_ Signal4 : 48|16@0+ (1,0) [0|65535] \"\"").unwrap(),
1146        )
1147        .unwrap();
1148
1149        const MAX_CAP: usize = crate::Signals::max_capacity();
1150        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1151        signals[0] = Some(signal1);
1152        signals[1] = Some(signal2);
1153        signals[2] = Some(signal3);
1154        signals[3] = Some(signal4);
1155
1156        let message = Message::parse(&mut parser, &signals, 4, crate::ParseOptions::new()).unwrap();
1157        assert_eq!(message.signals().len(), 4);
1158    }
1159
1160    #[test]
1161    fn test_message_big_endian_bit_range_calculation() {
1162        // Test big-endian bit range calculation
1163        // BE bit 0 -> physical bit 7
1164        // BE bit 7 -> physical bit 0
1165        // BE bit 8 -> physical bit 15
1166        // BE bit 15 -> physical bit 8
1167        let data = b"BO_ 256 EngineData : 8 ECM";
1168        let mut parser = Parser::new(data).unwrap();
1169
1170        // Signal starting at BE bit 0, length 8 -> should map to physical bits 0-7
1171        let signal =
1172            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 0|8@1+ (1,0) [0|255] \"\"").unwrap())
1173                .unwrap();
1174
1175        const MAX_CAP: usize = crate::Signals::max_capacity();
1176        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1177        signals[0] = Some(signal);
1178
1179        let message = Message::parse(&mut parser, &signals, 1, crate::ParseOptions::new()).unwrap();
1180        // The signal should be valid and fit within the message
1181        assert_eq!(message.signals().len(), 1);
1182    }
1183
1184    #[test]
1185    fn test_message_little_endian_bit_range_calculation() {
1186        // Test little-endian bit range calculation
1187        // LE bit N -> physical bit N (straightforward mapping)
1188        let data = b"BO_ 256 EngineData : 8 ECM";
1189        let mut parser = Parser::new(data).unwrap();
1190
1191        // Signal starting at LE bit 0, length 8 -> should map to physical bits 0-7
1192        let signal =
1193            Signal::parse(&mut Parser::new(b"SG_ Signal1 : 0|8@0+ (1,0) [0|255] \"\"").unwrap())
1194                .unwrap();
1195
1196        const MAX_CAP: usize = crate::Signals::max_capacity();
1197        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1198        signals[0] = Some(signal);
1199
1200        let message = Message::parse(&mut parser, &signals, 1, crate::ParseOptions::new()).unwrap();
1201        // The signal should be valid and fit within the message
1202        assert_eq!(message.signals().len(), 1);
1203    }
1204
1205    #[test]
1206    #[cfg(feature = "alloc")]
1207    fn test_message_signal_overlap_big_endian() {
1208        // Test signal overlap detection with big-endian signals
1209        let signal1 = SignalBuilder::new()
1210            .name("Signal1")
1211            .start_bit(0) // BE bit 0 -> physical bit 7, extends to physical bit 0
1212            .length(8)
1213            .byte_order(ByteOrder::BigEndian)
1214            .unsigned(true)
1215            .factor(1.0)
1216            .offset(0.0)
1217            .min(0.0)
1218            .max(255.0)
1219            .receivers(Receivers::None)
1220            .build()
1221            .unwrap();
1222
1223        let signal2 = SignalBuilder::new()
1224            .name("Signal2")
1225            .start_bit(4) // BE bit 4 -> overlaps with Signal1
1226            .length(8)
1227            .byte_order(ByteOrder::BigEndian)
1228            .unsigned(true)
1229            .factor(1.0)
1230            .offset(0.0)
1231            .min(0.0)
1232            .max(255.0)
1233            .receivers(Receivers::None)
1234            .build()
1235            .unwrap();
1236
1237        let result = MessageBuilder::new()
1238            .id(256)
1239            .name("EngineData")
1240            .dlc(8)
1241            .sender("ECM")
1242            .add_signal(signal1)
1243            .add_signal(signal2)
1244            .build();
1245        assert!(result.is_err());
1246        match result.unwrap_err() {
1247            Error::Dbc(msg) => {
1248                // Check for format template text (language-agnostic) - extract text before first placeholder
1249                let template_text = lang::FORMAT_SIGNAL_OVERLAP.split("{}").next().unwrap();
1250                assert!(msg.contains(template_text.trim_end()));
1251            }
1252            _ => panic!("Expected Error::Dbc"),
1253        }
1254    }
1255
1256    #[test]
1257    #[cfg(feature = "alloc")]
1258    fn test_message_signal_extends_beyond_message_big_endian() {
1259        // Test that big-endian signals extending beyond message boundary are detected
1260        let signal = SignalBuilder::new()
1261            .name("Signal1")
1262            .start_bit(56) // BE bit 56 -> near end of 8-byte message
1263            .length(16) // Extends beyond 64-bit boundary
1264            .byte_order(ByteOrder::BigEndian)
1265            .unsigned(true)
1266            .factor(1.0)
1267            .offset(0.0)
1268            .min(0.0)
1269            .max(65535.0)
1270            .receivers(Receivers::None)
1271            .build()
1272            .unwrap();
1273
1274        let result = MessageBuilder::new()
1275            .id(256)
1276            .name("EngineData")
1277            .dlc(8) // 8 bytes = 64 bits max
1278            .sender("ECM")
1279            .add_signal(signal)
1280            .build();
1281        assert!(result.is_err());
1282        match result.unwrap_err() {
1283            Error::Dbc(msg) => {
1284                // Check for format template text (language-agnostic) - extract text before first placeholder
1285                let template_text =
1286                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
1287                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
1288            }
1289            _ => panic!("Expected Error::Dbc"),
1290        }
1291    }
1292
1293    #[test]
1294    fn test_parse_with_lenient_boundary_check() {
1295        // Test that lenient mode allows signals that extend beyond message boundaries
1296        let data = b"BO_ 256 Test : 8 ECM";
1297        let mut parser = Parser::new(data).unwrap();
1298
1299        // Signal that extends beyond 8-byte boundary (start_bit 63, length 8 = bits 63-70, exceeds 64 bits)
1300        let signal =
1301            Signal::parse(&mut Parser::new(b"SG_ CHECKSUM : 63|8@1+ (1,0) [0|255] \"\"").unwrap())
1302                .unwrap();
1303
1304        const MAX_CAP: usize = crate::Signals::max_capacity();
1305        let mut signals: [Option<Signal>; MAX_CAP] = [const { None }; MAX_CAP];
1306        signals[0] = Some(signal);
1307
1308        // Strict mode should fail
1309        let result = Message::parse(&mut parser, &signals, 1, crate::ParseOptions::new());
1310        assert!(result.is_err());
1311
1312        // Lenient mode should succeed
1313        let mut parser2 = Parser::new(data).unwrap();
1314        let message =
1315            Message::parse(&mut parser2, &signals, 1, crate::ParseOptions::lenient()).unwrap();
1316        assert_eq!(message.signals().len(), 1);
1317        assert_eq!(message.signals().at(0).unwrap().name(), "CHECKSUM");
1318    }
1319}