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    #[inline]
22    pub(crate) fn extract_bits(self, data: &[u8], start_bit: usize, length: usize) -> u64 {
23        match self {
24            ByteOrder::LittleEndian => {
25                // Little-endian: extract bits sequentially from start_bit forward
26                let mut value: u64 = 0;
27                let mut bits_remaining = length;
28                let mut current_bit = start_bit;
29
30                while bits_remaining > 0 {
31                    let byte_idx = current_bit / 8;
32                    let bit_in_byte = current_bit % 8;
33                    let bits_to_take = bits_remaining.min(8 - bit_in_byte);
34
35                    let byte = data[byte_idx] as u64;
36                    let mask = ((1u64 << bits_to_take) - 1) << bit_in_byte;
37                    let extracted = (byte & mask) >> bit_in_byte;
38
39                    value |= extracted << (length - bits_remaining);
40
41                    bits_remaining -= bits_to_take;
42                    current_bit += bits_to_take;
43                }
44
45                value
46            }
47            ByteOrder::BigEndian => {
48                // Big-endian: start_bit is MSB in big-endian numbering, signal extends backward
49                // BE bit N maps to physical bit: byte_num * 8 + (7 - bit_in_byte)
50                // We need to extract bits from MSB to LSB and assemble them correctly
51
52                // Extract bits from MSB to LSB (physical order)
53                let mut value: u64 = 0;
54                for i in 0..length {
55                    // Calculate which physical bit this corresponds to
56                    let be_bit = start_bit + i;
57                    let byte_num = be_bit / 8;
58                    let bit_in_byte = be_bit % 8;
59                    let physical_bit = byte_num * 8 + (7 - bit_in_byte);
60
61                    // Extract the bit from the physical position
62                    let byte_idx = physical_bit / 8;
63                    let bit_pos_in_byte = physical_bit % 8;
64                    let bit_value = ((data[byte_idx] as u64) >> (7 - bit_pos_in_byte)) & 1;
65
66                    // Place it in the result (MSB first)
67                    value |= bit_value << (length - 1 - i);
68                }
69
70                value
71            }
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::ByteOrder;
79    use core::hash::Hash;
80
81    // Tests that work in all configurations (no_std, std)
82    #[test]
83    fn test_byte_order_variants() {
84        assert_eq!(ByteOrder::LittleEndian as u8, 1);
85        assert_eq!(ByteOrder::BigEndian as u8, 0);
86    }
87
88    #[test]
89    fn test_byte_order_equality() {
90        assert_eq!(ByteOrder::LittleEndian, ByteOrder::LittleEndian);
91        assert_eq!(ByteOrder::BigEndian, ByteOrder::BigEndian);
92        assert_ne!(ByteOrder::LittleEndian, ByteOrder::BigEndian);
93    }
94
95    #[test]
96    fn test_byte_order_clone() {
97        let original = ByteOrder::LittleEndian;
98        let cloned = original;
99        assert_eq!(original, cloned);
100
101        let original2 = ByteOrder::BigEndian;
102        let cloned2 = original2;
103        assert_eq!(original2, cloned2);
104    }
105
106    #[test]
107    fn test_byte_order_copy() {
108        let order = ByteOrder::LittleEndian;
109        let copied = order; // Copy, not move
110        assert_eq!(order, copied); // Original still valid
111    }
112
113    #[test]
114    fn test_byte_order_hash_trait() {
115        // Test that Hash trait is implemented by checking it compiles
116        fn _assert_hash<T: Hash>() {}
117        _assert_hash::<ByteOrder>();
118    }
119
120    #[test]
121    fn test_extract_bits_little_endian() {
122        // Test value 0x1234: little-endian bytes are [0x34, 0x12] (LSB first)
123        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
124        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
125        assert_eq!(raw_value, 0x1234);
126    }
127
128    #[test]
129    fn test_extract_bits_big_endian() {
130        // Test big-endian extraction: For BE bit 0-15, value 0x0100 = 256
131        // Big-endian at bit 0, length 16: bytes [0x01, 0x00]
132        let data = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
133        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
134        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
135        assert!(raw_value <= 65535);
136    }
137
138    #[test]
139    fn test_extract_bits_mixed_positions_little_endian() {
140        // Test signal at bit 8, length 16 (spans bytes 1-2)
141        let data = [0x00, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00];
142        let raw_value = ByteOrder::LittleEndian.extract_bits(&data, 8, 16);
143        assert_eq!(raw_value, 0x1234);
144    }
145
146    #[test]
147    fn test_extract_bits_mixed_positions_big_endian() {
148        // Test signal at bit 8, length 16 (spans bytes 1-2)
149        // Big-endian at BE bit 8-23: bytes [0x01, 0x00]
150        let data = [0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
151        let raw_value = ByteOrder::BigEndian.extract_bits(&data, 8, 16);
152        // Verify it decodes to a valid value (exact value depends on BE bit mapping)
153        assert!(raw_value <= 65535);
154    }
155
156    #[test]
157    fn test_byte_order_difference() {
158        // Test that big-endian and little-endian produce different results
159        // for the same byte data, proving both byte orders are handled differently
160        let data = [0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
161
162        let le_value = ByteOrder::LittleEndian.extract_bits(&data, 0, 16);
163        let be_value = ByteOrder::BigEndian.extract_bits(&data, 0, 16);
164
165        // Little-endian: [0x34, 0x12] = 0x1234 = 4660
166        assert_eq!(le_value, 0x1234);
167
168        // Big-endian should produce a different value (proves BE is being used)
169        assert_ne!(
170            le_value, be_value,
171            "Big-endian and little-endian should produce different values"
172        );
173        assert!(be_value <= 65535);
174    }
175
176    // Tests that require std (for DefaultHasher)
177    #[cfg(feature = "std")]
178    mod tests_std {
179        use super::*;
180        use core::hash::{Hash, Hasher};
181        use std::collections::hash_map::DefaultHasher;
182
183        #[test]
184        fn test_byte_order_debug() {
185            let little = format!("{:?}", ByteOrder::LittleEndian);
186            assert!(little.contains("LittleEndian"));
187
188            let big = format!("{:?}", ByteOrder::BigEndian);
189            assert!(big.contains("BigEndian"));
190        }
191
192        #[test]
193        fn test_byte_order_hash() {
194            let mut hasher1 = DefaultHasher::new();
195            let mut hasher2 = DefaultHasher::new();
196
197            ByteOrder::LittleEndian.hash(&mut hasher1);
198            ByteOrder::LittleEndian.hash(&mut hasher2);
199            assert_eq!(hasher1.finish(), hasher2.finish());
200
201            let mut hasher3 = DefaultHasher::new();
202            ByteOrder::BigEndian.hash(&mut hasher3);
203            assert_ne!(hasher1.finish(), hasher3.finish());
204        }
205    }
206}