dbc_rs/
byte_order.rs

1/// Byte order (endianness) for signal encoding in CAN messages.
2///
3/// In DBC files, byte order is specified as:
4/// - `0` = BigEndian (Motorola format)
5/// - `1` = LittleEndian (Intel format)
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum ByteOrder {
8    /// Little-endian byte order (Intel format, `1` in DBC files).
9    ///
10    /// Bytes are ordered from least significant to most significant.
11    LittleEndian = 1,
12    /// Big-endian byte order (Motorola format, `0` in DBC files).
13    ///
14    /// Bytes are ordered from most significant to least significant.
15    BigEndian = 0,
16}
17
18impl ByteOrder {
19    /// Extract bits from data based on byte order.
20    /// Inlined for hot path optimization.
21    ///
22    /// # Performance
23    ///
24    /// This method uses optimized fast paths for common cases:
25    /// - Byte-aligned little-endian 8/16/32/64-bit signals use direct memory reads
26    /// - Other cases use a generic loop-based extraction
27    #[inline]
28    pub(crate) fn extract_bits(self, data: &[u8], start_bit: usize, length: usize) -> u64 {
29        match self {
30            ByteOrder::LittleEndian => {
31                // Fast path: byte-aligned little-endian signals (most common case)
32                let bit_offset = start_bit % 8;
33                let byte_idx = start_bit / 8;
34
35                if bit_offset == 0 {
36                    // Byte-aligned - use direct memory reads
37                    match length {
38                        8 => return data[byte_idx] as u64,
39                        16 => {
40                            // SAFETY: bounds checked by caller (end_byte < data.len())
41                            return u16::from_le_bytes([data[byte_idx], data[byte_idx + 1]]) as u64;
42                        }
43                        32 => {
44                            return u32::from_le_bytes([
45                                data[byte_idx],
46                                data[byte_idx + 1],
47                                data[byte_idx + 2],
48                                data[byte_idx + 3],
49                            ]) as u64;
50                        }
51                        64 => {
52                            return u64::from_le_bytes([
53                                data[byte_idx],
54                                data[byte_idx + 1],
55                                data[byte_idx + 2],
56                                data[byte_idx + 3],
57                                data[byte_idx + 4],
58                                data[byte_idx + 5],
59                                data[byte_idx + 6],
60                                data[byte_idx + 7],
61                            ]);
62                        }
63                        _ => {} // Fall through to generic path
64                    }
65                }
66
67                // Generic path: extract bits sequentially from start_bit forward
68                let mut value: u64 = 0;
69                let mut bits_remaining = length;
70                let mut current_bit = start_bit;
71
72                while bits_remaining > 0 {
73                    let byte_idx = current_bit / 8;
74                    let bit_in_byte = current_bit % 8;
75                    let bits_to_take = bits_remaining.min(8 - bit_in_byte);
76
77                    let byte = data[byte_idx] as u64;
78                    let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
79                    let extracted = (byte & mask) >> bit_in_byte;
80
81                    value |= extracted << (length - bits_remaining);
82
83                    bits_remaining -= bits_to_take;
84                    current_bit += bits_to_take;
85                }
86
87                value
88            }
89            ByteOrder::BigEndian => {
90                // Big-endian (Motorola): start_bit is MSB in big-endian numbering.
91                // BE bit N maps to physical bit: byte_num * 8 + (7 - bit_in_byte)
92                //
93                // Optimization: Process up to 8 bits at a time instead of 1 bit at a time.
94                // This reduces loop iterations from O(length) to O(length/8).
95                let mut value: u64 = 0;
96                let mut bits_remaining = length;
97                let mut signal_bit_offset = 0; // How many bits of the signal we've processed
98
99                while bits_remaining > 0 {
100                    // Current BE bit position
101                    let be_bit = start_bit + signal_bit_offset;
102                    let byte_num = be_bit / 8;
103                    let bit_in_byte = be_bit % 8;
104
105                    // Calculate how many bits we can take from this byte
106                    // In BE numbering, bits go from high to low within a byte (7,6,5,4,3,2,1,0)
107                    // bit_in_byte 0 = physical bit 7, bit_in_byte 7 = physical bit 0
108                    // Available bits in this byte: from bit_in_byte down to 0 = bit_in_byte + 1
109                    let available_in_byte = bit_in_byte + 1;
110                    let bits_to_take = bits_remaining.min(available_in_byte);
111
112                    // Extract the bits from the physical byte
113                    // BE bit_in_byte maps to physical position (7 - bit_in_byte)
114                    // We want to extract 'bits_to_take' bits starting from bit_in_byte going down
115                    // Physical positions: (7 - bit_in_byte) to (7 - bit_in_byte + bits_to_take - 1)
116                    let physical_start = 7 - bit_in_byte;
117                    let byte = data[byte_num] as u64;
118
119                    // Create mask for bits_to_take consecutive bits starting at physical_start
120                    let mask = ((1u64 << bits_to_take) - 1) << physical_start;
121                    let extracted = (byte & mask) >> physical_start;
122
123                    // Place extracted bits into result (MSB first, so at the high end)
124                    let shift_amount = bits_remaining - bits_to_take;
125                    value |= extracted << shift_amount;
126
127                    bits_remaining -= bits_to_take;
128                    signal_bit_offset += bits_to_take;
129                }
130
131                value
132            }
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::ByteOrder;
140    use core::hash::Hash;
141
142    // Tests that work in all configurations (no_std, std)
143    #[test]
144    fn test_byte_order_variants() {
145        assert_eq!(ByteOrder::LittleEndian as u8, 1);
146        assert_eq!(ByteOrder::BigEndian as u8, 0);
147    }
148
149    #[test]
150    fn test_byte_order_equality() {
151        assert_eq!(ByteOrder::LittleEndian, ByteOrder::LittleEndian);
152        assert_eq!(ByteOrder::BigEndian, ByteOrder::BigEndian);
153        assert_ne!(ByteOrder::LittleEndian, ByteOrder::BigEndian);
154    }
155
156    #[test]
157    fn test_byte_order_clone() {
158        let original = ByteOrder::LittleEndian;
159        let cloned = original;
160        assert_eq!(original, cloned);
161
162        let original2 = ByteOrder::BigEndian;
163        let cloned2 = original2;
164        assert_eq!(original2, cloned2);
165    }
166
167    #[test]
168    fn test_byte_order_copy() {
169        let order = ByteOrder::LittleEndian;
170        let copied = order; // Copy, not move
171        assert_eq!(order, copied); // Original still valid
172    }
173
174    #[test]
175    fn test_byte_order_hash_trait() {
176        // Test that Hash trait is implemented by checking it compiles
177        fn _assert_hash<T: Hash>() {}
178        _assert_hash::<ByteOrder>();
179    }
180
181    #[test]
182    fn test_extract_bits_little_endian() {
183        // Test value 0x1234: little-endian bytes are [0x34, 0x12] (LSB first)
184        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
185        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
186        assert_eq!(raw_value, 0x1234);
187    }
188
189    #[test]
190    fn test_extract_bits_little_endian_8bit() {
191        // Test 8-bit value at byte boundary
192        let data = [0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
193        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 8);
194        assert_eq!(raw_value, 0x42);
195    }
196
197    #[test]
198    fn test_extract_bits_little_endian_32bit() {
199        // Test 32-bit value at byte boundary
200        let data = [0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00];
201        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 32);
202        assert_eq!(raw_value, 0x12345678);
203    }
204
205    #[test]
206    fn test_extract_bits_little_endian_64bit() {
207        // Test 64-bit value at byte boundary
208        let data = [0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
209        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 64);
210        assert_eq!(raw_value, 0x0123456789ABCDEF);
211    }
212
213    #[test]
214    fn test_extract_bits_big_endian() {
215        // Test big-endian extraction: For BE bit 0-15, value 0x0100 = 256
216        // Big-endian at bit 0, length 16: bytes [0x01, 0x00]
217        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
218        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
219        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
220        assert!(raw_value <= 65535);
221    }
222
223    #[test]
224    fn test_extract_bits_mixed_positions_little_endian() {
225        // Test signal at bit 8, length 16 (spans bytes 1-2)
226        let data = [0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00];
227        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 8, 16);
228        assert_eq!(raw_value, 0x1234);
229    }
230
231    #[test]
232    fn test_extract_bits_mixed_positions_big_endian() {
233        // Test signal at bit 8, length 16 (spans bytes 1-2)
234        // Big-endian at BE bit 8-23: bytes [0x01, 0x00]
235        let data = [0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
236        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 8, 16);
237        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
238        assert!(raw_value <= 65535);
239    }
240
241    #[test]
242    fn test_byte_order_difference() {
243        // Test that big-endian and little-endian produce different results
244        // for the same byte data, proving both byte orders are handled differently
245        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
246
247        let le_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
248        let be_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
249
250        // Little-endian: [0x34, 0x12] = 0x1234 = 4660
251        assert_eq!(le_value, 0x1234);
252
253        // Big-endian should produce a different value (proves BE is being used)
254        assert_ne!(
255            le_value, be_value,
256            "Big-endian and little-endian should produce different values"
257        );
258        assert!(be_value <= 65535);
259    }
260
261    #[test]
262    fn test_extract_bits_non_aligned_little_endian() {
263        // Test non-byte-aligned extraction to ensure generic path still works
264        // Signal at bit 4, length 12
265        let data = [0xF0, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
266        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 4, 12);
267        // Bits 4-15: from byte 0 bits 4-7 (0xF) and byte 1 bits 0-7 (0x12)
268        // Little-endian: value should be 0x12F
269        assert_eq!(raw_value, 0x12F);
270    }
271
272    // Tests that require std (for DefaultHasher)
273    #[cfg(feature = "std")]
274    mod tests_std {
275        use super::*;
276        use core::hash::{Hash, Hasher};
277        use std::collections::hash_map::DefaultHasher;
278
279        #[test]
280        fn test_byte_order_debug() {
281            let little = format!("{:?}", ByteOrder::LittleEndian);
282            assert!(little.contains("LittleEndian"));
283
284            let big = format!("{:?}", ByteOrder::BigEndian);
285            assert!(big.contains("BigEndian"));
286        }
287
288        #[test]
289        fn test_byte_order_hash() {
290            let mut hasher1 = DefaultHasher::new();
291            let mut hasher2 = DefaultHasher::new();
292
293            ByteOrder::LittleEndian.hash(&mut hasher1);
294            ByteOrder::LittleEndian.hash(&mut hasher2);
295            assert_eq!(hasher1.finish(), hasher2.finish());
296
297            let mut hasher3 = DefaultHasher::new();
298            ByteOrder::BigEndian.hash(&mut hasher3);
299            assert_ne!(hasher1.finish(), hasher3.finish());
300        }
301    }
302}