static LOOKUP: [u8; 16] = [
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf,
];
pub const MAX_BITMAP_PAYLOAD_BYTES: i64 = 512 * 1024 * 1024;
pub const MAX_OFFSET_FOR_BITMAP_LENGTH: i64 = (MAX_BITMAP_PAYLOAD_BYTES * 8) - 1;
#[inline]
pub fn is_valid_bit_offset(offset: i64) -> bool {
(0..=MAX_OFFSET_FOR_BITMAP_LENGTH).contains(&offset)
}
#[inline]
pub fn try_validate_length_in_bytes(offset: i64) -> Option<i32> {
if !is_valid_bit_offset(offset) {
return None;
}
Some(((offset >> 3) + 1) as i32)
}
#[inline]
pub fn try_validate_bitfield_offset(
offset: i64,
bit_count: u8,
multiply_offset: bool,
) -> Option<(i64, i64)> {
if bit_count == 0 {
return None;
}
let normalized_offset = if multiply_offset {
offset.checked_mul(i64::from(bit_count))?
} else {
offset
};
if normalized_offset < 0 {
return None;
}
let end_offset = normalized_offset.checked_add(i64::from(bit_count) - 1)?;
is_valid_bit_offset(end_offset).then_some((normalized_offset, end_offset))
}
#[inline]
pub fn try_validate_bit_pos_offsets(
start_offset: i64,
end_offset: i64,
offset_type: u8,
has_start_offset: bool,
has_end_offset: bool,
) -> bool {
let max_offset = if offset_type == 0x1 {
MAX_OFFSET_FOR_BITMAP_LENGTH
} else {
MAX_BITMAP_PAYLOAD_BYTES - 1
};
if has_start_offset && (start_offset < -max_offset || start_offset > max_offset) {
return true;
}
if has_end_offset && (end_offset < -max_offset || end_offset > max_offset) {
return true;
}
false
}
#[inline]
pub fn normalize_bit_count_offsets(
start_offset: i64,
end_offset: i64,
offset_type: u8,
) -> (i64, i64) {
let max_offset = if offset_type == 0x1 {
MAX_OFFSET_FOR_BITMAP_LENGTH
} else {
MAX_BITMAP_PAYLOAD_BYTES - 1
};
let start = start_offset.clamp(-max_offset, max_offset);
let end = end_offset.clamp(-max_offset, max_offset);
(start, end)
}
#[inline]
pub fn index(offset: i64) -> Option<usize> {
if !is_valid_bit_offset(offset) {
return None;
}
Some((offset >> 3) as usize)
}
#[inline]
pub fn length_in_bytes(offset: i64) -> Option<i32> {
try_validate_length_in_bytes(offset)
}
#[inline]
pub fn length(offset: i64) -> Option<i32> {
length_in_bytes(offset)
}
#[inline]
pub fn is_large_enough(vlen: i32, offset: i64) -> bool {
length_in_bytes(offset).is_some_and(|len| len <= vlen)
}
#[inline]
pub fn new_block_alloc_length(value_len: i32, offset: i64) -> i32 {
let length_in_bytes = length(offset).unwrap_or(i32::MAX);
if value_len > length_in_bytes {
value_len
} else {
length_in_bytes
}
}
pub fn update_bitmap(value: &mut [u8], offset: i64, set: u8) -> u8 {
let Some(byte_index) = index(offset) else {
return 0;
};
let bit_index = 7 - (offset & 7) as u32;
let Some(byte_val) = value.get_mut(byte_index) else {
return 0;
};
let old_val = (*byte_val >> bit_index) & 1;
*byte_val = (*byte_val & !(1 << bit_index)) | ((set & 1) << bit_index);
old_val
}
pub fn get_bit(offset: i64, value: &[u8]) -> u8 {
let Some(byte_index) = index(offset) else {
return 0;
};
let Some(byte_val) = value.get(byte_index) else {
return 0;
};
let bit_index = 7 - (offset & 7) as u32;
(byte_val >> bit_index) & 1
}
#[inline]
pub(crate) fn process_negative_offset(offset: i64, val_len: i64) -> i64 {
if val_len <= 0 {
0
} else {
(offset % val_len) + val_len
}
}
#[inline]
pub(crate) fn reverse(n: u8) -> u8 {
(LOOKUP[(n & 0b1111) as usize] << 4) | LOOKUP[(n >> 4) as usize]
}
#[cfg(test)]
mod tests {
use super::{
MAX_BITMAP_PAYLOAD_BYTES, MAX_OFFSET_FOR_BITMAP_LENGTH, index, is_large_enough,
is_valid_bit_offset, length, new_block_alloc_length, normalize_bit_count_offsets,
process_negative_offset, reverse, try_validate_bit_pos_offsets, try_validate_bitfield_offset,
try_validate_length_in_bytes, update_bitmap,
};
#[test]
fn offset_bounds() {
assert!(is_valid_bit_offset(0));
assert!(is_valid_bit_offset(MAX_OFFSET_FOR_BITMAP_LENGTH));
assert!(!is_valid_bit_offset(-1));
assert!(!is_valid_bit_offset(MAX_OFFSET_FOR_BITMAP_LENGTH + 1));
assert_eq!(MAX_OFFSET_FOR_BITMAP_LENGTH, 512 * 1024 * 1024 * 8 - 1);
}
#[test]
fn length_in_bytes_conversion() {
assert_eq!(try_validate_length_in_bytes(0), Some(1));
assert_eq!(try_validate_length_in_bytes(7), Some(1));
assert_eq!(try_validate_length_in_bytes(8), Some(2));
assert_eq!(try_validate_length_in_bytes(-1), None);
assert_eq!(length(15), Some(2));
}
#[test]
fn bitfield_offset_forms() {
assert_eq!(try_validate_bitfield_offset(0, 0, false), None);
assert_eq!(try_validate_bitfield_offset(9, 8, false), Some((9, 16)));
assert_eq!(try_validate_bitfield_offset(2, 8, true), Some((16, 23)));
assert_eq!(try_validate_bitfield_offset(-1, 8, false), None);
assert_eq!(try_validate_bitfield_offset(i64::MAX / 2, 64, true), None);
assert_eq!(
try_validate_bitfield_offset(MAX_OFFSET_FOR_BITMAP_LENGTH, 2, false),
None
);
}
#[test]
fn bit_pos_offsets_bounds() {
assert!(try_validate_bit_pos_offsets(
MAX_BITMAP_PAYLOAD_BYTES,
-1,
0x0,
true,
true
));
assert!(!try_validate_bit_pos_offsets(
MAX_BITMAP_PAYLOAD_BYTES - 1,
-1,
0x0,
true,
true
));
assert!(!try_validate_bit_pos_offsets(
MAX_OFFSET_FOR_BITMAP_LENGTH,
-1,
0x1,
true,
true
));
assert!(!try_validate_bit_pos_offsets(
-MAX_BITMAP_PAYLOAD_BYTES,
0,
0x0,
false,
false
));
}
#[test]
fn normalize_clamps() {
let (s, e) = normalize_bit_count_offsets(i64::MIN, i64::MAX, 0x0);
assert_eq!(
(s, e),
(
-(MAX_BITMAP_PAYLOAD_BYTES - 1),
MAX_BITMAP_PAYLOAD_BYTES - 1
)
);
let (s, e) = normalize_bit_count_offsets(-5, 5, 0x0);
assert_eq!((s, e), (-5, 5));
}
#[test]
fn index_and_alloc() {
assert_eq!(index(0), Some(0));
assert_eq!(index(8), Some(1));
assert_eq!(index(-1), None);
assert!(is_large_enough(1, 7));
assert!(!is_large_enough(1, 8));
assert!(is_large_enough(2, 8));
assert_eq!(new_block_alloc_length(3, 8), 3);
assert_eq!(new_block_alloc_length(1, 8), 2);
assert_eq!(new_block_alloc_length(1, 0), 1);
}
#[test]
fn update_and_get_bits() {
let mut val = [0u8; 2];
assert_eq!(update_bitmap(&mut val, 0, 1), 0);
assert_eq!(val, [0x80, 0x00]);
assert_eq!(update_bitmap(&mut val, 0, 1), 1);
assert_eq!(update_bitmap(&mut val, 9, 1), 0);
assert_eq!(val, [0x80, 0x40]);
assert_eq!(update_bitmap(&mut val, 9, 0), 1);
assert_eq!(val, [0x80, 0x00]);
assert_eq!(super::get_bit(0, &val), 1);
assert_eq!(super::get_bit(7, &val), 0);
assert_eq!(super::get_bit(16, &val), 0);
}
#[test]
fn negative_offset_and_reverse() {
assert_eq!(process_negative_offset(-1, 5), 4);
assert_eq!(process_negative_offset(-5, 5), 5);
assert_eq!(process_negative_offset(-6, 5), 4);
assert_eq!(process_negative_offset(3, 0), 0);
assert_eq!(reverse(0b0000_0001), 0b1000_0000);
assert_eq!(reverse(0b1011_0001), 0b1000_1101);
assert_eq!(reverse(0), 0);
assert_eq!(reverse(0xff), 0xff);
}
}