dbc_rs/signal/decode.rs
1use super::Signal;
2use crate::{ByteOrder, Error, Result};
3
4impl Signal {
5 /// Decode the signal value from CAN message data bytes.
6 ///
7 /// Extracts the raw value from bits based on the signal's start bit, length, and byte order,
8 /// then applies factor and offset to return the physical/engineering value.
9 ///
10 /// # Arguments
11 ///
12 /// * `data` - The CAN message data bytes (up to 8 bytes for classic CAN, 64 for CAN FD)
13 ///
14 /// # Returns
15 ///
16 /// * `Ok(f64)` - The physical value (raw * factor + offset)
17 /// * `Err(Error)` - If the signal extends beyond the data length
18 ///
19 /// # Examples
20 ///
21 /// ```rust,no_run
22 /// use dbc_rs::Dbc;
23 ///
24 /// let dbc = Dbc::parse(r#"VERSION "1.0"
25 ///
26 /// BU_: ECM
27 ///
28 /// BO_ 256 Engine : 8 ECM
29 /// SG_ RPM : 0|16@1+ (0.25,0) [0|8000] "rpm" *
30 /// "#)?;
31 ///
32 /// let message = dbc.messages().at(0).unwrap();
33 /// let signal = message.signals().at(0).unwrap();
34 ///
35 /// // Decode a CAN message with RPM value of 2000 (raw: 8000)
36 /// let data = [0x20, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
37 /// let rpm = signal.decode(&data)?;
38 /// assert_eq!(rpm, 2000.0);
39 /// # Ok::<(), dbc_rs::Error>(())
40 /// ```
41 /// Decode signal value from CAN payload - optimized for high-throughput decoding.
42 #[inline]
43 pub fn decode(&self, data: &[u8]) -> Result<f64> {
44 // Cache conversions to usize (common in hot path)
45 let start_bit = self.start_bit as usize;
46 let length = self.length as usize;
47 let end_byte = (start_bit + length - 1) / 8;
48
49 // Bounds check - early return for invalid signals
50 if end_byte >= data.len() {
51 return Err(Error::Decoding(Error::SIGNAL_EXTENDS_BEYOND_DATA));
52 }
53
54 // Extract bits based on byte order
55 let raw_value = match self.byte_order {
56 ByteOrder::LittleEndian => Self::extract_bits_little_endian(data, start_bit, length),
57 ByteOrder::BigEndian => Self::extract_bits_big_endian(data, start_bit, length),
58 };
59
60 // Convert to signed/unsigned with optimized sign extension
61 let value = if self.unsigned {
62 raw_value as i64
63 } else {
64 // Sign extend for signed values
65 // Optimized: compute sign bit mask only once
66 let sign_bit_mask = 1u64 << (length - 1);
67 if (raw_value & sign_bit_mask) != 0 {
68 // Negative value - sign extend using bitwise mask
69 let mask = !((1u64 << length) - 1);
70 (raw_value | mask) as i64
71 } else {
72 raw_value as i64
73 }
74 };
75
76 // Apply factor and offset to get physical value (single mul-add operation)
77 Ok((value as f64) * self.factor + self.offset)
78 }
79
80 /// Extract bits from data using little-endian byte order.
81 /// Inlined for hot path optimization.
82 #[inline]
83 fn extract_bits_little_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
84 let mut value: u64 = 0;
85 let mut bits_remaining = length;
86 let mut current_bit = start_bit;
87
88 while bits_remaining > 0 {
89 let byte_idx = current_bit / 8;
90 let bit_in_byte = current_bit % 8;
91 let bits_to_take = bits_remaining.min(8 - bit_in_byte);
92
93 let byte = data[byte_idx] as u64;
94 let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
95 let extracted = (byte & mask) >> bit_in_byte;
96
97 value |= extracted << (length - bits_remaining);
98
99 bits_remaining -= bits_to_take;
100 current_bit += bits_to_take;
101 }
102
103 value
104 }
105
106 /// Extract bits from data using big-endian byte order.
107 /// Inlined for hot path optimization.
108 #[inline]
109 fn extract_bits_big_endian(data: &[u8], start_bit: usize, length: usize) -> u64 {
110 let mut value: u64 = 0;
111 let mut bits_remaining = length;
112 let mut current_bit = start_bit;
113
114 while bits_remaining > 0 {
115 let byte_idx = current_bit / 8;
116 let bit_in_byte = current_bit % 8;
117 let bits_to_take = bits_remaining.min(8 - bit_in_byte);
118
119 let byte = data[byte_idx] as u64;
120 let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
121 let extracted = (byte & mask) >> bit_in_byte;
122
123 // For big-endian, we need to reverse the bit order within bytes
124 // and place bits in the correct position
125 value |= extracted << (length - bits_remaining);
126
127 bits_remaining -= bits_to_take;
128 current_bit += bits_to_take;
129 }
130
131 value
132 }
133}