use crate::error::BitError;
use crate::string::{FixedStr, armor_char_to_sixbit};
#[derive(Debug, Clone)]
pub struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
bit_len: usize,
}
impl<'a> BitReader<'a> {
#[must_use]
pub const fn new(armored: &'a [u8], fill_bits: u8) -> Self {
let total_bits = armored.len() * 6;
let bit_len = total_bits.saturating_sub(fill_bits as usize);
Self {
data: armored,
bit_pos: 0,
bit_len,
}
}
#[must_use]
pub const fn remaining_bits(&self) -> usize {
self.bit_len.saturating_sub(self.bit_pos)
}
pub fn read_bool(&mut self) -> Result<bool, BitError> {
Ok(self.read_u64(1)? != 0)
}
#[allow(
clippy::cast_possible_truncation,
reason = "nbits <= 8 checked above, value fits u8"
)]
pub fn read_u8(&mut self, nbits: u32) -> Result<u8, BitError> {
if nbits == 0 || nbits > 8 {
return Err(BitError::OutOfRange { field: "read_u8" });
}
Ok(self.read_u64(nbits)? as u8)
}
#[allow(
clippy::cast_possible_truncation,
reason = "nbits <= 16 checked above, value fits u16"
)]
pub fn read_u16(&mut self, nbits: u32) -> Result<u16, BitError> {
if nbits == 0 || nbits > 16 {
return Err(BitError::OutOfRange { field: "read_u16" });
}
Ok(self.read_u64(nbits)? as u16)
}
#[allow(
clippy::cast_possible_truncation,
reason = "nbits <= 32 checked above, value fits u32"
)]
pub fn read_u32(&mut self, nbits: u32) -> Result<u32, BitError> {
if nbits == 0 || nbits > 32 {
return Err(BitError::OutOfRange { field: "read_u32" });
}
Ok(self.read_u64(nbits)? as u32)
}
pub fn read_u64(&mut self, nbits: u32) -> Result<u64, BitError> {
if nbits == 0 || nbits > 64 {
return Err(BitError::OutOfRange { field: "read_u64" });
}
let nbits = nbits as usize;
if self.bit_pos + nbits > self.bit_len {
return Err(BitError::UnexpectedEof);
}
let mut value: u64 = 0;
for _ in 0..nbits {
value = (value << 1) | u64::from(self.get_bit(self.bit_pos));
self.bit_pos += 1;
}
Ok(value)
}
#[allow(
clippy::cast_possible_truncation,
reason = "nbits <= 8 checked above, value fits i8"
)]
pub fn read_i8(&mut self, nbits: u32) -> Result<i8, BitError> {
if nbits == 0 || nbits > 8 {
return Err(BitError::OutOfRange { field: "read_i8" });
}
Ok(self.read_i64(nbits)? as i8)
}
#[allow(
clippy::cast_possible_truncation,
reason = "nbits <= 32 checked above, value fits i32"
)]
pub fn read_i32(&mut self, nbits: u32) -> Result<i32, BitError> {
if nbits == 0 || nbits > 32 {
return Err(BitError::OutOfRange { field: "read_i32" });
}
Ok(self.read_i64(nbits)? as i32)
}
pub fn read_i64(&mut self, nbits: u32) -> Result<i64, BitError> {
if nbits == 0 || nbits > 64 {
return Err(BitError::OutOfRange { field: "read_i64" });
}
let raw = self.read_u64(nbits)?;
Ok(sign_extend(raw, nbits))
}
pub fn skip(&mut self, nbits: u32) -> Result<(), BitError> {
let nbits = nbits as usize;
if self.bit_pos + nbits > self.bit_len {
return Err(BitError::UnexpectedEof);
}
self.bit_pos += nbits;
Ok(())
}
pub fn read_sixbit_ascii<const N: usize>(
&mut self,
nchars: usize,
) -> Result<FixedStr<N>, BitError> {
if nchars > N {
return Err(BitError::OutOfRange {
field: "read_sixbit_ascii",
});
}
let mut buf = [0u8; N];
for slot in buf.iter_mut().take(nchars) {
let sixbit = self.read_u8(6)?;
*slot = crate::string::sixbit_to_ascii(sixbit);
}
Ok(FixedStr::from_raw(buf, nchars))
}
const fn get_bit(&self, i: usize) -> u8 {
let symbol = armor_char_to_sixbit(self.data[i / 6]);
(symbol >> (5 - (i % 6))) & 1
}
}
const fn sign_extend(raw: u64, nbits: u32) -> i64 {
if nbits >= 64 {
return raw.cast_signed();
}
let shift = 64 - nbits;
(raw << shift).cast_signed() >> shift
}
#[cfg(kani)]
mod kani_proofs {
use super::*;
#[kani::proof]
fn read_u64_never_panics() {
let data: [u8; 4] = kani::any();
let fill_bits: u8 = kani::any();
let nbits: u32 = kani::any();
let mut r = BitReader::new(&data, fill_bits);
let _ = r.read_u64(nbits);
}
#[kani::proof]
fn read_i64_never_panics() {
let data: [u8; 4] = kani::any();
let fill_bits: u8 = kani::any();
let nbits: u32 = kani::any();
let mut r = BitReader::new(&data, fill_bits);
let _ = r.read_i64(nbits);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_known_unsigned_values() {
let armored = b"0w";
let mut r = BitReader::new(armored, 0);
assert_eq!(r.read_u8(6).unwrap(), 0);
assert_eq!(r.read_u8(6).unwrap(), 0b11_1111);
}
#[test]
fn respects_fill_bits() {
let armored = b"0";
let r = BitReader::new(armored, 3);
assert_eq!(r.remaining_bits(), 3);
}
#[test]
fn eof_when_reading_past_end() {
let armored = b"0";
let mut r = BitReader::new(armored, 0);
assert_eq!(r.read_u8(6).unwrap(), 0);
assert_eq!(r.read_bool().unwrap_err(), BitError::UnexpectedEof);
}
#[test]
fn sign_extend_negative_values() {
assert_eq!(sign_extend(0b1000, 4), -8);
assert_eq!(sign_extend(0b0111, 4), 7);
assert_eq!(sign_extend(0b1, 1), -1);
}
}