use super::{ExiError, ExiResult};
#[derive(Debug)]
pub struct BitWriter<'a> {
buf: &'a mut [u8],
pos: usize,
}
impl<'a> BitWriter<'a> {
#[must_use]
pub fn new(buf: &'a mut [u8]) -> Self {
Self { buf, pos: 0 }
}
#[must_use]
pub const fn bit_len(&self) -> usize {
self.pos
}
#[must_use]
pub const fn byte_len(&self) -> usize {
self.pos.div_ceil(8)
}
#[must_use]
pub const fn is_aligned(&self) -> bool {
self.pos.is_multiple_of(8)
}
pub fn write_bit(&mut self, bit: bool) -> ExiResult<()> {
let byte = self.pos / 8;
let off = self.pos % 8;
let slot = self.buf.get_mut(byte).ok_or(ExiError::OutputFull)?;
if off == 0 {
*slot = 0;
}
if bit {
*slot |= 1 << (7 - off);
}
self.pos += 1;
Ok(())
}
pub fn write_bits(&mut self, value: u64, n: u32) -> ExiResult<()> {
debug_assert!(n <= 64, "bit width {n} out of range");
debug_assert!(n == 64 || value >> n == 0, "value {value} exceeds {n} bits");
for i in (0..n).rev() {
self.write_bit((value >> i) & 1 == 1)?;
}
Ok(())
}
pub fn write_byte(&mut self, byte: u8) -> ExiResult<()> {
self.write_bits(u64::from(byte), 8)
}
pub fn write_bytes(&mut self, bytes: &[u8]) -> ExiResult<()> {
for &b in bytes {
self.write_byte(b)?;
}
Ok(())
}
pub fn pad_to_byte(&mut self) -> ExiResult<()> {
while !self.is_aligned() {
self.write_bit(false)?;
}
Ok(())
}
pub fn finish(mut self) -> ExiResult<usize> {
self.pad_to_byte()?;
Ok(self.byte_len())
}
}
#[derive(Debug, Clone)]
pub struct BitReader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> BitReader<'a> {
#[must_use]
pub const fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
#[must_use]
pub const fn bit_pos(&self) -> usize {
self.pos
}
#[must_use]
pub const fn bits_remaining(&self) -> usize {
self.buf.len() * 8 - self.pos
}
#[must_use]
pub const fn is_aligned(&self) -> bool {
self.pos.is_multiple_of(8)
}
pub fn read_bit(&mut self) -> ExiResult<bool> {
let byte = self.pos / 8;
let off = self.pos % 8;
let slot = *self.buf.get(byte).ok_or(ExiError::UnexpectedEnd)?;
self.pos += 1;
Ok((slot >> (7 - off)) & 1 == 1)
}
pub fn read_bits(&mut self, n: u32) -> ExiResult<u64> {
debug_assert!(n <= 64, "bit width {n} out of range");
if usize::try_from(n).unwrap_or(usize::MAX) > self.bits_remaining() {
return Err(ExiError::UnexpectedEnd);
}
let mut acc = 0u64;
for _ in 0..n {
acc = (acc << 1) | u64::from(self.read_bit()?);
}
Ok(acc)
}
pub fn read_byte(&mut self) -> ExiResult<u8> {
#[allow(clippy::cast_possible_truncation)]
Ok(self.read_bits(8)? as u8)
}
pub fn read_bytes(&mut self, out: &mut [u8]) -> ExiResult<()> {
if out.len().saturating_mul(8) > self.bits_remaining() {
return Err(ExiError::UnexpectedEnd);
}
for slot in out.iter_mut() {
*slot = self.read_byte()?;
}
Ok(())
}
pub fn skip_padding(&mut self) -> ExiResult<()> {
while !self.is_aligned() {
if self.read_bit()? {
return Err(ExiError::NonZeroPadding);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
#[test]
fn writes_msb_first() {
let mut buf = [0xFFu8; 2];
let mut w = BitWriter::new(&mut buf);
w.write_bits(0b101, 3).unwrap();
w.write_bits(0b11, 2).unwrap();
let len = w.finish().unwrap();
assert_eq!(len, 1);
assert_eq!(buf[0], 0b1011_1000);
}
#[test]
fn write_past_end_is_an_error() {
let mut buf = [0u8; 1];
let mut w = BitWriter::new(&mut buf);
w.write_bits(0, 8).unwrap();
assert_eq!(w.write_bit(false), Err(ExiError::OutputFull));
}
#[test]
fn read_past_end_is_an_error() {
let mut r = BitReader::new(&[0xAB]);
assert_eq!(r.read_bits(8).unwrap(), 0xAB);
assert_eq!(r.read_bit(), Err(ExiError::UnexpectedEnd));
}
#[test]
fn truncated_multi_bit_read_does_not_consume() {
let mut r = BitReader::new(&[0xAB]);
assert_eq!(r.read_bits(9), Err(ExiError::UnexpectedEnd));
assert_eq!(r.bit_pos(), 0, "a failed read must not advance the cursor");
}
#[test]
fn non_zero_padding_is_rejected() {
let mut r = BitReader::new(&[0b0000_0001]);
r.read_bits(4).unwrap();
assert_eq!(r.skip_padding(), Err(ExiError::NonZeroPadding));
}
proptest! {
#[test]
fn bit_roundtrip(chunks in prop::collection::vec((0u64..u64::MAX, 1u32..=64), 1..40)) {
let mut buf = [0u8; 512];
let mut w = BitWriter::new(&mut buf);
let mut written = alloc::vec::Vec::new();
for &(v, n) in &chunks {
let v = if n == 64 { v } else { v & ((1u64 << n) - 1) };
if w.write_bits(v, n).is_err() { break; }
written.push((v, n));
}
let len = w.finish().unwrap();
let mut r = BitReader::new(&buf[..len]);
for (v, n) in written {
prop_assert_eq!(r.read_bits(n).unwrap(), v);
}
r.skip_padding().unwrap();
}
}
}