simple_bitrange/
lib.rs

1//! # A simple bit range crate
2//!
3//! This crate aims to facilitate the extraction of bits in a small, simple crate. While it does
4//! not have as many bells and whistle as many other crates, simplicity is the key here.
5//!
6//! # Usage examples
7//!
8//! Extract bits from slice of bytes:
9//!
10//! ```rust
11//! # use simple_bitrange::*;
12//! let y: u32 = 0b00001111_11110000_01010000_00001010;
13//! let p: &[u8] = &y.to_le_bytes();
14//! let ret: u32 = p.range_read_le(..);
15//!
16//! assert_eq!(ret, y);
17//! ```
18
19#![no_std]
20#![deny(missing_docs)]
21
22use core::iter::{DoubleEndedIterator, ExactSizeIterator};
23use core::mem::size_of;
24use core::ops::{Bound, RangeBounds};
25
26/// A simple bit extraction definition.
27pub trait BitRangeRead<U> {
28    // /// Reads a range of bits from the type in native endian.
29    // fn range_read_ne<R: RangeBounds<usize>>(self, range: R) -> U;
30    /// Reads a range of bits from the type in little endian.
31    fn range_read_le<R: RangeBounds<usize>>(self, range: R) -> U;
32    /// Reads a range of bits from the type in big endian.
33    fn range_read_be<R: RangeBounds<usize>>(self, range: R) -> U;
34}
35
36/// A simple bit write definition.
37pub trait BitRangeWrite<U> {
38    // /// Writes a range of bits into a specific range using native endian.
39    // fn range_write_ne<R: RangeBounds<usize>>(self, range: R, value: U);
40    /// Writes a range of bits into a specific range using little endian.
41    fn range_write_le<R: RangeBounds<usize>>(self, range: R, value: U);
42    /// Writes a range of bits into a specific range using big endian.
43    fn range_write_be<R: RangeBounds<usize>>(self, range: R, value: U);
44}
45
46// macro_rules! impl_bit_range {
47//     ($($numeric:ty,)*) => {$(
48//         impl BitRangeRead<$numeric> for $numeric {
49//             fn range_read<R: RangeBounds<usize>>(self, range: R) -> $numeric {
50//                 let masked = match range.end_bound() {
51//                     Bound::Included(end) => self & ((1 << (*end + 1)) - 1),
52//                     Bound::Excluded(end) => self & ((1 << *end) - 1),
53//                     Bound::Unbounded => self,
54//                 };
55//
56//                 match range.start_bound() {
57//                     Bound::Included(start) => masked >> *start,
58//                     Bound::Excluded(start) => masked >> (*start + 1),
59//                     Bound::Unbounded => masked,
60//                 }
61//             }
62//         })*
63//     }
64// }
65//
66// // Basic type implementations
67// impl_bit_range!(u8, u16, u32, u64, u128,);
68
69macro_rules! impl_bit_range_slice {
70    ($($numeric:ty,)*) => {$(
71        impl BitRangeRead<$numeric> for &'_ [u8] {
72            #[cfg_attr(feature = "enable-inline", inline)]
73            #[cfg_attr(feature = "never-inline", inline(never))]
74            fn range_read_le<R: RangeBounds<usize>>(self, range: R) -> $numeric {
75                let res: u64 = bit_range_read_le_iter_impl(self.iter(), range);
76                res as $numeric
77            }
78
79            #[cfg_attr(feature = "enable-inline", inline)]
80            #[cfg_attr(feature = "never-inline", inline(never))]
81            fn range_read_be<R: RangeBounds<usize>>(self, range: R) -> $numeric {
82                let res: u64 = bit_range_read_le_iter_impl(self.iter().rev(), range);
83                res as $numeric
84            }
85        })*
86    }
87}
88
89// Slice implementations
90impl_bit_range_slice!(u8, u16, u32, u64,);
91
92macro_rules! impl_bit_range_write_slice {
93    ($($numeric:ty,)*) => {$(
94        impl BitRangeWrite<$numeric> for &'_ mut [u8] {
95            #[cfg_attr(feature = "enable-inline", inline)]
96            #[cfg_attr(feature = "never-inline", inline(never))]
97            fn range_write_le<R: RangeBounds<usize>>(self, range: R, value: $numeric) {
98                write_le_compound(self, value as u64, range);
99            }
100
101            #[cfg_attr(feature = "enable-inline", inline)]
102            #[cfg_attr(feature = "never-inline", inline(never))]
103            fn range_write_be<R: RangeBounds<usize>>(self, range: R, value: $numeric) {
104                write_be_compound(self, value as u64, range);
105            }
106        })*
107    }
108}
109
110// Slice implementations
111impl_bit_range_write_slice!(u8, u16, i32, u32, u64,);
112
113/// Helper of common code
114#[inline(always)]
115fn setup_iter<'a, R>(input_len: usize, range: R) -> (usize, usize, usize, usize)
116where
117    R: RangeBounds<usize>,
118{
119    let start_bit = match range.start_bound() {
120        Bound::Included(start) => *start,
121        Bound::Excluded(start) => *start + 1,
122        Bound::Unbounded => 0,
123    };
124    let end_bit = match range.end_bound() {
125        Bound::Included(end) => *end,
126        Bound::Excluded(end) => *end - 1,
127        Bound::Unbounded => size_of::<u64>() * 8 - 1,
128    };
129    let total_bits = end_bit - start_bit + 1;
130    let start_byte = start_bit / 8;
131    let end_byte = (end_bit / 8).min(input_len);
132
133    let start_bit = start_bit - start_byte * 8;
134
135    (start_bit, total_bits, start_byte, end_byte)
136}
137
138
139#[cfg_attr(feature = "enable-inline", inline)]
140#[cfg_attr(feature = "never-inline", inline(never))]
141fn bit_range_read_le_iter_impl<'a, I, R>(input: I, range: R) -> u64
142where
143    I: Iterator<Item = &'a u8> + DoubleEndedIterator + ExactSizeIterator,
144    R: RangeBounds<usize>,
145{
146    let (start_bit, total_bits, start_byte, end_byte) = setup_iter(input.len(), range);
147
148    let iter = input.skip(start_byte).take(end_byte + 1);
149
150    // The rust compiler is smart enough to see through this and not so u128 operations.
151    let mask = (1 << total_bits) - 1;
152    let mut output = read_u128_le(iter);
153    output >>= start_bit;
154    output &= mask;
155
156    output as u64
157}
158
159#[cfg_attr(feature = "enable-inline", inline)]
160#[cfg_attr(feature = "never-inline", inline(never))]
161fn write_le_compound<R>(output: &mut [u8], val: u64, range: R)
162where
163    R: RangeBounds<usize>,
164{
165    let (start_bit, total_bits, start_byte, end_byte) = setup_iter(output.len(), range);
166    let iter = output.iter().skip(start_byte).take(end_byte + 1);
167
168    // Extract area as u128
169    let mut work_value = read_u128_le(iter);
170
171    let mask = ((1 << total_bits) - 1) << start_bit;
172    let val = ((val as u128) << start_bit) & mask;
173
174    // Modify area
175    work_value &= !mask;
176    work_value |= val;
177
178    let iter = output.iter_mut().skip(start_byte).take(end_byte + 1);
179
180    // Write area back
181    write_value_le(iter, work_value);
182}
183
184#[cfg_attr(feature = "enable-inline", inline)]
185#[cfg_attr(feature = "never-inline", inline(never))]
186fn write_be_compound<R>(output: &mut [u8], val: u64, range: R)
187where
188    R: RangeBounds<usize>,
189{
190    let (start_bit, total_bits, start_byte, end_byte) = setup_iter(output.len(), range);
191    let iter = output.iter().rev().skip(start_byte).take(end_byte + 1);
192
193    // Extract area as u128
194    let mut work_value = read_u128_le(iter);
195
196    let mask = ((1 << total_bits) - 1) << start_bit;
197    let val = ((val as u128) << start_bit) & mask;
198
199    // Modify area
200    work_value &= !mask;
201    work_value |= val;
202
203    let iter = output.iter_mut().rev().skip(start_byte).take(end_byte + 1);
204
205    // Write area back
206    write_value_le(iter, work_value);
207}
208
209#[inline(always)]
210fn read_u128_le<'a, I>(input: I) -> u128
211where
212    I: Iterator<Item = &'a u8> + DoubleEndedIterator,
213{
214    input.rev().fold(0, |acc, x| (acc << 8) | *x as u128)
215}
216
217#[inline(always)]
218fn write_value_le<'a, O>(output: O, value: u128)
219where
220    O: Iterator<Item = &'a mut u8> + DoubleEndedIterator,
221{
222    let val_as_bytes = &value.to_be_bytes();
223    val_as_bytes
224        .iter()
225        .rev()
226        .zip(output)
227        .for_each(|(i, o)| *o = *i);
228}
229
230#[cfg(test)]
231#[macro_use]
232extern crate std;
233
234#[cfg(test)]
235mod tests;