pub(crate) fn compact_bits(x_values: &[u32], k: u8) -> Vec<u8> {
assert!(k >= 8);
let mut full_proof = Vec::<u8>::with_capacity((x_values.len() * usize::from(k)).div_ceil(8));
let mut val = 0_u8;
let mut bits_left = 8;
for x in x_values {
let mut mask = ((1_u64 << k) - 1) as u32;
let mut x_bits = k;
assert!((x | mask) == mask);
while x_bits > 0 {
let bits_to_copy = std::cmp::min(bits_left, x_bits);
if x_bits > bits_left {
val |= ((x & mask) >> (x_bits - bits_left)) as u8;
} else {
val |= ((x & mask) << (bits_left - x_bits)) as u8;
}
mask >>= bits_to_copy;
bits_left -= bits_to_copy;
x_bits -= bits_to_copy;
if bits_left == 0 {
full_proof.push(val);
val = 0;
bits_left = 8;
}
}
}
if bits_left < 8 {
full_proof.push(val);
}
full_proof
}
pub(crate) fn expand_bits(proof: &[u8], k: u8) -> Option<Vec<u32>> {
if k == 0 {
return None;
}
if k > 32 {
return None;
}
let mut x_values = Vec::<u32>::with_capacity(proof.len() * 8 / usize::from(k));
let mut val = 0_u32;
let mut bits_left = k;
for byte in proof {
let mut byte_bits = 8;
let mut byte_mask = 0xff_u8;
while byte_bits > 0 {
if bits_left > byte_bits {
val |= u32::from(byte & byte_mask) << (bits_left - byte_bits);
} else {
val |= u32::from(byte & byte_mask) >> (byte_bits - bits_left);
}
let bits_copied = std::cmp::min(byte_bits, bits_left);
bits_left -= bits_copied;
byte_bits -= bits_copied;
if byte_bits > 0 {
byte_mask >>= bits_copied;
}
if bits_left == 0 {
x_values.push(val);
bits_left = k;
val = 0;
}
}
}
if val != 0 { None } else { Some(x_values) }
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
fn print_bits(input: &[u8]) -> String {
let mut ret = String::new();
for b in input {
ret += format!("{b:08b}").as_str();
}
ret
}
#[rstest]
fn compact_bits_fields(
#[values(8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 28, 29, 30, 31, 32)] num_bits: u8,
#[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] field: u32,
#[values(false, true)] tail: bool,
) {
let mask: u32 = 0xffffffff >> (32 - num_bits);
let mut input = [0_u32; 11];
input[field as usize] = mask;
let zero = "0".repeat(num_bits.into());
let one = "1".repeat(num_bits.into());
let mut expect = String::new();
for i in 0..11 {
if i == field {
expect += &one;
} else {
expect += &zero;
}
}
let padding = expect.len() % 8;
let padding = if padding == 0 { padding } else { 8 - padding };
expect += &(if tail { "1" } else { "0" }).repeat(padding);
let mut bits = compact_bits(&input, num_bits);
if padding > 0 && tail {
*bits.last_mut().unwrap() |= 0xff >> (8 - padding);
}
assert_eq!(expect.len() % 8, 0);
assert_eq!(print_bits(bits.as_slice()), expect);
if padding > 0 && tail {
if let Some(round_trip) = expand_bits(bits.as_slice(), num_bits) {
assert!(round_trip.len() > input.len());
}
} else {
let round_trip = expand_bits(bits.as_slice(), num_bits).expect("expand_bits");
assert_eq!(round_trip, input);
}
}
#[rstest]
fn compact_bits_values(#[values(8, 9, 10, 11, 12, 13, 14, 15)] num_bits: u8) {
let mask: u32 = 0xffffffff >> (32 - num_bits);
let mut input = [0_u32; 11];
let mut expect = String::new();
for (idx, ref mut v) in &mut input.iter_mut().enumerate() {
**v = idx as u32 & mask;
let bits = format!("{:0width$b}", idx as u32 & mask, width = num_bits as usize);
expect += bits.as_str();
}
let padding = expect.len() % 8;
let padding = if padding == 0 { padding } else { 8 - padding };
expect += &"0".repeat(padding);
let result = compact_bits(&input, num_bits);
assert_eq!(print_bits(result.as_slice()), expect);
let round_trip = expand_bits(result.as_slice(), num_bits).expect("expand_bits");
assert_eq!(round_trip, input);
}
}