dbc_rs/signal/
signal.rs

1use crate::{
2    ByteOrder, Parser, Receivers,
3    error::{ParseError, ParseResult},
4};
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct Signal<'a> {
8    name: &'a str,
9    start_bit: u16,
10    length: u16,
11    byte_order: ByteOrder,
12    unsigned: bool,
13    factor: f64,
14    offset: f64,
15    min: f64,
16    max: f64,
17    unit: Option<&'a str>,
18    receivers: Receivers<'a>,
19}
20
21impl<'a> Signal<'a> {
22    pub(crate) fn validate(name: &str, length: u16, min: f64, max: f64) -> ParseResult<()> {
23        if name.trim().is_empty() {
24            return Err(ParseError::Version(crate::error::lang::SIGNAL_NAME_EMPTY));
25        }
26
27        // Validate length: must be between 1 and 512 bits
28        // - Classic CAN (2.0A/2.0B): DLC up to 8 bytes (64 bits)
29        // - CAN FD: DLC up to 64 bytes (512 bits)
30        // Signal length is validated against message DLC in Message::validate
31        // Note: name is parsed before this validation, so we can include it in error messages
32        if length == 0 {
33            #[cfg(feature = "alloc")]
34            {
35                use crate::error::messages;
36                let msg = messages::signal_length_too_small(name, length);
37                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
38            }
39            #[cfg(not(feature = "alloc"))]
40            {
41                return Err(ParseError::Version(
42                    crate::error::lang::SIGNAL_LENGTH_TOO_SMALL,
43                ));
44            }
45        }
46        if length > 512 {
47            #[cfg(feature = "alloc")]
48            {
49                use crate::error::messages;
50                let msg = messages::signal_length_too_large(name, length);
51                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
52            }
53            #[cfg(not(feature = "alloc"))]
54            {
55                return Err(ParseError::Version(
56                    crate::error::lang::SIGNAL_LENGTH_TOO_LARGE,
57                ));
58            }
59        }
60
61        // Note: start_bit validation (boundary checks and overlap detection) is done in
62        // Message::validate, not here, because:
63        // 1. The actual message size depends on DLC (1-64 bytes for CAN FD)
64        // 2. Overlap detection requires comparing multiple signals
65        // 3. This allows signals to be created independently and validated when added to a message
66
67        // Validate min <= max
68        if min > max {
69            // In no_std, we can't format the error message, so use a generic error
70            return Err(ParseError::Version(
71                crate::error::lang::FORMAT_INVALID_RANGE,
72            ));
73        }
74
75        Ok(())
76    }
77
78    #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
79    #[allow(dead_code)] // Only used by builders (std-only)
80    pub(crate) fn new(
81        name: &'a str,
82        start_bit: u16,
83        length: u16,
84        byte_order: ByteOrder,
85        unsigned: bool,
86        factor: f64,
87        offset: f64,
88        min: f64,
89        max: f64,
90        unit: Option<&'a str>,
91        receivers: Receivers<'a>,
92    ) -> Self {
93        // Validation should have been done prior (by builder or parse)
94        Self {
95            name,
96            start_bit,
97            length,
98            byte_order,
99            unsigned,
100            factor,
101            offset,
102            min,
103            max,
104            unit,
105            receivers,
106        }
107    }
108
109    fn parse_position<'b>(
110        parser: &mut Parser<'b>,
111        #[cfg_attr(not(feature = "alloc"), allow(unused_variables))] signal_name: &str,
112    ) -> ParseResult<(u16, u16, ByteOrder, bool)> {
113        // Parse start_bit
114        let start_bit = match parser.parse_u32() {
115            Ok(v) => v as u16,
116            Err(_) => {
117                #[cfg(feature = "alloc")]
118                {
119                    use crate::error::messages;
120                    let msg = messages::signal_start_bit_invalid(signal_name, 0);
121                    return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
122                }
123                #[cfg(not(feature = "alloc"))]
124                {
125                    return Err(ParseError::Version(
126                        crate::error::lang::SIGNAL_PARSE_INVALID_START_BIT,
127                    ));
128                }
129            }
130        };
131
132        // Validate start_bit range
133        if start_bit > 511 {
134            #[cfg(feature = "alloc")]
135            {
136                use crate::error::messages;
137                let msg = messages::signal_start_bit_invalid(signal_name, start_bit);
138                return Err(ParseError::Version(Box::leak(msg.into_boxed_str())));
139            }
140            #[cfg(not(feature = "alloc"))]
141            {
142                return Err(ParseError::Version(
143                    crate::error::lang::SIGNAL_PARSE_INVALID_START_BIT,
144                ));
145            }
146        }
147
148        // Expect pipe
149        parser.expect(b"|").map_err(|_| ParseError::Expected("Expected pipe"))?;
150
151        // Parse length
152        let length = parser
153            .parse_u32()
154            .map_err(|_| ParseError::Version(crate::error::lang::SIGNAL_PARSE_INVALID_LENGTH))?
155            as u16;
156
157        // Expect @
158        parser.expect(b"@").map_err(|_| ParseError::Expected("Expected @"))?;
159
160        // Parse byte order (0 or 1)
161        // Try to expect '0' or '1' directly
162        let bo_byte = if parser.expect(b"0").is_ok() {
163            b'0'
164        } else if parser.expect(b"1").is_ok() {
165            b'1'
166        } else {
167            return Err(ParseError::Expected("Expected byte order"));
168        };
169
170        let byte_order = match bo_byte {
171            b'0' => ByteOrder::BigEndian,    // 0 = Motorola (big-endian)
172            b'1' => ByteOrder::LittleEndian, // 1 = Intel (little-endian)
173            _ => return Err(ParseError::InvalidChar(bo_byte as char)),
174        };
175
176        // Parse sign (+ or -)
177        let sign_byte = if parser.expect(b"+").is_ok() {
178            b'+'
179        } else if parser.expect(b"-").is_ok() {
180            b'-'
181        } else {
182            return Err(ParseError::Expected("Expected sign (+ or -)"));
183        };
184
185        let unsigned = match sign_byte {
186            b'+' => true,
187            b'-' => false,
188            _ => return Err(ParseError::InvalidChar(sign_byte as char)),
189        };
190
191        Ok((start_bit, length, byte_order, unsigned))
192    }
193
194    fn parse_factor_offset<'b>(parser: &mut Parser<'b>) -> ParseResult<(f64, f64)> {
195        // Expect opening parenthesis
196        parser
197            .expect(b"(")
198            .map_err(|_| ParseError::Expected("Expected opening parenthesis"))?;
199
200        // Skip whitespace
201        parser.skip_newlines_and_spaces();
202
203        // Parse factor (may be empty, default to 0.0)
204        // parse_f64() stops at comma/paren without consuming them
205        // If parsing fails immediately (pos unchanged), we're at a delimiter (empty factor)
206        let pos_before = parser.pos();
207        let factor = match parser.parse_f64() {
208            Ok(val) => val,
209            Err(_) => {
210                // Check if position didn't change (we're at delimiter)
211                if parser.pos() == pos_before {
212                    0.0 // Empty factor
213                } else {
214                    // Position changed but parsing failed - invalid format
215                    return Err(ParseError::Version(
216                        crate::error::lang::SIGNAL_PARSE_INVALID_FACTOR,
217                    ));
218                }
219            }
220        };
221
222        // Expect comma
223        parser.expect(b",").map_err(|_| ParseError::Expected("Expected comma"))?;
224
225        // Skip whitespace
226        parser.skip_newlines_and_spaces();
227
228        // Parse offset (may be empty, default to 0.0)
229        let pos_before = parser.pos();
230        let offset = match parser.parse_f64() {
231            Ok(val) => val,
232            Err(_) => {
233                // Check if position didn't change (we're at closing paren)
234                if parser.pos() == pos_before {
235                    0.0 // Empty offset
236                } else {
237                    return Err(ParseError::Version(
238                        crate::error::lang::SIGNAL_PARSE_INVALID_OFFSET,
239                    ));
240                }
241            }
242        };
243
244        // Skip whitespace
245        parser.skip_newlines_and_spaces();
246
247        // Expect closing parenthesis
248        parser
249            .expect(b")")
250            .map_err(|_| ParseError::Expected("Expected closing parenthesis"))?;
251
252        Ok((factor, offset))
253    }
254
255    fn parse_range<'b>(parser: &mut Parser<'b>) -> ParseResult<(f64, f64)> {
256        // Expect opening bracket
257        parser
258            .expect(b"[")
259            .map_err(|_| ParseError::Expected("Expected opening bracket"))?;
260
261        // Skip whitespace
262        parser.skip_newlines_and_spaces();
263
264        // Parse min (may be empty, default to 0.0)
265        let pos_before = parser.pos();
266        let min = match parser.parse_f64() {
267            Ok(val) => val,
268            Err(_) => {
269                // Check if position didn't change (we're at pipe or closing bracket)
270                if parser.pos() == pos_before {
271                    0.0 // Empty min
272                } else {
273                    return Err(ParseError::Version(
274                        crate::error::lang::SIGNAL_PARSE_INVALID_MIN,
275                    ));
276                }
277            }
278        };
279
280        // Expect pipe
281        parser.expect(b"|").map_err(|_| ParseError::Expected("Expected pipe"))?;
282
283        // Skip whitespace
284        parser.skip_newlines_and_spaces();
285
286        // Parse max (may be empty, default to 0.0)
287        let pos_before = parser.pos();
288        let max = match parser.parse_f64() {
289            Ok(val) => val,
290            Err(_) => {
291                // Check if position didn't change (we're at closing bracket)
292                if parser.pos() == pos_before {
293                    0.0 // Empty max
294                } else {
295                    return Err(ParseError::Version(
296                        crate::error::lang::SIGNAL_PARSE_INVALID_MAX,
297                    ));
298                }
299            }
300        };
301
302        // Skip whitespace
303        parser.skip_newlines_and_spaces();
304
305        // Expect closing bracket
306        parser
307            .expect(b"]")
308            .map_err(|_| ParseError::Expected("Expected closing bracket"))?;
309
310        Ok((min, max))
311    }
312
313    fn parse_unit<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Option<&'a str>> {
314        const MAX_UNIT_LENGTH: u16 = 256;
315
316        // Expect opening quote
317        parser
318            .expect(b"\"")
319            .map_err(|_| ParseError::Expected("Expected opening quote"))?;
320
321        // Use take_until_quote to read the unit (allow any printable characters)
322        let unit_bytes = parser.take_until_quote(false, MAX_UNIT_LENGTH).map_err(|e| match e {
323            ParseError::MaxStrLength(_) => {
324                ParseError::Version(crate::error::lang::SIGNAL_PARSE_UNIT_TOO_LONG)
325            }
326            _ => ParseError::Expected("Expected closing quote"),
327        })?;
328
329        // Convert bytes to string slice
330        let unit_str = core::str::from_utf8(unit_bytes)
331            .map_err(|_| ParseError::Expected("Invalid UTF-8 in unit"))?;
332
333        let unit = if unit_str.is_empty() {
334            None
335        } else {
336            Some(unit_str)
337        };
338
339        Ok(unit)
340    }
341
342    pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
343        // When called from Dbc::parse, find_next_keyword already consumed "SG_" and advanced past it
344        // So the parser is now at the space after "SG_". We just need to skip that whitespace.
345        // But if "SG_" wasn't consumed (standalone call), we need to expect it first.
346        // Try to expect "SG_" - if it fails, we're already past it from find_next_keyword
347        let _ = parser.expect(crate::SG_.as_bytes()).ok(); // Ignore error if already past "SG_"
348
349        // Skip whitespace after "SG_"
350        parser.skip_newlines_and_spaces();
351
352        // Parse signal name (identifier)
353        let name = parser
354            .parse_identifier()
355            .map_err(|_| ParseError::Version(crate::error::lang::SIGNAL_NAME_EMPTY))?;
356
357        // Skip whitespace (optional before colon) - handle multiplexer indicator
358        // According to spec: multiplexer_indicator = ' ' | [m multiplexer_switch_value] [M]
359        // For now, we just skip whitespace and any potential multiplexer indicator
360        parser.skip_newlines_and_spaces();
361
362        // Skip potential multiplexer indicator (m followed by number, or M)
363        // For simplicity, skip any 'm' or 'M' followed by digits
364        if parser.expect(b"m").is_ok() || parser.expect(b"M").is_ok() {
365            // Skip any digits that follow
366            loop {
367                let _pos_before = parser.pos();
368                // Try to consume a digit
369                if parser.expect(b"0").is_ok()
370                    || parser.expect(b"1").is_ok()
371                    || parser.expect(b"2").is_ok()
372                    || parser.expect(b"3").is_ok()
373                    || parser.expect(b"4").is_ok()
374                    || parser.expect(b"5").is_ok()
375                    || parser.expect(b"6").is_ok()
376                    || parser.expect(b"7").is_ok()
377                    || parser.expect(b"8").is_ok()
378                    || parser.expect(b"9").is_ok()
379                {
380                    // Consumed a digit, continue
381                } else {
382                    // Not a digit, stop
383                    break;
384                }
385            }
386            // Skip whitespace after multiplexer indicator
387            parser.skip_newlines_and_spaces();
388        }
389
390        // Expect colon
391        parser.expect(b":").map_err(|_| ParseError::Expected("Expected colon"))?;
392
393        // Skip whitespace after colon
394        parser.skip_newlines_and_spaces();
395
396        // Parse position: start_bit|length@byteOrderSign
397        let (start_bit, length, byte_order, unsigned) = Self::parse_position(parser, name)?;
398
399        // Skip whitespace
400        parser.skip_newlines_and_spaces();
401
402        // Parse factor and offset: (factor,offset)
403        let (factor, offset) = Self::parse_factor_offset(parser)?;
404
405        // Skip whitespace
406        parser.skip_newlines_and_spaces();
407
408        // Parse range: [min|max]
409        let (min, max) = Self::parse_range(parser)?;
410
411        // Skip whitespace
412        parser.skip_newlines_and_spaces();
413
414        // Parse unit: "unit" or ""
415        let unit = Self::parse_unit(parser)?;
416
417        // Skip whitespace (but not newlines) before parsing receivers
418        // Newlines indicate end of signal line, so we need to preserve them for Receivers::parse
419        let _ = parser.skip_whitespace().ok(); // Ignore error if no whitespace
420
421        // Parse receivers (may be empty/None if at end of line)
422        let receivers = Receivers::parse(parser)?;
423
424        // Validate before construction
425        Self::validate(name, length, min, max)?;
426        // Construct directly (validation already done)
427        Ok(Self {
428            name,
429            start_bit,
430            length,
431            byte_order,
432            unsigned,
433            factor,
434            offset,
435            min,
436            max,
437            unit,
438            receivers,
439        })
440    }
441
442    #[inline]
443    #[must_use = "return value should be checked"]
444    pub fn name(&self) -> &str {
445        self.name
446    }
447
448    #[inline]
449    #[must_use]
450    pub fn start_bit(&self) -> u16 {
451        self.start_bit
452    }
453
454    #[inline]
455    #[must_use]
456    pub fn length(&self) -> u16 {
457        self.length
458    }
459
460    #[inline]
461    #[must_use]
462    pub fn byte_order(&self) -> ByteOrder {
463        self.byte_order
464    }
465
466    #[inline]
467    #[must_use]
468    pub fn is_unsigned(&self) -> bool {
469        self.unsigned
470    }
471
472    #[inline]
473    #[must_use]
474    pub fn factor(&self) -> f64 {
475        self.factor
476    }
477
478    #[inline]
479    #[must_use]
480    pub fn offset(&self) -> f64 {
481        self.offset
482    }
483
484    #[inline]
485    #[must_use]
486    pub fn min(&self) -> f64 {
487        self.min
488    }
489
490    #[inline]
491    #[must_use]
492    pub fn max(&self) -> f64 {
493        self.max
494    }
495
496    #[inline]
497    #[must_use]
498    pub fn unit(&self) -> Option<&'a str> {
499        self.unit
500    }
501
502    #[inline]
503    #[must_use]
504    pub fn receivers(&self) -> &Receivers<'a> {
505        &self.receivers
506    }
507
508    #[cfg(feature = "alloc")]
509    #[must_use]
510    pub fn to_dbc_string(&self) -> String {
511        let mut result = String::with_capacity(100); // Pre-allocate reasonable capacity
512
513        result.push_str(" SG_ ");
514        result.push_str(self.name());
515        result.push_str(" : ");
516        result.push_str(&self.start_bit().to_string());
517        result.push('|');
518        result.push_str(&self.length().to_string());
519        result.push('@');
520
521        // Byte order: 0 for BigEndian (Motorola), 1 for LittleEndian (Intel)
522        // Per Vector DBC spec v1.0.1: "Big endian is stored as '0', little endian is stored as '1'"
523        match self.byte_order() {
524            ByteOrder::BigEndian => result.push('0'), // @0 = Big Endian (Motorola)
525            ByteOrder::LittleEndian => result.push('1'), // @1 = Little Endian (Intel)
526        }
527
528        // Sign: + for unsigned, - for signed
529        if self.is_unsigned() {
530            result.push('+');
531        } else {
532            result.push('-');
533        }
534
535        // Factor and offset: (factor,offset)
536        result.push_str(" (");
537        result.push_str(&format!("{}", self.factor()));
538        result.push(',');
539        result.push_str(&format!("{}", self.offset()));
540        result.push(')');
541
542        // Min and max: [min|max]
543        result.push_str(" [");
544        result.push_str(&format!("{}", self.min()));
545        result.push('|');
546        result.push_str(&format!("{}", self.max()));
547        result.push(']');
548
549        // Unit: "unit" or ""
550        result.push(' ');
551        if let Some(unit) = self.unit() {
552            result.push('"');
553            result.push_str(unit);
554            result.push('"');
555        } else {
556            result.push_str("\"\"");
557        }
558
559        // Receivers: * for Broadcast, space-separated list for Nodes, or empty
560        match self.receivers() {
561            Receivers::Broadcast => {
562                result.push(' ');
563                result.push('*');
564            }
565            Receivers::Nodes(_, count) => {
566                if *count > 0 {
567                    result.push(' ');
568                    for (i, node) in self.receivers().iter().enumerate() {
569                        if i > 0 {
570                            result.push(' ');
571                        }
572                        result.push_str(node);
573                    }
574                }
575            }
576            Receivers::None => {
577                // No receivers specified - nothing to add
578            }
579        }
580
581        result
582    }
583}
584
585// Custom Eq implementation that handles f64 (treats NaN as equal to NaN)
586impl<'a> Eq for Signal<'a> {}
587
588// Custom Hash implementation that handles f64 (treats NaN consistently)
589impl<'a> core::hash::Hash for Signal<'a> {
590    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
591        self.name.hash(state);
592        self.start_bit.hash(state);
593        self.length.hash(state);
594        self.byte_order.hash(state);
595        self.unsigned.hash(state);
596        // Handle f64: convert to bits for hashing (NaN will have consistent representation)
597        self.factor.to_bits().hash(state);
598        self.offset.to_bits().hash(state);
599        self.min.to_bits().hash(state);
600        self.max.to_bits().hash(state);
601        self.unit.hash(state);
602        self.receivers.hash(state);
603    }
604}
605
606#[cfg(feature = "alloc")]
607impl<'a> core::fmt::Display for Signal<'a> {
608    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
609        write!(f, "{}", self.to_dbc_string())
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    #![allow(clippy::float_cmp)]
616    use super::*;
617    use crate::{
618        Error, Parser,
619        error::{ParseError, lang},
620    };
621    #[cfg(feature = "alloc")]
622    use crate::{MessageBuilder, ReceiversBuilder, SignalBuilder};
623
624    #[test]
625    #[cfg(feature = "alloc")]
626    fn test_signal_new_valid() {
627        let signal = 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(ReceiversBuilder::new().broadcast().build().unwrap())
639            .build()
640            .unwrap();
641        assert_eq!(signal.name(), "RPM");
642        assert_eq!(signal.start_bit(), 0);
643        assert_eq!(signal.length(), 16);
644        assert_eq!(signal.byte_order(), ByteOrder::BigEndian);
645        assert!(signal.is_unsigned());
646        assert_eq!(signal.factor(), 0.25);
647        assert_eq!(signal.offset(), 0.0);
648        assert_eq!(signal.min(), 0.0);
649        assert_eq!(signal.max(), 8000.0);
650        assert_eq!(signal.unit(), Some("rpm"));
651        assert_eq!(signal.receivers(), &Receivers::Broadcast);
652    }
653
654    #[test]
655    #[cfg(feature = "alloc")]
656    fn test_signal_new_empty_name() {
657        let result = SignalBuilder::new()
658            .name("")
659            .start_bit(0)
660            .length(16)
661            .byte_order(ByteOrder::BigEndian)
662            .unsigned(true)
663            .factor(1.0)
664            .offset(0.0)
665            .min(0.0)
666            .max(100.0)
667            .receivers(ReceiversBuilder::new().none().build().unwrap())
668            .build();
669        assert!(result.is_err());
670        match result.unwrap_err() {
671            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_NAME_EMPTY)),
672            _ => panic!("Expected Error::Signal"),
673        }
674    }
675
676    #[test]
677    #[cfg(feature = "alloc")]
678    fn test_signal_new_zero_length() {
679        let result = SignalBuilder::new()
680            .name("Test")
681            .start_bit(0)
682            .length(0)
683            .byte_order(ByteOrder::BigEndian)
684            .unsigned(true)
685            .factor(1.0)
686            .offset(0.0)
687            .min(0.0)
688            .max(100.0)
689            .receivers(ReceiversBuilder::new().none().build().unwrap())
690            .build();
691        assert!(result.is_err());
692        match result.unwrap_err() {
693            Error::Signal(msg) => {
694                // Check for either the old constant or the new formatted message
695                assert!(
696                    msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)
697                        || msg.contains("Signal 'Test'")
698                        || msg.contains("0 bits")
699                );
700            }
701            _ => panic!("Expected Error::Signal"),
702        }
703    }
704
705    #[test]
706    #[cfg(feature = "alloc")]
707    fn test_signal_new_length_too_large() {
708        // length > 512 should fail validation (CAN FD maximum is 512 bits)
709        let result = SignalBuilder::new()
710            .name("Test")
711            .start_bit(0)
712            .length(513)
713            .byte_order(ByteOrder::BigEndian)
714            .unsigned(true)
715            .factor(1.0)
716            .offset(0.0)
717            .min(0.0)
718            .max(100.0)
719            .receivers(ReceiversBuilder::new().none().build().unwrap())
720            .build();
721        assert!(result.is_err());
722        match result.unwrap_err() {
723            Error::Signal(msg) => {
724                // Check for either the old constant or the new formatted message
725                assert!(
726                    msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)
727                        || msg.contains("Signal 'Test'")
728                        || msg.contains("513")
729                );
730            }
731            _ => panic!("Expected Error::Signal"),
732        }
733    }
734
735    #[test]
736    #[cfg(feature = "alloc")]
737    fn test_signal_new_overflow() {
738        // Signal with start_bit + length > 64 should be created successfully
739        // (validation against message DLC happens in Message::validate)
740        // This signal would fit in a CAN FD message (64 bytes = 512 bits)
741        let signal = SignalBuilder::new()
742            .name("Test")
743            .start_bit(60)
744            .length(10) // 60 + 10 = 70, fits in CAN FD (512 bits)
745            .byte_order(ByteOrder::BigEndian)
746            .unsigned(true)
747            .factor(1.0)
748            .offset(0.0)
749            .min(0.0)
750            .max(100.0)
751            .receivers(ReceiversBuilder::new().none().build().unwrap())
752            .build()
753            .unwrap();
754
755        // But it should fail when added to a message with DLC < 9 bytes
756        let result = MessageBuilder::new()
757            .id(256)
758            .name("TestMessage")
759            .dlc(8)
760            .sender("ECM")
761            .add_signal(signal)
762            .build();
763        assert!(result.is_err());
764        match result.unwrap_err() {
765            Error::Dbc(msg) => {
766                // Check for format template text (language-agnostic) - extract text before first placeholder
767                let template_text =
768                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
769                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
770            }
771            _ => panic!("Expected Error::Dbc"),
772        }
773    }
774
775    #[test]
776    #[cfg(feature = "alloc")]
777    fn test_signal_new_invalid_range() {
778        let result = SignalBuilder::new()
779            .name("Test")
780            .start_bit(0)
781            .length(8)
782            .byte_order(ByteOrder::BigEndian)
783            .unsigned(true)
784            .factor(1.0)
785            .offset(0.0)
786            .min(100.0)
787            .max(50.0)
788            .receivers(ReceiversBuilder::new().none().build().unwrap())
789            .build();
790        assert!(result.is_err());
791        match result.unwrap_err() {
792            Error::Signal(msg) => {
793                // Check for format template text (language-agnostic) - extract text before first placeholder
794                let template_text = lang::FORMAT_INVALID_RANGE.split("{}").next().unwrap();
795                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
796            }
797            _ => panic!("Expected Error::Signal"),
798        }
799    }
800
801    #[test]
802    #[cfg(feature = "alloc")]
803    fn test_signal_new_max_boundary() {
804        // Test that 64 bits at position 0 is valid
805        let signal = SignalBuilder::new()
806            .name("FullMessage")
807            .start_bit(0)
808            .length(64)
809            .byte_order(ByteOrder::BigEndian)
810            .unsigned(true)
811            .factor(1.0)
812            .offset(0.0)
813            .min(0.0)
814            .max(100.0)
815            .receivers(ReceiversBuilder::new().none().build().unwrap())
816            .build()
817            .unwrap();
818        assert_eq!(signal.length(), 64);
819    }
820
821    #[test]
822    #[cfg(feature = "alloc")]
823    fn test_signal_new_with_receivers() {
824        let signal = SignalBuilder::new()
825            .name("TestSignal")
826            .start_bit(8)
827            .length(16)
828            .byte_order(ByteOrder::LittleEndian)
829            .unsigned(false)
830            .factor(0.1)
831            .offset(-40.0)
832            .min(-40.0)
833            .max(215.0)
834            .unit("°C")
835            .receivers(ReceiversBuilder::new().add_node("ECM").add_node("TCM").build().unwrap())
836            .build()
837            .unwrap();
838        assert_eq!(signal.name(), "TestSignal");
839        assert!(!signal.is_unsigned());
840        assert_eq!(signal.unit(), Some("°C"));
841        match signal.receivers() {
842            Receivers::Nodes(_, count) => assert_eq!(*count, 2),
843            _ => panic!("Expected Nodes variant"),
844        }
845    }
846
847    #[test]
848    fn test_parse_valid_signal() {
849        let line = r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
850        let mut parser = Parser::new(line.as_bytes()).unwrap();
851        let sig = Signal::parse(&mut parser).unwrap();
852        assert_eq!(sig.name(), "RPM");
853        assert_eq!(sig.start_bit(), 0);
854        assert_eq!(sig.length(), 16);
855        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
856        assert!(sig.is_unsigned());
857        assert_eq!(sig.factor(), 0.25);
858        assert_eq!(sig.offset(), 0.);
859        assert_eq!(sig.min(), 0.);
860        assert_eq!(sig.max(), 8000.);
861        assert_eq!(sig.unit(), Some("rpm"));
862        // Check receivers using iter_nodes
863        let nodes: Vec<&str> = sig.receivers().iter().collect();
864        assert_eq!(nodes.len(), 1);
865        assert_eq!(nodes[0], "TCM");
866    }
867
868    #[test]
869    fn test_parse_signal_with_empty_unit_and_broadcast() {
870        let line = r#"SG_ ABSActive : 16|1@0+ (1,0) [0|1] "" *"#;
871        let mut parser = Parser::new(line.as_bytes()).unwrap();
872        let sig = Signal::parse(&mut parser).unwrap();
873        assert_eq!(sig.name(), "ABSActive");
874        assert_eq!(sig.start_bit(), 16);
875        assert_eq!(sig.length(), 1);
876        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
877        assert!(sig.is_unsigned());
878        assert_eq!(sig.factor(), 1.);
879        assert_eq!(sig.offset(), 0.);
880        assert_eq!(sig.min(), 0.);
881        assert_eq!(sig.max(), 1.);
882        assert_eq!(sig.unit(), None);
883        assert_eq!(sig.receivers(), &Receivers::Broadcast);
884    }
885
886    #[test]
887    fn test_parse_signal_with_negative_offset_and_min() {
888        let line = r#"SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM BCM"#;
889        let mut parser = Parser::new(line.as_bytes()).unwrap();
890        let sig = Signal::parse(&mut parser).unwrap();
891        assert_eq!(sig.name(), "Temperature");
892        assert_eq!(sig.start_bit(), 16);
893        assert_eq!(sig.length(), 8);
894        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
895        assert!(!sig.is_unsigned());
896        assert_eq!(sig.factor(), 1.);
897        assert_eq!(sig.offset(), -40.);
898        assert_eq!(sig.min(), -40.);
899        assert_eq!(sig.max(), 215.);
900        assert_eq!(sig.unit(), Some("°C"));
901        // Check receivers using iter_nodes
902        let nodes: Vec<&str> = sig.receivers().iter().collect();
903        assert_eq!(nodes.len(), 2);
904        assert_eq!(nodes[0], "TCM");
905        assert_eq!(nodes[1], "BCM");
906    }
907
908    #[test]
909    fn test_parse_signal_with_percent_unit() {
910        let line = r#"SG_ ThrottlePosition : 24|8@0+ (0.392157,0) [0|100] "%" *"#;
911        let mut parser = Parser::new(line.as_bytes()).unwrap();
912        let sig = Signal::parse(&mut parser).unwrap();
913        assert_eq!(sig.name(), "ThrottlePosition");
914        assert_eq!(sig.start_bit(), 24);
915        assert_eq!(sig.length(), 8);
916        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
917        assert!(sig.is_unsigned());
918        assert_eq!(sig.factor(), 0.392_157);
919        assert_eq!(sig.offset(), 0.);
920        assert_eq!(sig.min(), 0.);
921        assert_eq!(sig.max(), 100.);
922        assert_eq!(sig.unit(), Some("%"));
923        assert_eq!(sig.receivers(), &Receivers::Broadcast);
924    }
925
926    #[test]
927    fn test_parse_signal_missing_factors_and_limits() {
928        // Should use default values where missing
929        let line = r#"SG_ Simple : 10|4@0+ ( , ) [ | ] "" *"#;
930        let mut parser = Parser::new(line.as_bytes()).unwrap();
931        let sig = Signal::parse(&mut parser).unwrap();
932        assert_eq!(sig.name(), "Simple");
933        assert_eq!(sig.start_bit(), 10);
934        assert_eq!(sig.length(), 4);
935        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
936        assert!(sig.is_unsigned());
937        assert_eq!(sig.factor(), 0.);
938        assert_eq!(sig.offset(), 0.);
939        assert_eq!(sig.min(), 0.);
940        assert_eq!(sig.max(), 0.);
941        assert_eq!(sig.unit(), None);
942        assert_eq!(sig.receivers(), &Receivers::Broadcast);
943    }
944
945    #[test]
946    fn test_parse_signal_missing_start_bit() {
947        let line = r#"SG_ RPM : |16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
948        let mut parser = Parser::new(line.as_bytes()).unwrap();
949        let err = Signal::parse(&mut parser).unwrap_err();
950        match err {
951            ParseError::Version(msg) => {
952                // Check for either the old constant or the new formatted message
953                assert!(
954                    msg.contains(lang::SIGNAL_PARSE_INVALID_START_BIT)
955                        || msg.contains("Signal 'RPM'")
956                );
957            }
958            _ => panic!("Expected ParseError"),
959        }
960    }
961
962    #[test]
963    fn test_parse_signal_invalid_range() {
964        // min > max should fail validation
965        let line = r#"SG_ Test : 0|8@0+ (1,0) [100|50] "unit" *"#;
966        let mut parser = Parser::new(line.as_bytes()).unwrap();
967        let err = Signal::parse(&mut parser).unwrap_err();
968        match err {
969            ParseError::Version(msg) => {
970                // Check for format template text (language-agnostic) - extract text before first placeholder
971                let template_text = lang::FORMAT_INVALID_RANGE.split("{}").next().unwrap();
972                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
973            }
974            e => panic!("Expected ParseError::Version, got: {:?}", e),
975        }
976    }
977
978    #[test]
979    #[cfg(feature = "alloc")]
980    fn test_parse_signal_overflow() {
981        // Signal with start_bit + length > 64 should parse successfully
982        // (validation against message DLC happens in Message::validate)
983        // This signal would fit in a CAN FD message (64 bytes = 512 bits)
984        let line = r#"SG_ Test : 60|10@0+ (1,0) [0|100] "unit" *"#;
985        let mut parser = Parser::new(line.as_bytes()).unwrap();
986        let signal = Signal::parse(&mut parser).unwrap();
987        assert_eq!(signal.start_bit(), 60);
988        assert_eq!(signal.length(), 10);
989
990        // But it should fail when added to a message with DLC < 9 bytes
991        let result = MessageBuilder::new()
992            .id(256)
993            .name("TestMessage")
994            .dlc(8)
995            .sender("ECM")
996            .add_signal(signal)
997            .build();
998        assert!(result.is_err());
999        match result.unwrap_err() {
1000            Error::Dbc(msg) => {
1001                // Check for format template text (language-agnostic) - extract text before first placeholder
1002                let template_text =
1003                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_MESSAGE.split("{}").next().unwrap();
1004                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
1005            }
1006            _ => panic!("Expected Error::Dbc"),
1007        }
1008    }
1009
1010    #[test]
1011    fn test_parse_signal_length_too_large() {
1012        // length > 512 should fail validation (CAN FD maximum is 512 bits)
1013        let line = r#"SG_ Test : 0|513@0+ (1,0) [0|100] "unit" *"#;
1014        let mut parser = Parser::new(line.as_bytes()).unwrap();
1015        let err = Signal::parse(&mut parser).unwrap_err();
1016        match err {
1017            ParseError::Version(msg) => {
1018                // Check for either the old constant or the new formatted message
1019                assert!(
1020                    msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)
1021                        || msg.contains("Signal 'Test'")
1022                        || msg.contains("513")
1023                );
1024            }
1025            e => panic!("Expected ParseError::Version, got: {:?}", e),
1026        }
1027    }
1028
1029    #[test]
1030    fn test_parse_signal_zero_length() {
1031        // length = 0 should fail validation
1032        let line = r#"SG_ Test : 0|0@0+ (1,0) [0|100] "unit" *"#;
1033        let mut parser = Parser::new(line.as_bytes()).unwrap();
1034        let err = Signal::parse(&mut parser).unwrap_err();
1035        match err {
1036            ParseError::Version(msg) => {
1037                // Check for either the old constant or the new formatted message
1038                assert!(
1039                    msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)
1040                        || msg.contains("Signal 'Test'")
1041                        || msg.contains("0 bits")
1042                );
1043            }
1044            e => panic!("Expected ParseError::Version, got: {:?}", e),
1045        }
1046    }
1047
1048    #[test]
1049    fn test_parse_signal_missing_length() {
1050        let line = r#"SG_ RPM : 0|@0+ (0.25,0) [0|8000] "rpm" TCM"#;
1051        let mut parser = Parser::new(line.as_bytes()).unwrap();
1052        let err = Signal::parse(&mut parser).unwrap_err();
1053        match err {
1054            ParseError::Version(msg) => assert!(msg.contains(lang::SIGNAL_PARSE_INVALID_LENGTH)),
1055            e => panic!("Expected ParseError::Version, got: {:?}", e),
1056        }
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "alloc")]
1061    fn test_signal_to_dbc_string() {
1062        // Test with Broadcast receiver
1063        let signal1 = SignalBuilder::new()
1064            .name("RPM")
1065            .start_bit(0)
1066            .length(16)
1067            .byte_order(ByteOrder::BigEndian)
1068            .unsigned(true)
1069            .factor(0.25)
1070            .offset(0.0)
1071            .min(0.0)
1072            .max(8000.0)
1073            .unit("rpm")
1074            .receivers(ReceiversBuilder::new().broadcast().build().unwrap())
1075            .build()
1076            .unwrap();
1077        assert_eq!(
1078            signal1.to_dbc_string(),
1079            " SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *"
1080        );
1081
1082        // Test with Nodes receiver
1083        let signal2 = SignalBuilder::new()
1084            .name("Temperature")
1085            .start_bit(16)
1086            .length(8)
1087            .byte_order(ByteOrder::LittleEndian)
1088            .unsigned(false)
1089            .factor(1.0)
1090            .offset(-40.0)
1091            .min(-40.0)
1092            .max(215.0)
1093            .unit("°C")
1094            .receivers(ReceiversBuilder::new().add_node("TCM").add_node("BCM").build().unwrap())
1095            .build()
1096            .unwrap();
1097        assert_eq!(
1098            signal2.to_dbc_string(),
1099            " SG_ Temperature : 16|8@1- (1,-40) [-40|215] \"°C\" TCM BCM"
1100        );
1101
1102        // Test with None receiver and empty unit
1103        let signal3 = SignalBuilder::new()
1104            .name("Flag")
1105            .start_bit(24)
1106            .length(1)
1107            .byte_order(ByteOrder::BigEndian)
1108            .unsigned(true)
1109            .factor(1.0)
1110            .offset(0.0)
1111            .min(0.0)
1112            .max(1.0)
1113            .receivers(ReceiversBuilder::new().none().build().unwrap())
1114            .build()
1115            .unwrap();
1116        assert_eq!(
1117            signal3.to_dbc_string(),
1118            " SG_ Flag : 24|1@0+ (1,0) [0|1] \"\""
1119        );
1120    }
1121
1122    // Note: Helper parsing functions (parse_name_and_prefix, parse_position, etc.) are now internal
1123    // and use the Parser directly. Their functionality is tested through Signal::parse tests above.
1124    // All tests for these helper methods have been removed as they are implementation details.
1125}