dbc_rs/
message.rs

1use crate::{Error, Signal, error::messages};
2use alloc::{boxed::Box, format, string::String, string::ToString, vec::Vec};
3
4/// Represents a CAN message within a DBC file.
5///
6/// A message contains a CAN ID, name, data length code (DLC), sender node,
7/// and a list of signals that make up the message payload.
8///
9/// # Examples
10///
11/// ```rust
12/// use dbc_rs::Message;
13///
14/// let message = Message::builder()
15///     .id(256)
16///     .name("EngineData")
17///     .dlc(8)
18///     .sender("ECM")
19///     .build()?;
20/// # Ok::<(), dbc_rs::Error>(())
21/// ```
22#[derive(Debug, Clone, PartialEq)]
23pub struct Message {
24    id: u32,
25    name: Box<str>,
26    dlc: u8,
27    sender: Box<str>,
28    signals: Vec<Signal>,
29}
30
31impl Message {
32    /// Validate message parameters
33    fn validate(
34        id: u32,
35        name: &str,
36        dlc: u8,
37        sender: &str,
38        signals: &[Signal],
39    ) -> Result<(), Error> {
40        if name.trim().is_empty() {
41            return Err(Error::Message(messages::MESSAGE_NAME_EMPTY.to_string()));
42        }
43
44        if sender.trim().is_empty() {
45            return Err(Error::Message(messages::MESSAGE_SENDER_EMPTY.to_string()));
46        }
47
48        // Validate DLC: must be between 1 and 8 bytes
49        if dlc == 0 {
50            return Err(Error::Message(messages::MESSAGE_DLC_TOO_SMALL.to_string()));
51        }
52        if dlc > 8 {
53            return Err(Error::Message(messages::MESSAGE_DLC_TOO_LARGE.to_string()));
54        }
55
56        // Validate CAN ID range
57        // Standard 11-bit: 0-0x7FF (0-2047)
58        // Extended 29-bit: 0x800-0x1FFFFFFF (2048-536870911)
59        // IDs > 0x1FFFFFFF are invalid
60        const MAX_STANDARD_ID: u32 = 0x7FF; // 2047
61        const MIN_EXTENDED_ID: u32 = 0x800; // 2048
62        const MAX_EXTENDED_ID: u32 = 0x1FFFFFFF; // 536870911
63
64        // Validate that ID is within valid CAN ID ranges
65        if id > MAX_EXTENDED_ID {
66            return Err(Error::Message(messages::message_id_out_of_range(id)));
67        }
68
69        // Explicit validation: standard IDs must be 0-0x7FF
70        // Extended IDs must be 0x800-0x1FFFFFFF
71        // This check ensures proper range validation for both types
72        if id <= MAX_STANDARD_ID {
73            // Valid standard 11-bit ID (0-2047) - explicitly validated
74        } else if (MIN_EXTENDED_ID..=MAX_EXTENDED_ID).contains(&id) {
75            // Valid extended 29-bit ID (2048-536870911) - explicitly validated
76        }
77
78        // Validate that all signals fit within the message size (DLC * 8 bits)
79        let max_bits = dlc as u16 * 8;
80        for signal in signals {
81            let end_bit = signal.start_bit() as u16 + signal.length() as u16;
82            if end_bit > max_bits {
83                return Err(Error::Message(messages::signal_extends_beyond_message(
84                    signal.name(),
85                    signal.start_bit(),
86                    signal.length(),
87                    end_bit,
88                    max_bits,
89                    dlc,
90                )));
91            }
92        }
93
94        // Validate signal overlap detection
95        // Check if any two signals overlap in the same message
96        // For simplicity, we check if their bit ranges overlap
97        // This works for both little-endian and big-endian signals
98        for (i, sig1) in signals.iter().enumerate() {
99            let sig1_start = sig1.start_bit() as u16;
100            let sig1_end = sig1_start + sig1.length() as u16;
101
102            for sig2 in signals.iter().skip(i + 1) {
103                let sig2_start = sig2.start_bit() as u16;
104                let sig2_end = sig2_start + sig2.length() as u16;
105
106                // Check if ranges overlap
107                // Two ranges overlap if: sig1_start < sig2_end && sig2_start < sig1_end
108                if sig1_start < sig2_end && sig2_start < sig1_end {
109                    return Err(Error::Message(messages::signal_overlap(
110                        sig1.name(),
111                        sig2.name(),
112                        name,
113                    )));
114                }
115            }
116        }
117
118        Ok(())
119    }
120
121    /// Create a new Message with the given parameters
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if:
126    /// - `name` is empty
127    /// - `sender` is empty
128    /// - `dlc` is 0 or greater than 8
129    /// - `id` is out of valid CAN ID range (standard 11-bit: 0-2047, extended 29-bit: 0-536870911)
130    /// - Any signal extends beyond the message boundary (DLC * 8 bits)
131    /// - Any signals overlap within the message
132    ///
133    /// This is an internal constructor. For public API usage, use [`Message::builder()`] instead.
134    pub(crate) fn new(
135        id: u32,
136        name: impl AsRef<str>,
137        dlc: u8,
138        sender: impl AsRef<str>,
139        signals: Vec<Signal>,
140    ) -> Result<Self, Error> {
141        let name_str = name.as_ref();
142        let sender_str = sender.as_ref();
143        Self::validate(id, name_str, dlc, sender_str, &signals)?;
144
145        Ok(Self {
146            id,
147            name: name_str.into(),
148            dlc,
149            sender: sender_str.into(),
150            signals,
151        })
152    }
153
154    /// Create a new builder for constructing a `Message`
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use dbc_rs::{Message, Signal, ByteOrder, Receivers};
160    ///
161    /// let signal = Signal::builder()
162    ///     .name("RPM")
163    ///     .start_bit(0)
164    ///     .length(16)
165    ///     .build()?;
166    ///
167    /// let message = Message::builder()
168    ///     .id(256)
169    ///     .name("EngineData")
170    ///     .dlc(8)
171    ///     .sender("ECM")
172    ///     .add_signal(signal)
173    ///     .build()?;
174    /// # Ok::<(), dbc_rs::Error>(())
175    /// ```
176    pub fn builder() -> MessageBuilder {
177        MessageBuilder::new()
178    }
179
180    pub(super) fn parse(message: &str, signals: Vec<Signal>) -> Result<Self, Error> {
181        let parts: Vec<&str> = message.split_whitespace().collect();
182        if parts.len() != 6 {
183            return Err(Error::Message(messages::MESSAGE_INVALID_FORMAT.to_string()));
184        }
185
186        let id = parts[1]
187            .parse()
188            .map_err(|_| Error::Message(messages::MESSAGE_INVALID_ID.to_string()))?;
189        let name = parts[2];
190        let dlc = parts[4]
191            .parse()
192            .map_err(|_| Error::Message(messages::MESSAGE_INVALID_DLC.to_string()))?;
193        let sender = parts[5];
194
195        // Validate the parsed message using the same validation as new()
196        Self::validate(id, name, dlc, sender, &signals)?;
197
198        Ok(Self {
199            id,
200            name: name.into(),
201            dlc,
202            sender: sender.into(),
203            signals,
204        })
205    }
206
207    /// Get the CAN message ID
208    #[inline]
209    pub fn id(&self) -> u32 {
210        self.id
211    }
212
213    /// Get the message name
214    #[inline]
215    pub fn name(&self) -> &str {
216        &self.name
217    }
218
219    /// Get the Data Length Code (DLC)
220    #[inline]
221    pub fn dlc(&self) -> u8 {
222        self.dlc
223    }
224
225    /// Get the sender node name
226    #[inline]
227    pub fn sender(&self) -> &str {
228        &self.sender
229    }
230
231    /// Get a read-only slice of signals in this message
232    #[inline]
233    pub fn signals(&self) -> &[Signal] {
234        &self.signals
235    }
236
237    /// Find a signal by name in this message
238    pub fn find_signal(&self, name: &str) -> Option<&Signal> {
239        self.signals.iter().find(|s| s.name() == name)
240    }
241
242    /// Format message in DBC file format (e.g., `BO_ 256 EngineData : 8 ECM`)
243    ///
244    /// This method formats the message header only. To include signals, use
245    /// `to_dbc_string_with_signals()`.
246    ///
247    /// Useful for debugging and visualization of the message in DBC format.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use dbc_rs::{Message, Signal, ByteOrder, Receivers};
253    ///
254    /// let signal = Signal::builder()
255    ///     .name("RPM")
256    ///     .start_bit(0)
257    ///     .length(16)
258    ///     .byte_order(ByteOrder::BigEndian)
259    ///     .unsigned(true)
260    ///     .factor(0.25)
261    ///     .offset(0.0)
262    ///     .min(0.0)
263    ///     .max(8000.0)
264    ///     .unit("rpm")
265    ///     .receivers(Receivers::Broadcast)
266    ///     .build()?;
267    ///
268    /// let message = Message::builder()
269    ///     .id(256)
270    ///     .name("EngineData")
271    ///     .dlc(8)
272    ///     .sender("ECM")
273    ///     .add_signal(signal)
274    ///     .build()?;
275    /// assert_eq!(message.to_dbc_string(), "BO_ 256 EngineData : 8 ECM");
276    /// # Ok::<(), dbc_rs::Error>(())
277    /// ```
278    pub fn to_dbc_string(&self) -> String {
279        use alloc::format;
280        format!(
281            "BO_ {} {} : {} {}",
282            self.id(),
283            self.name(),
284            self.dlc(),
285            self.sender()
286        )
287    }
288
289    /// Format message in DBC file format including all signals
290    ///
291    /// This method formats the message header followed by all its signals,
292    /// each on a new line. Useful for debugging and visualization.
293    ///
294    /// # Examples
295    ///
296    /// ```
297    /// use dbc_rs::{Message, Signal, ByteOrder, Receivers};
298    ///
299    /// let signal = Signal::builder()
300    ///     .name("RPM")
301    ///     .start_bit(0)
302    ///     .length(16)
303    ///     .byte_order(ByteOrder::BigEndian)
304    ///     .unsigned(true)
305    ///     .factor(0.25)
306    ///     .offset(0.0)
307    ///     .min(0.0)
308    ///     .max(8000.0)
309    ///     .unit("rpm")
310    ///     .receivers(Receivers::Broadcast)
311    ///     .build()?;
312    ///
313    /// let message = Message::builder()
314    ///     .id(256)
315    ///     .name("EngineData")
316    ///     .dlc(8)
317    ///     .sender("ECM")
318    ///     .add_signal(signal)
319    ///     .build()?;
320    /// let dbc_str = message.to_dbc_string_with_signals();
321    /// assert!(dbc_str.contains("BO_ 256 EngineData : 8 ECM"));
322    /// assert!(dbc_str.contains("SG_ RPM"));
323    /// # Ok::<(), dbc_rs::Error>(())
324    /// ```
325    pub fn to_dbc_string_with_signals(&self) -> String {
326        let mut result = String::with_capacity(200 + (self.signals.len() * 100));
327        result.push_str(&self.to_dbc_string());
328        result.push('\n');
329
330        for signal in &self.signals {
331            result.push_str(&signal.to_dbc_string());
332            result.push('\n');
333        }
334
335        result
336    }
337}
338
339/// Builder for constructing a `Message` with a fluent API
340///
341/// This builder provides a more ergonomic way to construct `Message` instances,
342/// especially when building messages with multiple signals.
343///
344/// # Examples
345///
346/// ```
347/// use dbc_rs::{Message, Signal, ByteOrder, Receivers};
348///
349/// // Message with no signals
350/// let message = Message::builder()
351///     .id(256)
352///     .name("EngineData")
353///     .dlc(8)
354///     .sender("ECM")
355///     .build()?;
356///
357/// // Message with multiple signals
358/// let signal1 = Signal::builder()
359///     .name("RPM")
360///     .start_bit(0)
361///     .length(16)
362///     .build()?;
363///
364/// let signal2 = Signal::builder()
365///     .name("Temperature")
366///     .start_bit(16)
367///     .length(8)
368///     .build()?;
369///
370/// let message = Message::builder()
371///     .id(256)
372///     .name("EngineData")
373///     .dlc(8)
374///     .sender("ECM")
375///     .add_signal(signal1)
376///     .add_signal(signal2)
377///     .build()?;
378/// # Ok::<(), dbc_rs::Error>(())
379/// ```
380#[derive(Debug, Clone)]
381pub struct MessageBuilder {
382    id: Option<u32>,
383    name: Option<Box<str>>,
384    dlc: Option<u8>,
385    sender: Option<Box<str>>,
386    signals: Vec<Signal>,
387}
388
389impl MessageBuilder {
390    fn new() -> Self {
391        Self {
392            id: None,
393            name: None,
394            dlc: None,
395            sender: None,
396            signals: Vec::new(),
397        }
398    }
399
400    /// Set the CAN message ID (required)
401    pub fn id(mut self, id: u32) -> Self {
402        self.id = Some(id);
403        self
404    }
405
406    /// Set the message name (required)
407    pub fn name(mut self, name: impl AsRef<str>) -> Self {
408        self.name = Some(name.as_ref().into());
409        self
410    }
411
412    /// Set the Data Length Code (DLC) (required)
413    pub fn dlc(mut self, dlc: u8) -> Self {
414        self.dlc = Some(dlc);
415        self
416    }
417
418    /// Set the sender node name (required)
419    pub fn sender(mut self, sender: impl AsRef<str>) -> Self {
420        self.sender = Some(sender.as_ref().into());
421        self
422    }
423
424    /// Add a signal to the message
425    pub fn add_signal(mut self, signal: Signal) -> Self {
426        self.signals.push(signal);
427        self
428    }
429
430    /// Add multiple signals to the message
431    pub fn add_signals(mut self, signals: impl IntoIterator<Item = Signal>) -> Self {
432        self.signals.extend(signals);
433        self
434    }
435
436    /// Set all signals at once (replaces any existing signals)
437    pub fn signals(mut self, signals: Vec<Signal>) -> Self {
438        self.signals = signals;
439        self
440    }
441
442    /// Clear all signals
443    pub fn clear_signals(mut self) -> Self {
444        self.signals.clear();
445        self
446    }
447
448    /// Validate the current builder state
449    ///
450    /// This method performs the same validation as `Message::validate()` but on the
451    /// builder's current state. Useful for checking validity before calling `build()`.
452    ///
453    /// # Errors
454    ///
455    /// Returns an error if:
456    /// - Required fields (`id`, `name`, `dlc`, `sender`) are missing
457    /// - Validation fails (same as `Message::validate()`)
458    pub fn validate(&self) -> Result<(), Error> {
459        let id = self
460            .id
461            .ok_or_else(|| Error::Message(messages::MESSAGE_ID_REQUIRED.to_string()))?;
462        let name = self
463            .name
464            .as_ref()
465            .ok_or_else(|| Error::Message(messages::MESSAGE_NAME_EMPTY.to_string()))?;
466        let dlc = self
467            .dlc
468            .ok_or_else(|| Error::Message(messages::MESSAGE_DLC_REQUIRED.to_string()))?;
469        let sender = self
470            .sender
471            .as_ref()
472            .ok_or_else(|| Error::Message(messages::MESSAGE_SENDER_EMPTY.to_string()))?;
473
474        Message::validate(id, name.as_ref(), dlc, sender.as_ref(), &self.signals)
475    }
476
477    /// Build the `Message` from the builder
478    ///
479    /// # Errors
480    ///
481    /// Returns an error if:
482    /// - Required fields (`id`, `name`, `dlc`, `sender`) are missing
483    /// - Validation fails (same validation logic as the internal constructor)
484    pub fn build(self) -> Result<Message, Error> {
485        let id = self
486            .id
487            .ok_or_else(|| Error::Message(messages::MESSAGE_ID_REQUIRED.to_string()))?;
488        let name = self
489            .name
490            .ok_or_else(|| Error::Message(messages::MESSAGE_NAME_EMPTY.to_string()))?;
491        let dlc = self
492            .dlc
493            .ok_or_else(|| Error::Message(messages::MESSAGE_DLC_REQUIRED.to_string()))?;
494        let sender = self
495            .sender
496            .ok_or_else(|| Error::Message(messages::MESSAGE_SENDER_EMPTY.to_string()))?;
497
498        Message::new(id, name.as_ref(), dlc, sender.as_ref(), self.signals)
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use crate::error::lang;
506    use crate::{ByteOrder, Receivers};
507
508    #[test]
509    fn test_message_new_valid() {
510        let signal = Signal::new(
511            "RPM",
512            0,
513            16,
514            ByteOrder::BigEndian,
515            true,
516            0.25,
517            0.0,
518            0.0,
519            8000.0,
520            Some("rpm" as &str),
521            Receivers::Broadcast,
522        )
523        .unwrap();
524
525        let message = Message::new(256, "EngineData", 8, "ECM", vec![signal]).unwrap();
526        assert_eq!(message.id(), 256);
527        assert_eq!(message.name(), "EngineData");
528        assert_eq!(message.dlc(), 8);
529        assert_eq!(message.sender(), "ECM");
530        assert_eq!(message.signals().len(), 1);
531    }
532
533    #[test]
534    fn test_message_new_empty_name() {
535        let signal = Signal::new(
536            "RPM",
537            0,
538            16,
539            ByteOrder::BigEndian,
540            true,
541            1.0,
542            0.0,
543            0.0,
544            100.0,
545            None::<&str>,
546            Receivers::None,
547        )
548        .unwrap();
549
550        let result = Message::new(256, "", 8, "ECM", vec![signal]);
551        assert!(result.is_err());
552        match result.unwrap_err() {
553            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_NAME_EMPTY)),
554            _ => panic!("Expected Signal error"),
555        }
556    }
557
558    #[test]
559    fn test_message_new_empty_sender() {
560        let signal = Signal::new(
561            "RPM",
562            0,
563            16,
564            ByteOrder::BigEndian,
565            true,
566            1.0,
567            0.0,
568            0.0,
569            100.0,
570            None::<&str>,
571            Receivers::None,
572        )
573        .unwrap();
574
575        let result = Message::new(256, "EngineData", 8, "", vec![signal]);
576        assert!(result.is_err());
577        match result.unwrap_err() {
578            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_SENDER_EMPTY)),
579            _ => panic!("Expected Signal error"),
580        }
581    }
582
583    #[test]
584    fn test_message_new_zero_dlc() {
585        let signal = Signal::new(
586            "RPM",
587            0,
588            16,
589            ByteOrder::BigEndian,
590            true,
591            1.0,
592            0.0,
593            0.0,
594            100.0,
595            None::<&str>,
596            Receivers::None,
597        )
598        .unwrap();
599
600        let result = Message::new(256, "EngineData", 0, "ECM", vec![signal]);
601        assert!(result.is_err());
602        match result.unwrap_err() {
603            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_DLC_TOO_SMALL)),
604            _ => panic!("Expected Signal error"),
605        }
606    }
607
608    #[test]
609    fn test_message_new_dlc_too_large() {
610        let signal = Signal::new(
611            "RPM",
612            0,
613            16,
614            ByteOrder::BigEndian,
615            true,
616            1.0,
617            0.0,
618            0.0,
619            100.0,
620            None::<&str>,
621            Receivers::None,
622        )
623        .unwrap();
624
625        let result = Message::new(256, "EngineData", 9, "ECM", vec![signal]);
626        assert!(result.is_err());
627        match result.unwrap_err() {
628            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_DLC_TOO_LARGE)),
629            _ => panic!("Expected Signal error"),
630        }
631    }
632
633    #[test]
634    fn test_message_new_signal_overflow() {
635        // Signal extends beyond DLC boundary
636        let signal = Signal::new(
637            "RPM",
638            0,
639            65, // This will fail Signal validation first
640            ByteOrder::BigEndian,
641            true,
642            1.0,
643            0.0,
644            0.0,
645            100.0,
646            None::<&str>,
647            Receivers::None,
648        );
649        assert!(signal.is_err()); // Signal validation catches this
650
651        // Test with a signal that fits Signal validation but exceeds message DLC
652        let signal = Signal::new(
653            "RPM",
654            0,
655            32, // 32 bits = 4 bytes, fits in 8-byte message
656            ByteOrder::BigEndian,
657            true,
658            1.0,
659            0.0,
660            0.0,
661            100.0,
662            None::<&str>,
663            Receivers::None,
664        )
665        .unwrap();
666
667        // Create message with DLC=2 (16 bits), but signal is 32 bits
668        let result = Message::new(256, "EngineData", 2, "ECM", vec![signal]);
669        assert!(result.is_err());
670        match result.unwrap_err() {
671            Error::Message(msg) => {
672                // Check for format template text (language-agnostic)
673                // Check for format template text (language-agnostic) - extract text before first placeholder
674                let template_text =
675                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
676                assert!(msg.contains(template_text.trim_end()));
677            }
678            _ => panic!("Expected Signal error"),
679        }
680    }
681
682    #[test]
683    fn test_message_new_multiple_signals() {
684        let signal1 = Signal::new(
685            "RPM",
686            0,
687            16,
688            ByteOrder::BigEndian,
689            true,
690            1.0,
691            0.0,
692            0.0,
693            100.0,
694            None::<&str>,
695            Receivers::None,
696        )
697        .unwrap();
698
699        let signal2 = Signal::new(
700            "Temperature",
701            16,
702            8,
703            ByteOrder::BigEndian,
704            true,
705            1.0,
706            0.0,
707            0.0,
708            100.0,
709            None::<&str>,
710            Receivers::None,
711        )
712        .unwrap();
713
714        let message = Message::new(256, "EngineData", 8, "ECM", vec![signal1, signal2]).unwrap();
715        assert_eq!(message.signals().len(), 2);
716    }
717
718    #[test]
719    fn test_message_parse_invalid_dlc() {
720        // Test that parse also validates DLC
721        let line = "BO_ 256 EngineData : 9 ECM";
722        let signals = vec![];
723        let result = Message::parse(line, signals);
724        assert!(result.is_err());
725        match result.unwrap_err() {
726            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_DLC_TOO_LARGE)),
727            _ => panic!("Expected Signal error"),
728        }
729    }
730
731    #[test]
732    fn test_message_parse_zero_dlc() {
733        // Test that parse also validates DLC
734        let line = "BO_ 256 EngineData : 0 ECM";
735        let signals = vec![];
736        let result = Message::parse(line, signals);
737        assert!(result.is_err());
738        match result.unwrap_err() {
739            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_DLC_TOO_SMALL)),
740            _ => panic!("Expected Signal error"),
741        }
742    }
743
744    #[test]
745    fn test_message_parse_signal_overflow() {
746        // Test that parse validates signals fit within message DLC
747        let signal = Signal::new(
748            "RPM",
749            0,
750            32, // 32 bits = 4 bytes
751            ByteOrder::BigEndian,
752            true,
753            1.0,
754            0.0,
755            0.0,
756            100.0,
757            None::<&str>,
758            Receivers::None,
759        )
760        .unwrap();
761
762        // Message with DLC=2 (16 bits), but signal is 32 bits
763        let line = "BO_ 256 EngineData : 2 ECM";
764        let result = Message::parse(line, vec![signal]);
765        assert!(result.is_err());
766        match result.unwrap_err() {
767            Error::Message(msg) => {
768                // Check for format template text (language-agnostic)
769                // Check for format template text (language-agnostic) - extract text before first placeholder
770                let template_text =
771                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
772                assert!(msg.contains(template_text.trim_end()));
773            }
774            _ => panic!("Expected Signal error"),
775        }
776    }
777
778    #[test]
779    fn test_message_parse_invalid_format() {
780        // Test parse with wrong number of parts
781        let line = "BO_ 256 EngineData : 8";
782        let result = Message::parse(line, vec![]);
783        assert!(result.is_err());
784        match result.unwrap_err() {
785            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_INVALID_FORMAT)),
786            _ => panic!("Expected Signal error"),
787        }
788    }
789
790    #[test]
791    fn test_message_parse_invalid_id() {
792        // Test parse with invalid message ID
793        let line = "BO_ abc EngineData : 8 ECM";
794        let result = Message::parse(line, vec![]);
795        assert!(result.is_err());
796        match result.unwrap_err() {
797            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_INVALID_ID)),
798            _ => panic!("Expected Signal error"),
799        }
800    }
801
802    #[test]
803    fn test_message_parse_invalid_dlc_string() {
804        // Test parse with invalid DLC (non-numeric)
805        let line = "BO_ 256 EngineData : abc ECM";
806        let result = Message::parse(line, vec![]);
807        assert!(result.is_err());
808        match result.unwrap_err() {
809            Error::Message(msg) => assert!(msg.contains(lang::MESSAGE_INVALID_DLC)),
810            _ => panic!("Expected Signal error"),
811        }
812    }
813
814    #[test]
815    fn test_message_to_dbc_string() {
816        let signal = Signal::new(
817            "RPM",
818            0,
819            16,
820            ByteOrder::BigEndian,
821            true,
822            0.25,
823            0.0,
824            0.0,
825            8000.0,
826            Some("rpm" as &str),
827            Receivers::Broadcast,
828        )
829        .unwrap();
830
831        let message = Message::new(256, "EngineData", 8, "ECM", vec![signal]).unwrap();
832        assert_eq!(message.to_dbc_string(), "BO_ 256 EngineData : 8 ECM");
833    }
834
835    #[test]
836    fn test_message_to_dbc_string_with_signals() {
837        let signal1 = Signal::new(
838            "RPM",
839            0,
840            16,
841            ByteOrder::BigEndian,
842            true,
843            0.25,
844            0.0,
845            0.0,
846            8000.0,
847            Some("rpm" as &str),
848            Receivers::Broadcast,
849        )
850        .unwrap();
851
852        let signal2 = Signal::new(
853            "Temperature",
854            16,
855            8,
856            ByteOrder::LittleEndian,
857            false,
858            1.0,
859            -40.0,
860            -40.0,
861            215.0,
862            Some("°C" as &str),
863            Receivers::None,
864        )
865        .unwrap();
866
867        let message = Message::new(256, "EngineData", 8, "ECM", vec![signal1, signal2]).unwrap();
868        let dbc_str = message.to_dbc_string_with_signals();
869
870        assert!(dbc_str.contains("BO_ 256 EngineData : 8 ECM"));
871        assert!(dbc_str.contains("SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\" *"));
872        assert!(dbc_str.contains("SG_ Temperature : 16|8@0- (1,-40) [-40|215] \"°C\""));
873    }
874
875    #[test]
876    fn test_message_id_out_of_range() {
877        let signal = Signal::new(
878            "RPM",
879            0,
880            16,
881            ByteOrder::BigEndian,
882            true,
883            1.0,
884            0.0,
885            0.0,
886            100.0,
887            None::<&str>,
888            Receivers::None,
889        )
890        .unwrap();
891
892        // Test ID that exceeds extended 29-bit range
893        let result = Message::new(0x20000000, "Test", 8, "ECM", vec![signal.clone()]);
894        assert!(result.is_err());
895        match result.unwrap_err() {
896            Error::Message(msg) => {
897                // Check for format template text (language-agnostic) - extract text before first placeholder
898                let template_text =
899                    lang::FORMAT_MESSAGE_ID_OUT_OF_RANGE.split("{}").next().unwrap();
900                assert!(msg.contains(template_text.trim_end()));
901            }
902            _ => panic!("Expected Signal error"),
903        }
904
905        // Test valid standard ID (11-bit) - minimum
906        let result = Message::new(0, "Test", 8, "ECM", vec![signal.clone()]);
907        assert!(result.is_ok());
908
909        // Test valid standard ID (11-bit) - maximum
910        let result = Message::new(0x7FF, "Test", 8, "ECM", vec![signal.clone()]);
911        assert!(result.is_ok());
912
913        // Test valid extended ID (29-bit) - minimum
914        let result = Message::new(0x800, "Test", 8, "ECM", vec![signal.clone()]);
915        assert!(result.is_ok());
916
917        // Test valid extended ID (29-bit) - maximum
918        let result = Message::new(0x1FFFFFFF, "Test", 8, "ECM", vec![signal]);
919        assert!(result.is_ok());
920    }
921
922    #[test]
923    fn test_signal_overlap() {
924        // Two signals that overlap: signal1 at 0-15, signal2 at 8-23
925        let signal1 = Signal::new(
926            "Signal1",
927            0,
928            16,
929            ByteOrder::BigEndian,
930            true,
931            1.0,
932            0.0,
933            0.0,
934            100.0,
935            None::<&str>,
936            Receivers::None,
937        )
938        .unwrap();
939
940        let signal2 = Signal::new(
941            "Signal2",
942            8,
943            16,
944            ByteOrder::BigEndian,
945            true,
946            1.0,
947            0.0,
948            0.0,
949            100.0,
950            None::<&str>,
951            Receivers::None,
952        )
953        .unwrap();
954
955        let result = Message::new(256, "TestMessage", 8, "ECM", vec![signal1, signal2]);
956        assert!(result.is_err());
957        match result.unwrap_err() {
958            Error::Message(msg) => {
959                // Check for format template text and signal names (language-agnostic)
960                // Check for format template text (language-agnostic) - extract text before first placeholder
961                let template_text = lang::FORMAT_SIGNAL_OVERLAP.split("{}").next().unwrap();
962                assert!(msg.contains(template_text.trim_end()));
963                assert!(msg.contains("Signal1"));
964                assert!(msg.contains("Signal2"));
965                assert!(msg.contains("TestMessage"));
966            }
967            _ => panic!("Expected Signal error"),
968        }
969    }
970
971    #[test]
972    fn test_signal_no_overlap() {
973        // Two signals that don't overlap: signal1 at 0-15, signal2 at 16-31
974        let signal1 = Signal::new(
975            "Signal1",
976            0,
977            16,
978            ByteOrder::BigEndian,
979            true,
980            1.0,
981            0.0,
982            0.0,
983            100.0,
984            None::<&str>,
985            Receivers::None,
986        )
987        .unwrap();
988
989        let signal2 = Signal::new(
990            "Signal2",
991            16,
992            16,
993            ByteOrder::BigEndian,
994            true,
995            1.0,
996            0.0,
997            0.0,
998            100.0,
999            None::<&str>,
1000            Receivers::None,
1001        )
1002        .unwrap();
1003
1004        let result = Message::new(256, "TestMessage", 8, "ECM", vec![signal1, signal2]);
1005        assert!(result.is_ok());
1006    }
1007
1008    #[test]
1009    fn test_signal_overlap_adjacent() {
1010        // Two signals that are adjacent but don't overlap: signal1 at 0-15, signal2 at 16-23
1011        let signal1 = Signal::new(
1012            "Signal1",
1013            0,
1014            16,
1015            ByteOrder::BigEndian,
1016            true,
1017            1.0,
1018            0.0,
1019            0.0,
1020            100.0,
1021            None::<&str>,
1022            Receivers::None,
1023        )
1024        .unwrap();
1025
1026        let signal2 = Signal::new(
1027            "Signal2",
1028            16,
1029            8,
1030            ByteOrder::BigEndian,
1031            true,
1032            1.0,
1033            0.0,
1034            0.0,
1035            100.0,
1036            None::<&str>,
1037            Receivers::None,
1038        )
1039        .unwrap();
1040
1041        let result = Message::new(256, "TestMessage", 8, "ECM", vec![signal1, signal2]);
1042        assert!(result.is_ok());
1043    }
1044
1045    #[test]
1046    fn test_signal_overlap_identical_position() {
1047        // Two signals at the exact same position (definitely overlap)
1048        let signal1 = Signal::new(
1049            "Signal1",
1050            0,
1051            8,
1052            ByteOrder::BigEndian,
1053            true,
1054            1.0,
1055            0.0,
1056            0.0,
1057            100.0,
1058            None::<&str>,
1059            Receivers::None,
1060        )
1061        .unwrap();
1062
1063        let signal2 = Signal::new(
1064            "Signal2",
1065            0,
1066            8,
1067            ByteOrder::BigEndian,
1068            true,
1069            1.0,
1070            0.0,
1071            0.0,
1072            100.0,
1073            None::<&str>,
1074            Receivers::None,
1075        )
1076        .unwrap();
1077
1078        let result = Message::new(256, "TestMessage", 8, "ECM", vec![signal1, signal2]);
1079        assert!(result.is_err());
1080        match result.unwrap_err() {
1081            Error::Message(msg) => {
1082                // Check for format template text (language-agnostic)
1083                // Check for format template text (language-agnostic) - extract text before first placeholder
1084                let template_text = lang::FORMAT_SIGNAL_OVERLAP.split("{}").next().unwrap();
1085                assert!(msg.contains(template_text.trim_end()));
1086            }
1087            _ => panic!("Expected Signal error"),
1088        }
1089    }
1090}