dbc_rs/
signal.rs

1use crate::{Error, error::messages};
2use alloc::{
3    boxed::Box,
4    string::{String, ToString},
5    vec::Vec,
6};
7
8/// Represents a signal within a CAN message.
9///
10/// A signal defines how a portion of the message payload should be
11/// interpreted, including bit position, length, scaling factors,
12/// and value ranges.
13///
14/// # Examples
15///
16/// ```rust
17/// use dbc_rs::{Signal, ByteOrder, Receivers};
18///
19/// let signal = Signal::builder()
20///     .name("RPM")
21///     .start_bit(0)
22///     .length(16)
23///     .byte_order(ByteOrder::BigEndian)
24///     .unsigned(true)
25///     .factor(0.25)
26///     .offset(0.0)
27///     .min(0.0)
28///     .max(8000.0)
29///     .unit("rpm")
30///     .receivers(Receivers::Broadcast)
31///     .build()?;
32/// # Ok::<(), dbc_rs::Error>(())
33/// ```
34#[derive(Debug, Clone, PartialEq)]
35pub struct Signal {
36    name: Box<str>,
37    start_bit: u8,
38    length: u8,
39    byte_order: ByteOrder,
40    unsigned: bool,
41    factor: f64,
42    offset: f64,
43    min: f64,
44    max: f64,
45    unit: Option<Box<str>>,
46    receivers: Receivers,
47}
48
49/// Byte order (endianness) for signal encoding.
50///
51/// Determines how multi-byte signals are encoded within the CAN message.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum ByteOrder {
54    /// Little-endian byte order (Intel format).
55    LittleEndian = 0,
56    /// Big-endian byte order (Motorola format).
57    BigEndian = 1,
58}
59
60/// Receiver specification for a signal.
61///
62/// Defines which nodes on the CAN bus should receive and process this signal.
63#[derive(Debug, Clone, PartialEq)]
64pub enum Receivers {
65    /// Signal is broadcast to all nodes.
66    Broadcast,
67    /// Signal is sent to specific nodes.
68    Nodes(Vec<Box<str>>),
69    /// Signal has no receivers specified.
70    None,
71}
72
73impl Signal {
74    /// Validate signal parameters
75    fn validate(name: &str, start_bit: u8, length: u8, min: f64, max: f64) -> Result<(), Error> {
76        if name.trim().is_empty() {
77            return Err(Error::Signal(messages::SIGNAL_NAME_EMPTY.to_string()));
78        }
79
80        // Validate length: must be between 1 and 64 bits
81        if length == 0 {
82            return Err(Error::Signal(messages::SIGNAL_LENGTH_TOO_SMALL.to_string()));
83        }
84        if length > 64 {
85            return Err(Error::Signal(messages::SIGNAL_LENGTH_TOO_LARGE.to_string()));
86        }
87
88        // Validate start_bit + length doesn't exceed 64 (CAN message max size)
89        let end_bit = start_bit as u16 + length as u16;
90        if end_bit > 64 {
91            return Err(Error::Signal(messages::signal_extends_beyond_can(
92                start_bit, length, end_bit,
93            )));
94        }
95
96        // Validate min <= max
97        if min > max {
98            return Err(Error::Signal(messages::invalid_range(min, max)));
99        }
100
101        Ok(())
102    }
103
104    /// Create a new Signal with the given parameters
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if:
109    /// - `name` is empty
110    /// - `length` is 0 or greater than 64
111    /// - `start_bit + length` exceeds 64 (signal would overflow CAN message)
112    /// - `min > max` (invalid range)
113    ///
114    /// This is an internal constructor. For public API usage, use [`Signal::builder()`] instead.
115    #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
116    pub(crate) fn new(
117        name: impl AsRef<str>,
118        start_bit: u8,
119        length: u8,
120        byte_order: ByteOrder,
121        unsigned: bool,
122        factor: f64,
123        offset: f64,
124        min: f64,
125        max: f64,
126        unit: Option<impl AsRef<str>>,
127        receivers: Receivers,
128    ) -> Result<Self, Error> {
129        let name_str = name.as_ref();
130        Self::validate(name_str, start_bit, length, min, max)?;
131
132        Ok(Self {
133            name: name_str.into(),
134            start_bit,
135            length,
136            byte_order,
137            unsigned,
138            factor,
139            offset,
140            min,
141            max,
142            unit: unit.map(|u| u.as_ref().into()),
143            receivers,
144        })
145    }
146
147    /// Create a new builder for constructing a `Signal`
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use dbc_rs::{Signal, ByteOrder, Receivers};
153    ///
154    /// let signal = Signal::builder()
155    ///     .name("RPM")
156    ///     .start_bit(0)
157    ///     .length(16)
158    ///     .byte_order(ByteOrder::BigEndian)
159    ///     .unsigned(true)
160    ///     .factor(0.25)
161    ///     .offset(0.0)
162    ///     .min(0.0)
163    ///     .max(8000.0)
164    ///     .unit("rpm")
165    ///     .receivers(Receivers::Broadcast)
166    ///     .build()?;
167    /// # Ok::<(), dbc_rs::Error>(())
168    /// ```
169    pub fn builder() -> SignalBuilder {
170        SignalBuilder::new()
171    }
172
173    pub(super) fn parse(line: &str) -> Result<Self, Error> {
174        // INSERT_YOUR_CODE
175
176        // Trim and check for SG_
177        let line = line.trim_start();
178        let line = line
179            .strip_prefix("SG_")
180            .or_else(|| line.strip_prefix("SG_"))
181            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_EXPECTED_SG.to_string()))?;
182        let line = line.trim();
183
184        // name until ':'
185        let (name, rest) = match line.split_once(':') {
186            Some((n, r)) => (n.trim(), r.trim()),
187            None => {
188                return Err(Error::Signal(
189                    messages::SIGNAL_PARSE_MISSING_COLON.to_string(),
190                ));
191            }
192        };
193
194        // startBit|length@byteOrderSign
195        // e.g. 0|16@0+
196        let mut rest = rest;
197        let mut tokens = rest.splitn(2, ' ');
198        let pos = tokens
199            .next()
200            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_MISSING_POSITION.to_string()))?
201            .trim();
202        rest = tokens
203            .next()
204            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_MISSING_REST.to_string()))?
205            .trim();
206
207        // e.g. 0|16@0+
208        let (bitlen, bosign) = pos
209            .split_once('@')
210            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_EXPECTED_AT.to_string()))?;
211        let (startbit, length) = bitlen
212            .split_once('|')
213            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_EXPECTED_PIPE.to_string()))?;
214
215        let start_bit: u8 = startbit
216            .trim()
217            .parse()
218            .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_START_BIT.to_string()))?;
219        let length: u8 = length
220            .trim()
221            .parse()
222            .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_LENGTH.to_string()))?;
223
224        // Parse byte order and sign
225        let bosign = bosign.trim();
226        let (byte_order, unsigned) = {
227            let mut chars = bosign.chars();
228            let bo = chars.next().ok_or_else(|| {
229                Error::Signal(messages::SIGNAL_PARSE_MISSING_BYTE_ORDER.to_string())
230            })?;
231            let sign = chars
232                .next()
233                .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_MISSING_SIGN.to_string()))?;
234            let byte_order = match bo {
235                '0' => ByteOrder::LittleEndian,
236                '1' => ByteOrder::BigEndian,
237                _ => return Err(Error::Signal(messages::unknown_byte_order(bo))),
238            };
239            let unsigned = match sign {
240                '+' => true,
241                '-' => false,
242                _ => return Err(Error::Signal(messages::unknown_sign(sign))),
243            };
244            (byte_order, unsigned)
245        };
246
247        // Now next token: (factor,offset)
248        let rest = rest.trim_start();
249        let (f_and_rest, rest) = match rest.trim_start().split_once(')') {
250            Some((f, r)) => (f, r),
251            None => {
252                return Err(Error::Signal(
253                    messages::SIGNAL_PARSE_MISSING_CLOSING_PAREN.to_string(),
254                ));
255            }
256        };
257        let f_and_rest = f_and_rest.trim_start();
258        let f_and_rest = f_and_rest.strip_prefix('(').ok_or_else(|| {
259            Error::Signal(messages::SIGNAL_PARSE_MISSING_OPENING_PAREN.to_string())
260        })?;
261        let (factor_str, offset_str) = f_and_rest
262            .split_once(',')
263            .ok_or_else(|| Error::Signal(messages::SIGNAL_PARSE_MISSING_COMMA.to_string()))?;
264        let (factor_str, offset_str) = (factor_str.trim(), offset_str.trim());
265        let factor: f64 = if factor_str.is_empty() {
266            0.
267        } else {
268            factor_str
269                .parse()
270                .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_FACTOR.to_string()))?
271        };
272        let offset: f64 = if offset_str.is_empty() {
273            0.
274        } else {
275            offset_str
276                .parse()
277                .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_OFFSET.to_string()))?
278        };
279        let rest = rest.trim_start();
280
281        // Next token: [min|max]
282        let (minmax, rest) = match rest.split_once(']') {
283            Some((m, r)) => (m, r),
284            None => {
285                return Err(Error::Signal(
286                    messages::SIGNAL_PARSE_MISSING_CLOSING_BRACKET.to_string(),
287                ));
288            }
289        };
290        let minmax = minmax.trim_start().strip_prefix('[').ok_or_else(|| {
291            Error::Signal(messages::SIGNAL_PARSE_MISSING_OPENING_BRACKET.to_string())
292        })?;
293        let (min_str, max_str) = minmax.split_once('|').ok_or_else(|| {
294            Error::Signal(messages::SIGNAL_PARSE_MISSING_PIPE_IN_RANGE.to_string())
295        })?;
296        let (min_str, max_str) = (min_str.trim(), max_str.trim());
297        let min: f64 = if min_str.is_empty() {
298            0.
299        } else {
300            min_str
301                .parse()
302                .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_MIN.to_string()))?
303        };
304        let max: f64 = if max_str.is_empty() {
305            0.
306        } else {
307            max_str
308                .parse()
309                .map_err(|_| Error::Signal(messages::SIGNAL_PARSE_INVALID_MAX.to_string()))?
310        };
311        let mut rest = rest.trim_start();
312
313        // Now: unit in double quotes
314        if !rest.starts_with('"') {
315            return Err(Error::Signal(
316                messages::SIGNAL_PARSE_EXPECTED_UNIT_QUOTE.to_string(),
317            ));
318        }
319        rest = &rest[1..];
320        // Pre-allocate unit string (most units are short, 1-10 chars)
321        let mut unit_str = String::with_capacity(10);
322        for c in rest.chars() {
323            if c == '"' {
324                break;
325            }
326            unit_str.push(c);
327        }
328        // Advance past the closing quote in rest
329        rest = rest[unit_str.len()..].trim_start();
330        if rest.starts_with('"') {
331            rest = &rest[1..];
332        }
333        let unit = if unit_str.is_empty() {
334            None
335        } else {
336            Some(unit_str.into_boxed_str())
337        };
338        let rest = rest.trim_start();
339
340        // Receivers
341        let receivers = if rest.is_empty() {
342            Receivers::None
343        } else if rest == "*" {
344            Receivers::Broadcast
345        } else {
346            // Pre-allocate receivers Vec (most signals have 1-3 receivers)
347            let nodes: Vec<Box<str>> = rest.split_whitespace().map(|s| s.into()).collect();
348            if nodes.is_empty() {
349                Receivers::None
350            } else {
351                Receivers::Nodes(nodes)
352            }
353        };
354
355        // Validate the parsed signal using the same validation as new()
356        Self::validate(name, start_bit, length, min, max)?;
357
358        Ok(Signal {
359            name: name.into(),
360            start_bit,
361            length,
362            byte_order,
363            unsigned,
364            factor,
365            offset,
366            min,
367            max,
368            unit,
369            receivers,
370        })
371    }
372
373    /// Get the signal name.
374    #[inline]
375    pub fn name(&self) -> &str {
376        &self.name
377    }
378
379    /// Get the starting bit position within the message.
380    #[inline]
381    pub fn start_bit(&self) -> u8 {
382        self.start_bit
383    }
384
385    /// Get the signal length in bits.
386    #[inline]
387    pub fn length(&self) -> u8 {
388        self.length
389    }
390
391    /// Get the byte order (endianness) of the signal.
392    #[inline]
393    pub fn byte_order(&self) -> ByteOrder {
394        self.byte_order
395    }
396
397    /// Check if the signal is unsigned.
398    #[inline]
399    pub fn is_unsigned(&self) -> bool {
400        self.unsigned
401    }
402
403    /// Get the scaling factor for converting raw value to physical value.
404    #[inline]
405    pub fn factor(&self) -> f64 {
406        self.factor
407    }
408
409    /// Get the offset for converting raw value to physical value.
410    #[inline]
411    pub fn offset(&self) -> f64 {
412        self.offset
413    }
414
415    /// Get the minimum physical value.
416    #[inline]
417    pub fn min(&self) -> f64 {
418        self.min
419    }
420
421    /// Get the maximum physical value.
422    #[inline]
423    pub fn max(&self) -> f64 {
424        self.max
425    }
426
427    /// Get the unit string, if present.
428    #[inline]
429    pub fn unit(&self) -> Option<&str> {
430        self.unit.as_ref().map(|s| s.as_ref())
431    }
432
433    /// Get the receiver specification for this signal.
434    #[inline]
435    pub fn receivers(&self) -> &Receivers {
436        &self.receivers
437    }
438
439    /// Format signal in DBC file format (e.g., ` SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *`)
440    ///
441    /// Useful for debugging and visualization of the signal in DBC format.
442    /// Note: The leading space is included to match DBC file formatting conventions.
443    ///
444    /// # Examples
445    ///
446    /// ```
447    /// use dbc_rs::{Signal, ByteOrder, Receivers};
448    ///
449    /// let signal = Signal::builder()
450    ///     .name("RPM")
451    ///     .start_bit(0)
452    ///     .length(16)
453    ///     .byte_order(ByteOrder::BigEndian)
454    ///     .unsigned(true)
455    ///     .factor(0.25)
456    ///     .offset(0.0)
457    ///     .min(0.0)
458    ///     .max(8000.0)
459    ///     .unit("rpm")
460    ///     .receivers(Receivers::Broadcast)
461    ///     .build()?;
462    /// assert_eq!(signal.to_dbc_string(), " SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\" *");
463    /// # Ok::<(), dbc_rs::Error>(())
464    /// ```
465    pub fn to_dbc_string(&self) -> String {
466        use alloc::format;
467
468        let mut result = String::with_capacity(100); // Pre-allocate reasonable capacity
469
470        result.push_str(" SG_ ");
471        result.push_str(self.name());
472        result.push_str(" : ");
473        result.push_str(&self.start_bit().to_string());
474        result.push('|');
475        result.push_str(&self.length().to_string());
476        result.push('@');
477
478        // Byte order: 0 for LittleEndian, 1 for BigEndian
479        match self.byte_order() {
480            ByteOrder::LittleEndian => result.push('0'),
481            ByteOrder::BigEndian => result.push('1'),
482        }
483
484        // Sign: + for unsigned, - for signed
485        if self.is_unsigned() {
486            result.push('+');
487        } else {
488            result.push('-');
489        }
490
491        // Factor and offset: (factor,offset)
492        result.push_str(" (");
493        result.push_str(&format!("{}", self.factor()));
494        result.push(',');
495        result.push_str(&format!("{}", self.offset()));
496        result.push(')');
497
498        // Min and max: [min|max]
499        result.push_str(" [");
500        result.push_str(&format!("{}", self.min()));
501        result.push('|');
502        result.push_str(&format!("{}", self.max()));
503        result.push(']');
504
505        // Unit: "unit" or ""
506        result.push(' ');
507        if let Some(unit) = self.unit() {
508            result.push('"');
509            result.push_str(unit);
510            result.push('"');
511        } else {
512            result.push_str("\"\"");
513        }
514
515        // Receivers: * for Broadcast, space-separated list for Nodes, or empty
516        match self.receivers() {
517            Receivers::Broadcast => {
518                result.push(' ');
519                result.push('*');
520            }
521            Receivers::Nodes(nodes) => {
522                if !nodes.is_empty() {
523                    result.push(' ');
524                    for (i, node) in nodes.iter().enumerate() {
525                        if i > 0 {
526                            result.push(' ');
527                        }
528                        result.push_str(node.as_ref());
529                    }
530                }
531            }
532            Receivers::None => {
533                // No receivers specified - nothing to add
534            }
535        }
536
537        result
538    }
539}
540
541/// Builder for constructing a `Signal` with a fluent API
542///
543/// This builder provides a more ergonomic way to construct `Signal` instances,
544/// especially when many parameters are optional or have sensible defaults.
545///
546/// # Examples
547///
548/// ```
549/// use dbc_rs::{Signal, ByteOrder, Receivers};
550///
551/// // Minimal signal with defaults
552/// let signal = Signal::builder()
553///     .name("RPM")
554///     .start_bit(0)
555///     .length(16)
556///     .build()?;
557///
558/// // Full signal with all parameters
559/// let signal = Signal::builder()
560///     .name("Temperature")
561///     .start_bit(16)
562///     .length(8)
563///     .byte_order(ByteOrder::LittleEndian)
564///     .unsigned(false)
565///     .factor(1.0)
566///     .offset(-40.0)
567///     .min(-40.0)
568///     .max(215.0)
569///     .unit("°C")
570///     .receivers(Receivers::Broadcast)
571///     .build()?;
572/// # Ok::<(), dbc_rs::Error>(())
573/// ```
574#[derive(Debug, Clone)]
575pub struct SignalBuilder {
576    name: Option<Box<str>>,
577    start_bit: Option<u8>,
578    length: Option<u8>,
579    byte_order: ByteOrder,
580    unsigned: bool,
581    factor: f64,
582    offset: f64,
583    min: f64,
584    max: f64,
585    unit: Option<Box<str>>,
586    receivers: Receivers,
587}
588
589impl SignalBuilder {
590    fn new() -> Self {
591        Self {
592            name: None,
593            start_bit: None,
594            length: None,
595            byte_order: ByteOrder::BigEndian,
596            unsigned: true,
597            factor: 1.0,
598            offset: 0.0,
599            min: 0.0,
600            max: 0.0,
601            unit: None,
602            receivers: Receivers::None,
603        }
604    }
605
606    /// Set the signal name (required)
607    pub fn name(mut self, name: impl AsRef<str>) -> Self {
608        self.name = Some(name.as_ref().into());
609        self
610    }
611
612    /// Set the start bit position (required)
613    pub fn start_bit(mut self, start_bit: u8) -> Self {
614        self.start_bit = Some(start_bit);
615        self
616    }
617
618    /// Set the signal length in bits (required)
619    pub fn length(mut self, length: u8) -> Self {
620        self.length = Some(length);
621        self
622    }
623
624    /// Set the byte order (default: `BigEndian`)
625    pub fn byte_order(mut self, byte_order: ByteOrder) -> Self {
626        self.byte_order = byte_order;
627        self
628    }
629
630    /// Set whether the signal is unsigned (default: `true`)
631    pub fn unsigned(mut self, unsigned: bool) -> Self {
632        self.unsigned = unsigned;
633        self
634    }
635
636    /// Set the scaling factor (default: `1.0`)
637    pub fn factor(mut self, factor: f64) -> Self {
638        self.factor = factor;
639        self
640    }
641
642    /// Set the offset value (default: `0.0`)
643    pub fn offset(mut self, offset: f64) -> Self {
644        self.offset = offset;
645        self
646    }
647
648    /// Set the minimum value (default: `0.0`)
649    pub fn min(mut self, min: f64) -> Self {
650        self.min = min;
651        self
652    }
653
654    /// Set the maximum value (default: `0.0`)
655    pub fn max(mut self, max: f64) -> Self {
656        self.max = max;
657        self
658    }
659
660    /// Set the unit string (optional)
661    pub fn unit(mut self, unit: impl AsRef<str>) -> Self {
662        self.unit = Some(unit.as_ref().into());
663        self
664    }
665
666    /// Clear the unit (set to `None`)
667    pub fn no_unit(mut self) -> Self {
668        self.unit = None;
669        self
670    }
671
672    /// Set the receivers (default: `Receivers::None`)
673    pub fn receivers(mut self, receivers: Receivers) -> Self {
674        self.receivers = receivers;
675        self
676    }
677
678    /// Validate the current builder state
679    ///
680    /// This method performs the same validation as `Signal::validate()` but on the
681    /// builder's current state. Useful for checking validity before calling `build()`.
682    ///
683    /// # Errors
684    ///
685    /// Returns an error if:
686    /// - Required fields (`name`, `start_bit`, `length`) are missing
687    /// - Validation fails (same as `Signal::validate()`)
688    pub fn validate(&self) -> Result<(), Error> {
689        let name = self
690            .name
691            .as_ref()
692            .ok_or_else(|| Error::Signal(messages::SIGNAL_NAME_EMPTY.to_string()))?;
693        let start_bit = self
694            .start_bit
695            .ok_or_else(|| Error::Signal(messages::SIGNAL_START_BIT_REQUIRED.to_string()))?;
696        let length = self
697            .length
698            .ok_or_else(|| Error::Signal(messages::SIGNAL_LENGTH_REQUIRED.to_string()))?;
699
700        Signal::validate(name.as_ref(), start_bit, length, self.min, self.max)
701    }
702
703    /// Build the `Signal` from the builder
704    ///
705    /// # Errors
706    ///
707    /// Returns an error if:
708    /// - Required fields (`name`, `start_bit`, `length`) are missing
709    /// - Validation fails (same validation logic as the internal constructor)
710    pub fn build(self) -> Result<Signal, Error> {
711        let name = self
712            .name
713            .ok_or_else(|| Error::Signal(messages::SIGNAL_NAME_EMPTY.to_string()))?;
714        let start_bit = self
715            .start_bit
716            .ok_or_else(|| Error::Signal(messages::SIGNAL_START_BIT_REQUIRED.to_string()))?;
717        let length = self
718            .length
719            .ok_or_else(|| Error::Signal(messages::SIGNAL_LENGTH_REQUIRED.to_string()))?;
720
721        Signal::new(
722            name.as_ref(),
723            start_bit,
724            length,
725            self.byte_order,
726            self.unsigned,
727            self.factor,
728            self.offset,
729            self.min,
730            self.max,
731            self.unit.as_ref().map(|s| s.as_ref()),
732            self.receivers,
733        )
734    }
735}
736
737#[cfg(test)]
738mod tests {
739    use super::*;
740    use crate::Error;
741    use crate::error::lang;
742    extern crate std;
743
744    #[test]
745    fn test_signal_new_valid() {
746        let signal = Signal::new(
747            "RPM",
748            0,
749            16,
750            ByteOrder::BigEndian,
751            true,
752            0.25,
753            0.0,
754            0.0,
755            8000.0,
756            Some("rpm" as &str),
757            Receivers::Broadcast,
758        )
759        .unwrap();
760        assert_eq!(signal.name(), "RPM");
761        assert_eq!(signal.start_bit(), 0);
762        assert_eq!(signal.length(), 16);
763        assert_eq!(signal.byte_order(), ByteOrder::BigEndian);
764        assert!(signal.is_unsigned());
765        assert_eq!(signal.factor(), 0.25);
766        assert_eq!(signal.offset(), 0.0);
767        assert_eq!(signal.min(), 0.0);
768        assert_eq!(signal.max(), 8000.0);
769        assert_eq!(signal.unit(), Some("rpm"));
770        assert_eq!(signal.receivers(), &Receivers::Broadcast);
771    }
772
773    #[test]
774    fn test_signal_new_empty_name() {
775        let result = Signal::new(
776            "",
777            0,
778            16,
779            ByteOrder::BigEndian,
780            true,
781            1.0,
782            0.0,
783            0.0,
784            100.0,
785            None::<&str>,
786            Receivers::None,
787        );
788        assert!(result.is_err());
789        match result.unwrap_err() {
790            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_NAME_EMPTY)),
791            _ => panic!("Expected Signal error"),
792        }
793    }
794
795    #[test]
796    fn test_signal_new_zero_length() {
797        let result = Signal::new(
798            "Test",
799            0,
800            0,
801            ByteOrder::BigEndian,
802            true,
803            1.0,
804            0.0,
805            0.0,
806            100.0,
807            None::<&str>,
808            Receivers::None,
809        );
810        assert!(result.is_err());
811        match result.unwrap_err() {
812            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)),
813            _ => panic!("Expected Signal error"),
814        }
815    }
816
817    #[test]
818    fn test_signal_new_length_too_large() {
819        let result = Signal::new(
820            "Test",
821            0,
822            65,
823            ByteOrder::BigEndian,
824            true,
825            1.0,
826            0.0,
827            0.0,
828            100.0,
829            None::<&str>,
830            Receivers::None,
831        );
832        assert!(result.is_err());
833        match result.unwrap_err() {
834            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)),
835            _ => panic!("Expected Signal error"),
836        }
837    }
838
839    #[test]
840    fn test_signal_new_overflow() {
841        let result = Signal::new(
842            "Test",
843            60,
844            10,
845            ByteOrder::BigEndian,
846            true,
847            1.0,
848            0.0,
849            0.0,
850            100.0,
851            None::<&str>,
852            Receivers::None,
853        );
854        assert!(result.is_err());
855        match result.unwrap_err() {
856            Error::Signal(msg) => {
857                // Check for format template text (language-agnostic) - extract text before first placeholder
858                let template_text =
859                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_CAN.split("{}").next().unwrap();
860                assert!(msg.contains(template_text.trim_end()));
861            }
862            _ => panic!("Expected Signal error"),
863        }
864    }
865
866    #[test]
867    fn test_signal_new_invalid_range() {
868        let result = Signal::new(
869            "Test",
870            0,
871            8,
872            ByteOrder::BigEndian,
873            true,
874            1.0,
875            0.0,
876            100.0,
877            50.0,
878            None::<&str>,
879            Receivers::None,
880        );
881        assert!(result.is_err());
882        match result.unwrap_err() {
883            Error::Signal(msg) => {
884                // Check for format template text (language-agnostic) - extract text before first placeholder
885                let template_text = lang::FORMAT_INVALID_RANGE.split("{}").next().unwrap();
886                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
887            }
888            _ => panic!("Expected Signal error"),
889        }
890    }
891
892    #[test]
893    fn test_signal_new_max_boundary() {
894        // Test that 64 bits at position 0 is valid
895        let signal = Signal::new(
896            "FullMessage",
897            0,
898            64,
899            ByteOrder::BigEndian,
900            true,
901            1.0,
902            0.0,
903            0.0,
904            100.0,
905            None::<&str>,
906            Receivers::None,
907        )
908        .unwrap();
909        assert_eq!(signal.length(), 64);
910    }
911
912    #[test]
913    fn test_signal_new_with_receivers() {
914        let nodes = vec!["ECM".into(), "TCM".into()];
915        let unit: Option<&str> = Some("°C");
916        let signal = Signal::new(
917            "TestSignal",
918            8,
919            16,
920            ByteOrder::LittleEndian,
921            false,
922            0.1,
923            -40.0,
924            -40.0,
925            215.0,
926            unit,
927            Receivers::Nodes(nodes),
928        )
929        .unwrap();
930        assert_eq!(signal.name(), "TestSignal");
931        assert!(!signal.is_unsigned());
932        assert_eq!(signal.unit(), Some("°C"));
933        match signal.receivers() {
934            Receivers::Nodes(n) => assert_eq!(n.len(), 2),
935            _ => panic!("Expected Nodes variant"),
936        }
937    }
938
939    #[test]
940    fn test_parse_valid_signal() {
941        let line = r#" SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
942        let sig = Signal::parse(line).unwrap();
943        assert_eq!(sig.name(), "RPM");
944        assert_eq!(sig.start_bit(), 0);
945        assert_eq!(sig.length(), 16);
946        assert_eq!(sig.byte_order(), ByteOrder::LittleEndian);
947        assert!(sig.is_unsigned());
948        assert_eq!(sig.factor(), 0.25);
949        assert_eq!(sig.offset(), 0.);
950        assert_eq!(sig.min(), 0.);
951        assert_eq!(sig.max(), 8000.);
952        assert_eq!(sig.unit(), Some("rpm"));
953        assert_eq!(sig.receivers(), &Receivers::Nodes(vec!["TCM".into()]));
954    }
955
956    #[test]
957    fn test_parse_signal_with_empty_unit_and_broadcast() {
958        let line = r#" SG_ ABSActive : 16|1@0+ (1,0) [0|1] "" *"#;
959        let sig = Signal::parse(line).unwrap();
960        assert_eq!(sig.name(), "ABSActive");
961        assert_eq!(sig.start_bit(), 16);
962        assert_eq!(sig.length(), 1);
963        assert_eq!(sig.byte_order(), ByteOrder::LittleEndian);
964        assert!(sig.is_unsigned());
965        assert_eq!(sig.factor(), 1.);
966        assert_eq!(sig.offset(), 0.);
967        assert_eq!(sig.min(), 0.);
968        assert_eq!(sig.max(), 1.);
969        assert_eq!(sig.unit(), None);
970        assert_eq!(sig.receivers(), &Receivers::Broadcast);
971    }
972
973    #[test]
974    fn test_parse_signal_with_negative_offset_and_min() {
975        let line = r#" SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM BCM"#;
976        let sig = Signal::parse(line).unwrap();
977        assert_eq!(sig.name(), "Temperature");
978        assert_eq!(sig.start_bit(), 16);
979        assert_eq!(sig.length(), 8);
980        assert_eq!(sig.byte_order(), ByteOrder::LittleEndian);
981        assert!(!sig.is_unsigned());
982        assert_eq!(sig.factor(), 1.);
983        assert_eq!(sig.offset(), -40.);
984        assert_eq!(sig.min(), -40.);
985        assert_eq!(sig.max(), 215.);
986        assert_eq!(sig.unit(), Some("°C"));
987        assert_eq!(
988            sig.receivers(),
989            &Receivers::Nodes(vec!["TCM".into(), "BCM".into()])
990        );
991    }
992
993    #[test]
994    fn test_parse_signal_with_percent_unit() {
995        let line = r#" SG_ ThrottlePosition : 24|8@0+ (0.392157,0) [0|100] "%" *"#;
996        let sig = Signal::parse(line).unwrap();
997        assert_eq!(sig.name(), "ThrottlePosition");
998        assert_eq!(sig.start_bit(), 24);
999        assert_eq!(sig.length(), 8);
1000        assert_eq!(sig.byte_order(), ByteOrder::LittleEndian);
1001        assert!(sig.is_unsigned());
1002        assert_eq!(sig.factor(), 0.392157);
1003        assert_eq!(sig.offset(), 0.);
1004        assert_eq!(sig.min(), 0.);
1005        assert_eq!(sig.max(), 100.);
1006        assert_eq!(sig.unit(), Some("%"));
1007        assert_eq!(sig.receivers(), &Receivers::Broadcast);
1008    }
1009
1010    #[test]
1011    fn test_parse_signal_missing_factors_and_limits() {
1012        // Should use default values where missing
1013        let line = r#" SG_ Simple : 10|4@0+ ( , ) [ | ] "" *"#;
1014        let sig = Signal::parse(line).unwrap();
1015        assert_eq!(sig.name(), "Simple");
1016        assert_eq!(sig.start_bit(), 10);
1017        assert_eq!(sig.length(), 4);
1018        assert_eq!(sig.byte_order(), ByteOrder::LittleEndian);
1019        assert!(sig.is_unsigned());
1020        assert_eq!(sig.factor(), 0.);
1021        assert_eq!(sig.offset(), 0.);
1022        assert_eq!(sig.min(), 0.);
1023        assert_eq!(sig.max(), 0.);
1024        assert_eq!(sig.unit(), None);
1025        assert_eq!(sig.receivers(), &Receivers::Broadcast);
1026    }
1027
1028    #[test]
1029    fn test_parse_signal_missing_start_bit() {
1030        let line = r#" SG_ RPM : |16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
1031        let err = Signal::parse(line).unwrap_err();
1032        match err {
1033            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_PARSE_INVALID_START_BIT)),
1034            _ => panic!("Expected Signal error"),
1035        }
1036    }
1037
1038    #[test]
1039    fn test_parse_signal_invalid_range() {
1040        // min > max should fail validation
1041        let line = r#" SG_ Test : 0|8@0+ (1,0) [100|50] "unit" *"#;
1042        let err = Signal::parse(line).unwrap_err();
1043        match err {
1044            Error::Signal(msg) => {
1045                // Check for format template text (language-agnostic) - extract text before first placeholder
1046                let template_text = lang::FORMAT_INVALID_RANGE.split("{}").next().unwrap();
1047                assert!(msg.contains(template_text.trim_end_matches(':').trim_end()));
1048            }
1049            _ => panic!("Expected Signal error"),
1050        }
1051    }
1052
1053    #[test]
1054    fn test_parse_signal_overflow() {
1055        // start_bit + length > 64 should fail validation
1056        let line = r#" SG_ Test : 60|10@0+ (1,0) [0|100] "unit" *"#;
1057        let err = Signal::parse(line).unwrap_err();
1058        match err {
1059            Error::Signal(msg) => {
1060                // Check for format template text (language-agnostic) - extract text before first placeholder
1061                let template_text =
1062                    lang::FORMAT_SIGNAL_EXTENDS_BEYOND_CAN.split("{}").next().unwrap();
1063                assert!(msg.contains(template_text.trim_end()));
1064            }
1065            _ => panic!("Expected Signal error"),
1066        }
1067    }
1068
1069    #[test]
1070    fn test_parse_signal_length_too_large() {
1071        // length > 64 should fail validation
1072        let line = r#" SG_ Test : 0|65@0+ (1,0) [0|100] "unit" *"#;
1073        let err = Signal::parse(line).unwrap_err();
1074        match err {
1075            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)),
1076            _ => panic!("Expected Signal error"),
1077        }
1078    }
1079
1080    #[test]
1081    fn test_parse_signal_zero_length() {
1082        // length = 0 should fail validation
1083        let line = r#" SG_ Test : 0|0@0+ (1,0) [0|100] "unit" *"#;
1084        let err = Signal::parse(line).unwrap_err();
1085        match err {
1086            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)),
1087            _ => panic!("Expected Signal error"),
1088        }
1089    }
1090
1091    #[test]
1092    fn test_parse_signal_missing_length() {
1093        let line = r#" SG_ RPM : 0|@0+ (0.25,0) [0|8000] "rpm" TCM"#;
1094        let err = Signal::parse(line).unwrap_err();
1095        match err {
1096            Error::Signal(msg) => assert!(msg.contains(lang::SIGNAL_PARSE_INVALID_LENGTH)),
1097            _ => panic!("Expected Signal error"),
1098        }
1099    }
1100
1101    #[test]
1102    fn test_signal_to_dbc_string() {
1103        // Test with Broadcast receiver
1104        let signal1 = Signal::new(
1105            "RPM",
1106            0,
1107            16,
1108            ByteOrder::BigEndian,
1109            true,
1110            0.25,
1111            0.0,
1112            0.0,
1113            8000.0,
1114            Some("rpm" as &str),
1115            Receivers::Broadcast,
1116        )
1117        .unwrap();
1118        assert_eq!(
1119            signal1.to_dbc_string(),
1120            " SG_ RPM : 0|16@1+ (0.25,0) [0|8000] \"rpm\" *"
1121        );
1122
1123        // Test with Nodes receiver
1124        let signal2 = Signal::new(
1125            "Temperature",
1126            16,
1127            8,
1128            ByteOrder::LittleEndian,
1129            false,
1130            1.0,
1131            -40.0,
1132            -40.0,
1133            215.0,
1134            Some("°C" as &str),
1135            Receivers::Nodes(vec!["TCM".into(), "BCM".into()]),
1136        )
1137        .unwrap();
1138        assert_eq!(
1139            signal2.to_dbc_string(),
1140            " SG_ Temperature : 16|8@0- (1,-40) [-40|215] \"°C\" TCM BCM"
1141        );
1142
1143        // Test with None receiver and empty unit
1144        let signal3 = Signal::new(
1145            "Flag",
1146            24,
1147            1,
1148            ByteOrder::BigEndian,
1149            true,
1150            1.0,
1151            0.0,
1152            0.0,
1153            1.0,
1154            None::<&str>,
1155            Receivers::None,
1156        )
1157        .unwrap();
1158        assert_eq!(
1159            signal3.to_dbc_string(),
1160            " SG_ Flag : 24|1@1+ (1,0) [0|1] \"\""
1161        );
1162    }
1163}