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    /// Insert bits into data based on byte order.
137    /// This is the inverse of `extract_bits` - used for encoding signals.
138    /// Inlined for hot path optimization.
139    ///
140    /// # Arguments
141    ///
142    /// * `data` - Mutable byte slice to write into
143    /// * `start_bit` - Starting bit position (LSB for LE, MSB for BE)
144    /// * `length` - Number of bits to write
145    /// * `value` - The value to insert (must fit in `length` bits)
146    #[inline]
147    pub(crate) fn insert_bits(self, data: &mut [u8], start_bit: usize, length: usize, value: u64) {
148        match self {
149            ByteOrder::LittleEndian => {
150                // Fast path: byte-aligned little-endian signals (most common case)
151                let bit_offset = start_bit % 8;
152                let byte_idx = start_bit / 8;
153
154                if bit_offset == 0 {
155                    // Byte-aligned - use direct memory writes
156                    match length {
157                        8 => {
158                            data[byte_idx] = value as u8;
159                            return;
160                        }
161                        16 => {
162                            let bytes = (value as u16).to_le_bytes();
163                            data[byte_idx] = bytes[0];
164                            data[byte_idx + 1] = bytes[1];
165                            return;
166                        }
167                        32 => {
168                            let bytes = (value as u32).to_le_bytes();
169                            data[byte_idx] = bytes[0];
170                            data[byte_idx + 1] = bytes[1];
171                            data[byte_idx + 2] = bytes[2];
172                            data[byte_idx + 3] = bytes[3];
173                            return;
174                        }
175                        64 => {
176                            let bytes = value.to_le_bytes();
177                            data[byte_idx..byte_idx + 8].copy_from_slice(&bytes);
178                            return;
179                        }
180                        _ => {} // Fall through to generic path
181                    }
182                }
183
184                // Generic path: insert bits sequentially from start_bit forward
185                let mut bits_remaining = length;
186                let mut current_bit = start_bit;
187                let mut value_offset = 0;
188
189                while bits_remaining > 0 {
190                    let byte_idx = current_bit / 8;
191                    let bit_in_byte = current_bit % 8;
192                    let bits_to_write = bits_remaining.min(8 - bit_in_byte);
193
194                    // Extract the bits from value that we want to write
195                    let bits_mask = (1u64 << bits_to_write) - 1;
196                    let bits_to_insert = ((value >> value_offset) & bits_mask) as u8;
197
198                    // Create mask for the target position in the byte
199                    let target_mask = (bits_mask as u8) << bit_in_byte;
200
201                    // Clear the target bits and set the new value
202                    data[byte_idx] =
203                        (data[byte_idx] & !target_mask) | (bits_to_insert << bit_in_byte);
204
205                    bits_remaining -= bits_to_write;
206                    current_bit += bits_to_write;
207                    value_offset += bits_to_write;
208                }
209            }
210            ByteOrder::BigEndian => {
211                // Big-endian (Motorola): start_bit is MSB in big-endian numbering.
212                // BE bit N maps to physical bit: byte_num * 8 + (7 - bit_in_byte)
213                let mut bits_remaining = length;
214                let mut signal_bit_offset = 0; // How many bits of the signal we've processed
215
216                while bits_remaining > 0 {
217                    // Current BE bit position
218                    let be_bit = start_bit + signal_bit_offset;
219                    let byte_num = be_bit / 8;
220                    let bit_in_byte = be_bit % 8;
221
222                    // Calculate how many bits we can write to this byte
223                    // In BE numbering, bits go from high to low within a byte (7,6,5,4,3,2,1,0)
224                    let available_in_byte = bit_in_byte + 1;
225                    let bits_to_write = bits_remaining.min(available_in_byte);
226
227                    // Calculate physical position in byte
228                    // BE bit_in_byte maps to physical position (7 - bit_in_byte)
229                    let physical_start = 7 - bit_in_byte;
230
231                    // Extract the bits from value (MSB first, so from the high end)
232                    let shift_amount = bits_remaining - bits_to_write;
233                    let bits_mask = (1u64 << bits_to_write) - 1;
234                    let bits_to_insert = ((value >> shift_amount) & bits_mask) as u8;
235
236                    // Create mask for the target position in the byte
237                    let target_mask = (bits_mask as u8) << physical_start;
238
239                    // Clear the target bits and set the new value
240                    data[byte_num] =
241                        (data[byte_num] & !target_mask) | (bits_to_insert << physical_start);
242
243                    bits_remaining -= bits_to_write;
244                    signal_bit_offset += bits_to_write;
245                }
246            }
247        }
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::ByteOrder;
254    use core::hash::Hash;
255
256    // Tests that work in all configurations (no_std, std)
257    #[test]
258    fn test_byte_order_variants() {
259        assert_eq!(ByteOrder::LittleEndian as u8, 1);
260        assert_eq!(ByteOrder::BigEndian as u8, 0);
261    }
262
263    #[test]
264    fn test_byte_order_equality() {
265        assert_eq!(ByteOrder::LittleEndian, ByteOrder::LittleEndian);
266        assert_eq!(ByteOrder::BigEndian, ByteOrder::BigEndian);
267        assert_ne!(ByteOrder::LittleEndian, ByteOrder::BigEndian);
268    }
269
270    #[test]
271    fn test_byte_order_clone() {
272        let original = ByteOrder::LittleEndian;
273        let cloned = original;
274        assert_eq!(original, cloned);
275
276        let original2 = ByteOrder::BigEndian;
277        let cloned2 = original2;
278        assert_eq!(original2, cloned2);
279    }
280
281    #[test]
282    fn test_byte_order_copy() {
283        let order = ByteOrder::LittleEndian;
284        let copied = order; // Copy, not move
285        assert_eq!(order, copied); // Original still valid
286    }
287
288    #[test]
289    fn test_byte_order_hash_trait() {
290        // Test that Hash trait is implemented by checking it compiles
291        fn _assert_hash<T: Hash>() {}
292        _assert_hash::<ByteOrder>();
293    }
294
295    #[test]
296    fn test_extract_bits_little_endian() {
297        // Test value 0x1234: little-endian bytes are [0x34, 0x12] (LSB first)
298        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
299        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
300        assert_eq!(raw_value, 0x1234);
301    }
302
303    #[test]
304    fn test_extract_bits_little_endian_8bit() {
305        // Test 8-bit value at byte boundary
306        let data = [0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
307        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 8);
308        assert_eq!(raw_value, 0x42);
309    }
310
311    #[test]
312    fn test_extract_bits_little_endian_32bit() {
313        // Test 32-bit value at byte boundary
314        let data = [0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00];
315        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 32);
316        assert_eq!(raw_value, 0x12345678);
317    }
318
319    #[test]
320    fn test_extract_bits_little_endian_64bit() {
321        // Test 64-bit value at byte boundary
322        let data = [0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
323        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 64);
324        assert_eq!(raw_value, 0x0123456789ABCDEF);
325    }
326
327    #[test]
328    fn test_extract_bits_big_endian() {
329        // Test big-endian extraction: For BE bit 0-15, value 0x0100 = 256
330        // Big-endian at bit 0, length 16: bytes [0x01, 0x00]
331        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
332        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
333        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
334        assert!(raw_value <= 65535);
335    }
336
337    #[test]
338    fn test_extract_bits_mixed_positions_little_endian() {
339        // Test signal at bit 8, length 16 (spans bytes 1-2)
340        let data = [0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00];
341        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 8, 16);
342        assert_eq!(raw_value, 0x1234);
343    }
344
345    #[test]
346    fn test_extract_bits_mixed_positions_big_endian() {
347        // Test signal at bit 8, length 16 (spans bytes 1-2)
348        // Big-endian at BE bit 8-23: bytes [0x01, 0x00]
349        let data = [0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
350        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 8, 16);
351        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
352        assert!(raw_value <= 65535);
353    }
354
355    #[test]
356    fn test_byte_order_difference() {
357        // Test that big-endian and little-endian produce different results
358        // for the same byte data, proving both byte orders are handled differently
359        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
360
361        let le_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
362        let be_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
363
364        // Little-endian: [0x34, 0x12] = 0x1234 = 4660
365        assert_eq!(le_value, 0x1234);
366
367        // Big-endian should produce a different value (proves BE is being used)
368        assert_ne!(
369            le_value, be_value,
370            "Big-endian and little-endian should produce different values"
371        );
372        assert!(be_value <= 65535);
373    }
374
375    #[test]
376    fn test_extract_bits_non_aligned_little_endian() {
377        // Test non-byte-aligned extraction to ensure generic path still works
378        // Signal at bit 4, length 12
379        let data = [0xF0, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
380        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 4, 12);
381        // Bits 4-15: from byte 0 bits 4-7 (0xF) and byte 1 bits 0-7 (0x12)
382        // Little-endian: value should be 0x12F
383        assert_eq!(raw_value, 0x12F);
384    }
385
386    // ============================================================================
387    // insert_bits tests
388    // ============================================================================
389
390    #[test]
391    fn test_insert_bits_little_endian_8bit() {
392        let mut data = [0x00; 8];
393        ByteOrder::LittleEndian.insert_bits(&mut data, 0, 8, 0x42);
394        assert_eq!(data[0], 0x42);
395    }
396
397    #[test]
398    fn test_insert_bits_little_endian_16bit() {
399        let mut data = [0x00; 8];
400        ByteOrder::LittleEndian.insert_bits(&mut data, 0, 16, 0x1234);
401        // Little-endian: LSB first
402        assert_eq!(data[0], 0x34);
403        assert_eq!(data[1], 0x12);
404    }
405
406    #[test]
407    fn test_insert_bits_little_endian_32bit() {
408        let mut data = [0x00; 8];
409        ByteOrder::LittleEndian.insert_bits(&mut data, 0, 32, 0x12345678);
410        assert_eq!(data[0], 0x78);
411        assert_eq!(data[1], 0x56);
412        assert_eq!(data[2], 0x34);
413        assert_eq!(data[3], 0x12);
414    }
415
416    #[test]
417    fn test_insert_bits_little_endian_64bit() {
418        let mut data = [0x00; 8];
419        ByteOrder::LittleEndian.insert_bits(&mut data, 0, 64, 0x0123456789ABCDEF);
420        assert_eq!(data, [0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01]);
421    }
422
423    #[test]
424    fn test_insert_bits_little_endian_non_aligned() {
425        let mut data = [0x00; 8];
426        // Insert 12 bits at bit 4
427        ByteOrder::LittleEndian.insert_bits(&mut data, 4, 12, 0x12F);
428        // Verify by extracting
429        let extracted = ByteOrder::LittleEndian.extract_bits(&data, 4, 12);
430        assert_eq!(extracted, 0x12F);
431    }
432
433    #[test]
434    fn test_insert_extract_roundtrip_little_endian() {
435        // Round-trip test: insert then extract should return same value
436        let test_cases = [
437            (0, 8, 0x42u64),
438            (0, 16, 0x1234),
439            (8, 16, 0xABCD),
440            (4, 12, 0x123),
441            (0, 32, 0x12345678),
442            (0, 64, 0x0123456789ABCDEF),
443        ];
444
445        for (start_bit, length, value) in test_cases {
446            let mut data = [0x00; 8];
447            ByteOrder::LittleEndian.insert_bits(&mut data, start_bit, length, value);
448            let extracted = ByteOrder::LittleEndian.extract_bits(&data, start_bit, length);
449            assert_eq!(
450                extracted, value,
451                "Round-trip failed for start_bit={}, length={}, value=0x{:X}",
452                start_bit, length, value
453            );
454        }
455    }
456
457    #[test]
458    fn test_insert_extract_roundtrip_big_endian() {
459        // Round-trip test for big-endian
460        let test_cases = [
461            (7, 8, 0x42u64),  // 8-bit at MSB position 7
462            (7, 16, 0x1234),  // 16-bit spanning bytes 0-1
463            (15, 16, 0xABCD), // 16-bit spanning bytes 1-2
464        ];
465
466        for (start_bit, length, value) in test_cases {
467            let mut data = [0x00; 8];
468            ByteOrder::BigEndian.insert_bits(&mut data, start_bit, length, value);
469            let extracted = ByteOrder::BigEndian.extract_bits(&data, start_bit, length);
470            assert_eq!(
471                extracted, value,
472                "BE round-trip failed for start_bit={}, length={}, value=0x{:X}",
473                start_bit, length, value
474            );
475        }
476    }
477
478    #[test]
479    fn test_insert_bits_preserves_other_bits() {
480        // Test that insert_bits doesn't corrupt other bits
481        let mut data = [0xFF; 8];
482        ByteOrder::LittleEndian.insert_bits(&mut data, 8, 8, 0x00);
483        // Byte 0 should still be 0xFF, byte 1 should be 0x00
484        assert_eq!(data[0], 0xFF);
485        assert_eq!(data[1], 0x00);
486        assert_eq!(data[2], 0xFF);
487    }
488
489    #[test]
490    fn test_insert_bits_at_offset() {
491        let mut data = [0x00; 8];
492        // Insert 16-bit value at byte 2
493        ByteOrder::LittleEndian.insert_bits(&mut data, 16, 16, 0x5678);
494        assert_eq!(data[0], 0x00);
495        assert_eq!(data[1], 0x00);
496        assert_eq!(data[2], 0x78);
497        assert_eq!(data[3], 0x56);
498    }
499
500    // Tests that require std (for DefaultHasher)
501    #[cfg(feature = "std")]
502    mod tests_std {
503        use super::*;
504        use core::hash::{Hash, Hasher};
505        use std::collections::hash_map::DefaultHasher;
506
507        #[test]
508        fn test_byte_order_debug() {
509            let little = format!("{:?}", ByteOrder::LittleEndian);
510            assert!(little.contains("LittleEndian"));
511
512            let big = format!("{:?}", ByteOrder::BigEndian);
513            assert!(big.contains("BigEndian"));
514        }
515
516        #[test]
517        fn test_byte_order_hash() {
518            let mut hasher1 = DefaultHasher::new();
519            let mut hasher2 = DefaultHasher::new();
520
521            ByteOrder::LittleEndian.hash(&mut hasher1);
522            ByteOrder::LittleEndian.hash(&mut hasher2);
523            assert_eq!(hasher1.finish(), hasher2.finish());
524
525            let mut hasher3 = DefaultHasher::new();
526            ByteOrder::BigEndian.hash(&mut hasher3);
527            assert_ne!(hasher1.finish(), hasher3.finish());
528        }
529    }
530}