dbc_rs/signal/
signal.rs

1use crate::{
2    ByteOrder, Error, MAX_NAME_SIZE, Parser, Receivers, Result, compat::String, error::lang,
3};
4
5#[derive(Debug, Clone, PartialEq)]
6pub struct Signal {
7    name: String<{ MAX_NAME_SIZE }>,
8    start_bit: u16,
9    length: u16,
10    byte_order: ByteOrder,
11    unsigned: bool,
12    factor: f64,
13    offset: f64,
14    min: f64,
15    max: f64,
16    unit: Option<String<{ MAX_NAME_SIZE }>>,
17    receivers: Receivers,
18}
19
20impl Signal {
21    pub(crate) fn validate(name: &str, length: u16, min: f64, max: f64) -> Result<()> {
22        if name.trim().is_empty() {
23            return Err(Error::Validation(lang::SIGNAL_NAME_EMPTY));
24        }
25
26        // Validate length: must be between 1 and 512 bits
27        // - Classic CAN (2.0A/2.0B): DLC up to 8 bytes (64 bits)
28        // - CAN FD: DLC up to 64 bytes (512 bits)
29        // Signal length is validated against message DLC in Message::validate
30        // Note: name is parsed before this validation, so we can include it in error messages
31        if length == 0 {
32            return Err(Error::Validation(lang::SIGNAL_LENGTH_TOO_SMALL));
33        }
34        if length > 512 {
35            return Err(Error::Validation(lang::SIGNAL_LENGTH_TOO_LARGE));
36        }
37
38        // Note: start_bit validation (boundary checks and overlap detection) is done in
39        // Message::validate, not here, because:
40        // 1. The actual message size depends on DLC (1-64 bytes for CAN FD)
41        // 2. Overlap detection requires comparing multiple signals
42        // 3. This allows signals to be created independently and validated when added to a message
43
44        // Validate min <= max
45        if min > max {
46            return Err(Error::Validation(lang::INVALID_RANGE));
47        }
48
49        Ok(())
50    }
51
52    #[cfg(feature = "std")]
53    #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
54    pub(crate) fn new(
55        name: String<{ MAX_NAME_SIZE }>,
56        start_bit: u16,
57        length: u16,
58        byte_order: ByteOrder,
59        unsigned: bool,
60        factor: f64,
61        offset: f64,
62        min: f64,
63        max: f64,
64        unit: Option<String<{ MAX_NAME_SIZE }>>,
65        receivers: Receivers,
66    ) -> Self {
67        // Validation should have been done prior (by builder or parse)
68        Self {
69            name,
70            start_bit,
71            length,
72            byte_order,
73            unsigned,
74            factor,
75            offset,
76            min,
77            max,
78            unit,
79            receivers,
80        }
81    }
82
83    fn parse_position<'b>(parser: &mut Parser<'b>) -> Result<(u16, u16, ByteOrder, bool)> {
84        // Parse start_bit
85        let start_bit = match parser.parse_u32() {
86            Ok(v) => v as u16,
87            Err(_) => {
88                return Err(Error::Signal(lang::SIGNAL_PARSE_INVALID_START_BIT));
89            }
90        };
91
92        // Validate start_bit range
93        if start_bit > 511 {
94            return Err(Error::Signal(lang::SIGNAL_PARSE_INVALID_START_BIT));
95        }
96
97        // Expect pipe
98        parser.expect(b"|").map_err(|_| Error::Expected("Expected pipe"))?;
99
100        // Parse length
101        let length = parser
102            .parse_u32()
103            .map_err(|_| Error::Signal(lang::SIGNAL_PARSE_INVALID_LENGTH))?
104            as u16;
105
106        // Expect @
107        parser.expect(b"@").map_err(|_| Error::Expected("Expected @"))?;
108
109        // Parse byte order (0 or 1)
110        // Try to expect '0' or '1' directly
111        let bo_byte = if parser.expect(b"0").is_ok() {
112            b'0'
113        } else if parser.expect(b"1").is_ok() {
114            b'1'
115        } else {
116            return Err(Error::Expected("Expected byte order"));
117        };
118
119        let byte_order = match bo_byte {
120            b'0' => ByteOrder::BigEndian,    // 0 = Motorola (big-endian)
121            b'1' => ByteOrder::LittleEndian, // 1 = Intel (little-endian)
122            _ => return Err(Error::InvalidChar(bo_byte as char)),
123        };
124
125        // Parse sign (+ or -)
126        let sign_byte = if parser.expect(b"+").is_ok() {
127            b'+'
128        } else if parser.expect(b"-").is_ok() {
129            b'-'
130        } else {
131            return Err(Error::Expected("Expected sign (+ or -)"));
132        };
133
134        let unsigned = match sign_byte {
135            b'+' => true,
136            b'-' => false,
137            _ => return Err(Error::InvalidChar(sign_byte as char)),
138        };
139
140        Ok((start_bit, length, byte_order, unsigned))
141    }
142
143    fn parse_factor_offset<'b>(parser: &mut Parser<'b>) -> Result<(f64, f64)> {
144        // Expect opening parenthesis
145        parser
146            .expect(b"(")
147            .map_err(|_| Error::Expected("Expected opening parenthesis"))?;
148
149        // Skip whitespace
150        parser.skip_newlines_and_spaces();
151
152        // Parse factor (may be empty, default to 0.0)
153        // parse_f64() stops at comma/paren without consuming them
154        // If parsing fails immediately (pos unchanged), we're at a delimiter (empty factor)
155        let pos_before = parser.pos();
156        let factor = match parser.parse_f64() {
157            Ok(val) => val,
158            Err(_) => {
159                // Check if position didn't change (we're at delimiter)
160                if parser.pos() == pos_before {
161                    0.0 // Empty factor
162                } else {
163                    // Position changed but parsing failed - invalid format
164                    return Err(Error::Signal(lang::SIGNAL_PARSE_INVALID_FACTOR));
165                }
166            }
167        };
168
169        // Expect comma
170        parser.expect(b",").map_err(|_| Error::Expected("Expected comma"))?;
171
172        // Skip whitespace
173        parser.skip_newlines_and_spaces();
174
175        // Parse offset (may be empty, default to 0.0)
176        let pos_before = parser.pos();
177        let offset = match parser.parse_f64() {
178            Ok(val) => val,
179            Err(_) => {
180                // Check if position didn't change (we're at closing paren)
181                if parser.pos() == pos_before {
182                    0.0 // Empty offset
183                } else {
184                    return Err(Error::Signal(
185                        crate::error::lang::SIGNAL_PARSE_INVALID_OFFSET,
186                    ));
187                }
188            }
189        };
190
191        // Skip whitespace
192        parser.skip_newlines_and_spaces();
193
194        // Expect closing parenthesis
195        parser
196            .expect(b")")
197            .map_err(|_| Error::Expected("Expected closing parenthesis"))?;
198
199        Ok((factor, offset))
200    }
201
202    fn parse_range<'b>(parser: &mut Parser<'b>) -> Result<(f64, f64)> {
203        // Expect opening bracket
204        parser.expect(b"[").map_err(|_| Error::Expected("Expected opening bracket"))?;
205
206        // Skip whitespace
207        parser.skip_newlines_and_spaces();
208
209        // Parse min (may be empty, default to 0.0)
210        let pos_before = parser.pos();
211        let min = match parser.parse_f64() {
212            Ok(val) => val,
213            Err(_) => {
214                // Check if position didn't change (we're at pipe or closing bracket)
215                if parser.pos() == pos_before {
216                    0.0 // Empty min
217                } else {
218                    return Err(Error::Signal(crate::error::lang::SIGNAL_PARSE_INVALID_MIN));
219                }
220            }
221        };
222
223        // Expect pipe
224        parser.expect(b"|").map_err(|_| Error::Expected("Expected pipe"))?;
225
226        // Skip whitespace
227        parser.skip_newlines_and_spaces();
228
229        // Parse max (may be empty, default to 0.0)
230        let pos_before = parser.pos();
231        let max = match parser.parse_f64() {
232            Ok(val) => val,
233            Err(_) => {
234                // Check if position didn't change (we're at closing bracket)
235                if parser.pos() == pos_before {
236                    0.0 // Empty max
237                } else {
238                    return Err(Error::Signal(crate::error::lang::SIGNAL_PARSE_INVALID_MAX));
239                }
240            }
241        };
242
243        // Skip whitespace
244        parser.skip_newlines_and_spaces();
245
246        // Expect closing bracket
247        parser.expect(b"]").map_err(|_| Error::Expected("Expected closing bracket"))?;
248
249        Ok((min, max))
250    }
251
252    fn parse_unit(parser: &mut Parser) -> Result<Option<String<{ MAX_NAME_SIZE }>>> {
253        // Expect opening quote
254        parser.expect(b"\"").map_err(|_| Error::Expected("Expected opening quote"))?;
255
256        // Use take_until_quote to read the unit (allow any printable characters)
257        let unit_bytes = parser.take_until_quote(false, MAX_NAME_SIZE).map_err(|e| match e {
258            Error::MaxStrLength(_) => Error::Signal(crate::error::lang::SIGNAL_PARSE_UNIT_TOO_LONG),
259            _ => Error::Expected("Expected closing quote"),
260        })?;
261
262        // Convert bytes to string slice
263        let unit =
264            core::str::from_utf8(unit_bytes).map_err(|_e| Error::Expected(lang::INVALID_UTF8))?;
265
266        let unit: String<{ MAX_NAME_SIZE }> =
267            String::try_from(unit).map_err(|_| Error::Version(lang::MAX_NAME_SIZE_EXCEEDED))?;
268
269        let unit = if unit.is_empty() { None } else { Some(unit) };
270        Ok(unit)
271    }
272
273    pub(crate) fn parse(parser: &mut Parser) -> Result<Self> {
274        // Signal parsing must always start with "SG_" keyword
275        parser
276            .expect(crate::SG_.as_bytes())
277            .map_err(|_| Error::Expected("Expected SG_ keyword"))?;
278
279        // Skip whitespace after "SG_"
280        parser.skip_newlines_and_spaces();
281
282        // Parse signal name (identifier)
283        let name = parser
284            .parse_identifier()
285            .map_err(|_| Error::Signal(crate::error::lang::SIGNAL_NAME_EMPTY))?;
286
287        // Skip whitespace (optional before colon) - handle multiplexer indicator
288        // According to spec: multiplexer_indicator = ' ' | [m multiplexer_switch_value] [M]
289        // For now, we just skip whitespace and any potential multiplexer indicator
290        parser.skip_newlines_and_spaces();
291
292        // Skip potential multiplexer indicator (m followed by number, or M)
293        // For simplicity, skip any 'm' or 'M' followed by digits
294        if parser.expect(b"m").is_ok() || parser.expect(b"M").is_ok() {
295            // Skip any digits that follow
296            loop {
297                let _pos_before = parser.pos();
298                // Try to consume a digit
299                if parser.expect(b"0").is_ok()
300                    || parser.expect(b"1").is_ok()
301                    || parser.expect(b"2").is_ok()
302                    || parser.expect(b"3").is_ok()
303                    || parser.expect(b"4").is_ok()
304                    || parser.expect(b"5").is_ok()
305                    || parser.expect(b"6").is_ok()
306                    || parser.expect(b"7").is_ok()
307                    || parser.expect(b"8").is_ok()
308                    || parser.expect(b"9").is_ok()
309                {
310                    // Consumed a digit, continue
311                } else {
312                    // Not a digit, stop
313                    break;
314                }
315            }
316            // Skip whitespace after multiplexer indicator
317            parser.skip_newlines_and_spaces();
318        }
319
320        // Expect colon
321        parser.expect(b":").map_err(|_| Error::Expected("Expected colon"))?;
322
323        // Skip whitespace after colon
324        parser.skip_newlines_and_spaces();
325
326        // Parse position: start_bit|length@byteOrderSign
327        let (start_bit, length, byte_order, unsigned) = Self::parse_position(parser)?;
328
329        // Skip whitespace
330        parser.skip_newlines_and_spaces();
331
332        // Parse factor and offset: (factor,offset)
333        let (factor, offset) = Self::parse_factor_offset(parser)?;
334
335        // Skip whitespace
336        parser.skip_newlines_and_spaces();
337
338        // Parse range: [min|max]
339        let (min, max) = Self::parse_range(parser)?;
340
341        // Skip whitespace
342        parser.skip_newlines_and_spaces();
343
344        // Parse unit: "unit" or ""
345        let unit = Self::parse_unit(parser)?;
346
347        // Skip whitespace (but not newlines) before parsing receivers
348        // Newlines indicate end of signal line, so we need to preserve them for Receivers::parse
349        let _ = parser.skip_whitespace().ok(); // Ignore error if no whitespace
350
351        // Parse receivers (may be empty/None if at end of line)
352        let receivers = Receivers::parse(parser)?;
353
354        // Validate before construction
355        Self::validate(name, length, min, max).map_err(|e| {
356            crate::error::map_val_error(e, Error::Signal, || {
357                Error::Signal(crate::error::lang::SIGNAL_ERROR_PREFIX)
358            })
359        })?;
360
361        let name = crate::validate_name(name)?;
362
363        // Construct directly (validation already done)
364        Ok(Self {
365            name,
366            start_bit,
367            length,
368            byte_order,
369            unsigned,
370            factor,
371            offset,
372            min,
373            max,
374            unit,
375            receivers,
376        })
377    }
378
379    #[inline]
380    #[must_use = "return value should be checked"]
381    pub fn name(&self) -> &str {
382        self.name.as_ref()
383    }
384
385    #[inline]
386    #[must_use]
387    pub fn start_bit(&self) -> u16 {
388        self.start_bit
389    }
390
391    #[inline]
392    #[must_use]
393    pub fn length(&self) -> u16 {
394        self.length
395    }
396
397    #[inline]
398    #[must_use]
399    pub fn byte_order(&self) -> ByteOrder {
400        self.byte_order
401    }
402
403    #[inline]
404    #[must_use]
405    pub fn is_unsigned(&self) -> bool {
406        self.unsigned
407    }
408
409    #[inline]
410    #[must_use]
411    pub fn factor(&self) -> f64 {
412        self.factor
413    }
414
415    #[inline]
416    #[must_use]
417    pub fn offset(&self) -> f64 {
418        self.offset
419    }
420
421    #[inline]
422    #[must_use]
423    pub fn min(&self) -> f64 {
424        self.min
425    }
426
427    #[inline]
428    #[must_use]
429    pub fn max(&self) -> f64 {
430        self.max
431    }
432
433    #[inline]
434    #[must_use]
435    pub fn unit(&self) -> Option<&str> {
436        self.unit.as_ref().map(|u| u.as_ref())
437    }
438
439    #[inline]
440    #[must_use]
441    pub fn receivers(&self) -> &Receivers {
442        &self.receivers
443    }
444
445    /// Decode the signal value from CAN message data bytes.
446    ///
447    /// Extracts the raw value from bits based on the signal's start bit, length, and byte order,
448    /// then applies factor and offset to return the physical/engineering value.
449    ///
450    /// # Arguments
451    ///
452    /// * `data` - The CAN message data bytes (up to 8 bytes for classic CAN, 64 for CAN FD)
453    ///
454    /// # Returns
455    ///
456    /// * `Ok(f64)` - The physical value (raw * factor + offset)
457    /// * `Err(Error)` - If the signal extends beyond the data length
458    ///
459    /// # Examples
460    ///
461    /// ```rust,no_run
462    /// use dbc_rs::Dbc;
463    ///
464    /// let dbc = Dbc::parse(r#"VERSION "1.0"
465    ///
466    /// BU_: ECM
467    ///
468    /// BO_ 256 Engine : 8 ECM
469    ///  SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
470    /// "#)?;
471    ///
472    /// let message = dbc.messages().at(0).unwrap();
473    /// let signal = message.signals().at(0).unwrap();
474    ///
475    /// // Decode a CAN message with RPM value of 2000 (raw: 8000)
476    /// let data = [0x20, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
477    /// let rpm = signal.decode(&data)?;
478    /// assert_eq!(rpm, 2000.0);
479    /// # Ok::<(), dbc_rs::Error>(())
480    /// ```
481    pub fn decode(&self, data: &[u8]) -> Result<f64> {
482        let start_bit = self.start_bit as usize;
483        let length = self.length as usize;
484        let end_byte = (start_bit + length - 1) / 8;
485
486        if end_byte >= data.len() {
487            return Err(Error::Decoding(lang::SIGNAL_EXTENDS_BEYOND_DATA));
488        }
489
490        // Extract bits based on byte order
491        let raw_value = match self.byte_order {
492            ByteOrder::LittleEndian => Self::extract_bits_little_endian(data, start_bit, length),
493            ByteOrder::BigEndian => Self::extract_bits_big_endian(data, start_bit, length),
494        };
495
496        // Convert to signed/unsigned
497        let value = if self.unsigned {
498            raw_value as i64
499        } else {
500            // Sign extend for signed values
501            let sign_bit = 1u64 << (length - 1);
502            if (raw_value & sign_bit) != 0 {
503                // Negative value - sign extend
504                let mask = !((1u64 << length) - 1);
505                (raw_value | mask) as i64
506            } else {
507                raw_value as i64
508            }
509        };
510
511        // Apply factor and offset to get physical value
512        Ok((value as f64) * self.factor + self.offset)
513    }
514
515    /// Extract bits from data using little-endian byte order.
516    fn extract_bits_little_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
517        let mut value: u64 = 0;
518        let mut bits_remaining = length;
519        let mut current_bit = start_bit;
520
521        while bits_remaining > 0 {
522            let byte_idx = current_bit / 8;
523            let bit_in_byte = current_bit % 8;
524            let bits_to_take = bits_remaining.min(8 - bit_in_byte);
525
526            let byte = data[byte_idx] as u64;
527            let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
528            let extracted = (byte & mask) >> bit_in_byte;
529
530            value |= extracted << (length - bits_remaining);
531
532            bits_remaining -= bits_to_take;
533            current_bit += bits_to_take;
534        }
535
536        value
537    }
538
539    /// Extract bits from data using big-endian byte order.
540    fn extract_bits_big_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
541        let mut value: u64 = 0;
542        let mut bits_remaining = length;
543        let mut current_bit = start_bit;
544
545        while bits_remaining > 0 {
546            let byte_idx = current_bit / 8;
547            let bit_in_byte = current_bit % 8;
548            let bits_to_take = bits_remaining.min(8 - bit_in_byte);
549
550            let byte = data[byte_idx] as u64;
551            let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
552            let extracted = (byte & mask) >> bit_in_byte;
553
554            // For big-endian, we need to reverse the bit order within bytes
555            // and place bits in the correct position
556            value |= extracted << (length - bits_remaining);
557
558            bits_remaining -= bits_to_take;
559            current_bit += bits_to_take;
560        }
561
562        value
563    }
564
565    #[cfg(feature = "std")]
566    #[must_use]
567    pub fn to_dbc_string(&self) -> std::string::String {
568        let mut result = std::string::String::with_capacity(100); // Pre-allocate reasonable capacity
569
570        result.push_str(" SG_ ");
571        result.push_str(self.name());
572        result.push_str(" : ");
573        result.push_str(&self.start_bit().to_string());
574        result.push('|');
575        result.push_str(&self.length().to_string());
576        result.push('@');
577
578        // Byte order: 0 for BigEndian (Motorola), 1 for LittleEndian (Intel)
579        // Per Vector DBC spec v1.0.1: "Big endian is stored as '0', little endian is stored as '1'"
580        match self.byte_order() {
581            ByteOrder::BigEndian => result.push('0'), // @0 = Big Endian (Motorola)
582            ByteOrder::LittleEndian => result.push('1'), // @1 = Little Endian (Intel)
583        }
584
585        // Sign: + for unsigned, - for signed
586        if self.is_unsigned() {
587            result.push('+');
588        } else {
589            result.push('-');
590        }
591
592        // Factor and offset: (factor,offset)
593        result.push_str(" (");
594        use core::fmt::Write;
595        write!(result, "{}", self.factor()).unwrap();
596        result.push(',');
597        write!(result, "{}", self.offset()).unwrap();
598        result.push(')');
599
600        // Min and max: [min|max]
601        result.push_str(" [");
602        write!(result, "{}", self.min()).unwrap();
603        result.push('|');
604        write!(result, "{}", self.max()).unwrap();
605        result.push(']');
606
607        // Unit: "unit" or ""
608        result.push(' ');
609        if let Some(unit) = self.unit() {
610            result.push('"');
611            result.push_str(unit);
612            result.push('"');
613        } else {
614            result.push_str("\"\"");
615        }
616
617        // Receivers: * for Broadcast, space-separated list for Nodes, or empty
618        match self.receivers() {
619            Receivers::Broadcast => {
620                result.push(' ');
621                result.push('*');
622            }
623            Receivers::Nodes(nodes) => {
624                if !nodes.is_empty() {
625                    result.push(' ');
626                    for (i, node) in self.receivers().iter().enumerate() {
627                        if i > 0 {
628                            result.push(' ');
629                        }
630                        result.push_str(node.as_str());
631                    }
632                }
633            }
634            Receivers::None => {
635                // No receivers specified - nothing to add
636            }
637        }
638
639        result
640    }
641}
642
643// Custom Eq implementation that handles f64 (treats NaN as equal to NaN)
644impl Eq for Signal {}
645
646// Custom Hash implementation that handles f64 (treats NaN consistently)
647impl core::hash::Hash for Signal {
648    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
649        self.name.hash(state);
650        self.start_bit.hash(state);
651        self.length.hash(state);
652        self.byte_order.hash(state);
653        self.unsigned.hash(state);
654        // Handle f64: convert to bits for hashing (NaN will have consistent representation)
655        self.factor.to_bits().hash(state);
656        self.offset.to_bits().hash(state);
657        self.min.to_bits().hash(state);
658        self.max.to_bits().hash(state);
659        self.unit.hash(state);
660        self.receivers.hash(state);
661    }
662}
663
664#[cfg(feature = "std")]
665impl core::fmt::Display for Signal {
666    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
667        write!(f, "{}", self.to_dbc_string())
668    }
669}
670
671#[cfg(test)]
672mod tests {
673    #![allow(clippy::float_cmp)]
674    use super::*;
675    use crate::{Parser, error::lang};
676
677    #[test]
678    fn test_parse_valid_signal() {
679        let line = r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
680        let mut parser = Parser::new(line.as_bytes()).unwrap();
681        let sig = Signal::parse(&mut parser).unwrap();
682        assert_eq!(sig.name(), "RPM");
683        assert_eq!(sig.start_bit(), 0);
684        assert_eq!(sig.length(), 16);
685        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
686        assert!(sig.is_unsigned());
687        assert_eq!(sig.factor(), 0.25);
688        assert_eq!(sig.offset(), 0.);
689        assert_eq!(sig.min(), 0.);
690        assert_eq!(sig.max(), 8000.);
691        assert_eq!(sig.unit(), Some("rpm"));
692        // Check receivers using iter
693        let mut receivers_iter = sig.receivers().iter();
694        let receiver1 = receivers_iter.next().unwrap();
695        assert_eq!(receiver1.as_str(), "TCM");
696        assert_eq!(receivers_iter.next(), None);
697    }
698
699    #[test]
700    fn test_parse_signal_with_empty_unit_and_broadcast() {
701        let line = r#"SG_ ABSActive : 16|1@0+ (1,0) [0|1] "" *"#;
702        let mut parser = Parser::new(line.as_bytes()).unwrap();
703        let sig = Signal::parse(&mut parser).unwrap();
704        assert_eq!(sig.name(), "ABSActive");
705        assert_eq!(sig.start_bit(), 16);
706        assert_eq!(sig.length(), 1);
707        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
708        assert!(sig.is_unsigned());
709        assert_eq!(sig.factor(), 1.);
710        assert_eq!(sig.offset(), 0.);
711        assert_eq!(sig.min(), 0.);
712        assert_eq!(sig.max(), 1.);
713        assert_eq!(sig.unit(), None);
714        assert_eq!(sig.receivers(), &Receivers::Broadcast);
715    }
716
717    #[test]
718    fn test_parse_signal_with_negative_offset_and_min() {
719        let line = r#"SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM BCM"#;
720        let mut parser = Parser::new(line.as_bytes()).unwrap();
721        let sig = Signal::parse(&mut parser).unwrap();
722        assert_eq!(sig.name(), "Temperature");
723        assert_eq!(sig.start_bit(), 16);
724        assert_eq!(sig.length(), 8);
725        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
726        assert!(!sig.is_unsigned());
727        assert_eq!(sig.factor(), 1.);
728        assert_eq!(sig.offset(), -40.);
729        assert_eq!(sig.min(), -40.);
730        assert_eq!(sig.max(), 215.);
731        assert_eq!(sig.unit(), Some("°C"));
732        // Check receivers using iter
733        let mut receivers_iter = sig.receivers().iter();
734        let receiver1 = receivers_iter.next().unwrap();
735        assert_eq!(receiver1.as_str(), "TCM");
736        let receiver2 = receivers_iter.next().unwrap();
737        assert_eq!(receiver2.as_str(), "BCM");
738        assert_eq!(receivers_iter.next(), None);
739    }
740
741    #[test]
742    fn test_parse_signal_with_percent_unit() {
743        let line = r#"SG_ ThrottlePosition : 24|8@0+ (0.392157,0) [0|100] "%" *"#;
744        let mut parser = Parser::new(line.as_bytes()).unwrap();
745        let sig = Signal::parse(&mut parser).unwrap();
746        assert_eq!(sig.name(), "ThrottlePosition");
747        assert_eq!(sig.start_bit(), 24);
748        assert_eq!(sig.length(), 8);
749        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
750        assert!(sig.is_unsigned());
751        assert_eq!(sig.factor(), 0.392_157);
752        assert_eq!(sig.offset(), 0.);
753        assert_eq!(sig.min(), 0.);
754        assert_eq!(sig.max(), 100.);
755        assert_eq!(sig.unit(), Some("%"));
756        assert_eq!(sig.receivers(), &Receivers::Broadcast);
757    }
758
759    #[test]
760    fn test_parse_signal_missing_factors_and_limits() {
761        // Should use default values where missing
762        let line = r#"SG_ Simple : 10|4@0+ ( , ) [ | ] "" *"#;
763        let mut parser = Parser::new(line.as_bytes()).unwrap();
764        let sig = Signal::parse(&mut parser).unwrap();
765        assert_eq!(sig.name(), "Simple");
766        assert_eq!(sig.start_bit(), 10);
767        assert_eq!(sig.length(), 4);
768        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
769        assert!(sig.is_unsigned());
770        assert_eq!(sig.factor(), 0.);
771        assert_eq!(sig.offset(), 0.);
772        assert_eq!(sig.min(), 0.);
773        assert_eq!(sig.max(), 0.);
774        assert_eq!(sig.unit(), None);
775        assert_eq!(sig.receivers(), &Receivers::Broadcast);
776    }
777
778    #[test]
779    fn test_parse_signal_missing_start_bit() {
780        let line = r#"SG_ RPM : |16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
781        let mut parser = Parser::new(line.as_bytes()).unwrap();
782        let err = Signal::parse(&mut parser).unwrap_err();
783        match err {
784            Error::Signal(msg) => {
785                // Check for either the old constant or the new formatted message
786                assert!(
787                    msg.contains(lang::SIGNAL_PARSE_INVALID_START_BIT)
788                        || msg.contains("Signal 'RPM'")
789                );
790            }
791            _ => panic!("Expected Error::Signal"),
792        }
793    }
794
795    #[test]
796    fn test_parse_signal_invalid_range() {
797        // min > max should fail validation
798        let line = r#"SG_ Test : 0|8@0+ (1,0) [100|50] "unit" *"#;
799        let mut parser = Parser::new(line.as_bytes()).unwrap();
800        let err = Signal::parse(&mut parser).unwrap_err();
801        match err {
802            Error::Signal(msg) => {
803                assert!(msg.contains(lang::INVALID_RANGE));
804            }
805            e => panic!("Expected Error::Signal, got: {:?}", e),
806        }
807    }
808
809    #[test]
810    fn test_parse_signal_overflow() {
811        // Signal with start_bit + length > 64 should parse successfully
812        // (validation against message DLC happens in Message::validate)
813        // This signal would fit in a CAN FD message (64 bytes = 512 bits)
814        let line = r#"SG_ Test : 60|10@0+ (1,0) [0|100] "unit" *"#;
815        let mut parser = Parser::new(line.as_bytes()).unwrap();
816        let signal = Signal::parse(&mut parser).unwrap();
817        assert_eq!(signal.start_bit(), 60);
818        assert_eq!(signal.length(), 10);
819        // Note: Message validation tests are in message.rs and message_builder.rs
820    }
821
822    #[test]
823    fn test_parse_signal_length_too_large() {
824        // length > 512 should fail validation (CAN FD maximum is 512 bits)
825        let line = r#"SG_ Test : 0|513@0+ (1,0) [0|100] "unit" *"#;
826        let mut parser = Parser::new(line.as_bytes()).unwrap();
827        let err = Signal::parse(&mut parser).unwrap_err();
828        match err {
829            Error::Signal(msg) => {
830                // Check for either the old constant or the new formatted message
831                assert!(
832                    msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)
833                        || msg.contains("Signal 'Test'")
834                        || msg.contains("513")
835                );
836            }
837            e => panic!("Expected Error::Signal, got: {:?}", e),
838        }
839    }
840
841    #[test]
842    fn test_parse_signal_zero_length() {
843        // length = 0 should fail validation
844        let line = r#"SG_ Test : 0|0@0+ (1,0) [0|100] "unit" *"#;
845        let mut parser = Parser::new(line.as_bytes()).unwrap();
846        let err = Signal::parse(&mut parser).unwrap_err();
847        match err {
848            Error::Signal(msg) => {
849                // Check for either the old constant or the new formatted message
850                assert!(
851                    msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)
852                        || msg.contains("Signal 'Test'")
853                        || msg.contains("0 bits")
854                );
855            }
856            e => panic!("Expected Error::Signal, got: {:?}", e),
857        }
858    }
859
860    #[test]
861    fn test_parse_signal_missing_length() {
862        let line = r#"SG_ RPM : 0|@0+ (0.25,0) [0|8000] "rpm" TCM"#;
863        let mut parser = Parser::new(line.as_bytes()).unwrap();
864        let err = Signal::parse(&mut parser).unwrap_err();
865        match err {
866            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_PARSE_INVALID_LENGTH)),
867            e => panic!("Expected Error::Signal, got: {:?}", e),
868        }
869    }
870
871    // Tests that require std (for to_dbc_string)
872    #[cfg(feature = "std")]
873    mod tests_with_std {
874        use super::*;
875
876        #[test]
877        fn test_signal_to_dbc_string_round_trip() {
878            // Test round-trip: parse -> to_dbc_string -> parse
879            let test_cases = vec![
880                (
881                    r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *"#,
882                    " SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *",
883                ),
884                (
885                    r#"SG_ Temperature : 16|8@1- (1,-40) [-40|215] "°C" TCM BCM"#,
886                    " SG_ Temperature : 16|8@1- (1,-40) [-40|215] \"°C\" TCM BCM",
887                ),
888                (
889                    r#"SG_ Flag : 24|1@0+ (1,0) [0|1] "" *"#,
890                    " SG_ Flag : 24|1@0+ (1,0) [0|1] \"\" *",
891                ),
892            ];
893
894            for (input_line, expected_output) in test_cases {
895                // Parse the signal
896                let mut parser = Parser::new(input_line.as_bytes()).unwrap();
897                let signal = Signal::parse(&mut parser).unwrap();
898
899                // Convert to DBC string
900                let dbc_string = signal.to_dbc_string();
901                assert_eq!(dbc_string, expected_output);
902
903                // Round-trip: parse the output
904                let mut parser2 = Parser::new(dbc_string.as_bytes()).unwrap();
905                // Skip only the leading space, Signal::parse will handle SG_ keyword
906                parser2.skip_newlines_and_spaces();
907                let signal2 = Signal::parse(&mut parser2).unwrap();
908
909                // Verify round-trip
910                assert_eq!(signal.name(), signal2.name());
911                assert_eq!(signal.start_bit(), signal2.start_bit());
912                assert_eq!(signal.length(), signal2.length());
913                assert_eq!(signal.byte_order(), signal2.byte_order());
914                assert_eq!(signal.is_unsigned(), signal2.is_unsigned());
915                assert_eq!(signal.factor(), signal2.factor());
916                assert_eq!(signal.offset(), signal2.offset());
917                assert_eq!(signal.min(), signal2.min());
918                assert_eq!(signal.max(), signal2.max());
919                assert_eq!(signal.unit(), signal2.unit());
920            }
921        }
922    }
923}