bitvec_helpers-4.0.1/.cargo_vcs_info.json0000644000000001360000000000100140340ustar { "git": { "sha1": "587e8b46417de0038205dae89378b5cce5384b98" }, "path_in_vcs": "" }bitvec_helpers-4.0.1/.github/workflows/ci.yml000064400000000000000000000013231046102023000173360ustar 00000000000000name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: ci: name: Check, test, rustfmt and clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Rust, clippy and rustfmt uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - name: Check run: | cargo check --workspace --all-features - name: Test run: | cargo test --workspace --all-features - name: Rustfmt run: | cargo fmt --all --check - name: Clippy run: | cargo clippy --workspace --all-features --all-targets --tests -- --deny warnings bitvec_helpers-4.0.1/.gitignore000064400000000000000000000002031046102023000146070ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # These are backup files generated by rustfmt **/*.rs.bk bitvec_helpers-4.0.1/Cargo.lock0000644000000040360000000000100120120ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "anyhow" version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "bitstream-io" version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" dependencies = [ "core2", ] [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "bitvec_helpers" version = "4.0.1" dependencies = [ "anyhow", "bitstream-io", "bitvec", "funty", ] [[package]] name = "core2" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" dependencies = [ "memchr", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] bitvec_helpers-4.0.1/Cargo.toml0000644000000023600000000000100120330ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.85.0" name = "bitvec_helpers" version = "4.0.1" authors = ["quietvoid"] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "BitVec based bitstream reader and writer" readme = "README.md" license = "MIT" repository = "https://github.com/quietvoid/bitvec_helpers" [features] bitstream-io = ["dep:bitstream-io"] bitvec = [ "dep:bitvec", "dep:funty", "dep:anyhow", ] default = ["bitvec"] [lib] name = "bitvec_helpers" path = "src/lib.rs" [dependencies.anyhow] version = "1.0.100" optional = true [dependencies.bitstream-io] version = "4.9.0" optional = true [dependencies.bitvec] version = "1.0.1" optional = true [dependencies.funty] version = "2.0.0" optional = true bitvec_helpers-4.0.1/Cargo.toml.orig000064400000000000000000000011111046102023000155050ustar 00000000000000[package] name = "bitvec_helpers" version = "4.0.1" authors = ["quietvoid"] edition = "2024" rust-version = "1.85.0" license = "MIT" description = "BitVec based bitstream reader and writer" repository = "https://github.com/quietvoid/bitvec_helpers" [dependencies] anyhow = { version = "1.0.100", optional = true } bitvec = { version = "1.0.1", optional = true } funty = { version = "2.0.0", optional = true } bitstream-io = { version = "4.9.0", optional = true } [features] default = ["bitvec"] bitvec = ["dep:bitvec", "dep:funty", "dep:anyhow"] bitstream-io = ["dep:bitstream-io"] bitvec_helpers-4.0.1/LICENSE000064400000000000000000000020521046102023000136300ustar 00000000000000MIT License Copyright (c) 2025 quietvoid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bitvec_helpers-4.0.1/README.md000064400000000000000000000003721046102023000141050ustar 00000000000000# bitvec_helpers Bitstream reader/writer helpers. ## Features - `bitvec`: Expose [bitvec](https://github.com/ferrilab/bitvec) based reader, writer. - `bitstream-io`: Expose [bitstream-io](https://github.com/tuffy/bitstream-io) based reader, writer. bitvec_helpers-4.0.1/src/bitstream_io_impl/bitstream_io_reader.rs000064400000000000000000000101671046102023000234730ustar 00000000000000use std::io; use bitstream_io::{BigEndian, BitRead, BitReader, Integer}; pub struct BitstreamIoReader { bs: BitReader, len: u64, } /// Convenience type for Vec inner buffer pub type BsIoVecReader = BitstreamIoReader>>; /// Convenience type for &[u8] inner buffer pub type BsIoSliceReader<'a> = BitstreamIoReader>; impl BitstreamIoReader where R: io::Read + io::Seek, { pub fn new(read: R, len_bytes: u64) -> Self { Self { bs: BitReader::new(read), len: len_bytes * 8, } } #[inline(always)] pub fn read_bit(&mut self) -> io::Result { self.bs.read_bit() } #[inline(always)] pub fn read(&mut self) -> io::Result { self.bs.read::() } #[inline(always)] pub fn read_var(&mut self, bits: u32) -> io::Result { self.bs.read_var(bits) } #[inline(always)] pub fn read_ue(&mut self) -> io::Result { self.bs.read_unary::<1>().and_then(|leading_zeroes| { if leading_zeroes > 0 { self.bs .read_var::(leading_zeroes) .map(|v| v + (1 << leading_zeroes) - 1) } else { Ok(0) } }) } #[inline(always)] pub fn read_se(&mut self) -> io::Result { self.read_ue().map(|code_num| { let m = ((code_num + 1) as f64 / 2.0).floor() as u64; if code_num % 2 == 0 { -(m as i64) } else { m as i64 } }) } #[inline(always)] pub fn read_bytes(&mut self, buf: &mut [u8]) -> io::Result<()> { self.bs.read_bytes(buf) } #[inline(always)] pub fn byte_aligned(&self) -> bool { self.bs.byte_aligned() } #[inline(always)] pub fn available(&mut self) -> io::Result { self.bs.position_in_bits().map(|pos| self.len - pos) } #[inline(always)] pub fn skip_n(&mut self, n: u32) -> io::Result<()> { self.available().and_then(|avail| { if n as u64 > avail { Err(io::Error::new( io::ErrorKind::UnexpectedEof, "skip_n: out of bounds bits", )) } else { self.bs.skip(n) } }) } #[inline(always)] pub fn position_in_bits(&mut self) -> io::Result { self.bs.position_in_bits() } pub fn replace_inner(&mut self, read: R, len_bytes: u64) { self.len = len_bytes * 8; self.bs = BitReader::new(read); } } impl BsIoVecReader { pub fn from_vec(buf: Vec) -> Self { let len = buf.len() as u64; let read = io::Cursor::new(buf); Self::new(read, len) } pub fn replace_vec(&mut self, buf: Vec) { let len = buf.len() as u64; self.replace_inner(io::Cursor::new(buf), len); } } impl<'a> BsIoSliceReader<'a> { pub fn from_slice(buf: &'a [u8]) -> Self { let len = buf.len() as u64; let read = io::Cursor::new(buf); Self::new(read, len) } pub fn replace_slice(&mut self, buf: &'a [u8]) { let len = buf.len() as u64; self.replace_inner(io::Cursor::new(buf), len); } } impl Default for BsIoVecReader { fn default() -> Self { Self::from_vec(Vec::new()) } } impl Default for BsIoSliceReader<'_> { fn default() -> Self { Self::from_slice(&[]) } } #[test] fn read_var_validations() { let mut reader = BsIoSliceReader::from_slice(&[1]); assert!(reader.read_var::(9).is_err()); assert!(reader.read_var::(4).is_ok()); assert!(reader.read_var::(8).is_err()); assert!(reader.read_var::(4).is_ok()); assert!(reader.read_bit().is_err()); } #[test] fn skip_n_validations() { let mut reader = BsIoSliceReader::from_slice(&[1]); assert!(reader.skip_n(9).is_err()); assert!(reader.skip_n(7).is_ok()); assert!(reader.read_bit().is_ok()); assert!(reader.read_bit().is_err()); } bitvec_helpers-4.0.1/src/bitstream_io_impl/bitstream_io_writer.rs000064400000000000000000000044201046102023000235400ustar 00000000000000use std::io; use bitstream_io::{BigEndian, BitWrite, BitWriter, Integer}; use crate::signed_to_unsigned; pub struct BitstreamIoWriter(BitWriter, BigEndian>); impl BitstreamIoWriter { pub fn with_capacity(capacity: usize) -> Self { let write = Vec::with_capacity(capacity); Self(BitWriter::new(write)) } #[inline(always)] pub fn write_bit(&mut self, bit: bool) -> io::Result<()> { self.0.write_bit(bit) } #[inline(always)] pub fn write(&mut self, value: I) -> io::Result<()> { self.0.write::(value) } #[inline(always)] pub fn write_const(&mut self) -> io::Result<()> { self.0.write_const::() } #[inline(always)] pub fn write_var(&mut self, bits: u32, value: I) -> io::Result<()> { self.0.write_var(bits, value) } #[inline(always)] pub fn write_ue(&mut self, v: u64) -> io::Result<()> { if v == 0 { self.write_bit(true) } else { let mut tmp = v + 1; let mut leading_zeroes: i64 = -1; while tmp > 0 { tmp >>= 1; leading_zeroes += 1; } let leading_zeroes = leading_zeroes as u32; self.0.write_unary::<1>(leading_zeroes)?; let remaining = v + 1 - (1 << leading_zeroes); self.write_var(leading_zeroes, remaining) } } #[inline(always)] pub fn write_se(&mut self, v: i64) -> io::Result<()> { self.write_ue(signed_to_unsigned(v)) } #[inline(always)] pub fn write_bytes(&mut self, buf: &[u8]) -> io::Result<()> { self.0.write_bytes(buf) } #[inline(always)] pub fn byte_aligned(&self) -> bool { self.0.byte_aligned() } #[inline(always)] pub fn byte_align(&mut self) -> io::Result<()> { self.0.byte_align() } #[inline(always)] pub fn pad(&mut self, bits: u32) -> io::Result<()> { self.0.pad(bits) } /// None if the writer is not byte aligned pub fn as_slice(&mut self) -> Option<&[u8]> { self.0.writer().map(|e| e.as_slice()) } pub fn into_inner(self) -> Vec { self.0.into_writer() } } bitvec_helpers-4.0.1/src/bitstream_io_impl/mod.rs000064400000000000000000000000721046102023000202410ustar 00000000000000pub mod bitstream_io_reader; pub mod bitstream_io_writer; bitvec_helpers-4.0.1/src/bitvec_impl/bitslice_reader.rs000064400000000000000000000047371046102023000214110ustar 00000000000000use anyhow::{Result, bail}; use bitvec::{prelude::Msb0, slice::BitSlice, view::BitView}; use funty::Integral; use std::fmt; use super::reads::BitSliceReadExt; #[derive(Default)] pub struct BitSliceReader<'a> { bytes: &'a [u8], offset: usize, } impl<'a> BitSliceReader<'a> { pub fn new(bytes: &'a [u8]) -> Self { Self { bytes, offset: 0 } } fn bits(&self) -> &BitSlice { self.bytes.view_bits::() } #[inline(always)] pub fn get(&mut self) -> Result { BitSliceReadExt::get(self.bytes.view_bits::(), &mut self.offset) } #[inline(always)] pub fn get_n(&mut self, n: usize) -> Result { BitSliceReadExt::get_n(self.bytes.view_bits::(), n, &mut self.offset) } #[inline(always)] pub fn get_ue(&mut self) -> Result { BitSliceReadExt::get_ue(self.bytes.view_bits::(), &mut self.offset) } #[inline(always)] pub fn get_se(&mut self) -> Result { BitSliceReadExt::get_se(self.bytes.view_bits::(), &mut self.offset) } #[inline(always)] pub fn is_aligned(&self) -> bool { self.offset % 8 == 0 } #[inline(always)] pub fn available(&self) -> usize { self.bits().len() - self.offset } #[inline(always)] pub fn skip_n(&mut self, n: usize) -> Result<()> { if n > self.available() { bail!("Cannot skip more bits than available"); } self.offset += n; Ok(()) } pub fn available_slice(&self) -> &BitSlice { &self.bits()[self.offset..] } #[inline(always)] pub fn position(&self) -> usize { self.offset } } impl fmt::Debug for BitSliceReader<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "BitSliceReader: {{offset: {}, len: {}}}", self.offset, self.bytes.len() ) } } #[test] fn get_n_validations() { let mut reader = BitSliceReader::new(&[1]); assert!(reader.get_n::(9).is_err()); assert!(reader.get_n::(4).is_ok()); assert!(reader.get_n::(8).is_err()); assert!(reader.get_n::(4).is_ok()); assert!(reader.get().is_err()); } #[test] fn skip_n_validations() { let mut reader = BitSliceReader::new(&[1]); assert!(reader.skip_n(9).is_err()); assert!(reader.skip_n(7).is_ok()); assert!(reader.get().is_ok()); assert!(reader.get().is_err()); } bitvec_helpers-4.0.1/src/bitvec_impl/bitvec_reader.rs000064400000000000000000000045671046102023000210700ustar 00000000000000use anyhow::{Result, bail}; use bitvec::{prelude::Msb0, slice::BitSlice, vec::BitVec}; use funty::Integral; use std::fmt; use super::reads::BitSliceReadExt; #[derive(Default)] pub struct BitVecReader { bs: BitVec, offset: usize, } impl BitVecReader { pub fn new(data: Vec) -> Self { Self { bs: BitVec::from_vec(data), offset: 0, } } #[inline(always)] pub fn get(&mut self) -> Result { BitSliceReadExt::get(self.bs.as_bitslice(), &mut self.offset) } #[inline(always)] pub fn get_n(&mut self, n: usize) -> Result { BitSliceReadExt::get_n(self.bs.as_bitslice(), n, &mut self.offset) } #[inline(always)] pub fn get_ue(&mut self) -> Result { BitSliceReadExt::get_ue(self.bs.as_bitslice(), &mut self.offset) } #[inline(always)] pub fn get_se(&mut self) -> Result { BitSliceReadExt::get_se(self.bs.as_bitslice(), &mut self.offset) } #[inline(always)] pub fn is_aligned(&self) -> bool { self.offset % 8 == 0 } #[inline(always)] pub fn available(&self) -> usize { self.bs.len() - self.offset } #[inline(always)] pub fn skip_n(&mut self, n: usize) -> Result<()> { if n > self.available() { bail!("Cannot skip more bits than available"); } self.offset += n; Ok(()) } pub fn available_slice(&self) -> &BitSlice { &self.bs[self.offset..] } #[inline(always)] pub fn position(&self) -> usize { self.offset } } impl fmt::Debug for BitVecReader { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "BitVecReader: {{offset: {}, len: {}}}", self.offset, self.bs.len() ) } } #[test] fn get_n_validations() { let mut reader = BitVecReader::new(vec![1]); assert!(reader.get_n::(9).is_err()); assert!(reader.get_n::(4).is_ok()); assert!(reader.get_n::(8).is_err()); assert!(reader.get_n::(4).is_ok()); assert!(reader.get().is_err()); } #[test] fn skip_n_validations() { let mut reader = BitVecReader::new(vec![1]); assert!(reader.skip_n(9).is_err()); assert!(reader.skip_n(7).is_ok()); assert!(reader.get().is_ok()); assert!(reader.get().is_err()); } bitvec_helpers-4.0.1/src/bitvec_impl/bitvec_writer.rs000064400000000000000000000044531046102023000211340ustar 00000000000000use bitvec::{prelude::Msb0, vec::BitVec, view::BitView}; use funty::Integral; use crate::signed_to_unsigned; #[derive(Debug, Default)] pub struct BitVecWriter { bs: BitVec, offset: usize, } impl BitVecWriter { pub fn new() -> Self { Self { bs: BitVec::new(), offset: 0, } } pub fn with_capacity(capacity: usize) -> Self { Self { bs: BitVec::with_capacity(capacity), offset: 0, } } #[inline(always)] pub fn write(&mut self, v: bool) { self.bs.push(v); self.offset += 1; } #[inline(always)] pub fn write_n(&mut self, v: &T, n: usize) where T::Bytes: BitView, { let bytes = v.to_be_bytes(); let slice = bytes.view_bits::(); let bits_diff = if n as u32 > T::BITS { (n as u32 - T::BITS) as usize } else { 0 }; if bits_diff > 0 { self.bs.extend(std::iter::repeat_n(false, bits_diff)); self.bs.extend_from_bitslice(slice); } else { self.bs.extend_from_bitslice(&slice[slice.len() - n..]); } self.offset += n; } #[inline(always)] pub fn write_ue(&mut self, v: &u64) { if *v == 0 { self.bs.push(true); self.offset += 1; } else { let mut tmp = v + 1; let mut leading_zeroes: i64 = -1; while tmp > 0 { tmp >>= 1; leading_zeroes += 1; } let leading_zeroes = leading_zeroes as usize; let bits_iter = std::iter::repeat_n(false, leading_zeroes).chain(std::iter::once(true)); self.bs.extend(bits_iter); self.offset += leading_zeroes + 1; let remaining = v + 1 - (1 << leading_zeroes); self.write_n(&remaining, leading_zeroes); } } #[inline(always)] pub fn write_se(&mut self, v: &i64) { self.write_ue(&signed_to_unsigned(*v)); } #[inline(always)] pub fn is_aligned(&self) -> bool { self.offset % 8 == 0 } #[inline(always)] pub fn written_bits(&self) -> usize { self.offset } pub fn as_slice(&self) -> &[u8] { self.bs.as_raw_slice() } } bitvec_helpers-4.0.1/src/bitvec_impl/mod.rs000064400000000000000000000001231046102023000170310ustar 00000000000000pub mod bitslice_reader; pub mod bitvec_reader; pub mod bitvec_writer; mod reads; bitvec_helpers-4.0.1/src/bitvec_impl/reads.rs000064400000000000000000000045741046102023000173660ustar 00000000000000use anyhow::{Result, anyhow, bail, ensure}; use bitvec::{field::BitField, prelude::Msb0, slice::BitSlice}; use funty::Integral; pub(crate) struct BitSliceReadExt {} impl BitSliceReadExt { #[inline(always)] pub fn get(slice: &BitSlice, offset: &mut usize) -> Result { let val = { *slice .get(*offset) .ok_or_else(|| anyhow!("get: out of bounds"))? }; *offset += 1; Ok(val) } #[inline(always)] pub fn get_n( slice: &BitSlice, n: usize, offset: &mut usize, ) -> Result { let pos = *offset; let available = slice.len() - pos; ensure!(n <= available, "get_n: out of bounds bits"); let val = slice[pos..pos + n].load_be::(); *offset += n; Ok(val) } // bitstring.py implementation: https://github.com/scott-griffiths/bitstring/blob/master/bitstring.py#L1706 #[inline(always)] pub fn get_ue(slice: &BitSlice, offset: &mut usize) -> Result { let oldpos = *offset; let mut pos = oldpos; loop { match slice.get(pos) { Some(val) => { if !val { pos += 1; } else { break; } } None => bail!("get_ue: out of bounds index: {}", pos), } } let leading_zeroes = pos - oldpos; let endpos = pos + leading_zeroes + 1; let code_num = if leading_zeroes > 0 { if endpos > slice.len() { bail!("get_ue: out of bounds attempt"); } let code_num = (1 << leading_zeroes) - 1; code_num + slice[pos + 1..endpos].load_be::() } else { 0 }; *offset = endpos; Ok(code_num) } // bitstring.py implementation: https://github.com/scott-griffiths/bitstring/blob/master/bitstring.py#L1767 #[inline(always)] pub fn get_se(slice: &BitSlice, offset: &mut usize) -> Result { let code_num = Self::get_ue(slice, offset)?; let m = ((code_num + 1) as f64 / 2.0).floor() as u64; let val = if code_num % 2 == 0 { -(m as i64) } else { m as i64 }; Ok(val) } } bitvec_helpers-4.0.1/src/lib.rs000064400000000000000000000006631046102023000145340ustar 00000000000000#[cfg(feature = "bitvec")] mod bitvec_impl; #[cfg(feature = "bitvec")] pub use bitvec_impl::{bitslice_reader, bitvec_reader, bitvec_writer}; #[cfg(feature = "bitstream-io")] mod bitstream_io_impl; #[cfg(feature = "bitstream-io")] pub use bitstream_io_impl::{bitstream_io_reader, bitstream_io_writer}; pub(crate) fn signed_to_unsigned(v: i64) -> u64 { let u = if v.is_positive() { (v * 2) - 1 } else { -2 * v }; u as u64 }