use super::bitmap_manager::{index, try_validate_bitfield_offset};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitFieldSecondaryCommand {
Get,
Set,
IncrBy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BitFieldOverflow {
Wrap = 0,
Sat = 1,
Fail = 2,
}
pub const BIT_FIELD_SIGN_SIGNED: u8 = 0x80;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BitFieldCmdArgs {
pub secondary_command: BitFieldSecondaryCommand,
pub type_info: u8,
pub offset: i64,
pub value: i64,
pub overflow_type: u8,
}
impl BitFieldCmdArgs {
pub fn new(
secondary_command: BitFieldSecondaryCommand,
type_info: u8,
offset: i64,
value: i64,
overflow_type: u8,
) -> Self {
Self {
secondary_command,
type_info,
offset,
value,
overflow_type,
}
}
}
#[inline]
pub fn is_large_enough_for_type(args: &BitFieldCmdArgs, vlen: i32) -> bool {
length_from_type(args) <= vlen
}
#[inline]
pub fn length_from_type(args: &BitFieldCmdArgs) -> i32 {
let offset = args.offset;
let bit_count = args.type_info & 0x7F;
let end_offset = offset + i64::from(bit_count) - 1;
super::bitmap_manager::length_in_bytes(end_offset).unwrap_or(0)
}
#[inline]
pub fn new_block_alloc_length_from_type(args: &BitFieldCmdArgs, value_len: i32) -> i32 {
let length_in_bytes = length_from_type(args);
if value_len > length_in_bytes {
value_len
} else {
length_in_bytes
}
}
pub fn check_bitfield_overflow(
value: i64,
incr_by: i64,
bit_count: u8,
overflow_type: u8,
signed: bool,
) -> (i64, bool) {
let (new_value, overflow) = if signed {
check_signed_bitfield_overflow(value, incr_by, bit_count, overflow_type)
} else {
let (nv, overflow) =
check_unsigned_bitfield_overflow(value as u64, incr_by, bit_count, overflow_type);
(nv as i64, overflow)
};
let overflow = overflow_type == BitFieldOverflow::Fail as u8 && overflow;
(new_value, overflow)
}
fn check_unsigned_bitfield_overflow(
value: u64,
incr_by: i64,
bit_count: u8,
overflow_type: u8,
) -> (u64, bool) {
let max_val: u64 = if bit_count == 64 {
u64::MAX
} else {
(1u64 << bit_count) - 1
};
let max_add = max_val.wrapping_sub(value);
let neg = incr_by < 0;
let abs_incr_by = if incr_by < 0 {
(!incr_by as u64).wrapping_add(1)
} else {
incr_by as u64
};
let overflow = abs_incr_by > max_add;
let underflow = abs_incr_by > value && neg;
let mut result = if neg {
value.wrapping_sub(abs_incr_by)
} else {
value.wrapping_add(abs_incr_by)
};
result &= max_val;
match overflow_type {
x if x == BitFieldOverflow::Wrap as u8 => {
if overflow || underflow {
return (result, true);
}
(result, false)
}
x if x == BitFieldOverflow::Sat as u8 => {
if overflow {
(max_val, true)
} else if underflow {
(0, true)
} else {
(result, false)
}
}
x if x == BitFieldOverflow::Fail as u8 => {
if overflow || underflow {
(0, true)
} else {
(result, false)
}
}
_ => {
(0, true)
}
}
}
fn check_signed_bitfield_overflow(
value: i64,
incr_by: i64,
bit_count: u8,
overflow_type: u8,
) -> (i64, bool) {
let signbit: i64 = 1 << (bit_count - 1);
let mask: i64 = if bit_count == 64 { -1 } else { signbit - 1 };
let result = value.wrapping_add(incr_by);
let underflow = (result & signbit) == 0 && value < 0 && incr_by < 0;
let overflow = if bit_count == 64 {
result < 0 && value >= 0 && incr_by > 0
} else {
((result & !mask) as u64) > 0 && value >= 0 && incr_by > 0
};
match overflow_type {
x if x == BitFieldOverflow::Wrap as u8 => {
if underflow || overflow {
let mut res = result as u64;
if bit_count < 64 {
let msb = signbit as u64;
let smask = mask as u64;
res = if (res & msb) > 0 {
res | !smask
} else {
res & smask
};
}
return (res as i64, true);
}
(result, false)
}
x if x == BitFieldOverflow::Sat as u8 => {
let max_val: i64 = if bit_count == 64 {
i64::MAX
} else {
signbit - 1
};
if overflow {
(max_val, true)
} else if underflow {
(max_val.wrapping_neg().wrapping_sub(1), true)
} else {
(result, false)
}
}
x if x == BitFieldOverflow::Fail as u8 => {
if underflow || overflow {
(0, true)
} else {
(result, false)
}
}
_ => {
(0, true)
}
}
}
#[allow(clippy::too_many_arguments)]
fn get_value(
buf: &mut [u8; 8],
value: &[u8],
curr: &mut usize,
cend: usize,
vend: usize,
offset: i64,
encoding: u8,
signed: bool,
) -> i64 {
for slot in (0..8).rev() {
if *curr < cend {
buf[slot] = value[*curr];
*curr += 1;
}
}
let mut return_value = i64::from_le_bytes(*buf);
let left = (offset - ((offset >> 3) << 3)) as u32;
return_value <<= left;
if (64 - left) < u32::from(encoding) {
let lsb = if *curr < vend {
value[*curr] >> (8 - left)
} else {
0
};
return_value |= i64::from(lsb);
}
let right = 64 - u32::from(encoding);
if signed {
return_value >> right
} else {
((return_value as u64) >> right) as i64
}
}
#[allow(clippy::too_many_arguments)]
fn set_value(
buf: &[u8; 8],
value: &mut [u8],
curr: usize,
cend: usize,
bitmap_start: usize,
offset: i64,
encoding: i32,
unaligned_bits: i32,
new_value: i64,
) {
let byte_index_start = (offset >> 3) as usize;
let encoding = encoding as u32;
let mut tmp = new_value as u64
& if encoding == 64 {
u64::MAX
} else {
(1u64 << encoding) - 1
};
let mut pbits = encoding; let mut sbits: u32 = 0;
if encoding > unaligned_bits as u32 {
sbits = encoding - unaligned_bits as u32; let smask = (1u64 << sbits) - 1; let keep = 8 - sbits; let msb = ((tmp & smask) << keep) as u8; let b9 = value[curr] & ((1u16 << keep) - 1) as u8; value[curr] = msb | b9;
pbits = encoding - sbits; tmp &= !smask; }
let shf = unaligned_bits - encoding as i32; let mut mask: u64 = if encoding == 64 {
u64::MAX
} else {
(1u64 << pbits) - 1
};
if shf < 0 {
tmp >>= -shf;
mask = !((mask << sbits) >> -shf);
} else {
tmp <<= shf;
mask = !(mask << shf);
}
let old_v = u64::from_le_bytes(*buf);
let tmp = (old_v & mask) | tmp;
let mut curr = bitmap_start + byte_index_start;
for shift in [56u32, 48, 40, 32, 24, 16, 8, 0] {
if curr < cend {
value[curr] = ((tmp >> shift) & 0xFF) as u8;
curr += 1;
}
}
}
pub fn get_bitfield(
bitmap: &[u8],
bitmap_length: i64,
offset: i64,
encoding: u8,
signed: bool,
) -> Option<i64> {
let (offset, end_offset) = try_validate_bitfield_offset(offset, encoding, false)?;
let byte_index_start = index(offset)?;
let byte_index_end = index(end_offset)? + 1;
let mut buf = [0u8; 8];
if byte_index_start as i64 >= bitmap_length {
return Some(0);
}
let vend = bitmap_length as usize;
let mut curr = byte_index_start;
let cend = (byte_index_end).min(vend);
Some(get_value(
&mut buf, bitmap, &mut curr, cend, vend, offset, encoding, signed,
))
}
#[allow(clippy::too_many_arguments)]
pub fn set_bitfield(
bitmap: &mut [u8],
bitmap_length: i64,
offset: i64,
encoding: u8,
signed: bool,
new_value: i64,
overflow_type: u8,
) -> Option<(i64, bool)> {
let (offset, end_offset) = try_validate_bitfield_offset(offset, encoding, false)?;
let byte_index_start = index(offset)?;
let byte_index_end = index(end_offset)? + 1;
let mut buf = [0u8; 8];
if byte_index_start as i64 >= bitmap_length {
return None;
}
let vend = bitmap_length as usize;
let mut curr = byte_index_start;
let cend = byte_index_end.min(vend);
let old_value = get_value(
&mut buf, bitmap, &mut curr, cend, vend, offset, encoding, signed,
);
if overflow_type == BitFieldOverflow::Fail as u8 {
let (_, overflow) = check_bitfield_overflow(old_value, 0, encoding, overflow_type, signed);
if overflow {
return Some((0, true));
}
}
let left = (offset - ((offset >> 3) << 3)) as i32;
let unaligned_bits = 64 - left;
set_value(
&buf,
bitmap,
curr,
cend,
0,
offset,
i32::from(encoding),
unaligned_bits,
new_value,
);
Some((old_value, false))
}
#[allow(clippy::too_many_arguments)]
pub fn increment_bitfield(
value: &mut [u8],
val_len: i64,
offset: i64,
encoding: u8,
signed: bool,
increment_by_value: i64,
overflow_type: u8,
) -> Option<(i64, bool)> {
let (offset, end_offset) = try_validate_bitfield_offset(offset, encoding, false)?;
let byte_index_start = index(offset)?;
let byte_index_end = index(end_offset)? + 1;
let mut buf = [0u8; 8];
if byte_index_start as i64 >= val_len {
return None;
}
let vend = val_len as usize;
let mut curr = byte_index_start;
let cend = byte_index_end.min(vend);
let old_value = get_value(
&mut buf, value, &mut curr, cend, vend, offset, encoding, signed,
);
let (new_value, overflow) = check_bitfield_overflow(
old_value,
increment_by_value,
encoding,
overflow_type,
signed,
);
let left = (offset - ((offset >> 3) << 3)) as i32;
let unaligned_bits = 64 - left;
set_value(
&buf,
value,
curr,
cend,
0,
offset,
i32::from(encoding),
unaligned_bits,
new_value,
);
Some((new_value, overflow))
}
pub fn bit_field_execute(args: &BitFieldCmdArgs, value: &mut [u8]) -> Option<(i64, bool)> {
let bit_count = args.type_info & 0x7F;
let signed = (args.type_info & BIT_FIELD_SIGN_SIGNED) > 0;
let val_len = value.len() as i64;
match args.secondary_command {
BitFieldSecondaryCommand::Set => set_bitfield(
value,
val_len,
args.offset,
bit_count,
signed,
args.value,
args.overflow_type,
),
BitFieldSecondaryCommand::IncrBy => increment_bitfield(
value,
val_len,
args.offset,
bit_count,
signed,
args.value,
args.overflow_type,
),
BitFieldSecondaryCommand::Get => Some((
get_bitfield(value, val_len, args.offset, bit_count, signed)?,
false,
)),
}
}
pub fn bit_field_execute_ro(args: &BitFieldCmdArgs, value: &[u8]) -> Option<i64> {
let bit_count = args.type_info & 0x7F;
let signed = (args.type_info & BIT_FIELD_SIGN_SIGNED) > 0;
match args.secondary_command {
BitFieldSecondaryCommand::Get => {
get_bitfield(value, value.len() as i64, args.offset, bit_count, signed)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{
BitFieldCmdArgs, BitFieldOverflow, BitFieldSecondaryCommand, bit_field_execute,
bit_field_execute_ro, check_bitfield_overflow, check_signed_bitfield_overflow,
check_unsigned_bitfield_overflow, get_bitfield,
};
#[test]
fn get_set_roundtrip_matrix() {
for (signed, bits) in [
(false, 1u8),
(true, 1),
(false, 7),
(true, 8),
(false, 8),
(true, 16),
(false, 63),
(true, 64),
] {
let type_info = if signed { 0x80 | bits } else { bits };
let args = BitFieldCmdArgs::new(BitFieldSecondaryCommand::Set, type_info, 3, 0, 0);
let mut buf = vec![0u8; 16];
let max: i64 = if signed {
if bits == 64 {
i64::MAX
} else {
(1i64 << (bits - 1)) - 1
}
} else {
((1u64 << bits) - 1) as i64
};
let (old, ovf) = bit_field_execute(&args, &mut buf).unwrap();
assert_eq!((old, ovf), (0, false));
let _ = bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Set, type_info, 3, max, 0),
&mut buf,
)
.unwrap();
let v = bit_field_execute_ro(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Get, type_info, 3, 0, 0),
&buf,
)
.unwrap();
assert_eq!(v, max, "signed={signed} bits={bits}");
}
}
#[test]
fn unsigned_overflow_matrix() {
let fail = BitFieldOverflow::Fail as u8;
let wrap = BitFieldOverflow::Wrap as u8;
let sat = BitFieldOverflow::Sat as u8;
assert_eq!(
check_unsigned_bitfield_overflow(250, 10, 8, wrap),
(4, true)
);
assert_eq!(
check_unsigned_bitfield_overflow(250, 10, 8, sat),
(255, true)
);
assert_eq!(
check_unsigned_bitfield_overflow(250, 10, 8, fail),
(0, true)
);
assert_eq!(
check_unsigned_bitfield_overflow(5, -10, 8, wrap),
(251, true)
);
assert_eq!(check_unsigned_bitfield_overflow(5, -10, 8, sat), (0, true));
assert_eq!(check_unsigned_bitfield_overflow(5, -10, 8, fail), (0, true));
assert_eq!(
check_unsigned_bitfield_overflow(5, 10, 8, fail),
(15, false)
);
assert_eq!(
check_unsigned_bitfield_overflow(0, 255, 8, sat),
(255, false)
);
assert_eq!(
check_unsigned_bitfield_overflow(u64::MAX - 1, 10, 64, sat),
(u64::MAX, true)
);
assert_eq!(check_unsigned_bitfield_overflow(0, -1, 64, sat), (0, true));
assert_eq!(check_bitfield_overflow(250, 10, 8, fail, false), (0, true));
assert_eq!(check_bitfield_overflow(250, 10, 8, wrap, false), (4, false));
assert_eq!(
check_bitfield_overflow(250, 10, 8, sat, false),
(255, false)
);
}
#[test]
fn signed_overflow_matrix() {
let fail = BitFieldOverflow::Fail as u8;
let wrap = BitFieldOverflow::Wrap as u8;
let sat = BitFieldOverflow::Sat as u8;
assert_eq!(
check_signed_bitfield_overflow(120, 10, 8, wrap),
(-126, true)
);
assert_eq!(check_signed_bitfield_overflow(120, 10, 8, sat), (127, true));
assert_eq!(check_signed_bitfield_overflow(120, 10, 8, fail), (0, true));
assert_eq!(
check_signed_bitfield_overflow(-120, -10, 8, wrap),
(126, true)
);
assert_eq!(
check_signed_bitfield_overflow(-120, -10, 8, sat),
(-128, true)
);
assert_eq!(
check_signed_bitfield_overflow(-120, -10, 8, fail),
(0, true)
);
assert_eq!(
check_signed_bitfield_overflow(-120, 10, 8, fail),
(-110, false)
);
assert_eq!(
check_signed_bitfield_overflow(i64::MAX - 5, 10, 64, sat),
(i64::MAX, true)
);
assert_eq!(
check_signed_bitfield_overflow(i64::MIN + 5, -10, 64, sat),
(i64::MIN, true)
);
assert_eq!(
check_signed_bitfield_overflow(i64::MAX, 1, 64, wrap),
(i64::MIN, true)
);
assert_eq!(check_bitfield_overflow(120, 10, 8, fail, true), (0, true));
assert_eq!(
check_bitfield_overflow(-120, -10, 8, sat, true),
(-128, false)
);
}
#[test]
fn subcommand_combinations() {
let mut buf = vec![0u8; 4];
let (old, ovf) = bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Set, 16, 4, 0xABCD, 0),
&mut buf,
)
.unwrap();
assert_eq!((old, ovf), (0, false));
assert_eq!(
bit_field_execute_ro(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Get, 16, 4, 0, 0),
&buf
),
Some(0xABCD)
);
let mut buf = vec![0u8; 2];
let (v, ovf) = bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::IncrBy, 8, 0, 100, 0),
&mut buf,
)
.unwrap();
assert_eq!((v, ovf), (100, false));
let (v, ovf) = bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::IncrBy, 8, 0, 100, 0),
&mut buf,
)
.unwrap();
assert_eq!((v, ovf), (200, false));
let mut buf = vec![250u8, 0];
let (v, ovf) = bit_field_execute(
&BitFieldCmdArgs::new(
BitFieldSecondaryCommand::IncrBy,
8,
0,
10,
BitFieldOverflow::Fail as u8,
),
&mut buf,
)
.unwrap();
assert!(ovf);
assert_eq!(v, 0);
assert_eq!(buf[0], 0, "C# FAIL 溢出仍写入 newValue");
let mut short = vec![0u8; 1];
assert_eq!(
bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Set, 16, 8, 1, 0),
&mut short,
),
None
);
assert_eq!(get_bitfield(&[0u8; 2], 2, 100, 8, false), Some(0));
let mut buf = vec![0x00u8, 0xFF];
let _ = bit_field_execute(
&BitFieldCmdArgs::new(BitFieldSecondaryCommand::Set, 8, 4, 0xAB, 0),
&mut buf,
)
.unwrap();
assert_eq!(buf, vec![0x0A, 0xBF]);
}
}