dbc_rs/signal/
core.rs

1use super::Signal;
2use crate::{ByteOrder, Error, Receivers, Result};
3
4#[cfg(feature = "std")]
5use crate::MAX_NAME_SIZE;
6#[cfg(feature = "std")]
7use crate::compat::String;
8
9impl Signal {
10    pub(crate) fn validate(name: &str, length: u16, min: f64, max: f64) -> Result<()> {
11        if name.trim().is_empty() {
12            return Err(Error::Validation(Error::SIGNAL_NAME_EMPTY));
13        }
14
15        // Validate length: must be between 1 and 512 bits
16        // - Classic CAN (2.0A/2.0B): DLC up to 8 bytes (64 bits)
17        // - CAN FD: DLC up to 64 bytes (512 bits)
18        // Signal length is validated against message DLC in Message::validate
19        // Note: name is parsed before this validation, so we can include it in error messages
20        if length == 0 {
21            return Err(Error::Validation(Error::SIGNAL_LENGTH_TOO_SMALL));
22        }
23        if length > 512 {
24            return Err(Error::Validation(Error::SIGNAL_LENGTH_TOO_LARGE));
25        }
26
27        // Note: start_bit validation (boundary checks and overlap detection) is done in
28        // Message::validate, not here, because:
29        // 1. The actual message size depends on DLC (1-64 bytes for CAN FD)
30        // 2. Overlap detection requires comparing multiple signals
31        // 3. This allows signals to be created independently and validated when added to a message
32
33        // Validate min <= max
34        if min > max {
35            return Err(Error::Validation(Error::INVALID_RANGE));
36        }
37
38        Ok(())
39    }
40
41    #[cfg(feature = "std")]
42    #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
43    pub(crate) fn new(
44        name: String<{ MAX_NAME_SIZE }>,
45        start_bit: u16,
46        length: u16,
47        byte_order: ByteOrder,
48        unsigned: bool,
49        factor: f64,
50        offset: f64,
51        min: f64,
52        max: f64,
53        unit: Option<String<{ MAX_NAME_SIZE }>>,
54        receivers: Receivers,
55    ) -> Self {
56        // Validation should have been done prior (by builder or parse)
57        Self {
58            name,
59            start_bit,
60            length,
61            byte_order,
62            unsigned,
63            factor,
64            offset,
65            min,
66            max,
67            unit,
68            receivers,
69            is_multiplexer_switch: false,
70            multiplexer_switch_value: None,
71        }
72    }
73
74    #[inline]
75    #[must_use = "return value should be checked"]
76    pub fn name(&self) -> &str {
77        self.name.as_ref()
78    }
79
80    #[inline]
81    #[must_use = "return value should be used"]
82    pub fn start_bit(&self) -> u16 {
83        self.start_bit
84    }
85
86    #[inline]
87    #[must_use = "return value should be used"]
88    pub fn length(&self) -> u16 {
89        self.length
90    }
91
92    #[inline]
93    #[must_use = "return value should be used"]
94    pub fn byte_order(&self) -> ByteOrder {
95        self.byte_order
96    }
97
98    #[inline]
99    #[must_use = "return value should be used"]
100    pub fn is_unsigned(&self) -> bool {
101        self.unsigned
102    }
103
104    #[inline]
105    #[must_use = "return value should be used"]
106    pub fn factor(&self) -> f64 {
107        self.factor
108    }
109
110    #[inline]
111    #[must_use = "return value should be used"]
112    pub fn offset(&self) -> f64 {
113        self.offset
114    }
115
116    #[inline]
117    #[must_use = "return value should be used"]
118    pub fn min(&self) -> f64 {
119        self.min
120    }
121
122    #[inline]
123    #[must_use = "return value should be used"]
124    pub fn max(&self) -> f64 {
125        self.max
126    }
127
128    #[inline]
129    #[must_use = "return value should be used"]
130    pub fn unit(&self) -> Option<&str> {
131        self.unit.as_ref().map(|u| u.as_ref())
132    }
133
134    #[inline]
135    #[must_use = "return value should be used"]
136    pub fn receivers(&self) -> &Receivers {
137        &self.receivers
138    }
139
140    /// Check if this signal is a multiplexer switch (marked with 'M')
141    #[inline]
142    #[must_use = "return value should be used"]
143    pub fn is_multiplexer_switch(&self) -> bool {
144        self.is_multiplexer_switch
145    }
146
147    /// Get the multiplexer switch value if this is a multiplexed signal (marked with 'm0', 'm1', etc.)
148    /// Returns None if this is a normal signal (not multiplexed)
149    #[inline]
150    #[must_use = "return value should be used"]
151    pub fn multiplexer_switch_value(&self) -> Option<u64> {
152        self.multiplexer_switch_value
153    }
154}
155
156#[inline]
157fn canonical_f64_bits(v: f64) -> u64 {
158    // Ensure Hash/Eq are consistent and satisfy the contracts:
159
160    // - Treat -0.0 and 0.0 as equal (and hash identically)
161
162    // - Treat NaN values as equal (and hash identically)
163
164    if v == 0.0 {
165        0.0f64.to_bits()
166    } else if v.is_nan() {
167        f64::NAN.to_bits()
168    } else {
169        v.to_bits()
170    }
171}
172
173impl PartialEq for Signal {
174    fn eq(&self, other: &Self) -> bool {
175        self.name == other.name
176            && self.start_bit == other.start_bit
177            && self.length == other.length
178            && self.byte_order == other.byte_order
179            && self.unsigned == other.unsigned
180            && canonical_f64_bits(self.factor) == canonical_f64_bits(other.factor)
181            && canonical_f64_bits(self.offset) == canonical_f64_bits(other.offset)
182            && canonical_f64_bits(self.min) == canonical_f64_bits(other.min)
183            && canonical_f64_bits(self.max) == canonical_f64_bits(other.max)
184            && self.unit == other.unit
185            && self.receivers == other.receivers
186            && self.is_multiplexer_switch == other.is_multiplexer_switch
187            && self.multiplexer_switch_value == other.multiplexer_switch_value
188    }
189}
190
191// Custom Eq implementation that handles f64 (treats NaN as equal to NaN, and -0.0 == 0.0)
192impl Eq for Signal {}
193
194// Custom Hash implementation that handles f64 (treats NaN consistently)
195impl core::hash::Hash for Signal {
196    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
197        self.name.hash(state);
198        self.start_bit.hash(state);
199        self.length.hash(state);
200        self.byte_order.hash(state);
201        self.unsigned.hash(state);
202        // Handle f64: convert to bits for hashing (NaN will have consistent representation)
203        canonical_f64_bits(self.factor).hash(state);
204        canonical_f64_bits(self.offset).hash(state);
205        canonical_f64_bits(self.min).hash(state);
206        canonical_f64_bits(self.max).hash(state);
207        self.unit.hash(state);
208        self.receivers.hash(state);
209        self.is_multiplexer_switch.hash(state);
210        self.multiplexer_switch_value.hash(state);
211    }
212}