#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Extracted {
pub value: i64,
pub max: i64,
pub raw: u64,
}
pub fn extract_bits(
data: &[u8],
bit_offset: usize,
bits: usize,
signed: bool,
offset: i64,
) -> Option<Extracted> {
if bits == 0 || bits > 64 {
return None;
}
let byte_offset = bit_offset >> 3;
if byte_offset >= data.len() {
return None;
}
let mut data = &data[byte_offset..];
let mut first_bit = bit_offset & 7;
let mut value: u64 = 0;
let mut max_v: u64 = 0;
let mut magnitude: usize = 0;
let mut remaining = bits;
let mut bits_extracted = 0usize;
while remaining > 0 {
let Some(byte) = data.first().copied() else {
break;
};
let in_this_byte = (8 - first_bit).min(remaining);
let all_ones = (1u64 << in_this_byte) - 1;
let mask = all_ones << first_bit;
let chunk = ((byte as u64) & mask) >> first_bit;
value |= chunk << magnitude;
max_v |= all_ones << magnitude;
magnitude += in_this_byte;
remaining -= in_this_byte;
first_bit += in_this_byte;
bits_extracted += in_this_byte;
if first_bit >= 8 {
first_bit -= 8;
if !data.is_empty() {
data = &data[1..];
}
}
}
if bits_extracted < bits {
return None;
}
let mut value_signed = value as i64;
let mut max_signed = max_v as i64;
if signed {
max_signed = (max_v >> 1) as i64;
if offset != 0 {
value_signed = value as i64 + offset;
max_signed += offset;
} else {
let sign_bit = 1u64 << (bits - 1);
if value & sign_bit != 0 {
let extended = value as i64 | !(max_v as i64);
value_signed = extended;
}
}
} else if offset != 0 {
value_signed = value as i64 + offset;
max_signed += offset;
}
Some(Extracted {
value: value_signed,
max: max_signed,
raw: value,
})
}
#[cfg(test)]
pub fn is_unavailable(extracted: Extracted) -> bool {
let reserved: i64 = if extracted.max >= 7 {
2
} else if extracted.max > 1 {
1
} else {
0
};
reserved > 0 && extracted.value > extracted.max - reserved
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sentinel {
None,
Unknown,
OutOfRange,
Reserved,
}
pub fn classify_sentinel(
extracted: Extracted,
unknown: Option<u64>,
out_of_range: Option<u64>,
reserved: Option<u64>,
) -> Sentinel {
let raw = extracted.raw;
if unknown == Some(raw) {
Sentinel::Unknown
} else if out_of_range == Some(raw) {
Sentinel::OutOfRange
} else if reserved == Some(raw) {
Sentinel::Reserved
} else {
Sentinel::None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extracts_8_bit_unsigned() {
let data = [0xab];
let e = extract_bits(&data, 0, 8, false, 0).unwrap();
assert_eq!(e.value, 0xab);
assert_eq!(e.max, 0xff);
}
#[test]
fn extracts_across_byte_boundary() {
let data = [0xab, 0xbc];
let e = extract_bits(&data, 4, 12, false, 0).unwrap();
assert_eq!(e.value, 0xbca);
assert_eq!(e.max, 0xfff);
}
#[test]
fn extracts_signed_negative() {
let data = [0xfe, 0xff];
let e = extract_bits(&data, 0, 16, true, 0).unwrap();
assert_eq!(e.value, -2);
assert_eq!(e.max, 0x7fff);
}
#[test]
fn extracts_signed_positive() {
let data = [0x05, 0x00];
let e = extract_bits(&data, 0, 16, true, 0).unwrap();
assert_eq!(e.value, 5);
}
#[test]
fn detects_unavailable_8bit() {
let e = Extracted {
value: 255,
max: 255,
raw: 255,
};
assert!(is_unavailable(e));
assert!(is_unavailable(Extracted {
value: 254,
max: 255,
raw: 254,
}));
assert!(!is_unavailable(Extracted {
value: 253,
max: 255,
raw: 253,
}));
}
#[test]
fn one_bit_never_unavailable() {
assert!(!is_unavailable(Extracted {
value: 1,
max: 1,
raw: 1
}));
assert!(!is_unavailable(Extracted {
value: 0,
max: 1,
raw: 0
}));
}
#[test]
fn none_when_data_runs_out_inside_the_field() {
let data = [0xab];
assert!(extract_bits(&data, 0, 16, false, 0).is_none());
}
#[test]
fn returns_none_when_starts_past_end() {
let data = [0xff];
assert!(extract_bits(&data, 8, 8, false, 0).is_none());
}
}