Skip to main content

scannit_core/
conversion.rs

1use std::fmt::Write;
2
3///Converts an array of bytes into a hex string.
4pub fn as_hex_string(bytes: &[u8]) -> String {
5    let mut string_buffer = String::new();
6    for &byte in bytes {
7        // Writing to a String never fails, so ignore the Result.
8        let _ = write!(&mut string_buffer, "{:x}", byte);
9    }
10    string_buffer
11}
12
13pub fn get_bits_as_u8(bytes: &[u8], bit_offset_index: usize, bit_length: usize) -> u8 {
14    get_bits_as_u64(bytes, bit_offset_index, bit_length) as u8
15}
16
17pub fn get_bits_as_u16(bytes: &[u8], bit_offset_index: usize, bit_length: usize) -> u16 {
18    get_bits_as_u64(bytes, bit_offset_index, bit_length) as u16
19}
20
21pub fn get_bits_as_u32(bytes: &[u8], bit_offset_index: usize, bit_length: usize) -> u32 {
22    get_bits_as_u64(bytes, bit_offset_index, bit_length) as u32
23}
24
25pub fn get_bits_as_u64(bytes: &[u8], bit_offset_index: usize, bit_length: usize) -> u64 {
26    let byte_offset = bit_offset_index / 8;
27    let end_byte_index = (bit_offset_index + bit_length - 1) / 8;
28    let num_relevant_bytes = (end_byte_index - byte_offset) + 1;
29    let num_bits_to_mask = bit_offset_index % 8;
30    let leading_and_value_bits = num_bits_to_mask + bit_length;
31    let num_bits_to_shift = if num_relevant_bytes * 8 > leading_and_value_bits {
32        (num_relevant_bytes * 8) - leading_and_value_bits
33    } else {
34        0
35    };
36    let mut and_mask = 0u64;
37    for _ in 0..num_bits_to_mask {
38        and_mask = (and_mask << 1) + 1;
39    }
40    // Shift the 1s to the top of the bit-value, and negate the whole number to generate our mask
41    if num_bits_to_mask > 0 {
42        and_mask <<= 64 - num_bits_to_mask;
43    }
44    and_mask = !and_mask;
45    let bytes = &bytes[byte_offset..=end_byte_index];
46
47    let bit_position = 56;
48    // 1) Weld all the bytes together, moving up the number to make space for new bytes as we go
49    let mut welded_bytes = 0u64;
50    for (i, byte) in bytes.iter().enumerate() {
51        welded_bytes += u64::from(*byte) << (bit_position - (i * 8))
52    }
53
54    // 2) AND off the top bits that aren't part of what we want
55    // 3) Shift off the bottom bits that aren't part of what we want
56    // 4) And shift rightward based on what the user requested of us
57    ((welded_bytes & and_mask) >> num_bits_to_shift) >> ((8 - num_relevant_bytes) * 8)
58}
59
60#[cfg(test)]
61mod test {
62    use crate::conversion::{get_bits_as_u16, get_bits_as_u32, get_bits_as_u64, get_bits_as_u8};
63
64    #[test]
65    fn to_bits_should_handle_trailing_and_leading_bits() {
66        // want to get the middle 11 bits of a value that is divided like this:
67        // -------- ---XXXXX XXXXXX-- where the Xs are the values we want.
68        // They begin at index 11, and the value is 11 bits long.
69        let three_bytes: [u8; 3] = [0x00, 0x1F, 0xFC];
70        let expected = 2047u64;
71        let actual = get_bits_as_u64(&three_bytes, 11, 11);
72        assert_eq!(expected, actual);
73    }
74
75    #[test]
76    fn to_bits_should_handle_u8() {
77        let byte: [u8; 1] = [0b0011_0100];
78        let expected = 0b0000_1101;
79        // try to pull out of the four bits that span a nybble boundary
80        let actual = get_bits_as_u8(&byte, 2, 4);
81        assert_eq!(expected, actual);
82    }
83
84    #[test]
85    fn to_bits_should_handle_u16() {
86        // Interested in:          |----------------------| <-- those 16 bits
87        let bytes: [u8; 3] = [0b1111_0001, 0b0000_0001, 0b0010_0000];
88        let expected = 0b10001000_00001001;
89        let actual = get_bits_as_u16(&bytes, 3, 16);
90        assert_eq!(expected, actual);
91    }
92
93    #[rustfmt::skip]
94    #[test]
95    fn to_bits_should_handle_u32() {
96        // Interested in:               |--------------------------------------------------| <-- these 32 bits
97        let bytes: [u8; 5] = [0b1100_1001, 0b0000_1000, 0b0000_0001, 0b1101_0000, 0b0110_0011];
98        let expected: u32 = 0b10000100_00000000_11101000_00110001;
99        let actual = get_bits_as_u32(&bytes, 7, 32);
100        assert_eq!(expected, actual);
101    }
102
103    #[test]
104    fn to_bits_should_handle_values_that_dont_need_shifting() {
105        let bytes: [u8; 2] = [0x00, 0x12];
106        let expected = 0b0000_0010;
107        let actual = get_bits_as_u8(&bytes, 14, 2);
108        assert_eq!(expected, actual);
109    }
110
111    #[test]
112    fn to_bits_should_handle_straddled_values_at_end_of_array() {
113        // We seem to get right-shifted by one bit too many.
114        // These two bits are what we're asking for  |----|
115        let bytes: [u8; 3] = [0b1000_0010, 0b0101_1011, 0b0000_0101];
116        let expected = 0b0000_0010;
117        let actual = get_bits_as_u16(&bytes, 15, 2);
118        assert_eq!(expected, actual);
119    }
120}