ctoolbox/formats/eite/util/
bitwise.rs

1use anyhow::{Result, anyhow};
2
3/// Equivalent to JS internal bitwise mask (force into 0..=255).
4fn mask_byte(v: i32) -> u8 {
5    #![allow(clippy::as_conversions)]
6    (v & 0xFF) as u8
7}
8
9/// Bitwise AND (8-bit) – direct translation of `bitAnd8`.
10pub fn bit_and_8(a: u8, b: u8) -> u8 {
11    #![allow(clippy::as_conversions)]
12    mask_byte(i32::from(a & b))
13}
14
15/// Bitwise NOT (8-bit) – direct translation of `bitNot8`.
16pub fn bit_not_8(a: u8) -> u8 {
17    #![allow(clippy::as_conversions)]
18    mask_byte(i32::from(!a))
19}
20
21/// Bitwise left shift (8-bit) – bounds restricted to 0..=8 like original.
22pub fn bit_lshift_8(a: u8, places: u8) -> u8 {
23    #![allow(clippy::as_conversions)]
24    assert!(places <= 8);
25    mask_byte((u32::from(a) << places) as i32)
26}
27
28/// Bitwise logical right shift (8-bit).
29pub fn bit_rshift_8(a: u8, places: u8) -> u8 {
30    #![allow(clippy::as_conversions)]
31    assert!(places <= 8);
32    mask_byte((u32::from(a) >> places) as i32)
33}
34
35/// Return least significant byte from a 32-bit int (matches original intent).
36pub fn least_significant_byte(v: i32) -> u8 {
37    mask_byte(v)
38}
39
40/// Convert a single byte to an 8-length vector of bits (MSB first).
41///
42/// Original code padded leading zeros until length 8.
43pub fn byte_to_int_bit_array(byte: u8) -> Vec<u8> {
44    let mut bits = Vec::with_capacity(8);
45    for i in (0..8).rev() {
46        bits.push((byte >> i) & 1);
47    }
48    bits
49}
50
51/// Convert exactly 8 bits (0/1) into a single byte.
52///
53/// Returns error if:
54/// - `bits.len()` != 8
55/// - any bit is not 0 or 1
56pub fn byte_from_int_bit_array(bits: &[u8]) -> Result<u8> {
57    if bits.len() != 8 {
58        return Err(anyhow!(
59            "byte_from_int_bit_array: expected 8 bits, got {}",
60            bits.len()
61        ));
62    }
63    let mut val: u8 = 0;
64    for &b in bits {
65        if b > 1 {
66            return Err(anyhow!(
67                "byte_from_int_bit_array: invalid bit value {b}"
68            ));
69        }
70        val = (val << 1) | b;
71    }
72    Ok(val)
73}
74
75/// Flatten a byte slice into a vector of 0/1 bits (MSB first per byte).
76pub fn byte_array_to_int_bit_array(bytes: &[u8]) -> Vec<u8> {
77    let mut out = Vec::with_capacity(bytes.len() * 8);
78    for &b in bytes {
79        out.extend(byte_to_int_bit_array(b));
80    }
81    out
82}
83
84/// Convert a flat bit array (length multiple of 8) into bytes.
85///
86/// Ignores trailing bits < 8 (i.e., requires perfect multiple). If leftover bits exist,
87/// returns error to mirror strictness of original logic.
88///
89/// Returns error if any bit is not 0 or 1.
90pub fn byte_array_from_int_bit_array(bits: &[u8]) -> Result<Vec<u8>> {
91    if !bits.len().is_multiple_of(8) {
92        return Err(anyhow!(
93            "byte_array_from_int_bit_array: bit length {} not divisible by 8",
94            bits.len()
95        ));
96    }
97    let mut out = Vec::with_capacity(bits.len() / 8);
98    for chunk in bits.chunks(8) {
99        out.push(byte_from_int_bit_array(chunk)?);
100    }
101    Ok(out)
102}
103
104#[cfg(test)]
105#[allow(clippy::unwrap_in_result, clippy::panic_in_result_fn)]
106mod tests {
107    use super::*;
108
109    #[crate::ctb_test]
110    fn test_bitwise_ops() {
111        assert_eq!(bit_and_8(0b1111_0000, 0b1010_1010), 0b1010_0000);
112        assert_eq!(bit_not_8(0b0000_1111), 0b1111_0000);
113        assert_eq!(bit_lshift_8(0b0000_1111, 2), 0b0011_1100);
114        assert_eq!(bit_rshift_8(0b1111_0000, 4), 0b0000_1111);
115    }
116
117    #[crate::ctb_test]
118    fn test_byte_to_bits_and_back() -> Result<()> {
119        let cases = [
120            (0x00u8, [0, 0, 0, 0, 0, 0, 0, 0]),
121            (0xFFu8, [1, 1, 1, 1, 1, 1, 1, 1]),
122            (0x5Au8, [0, 1, 0, 1, 1, 0, 1, 0]), // 0x5A = 01011010
123            (0xC3u8, [1, 1, 0, 0, 0, 0, 1, 1]), // 0xC3 = 11000011
124        ];
125        for (byte, bits_expected) in cases.iter() {
126            let bits = byte_to_int_bit_array(*byte);
127            assert_eq!(bits.as_slice(), bits_expected);
128            let round = byte_from_int_bit_array(&bits)?;
129            assert_eq!(round, *byte);
130        }
131        Ok(())
132    }
133
134    #[crate::ctb_test]
135    fn test_bytes_to_bits_round_trip() -> Result<()> {
136        let data = b"\x00\x01\x7F\x80\xFF";
137        let bits = byte_array_to_int_bit_array(data);
138        assert_eq!(bits.len(), data.len() * 8);
139        let restored = byte_array_from_int_bit_array(&bits)?;
140        assert_eq!(restored, data);
141        Ok(())
142    }
143
144    #[crate::ctb_test]
145    fn test_byte_array_from_int_bit_array_invalid_length() {
146        let bits: Vec<u8> = vec![0u8, 1, 0]; // length 3
147        assert!(byte_array_from_int_bit_array(&bits).is_err());
148    }
149}