use bitvec::prelude::*;
use std::borrow::Cow;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PayloadBlockState {
NotPresent = 0,
Undefined = 1,
Zero = 2,
Unmapped = 3,
FullyPresent = 6,
PartiallyPresent = 7,
}
impl PayloadBlockState {
pub(crate) fn from_raw(raw: u8) -> Option<Self> {
match raw {
0 => Some(Self::NotPresent),
1 => Some(Self::Undefined),
2 => Some(Self::Zero),
3 => Some(Self::Unmapped),
6 => Some(Self::FullyPresent),
7 => Some(Self::PartiallyPresent),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectorBitmapState {
NotPresent = 0,
Present = 6,
}
impl SectorBitmapState {
pub(crate) fn from_raw(raw: u8) -> Option<Self> {
match raw {
0 => Some(Self::NotPresent),
6 => Some(Self::Present),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BatState {
Payload(PayloadBlockState),
SectorBitmap(SectorBitmapState),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BatEntry<'a> {
bytes: &'a [u8; 8],
is_sector_bitmap: bool,
}
impl BatEntry<'_> {
#[inline]
pub(crate) fn raw_state(&self) -> u8 {
self.bytes.view_bits::<Lsb0>()[0..3].load::<u8>()
}
#[inline]
#[must_use]
pub fn file_offset_mb(&self) -> u64 {
self.bytes.view_bits::<Lsb0>()[20..64].load::<u64>()
}
pub(crate) fn payload_state(&self) -> Option<PayloadBlockState> {
PayloadBlockState::from_raw(self.raw_state())
}
pub(crate) fn sector_bitmap_state(&self) -> Option<SectorBitmapState> {
SectorBitmapState::from_raw(self.raw_state())
}
pub fn state(&self) -> Result<BatState> {
let raw = self.raw_state();
if self.is_sector_bitmap {
match SectorBitmapState::from_raw(raw) {
Some(s) => Ok(BatState::SectorBitmap(s)),
None => Err(Error::InvalidSectorBitmapState(raw)),
}
} else {
match PayloadBlockState::from_raw(raw) {
Some(s) => Ok(BatState::Payload(s)),
None => Err(Error::InvalidBlockState(raw)),
}
}
}
pub(crate) fn is_sector_bitmap(&self) -> bool {
self.is_sector_bitmap
}
}
#[derive(Clone)]
pub struct Bat<'a> {
data: Cow<'a, [u8]>,
chunk_ratio: u64,
}
impl<'a> Bat<'a> {
pub(crate) fn new(data: &'a [u8], chunk_ratio: u64) -> Self {
Self {
data: Cow::Borrowed(data),
chunk_ratio,
}
}
#[must_use]
pub(crate) fn len(&self) -> usize {
self.data.len() / 8
}
#[cfg(test)]
#[must_use]
pub(crate) fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn is_sector_bitmap_entry(&self, index: usize) -> bool {
if self.chunk_ratio == 0 {
return false;
}
let Ok(chunk_ratio) = usize::try_from(self.chunk_ratio) else {
return false;
};
let stride = chunk_ratio.saturating_add(1);
index % stride == chunk_ratio
}
pub fn entry(&self, index: u64) -> Result<BatEntry<'_>> {
let idx = usize::try_from(index).map_err(|_| Error::BatEntryNotFound { index })?;
let offset = idx * 8;
if offset.saturating_add(8) > self.data.len() {
return Err(Error::BatEntryNotFound { index });
}
let bytes: &[u8; 8] = self.data[offset..offset + 8]
.try_into()
.expect("length already validated");
Ok(BatEntry {
bytes,
is_sector_bitmap: self.is_sector_bitmap_entry(idx),
})
}
fn entry_unchecked(&self, index: usize) -> BatEntry<'_> {
let offset = index * 8;
let bytes: &[u8; 8] = self.data[offset..offset + 8]
.try_into()
.expect("iterator index is within bounds");
BatEntry {
bytes,
is_sector_bitmap: self.is_sector_bitmap_entry(index),
}
}
pub fn entries(&self) -> impl Iterator<Item = BatEntry<'_>> + '_ {
(0..self.len()).map(move |i| self.entry_unchecked(i))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_buf(entries: &[u64]) -> Vec<u8> {
let mut buf = Vec::with_capacity(entries.len() * 8);
for &e in entries {
buf.extend_from_slice(&e.to_le_bytes());
}
buf
}
#[test]
fn entry_count_matches_buffer() {
let buf = make_buf(&[0, 0, 0, 0, 0, 0]);
let bat = Bat::new(&buf, 4);
assert_eq!(bat.len(), 6);
assert!(!bat.is_empty());
}
#[test]
fn empty_bat() {
let bat = Bat::new(&[], 4);
assert!(bat.is_empty());
}
#[test]
fn state_extraction_all_payload_values() {
let buf = make_buf(&[0, 1, 2, 3, 0, 6, 7, 4, 5]);
let bat = Bat::new(&buf, 4);
assert_eq!(
bat.entry(0).unwrap().payload_state(),
Some(PayloadBlockState::NotPresent)
);
assert_eq!(
bat.entry(1).unwrap().payload_state(),
Some(PayloadBlockState::Undefined)
);
assert_eq!(
bat.entry(2).unwrap().payload_state(),
Some(PayloadBlockState::Zero)
);
assert_eq!(
bat.entry(3).unwrap().payload_state(),
Some(PayloadBlockState::Unmapped)
);
assert!(bat.entry(4).unwrap().is_sector_bitmap());
assert_eq!(
bat.entry(5).unwrap().payload_state(),
Some(PayloadBlockState::FullyPresent)
);
assert_eq!(
bat.entry(6).unwrap().payload_state(),
Some(PayloadBlockState::PartiallyPresent)
);
assert_eq!(bat.entry(7).unwrap().payload_state(), None);
assert_eq!(bat.entry(8).unwrap().payload_state(), None);
}
#[test]
fn state_extraction_sector_bitmap() {
let buf = make_buf(&[0, 0, 0, 0, 0, 0, 0, 0, 6]);
let bat = Bat::new(&buf, 4);
let sb = bat.entry(4).unwrap();
assert!(sb.is_sector_bitmap());
assert_eq!(
sb.sector_bitmap_state(),
Some(SectorBitmapState::NotPresent)
);
assert_eq!(
sb.state().unwrap(),
BatState::SectorBitmap(SectorBitmapState::NotPresent)
);
let buf2 = make_buf(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 6]);
let bat2 = Bat::new(&buf2, 4);
let sb2 = bat2.entry(9).unwrap();
assert!(sb2.is_sector_bitmap());
assert_eq!(sb2.sector_bitmap_state(), Some(SectorBitmapState::Present));
}
#[test]
fn file_offset_mb_extraction() {
let offset_mb: u64 = 0x1234; let mut raw_bytes = [0u8; 8];
{
let bits = raw_bytes.view_bits_mut::<Lsb0>();
bits[0..3].store::<u8>(6u8); bits[20..64].store::<u64>(offset_mb);
}
let buf = make_buf(&[u64::from_le_bytes(raw_bytes)]);
let bat = Bat::new(&buf, 4);
let entry = bat.entry(0).unwrap();
assert_eq!(entry.raw_state(), 6);
assert_eq!(entry.file_offset_mb(), offset_mb);
}
#[test]
fn file_offset_mb_44bit_max() {
let max_offset: u64 = 0x0FFF_FFFF_FFFF;
let mut raw_bytes = [0u8; 8];
{
let bits = raw_bytes.view_bits_mut::<Lsb0>();
bits[0..3].store::<u8>(0u8); bits[20..64].store::<u64>(max_offset);
}
let buf = make_buf(&[u64::from_le_bytes(raw_bytes)]);
let bat = Bat::new(&buf, 4);
assert_eq!(bat.entry(0).unwrap().file_offset_mb(), max_offset);
}
#[test]
fn entry_out_of_bounds() {
let buf = make_buf(&[0, 0]);
let bat = Bat::new(&buf, 4);
assert!(bat.entry(2).is_err());
assert!(bat.entry(100).is_err());
}
#[test]
fn entries_iterator_yields_correct_count() {
let buf = make_buf(&[0, 1, 2, 3, 4, 5, 6, 7]);
let bat = Bat::new(&buf, 4);
let entries: Vec<_> = bat.entries().collect();
assert_eq!(entries.len(), 8);
for (i, entry) in entries.iter().enumerate() {
assert_eq!(
entry.raw_state(),
u8::try_from(i).expect("test index fits u8")
);
}
}
#[test]
fn interleaving_pattern() {
let buf = make_buf(&[0; 8]);
let bat = Bat::new(&buf, 3);
assert!(!bat.entry(0).unwrap().is_sector_bitmap());
assert!(!bat.entry(1).unwrap().is_sector_bitmap());
assert!(!bat.entry(2).unwrap().is_sector_bitmap());
assert!(bat.entry(3).unwrap().is_sector_bitmap());
assert!(!bat.entry(4).unwrap().is_sector_bitmap());
assert!(!bat.entry(5).unwrap().is_sector_bitmap());
assert!(!bat.entry(6).unwrap().is_sector_bitmap());
assert!(bat.entry(7).unwrap().is_sector_bitmap());
}
#[test]
fn bat_entry_state_returns_correct_variant() {
let buf = make_buf(&[6, 0, 0, 0, 6]);
let bat = Bat::new(&buf, 4);
assert_eq!(
bat.entry(0).unwrap().state().unwrap(),
BatState::Payload(PayloadBlockState::FullyPresent)
);
assert_eq!(
bat.entry(4).unwrap().state().unwrap(),
BatState::SectorBitmap(SectorBitmapState::Present)
);
}
#[test]
fn invalid_state_4_returns_error_for_payload() {
let buf = make_buf(&[4]);
let bat = Bat::new(&buf, 4);
let err = bat.entry(0).unwrap().state().unwrap_err();
assert!(matches!(err, Error::InvalidBlockState(4)));
}
#[test]
fn invalid_state_5_returns_error_for_payload() {
let buf = make_buf(&[5]);
let bat = Bat::new(&buf, 4);
let err = bat.entry(0).unwrap().state().unwrap_err();
assert!(matches!(err, Error::InvalidBlockState(5)));
}
}