pub mod cs0;
pub mod descriptor;
pub mod fid;
pub mod icb;
pub mod source;
fn u8_at(buf: &[u8], off: usize) -> Option<u8> {
buf.get(off).copied()
}
fn u16_le(buf: &[u8], off: usize) -> Option<u16> {
let chunk = buf.get(off..)?.first_chunk::<2>()?;
Some(u16::from_le_bytes(*chunk))
}
fn u32_le(buf: &[u8], off: usize) -> Option<u32> {
let chunk = buf.get(off..)?.first_chunk::<4>()?;
Some(u32::from_le_bytes(*chunk))
}
fn u64_le(buf: &[u8], off: usize) -> Option<u64> {
let chunk = buf.get(off..)?.first_chunk::<8>()?;
Some(u64::from_le_bytes(*chunk))
}
fn as_offset(value: u32) -> usize {
usize::try_from(value).unwrap_or(usize::MAX)
}
const TAG_LEN: usize = 16;
pub const TAG_PRIMARY_VOLUME: u16 = 1;
pub const TAG_ANCHOR_VOLUME_POINTER: u16 = 2;
pub const TAG_VOLUME_DESCRIPTOR_POINTER: u16 = 3;
pub const TAG_PARTITION: u16 = 5;
pub const TAG_LOGICAL_VOLUME: u16 = 6;
pub const TAG_TERMINATING: u16 = 8;
pub const TAG_FILE_SET: u16 = 256;
pub const TAG_FILE_IDENTIFIER: u16 = 257;
pub const TAG_ALLOCATION_EXTENT: u16 = 258;
pub const TAG_FILE_ENTRY: u16 = 261;
pub const TAG_EXTENDED_FILE_ENTRY: u16 = 266;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tag {
pub identifier: u16,
}
impl Tag {
#[must_use]
pub fn parse(buf: &[u8], offset: usize) -> Option<Self> {
let identifier = u16_le(buf, offset)?;
let bytes = buf.get(offset..offset.saturating_add(TAG_LEN))?;
let mut sum: u8 = 0;
let mut stored: u8 = 0;
for (i, &b) in bytes.iter().enumerate() {
if i == 4 {
stored = b;
} else {
sum = sum.wrapping_add(b);
}
}
if sum != stored {
return None;
}
Some(Self { identifier })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ExtentAd {
pub length: u32,
pub location: u32,
}
impl ExtentAd {
pub const LEN: usize = 8;
#[must_use]
pub fn parse(buf: &[u8], offset: usize) -> Option<Self> {
let length = u32_le(buf, offset)?;
let location = u32_le(buf, offset.saturating_add(4))?;
Some(Self { length, location })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LbAddr {
pub block: u32,
pub partition: u16,
}
impl LbAddr {
pub const LEN: usize = 6;
#[must_use]
pub fn parse(buf: &[u8], offset: usize) -> Option<Self> {
let block = u32_le(buf, offset)?;
let partition = u16_le(buf, offset.saturating_add(4))?;
Some(Self { block, partition })
}
}
const EXTENT_LENGTH_MASK: u32 = 0x3FFF_FFFF;
const EXTENT_TYPE_SHIFT: u32 = 30;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtentKind {
RecordedAllocated,
NotRecordedAllocated,
NotRecordedNotAllocated,
NextExtent,
}
impl ExtentKind {
#[must_use]
pub const fn from_raw(raw_length: u32) -> Self {
match raw_length.wrapping_shr(EXTENT_TYPE_SHIFT) {
0 => Self::RecordedAllocated,
1 => Self::NotRecordedAllocated,
2 => Self::NotRecordedNotAllocated,
_ => Self::NextExtent,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LongAd {
pub raw_length: u32,
pub location: LbAddr,
}
impl LongAd {
pub const LEN: usize = 16;
#[must_use]
pub fn parse(buf: &[u8], offset: usize) -> Option<Self> {
let raw_length = u32_le(buf, offset)?;
let location = LbAddr::parse(buf, offset.saturating_add(4))?;
Some(Self { raw_length, location })
}
#[must_use]
pub const fn length_bytes(&self) -> u32 {
self.raw_length & EXTENT_LENGTH_MASK
}
#[must_use]
pub const fn kind(&self) -> ExtentKind {
ExtentKind::from_raw(self.raw_length)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShortAd {
pub raw_length: u32,
pub position: u32,
}
impl ShortAd {
pub const LEN: usize = 8;
#[must_use]
pub fn parse(buf: &[u8], offset: usize) -> Option<Self> {
let raw_length = u32_le(buf, offset)?;
let position = u32_le(buf, offset.saturating_add(4))?;
Some(Self { raw_length, position })
}
#[must_use]
pub const fn length_bytes(&self) -> u32 {
self.raw_length & EXTENT_LENGTH_MASK
}
#[must_use]
pub const fn kind(&self) -> ExtentKind {
ExtentKind::from_raw(self.raw_length)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Extent {
pub partition_ref: Option<u16>,
pub block: u32,
pub length: u32,
pub kind: ExtentKind,
}
#[cfg(test)]
mod tests {
use proptest::prelude::{any, prop_assert, prop_assert_eq, proptest};
use super::{
EXTENT_LENGTH_MASK, ExtentAd, ExtentKind, LbAddr, LongAd, ShortAd,
TAG_ANCHOR_VOLUME_POINTER, Tag, as_offset, u8_at, u16_le, u32_le, u64_le,
};
fn fix_tag_checksum(mut tag: [u8; 16]) -> [u8; 16] {
let mut sum: u8 = 0;
for (i, &b) in tag.iter().enumerate() {
if i != 4 {
sum = sum.wrapping_add(b);
}
}
tag[4] = sum;
tag
}
#[test]
fn le_readers_assemble_little_endian() {
let buf = [0x11_u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
assert_eq!(u8_at(&buf, 0), Some(0x11));
assert_eq!(u16_le(&buf, 0), Some(0x2211));
assert_eq!(u32_le(&buf, 0), Some(0x4433_2211));
assert_eq!(u64_le(&buf, 0), Some(0x8877_6655_4433_2211));
}
#[test]
fn le_readers_out_of_bounds_are_none() {
let buf = [0x11_u8, 0x22, 0x33];
assert_eq!(u8_at(&buf, 3), None);
assert_eq!(u16_le(&buf, 2), None);
assert_eq!(u32_le(&buf, 0), None);
assert_eq!(u64_le(&buf, 0), None);
assert_eq!(u16_le(&buf, usize::MAX), None);
}
#[test]
fn as_offset_passes_through_and_saturates() {
assert_eq!(as_offset(0), 0);
assert_eq!(as_offset(0x1234_5678), 0x1234_5678_usize);
assert_eq!(as_offset(u32::MAX), usize::try_from(u32::MAX).unwrap_or(usize::MAX));
}
#[test]
fn tag_parses_with_valid_checksum() {
let tag = fix_tag_checksum([2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0]);
let parsed = Tag::parse(&tag, 0).expect("valid tag");
assert_eq!(parsed.identifier, TAG_ANCHOR_VOLUME_POINTER);
}
#[test]
fn tag_rejects_bad_checksum() {
let mut tag = fix_tag_checksum([6, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
tag[0] = tag[0].wrapping_add(1);
assert_eq!(Tag::parse(&tag, 0), None);
}
#[test]
fn tag_parses_at_offset() {
let tag = fix_tag_checksum([6, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 9, 0, 0, 0]);
let mut buf = vec![0xAA_u8; 5];
buf.extend_from_slice(&tag);
let parsed = Tag::parse(&buf, 5).expect("valid tag at offset 5");
assert_eq!(parsed.identifier, super::TAG_LOGICAL_VOLUME);
}
#[test]
fn tag_short_buffer_is_none() {
let tag = fix_tag_checksum([2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(Tag::parse(tag.get(..15).unwrap_or_default(), 0), None);
assert_eq!(Tag::parse(&tag, usize::MAX), None);
}
#[test]
fn extent_ad_parses_length_and_location() {
let buf = [0x00_u8, 0x08, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00];
let ad = ExtentAd::parse(&buf, 0).expect("extent_ad");
assert_eq!(ad.length, 0x0800);
assert_eq!(ad.location, 0x20);
assert_eq!(ExtentAd::parse(&buf, 1), None);
}
#[test]
fn lb_addr_parses_block_and_partition() {
let buf = [0x10_u8, 0x00, 0x00, 0x00, 0x02, 0x00];
let addr = LbAddr::parse(&buf, 0).expect("lb_addr");
assert_eq!(addr.block, 0x10);
assert_eq!(addr.partition, 2);
assert_eq!(LbAddr::parse(&buf, 1), None);
}
#[test]
fn long_ad_splits_length_and_type() {
let buf = [0x00_u8, 0x08, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00];
let ad = LongAd::parse(&buf, 0).expect("long_ad");
assert_eq!(ad.length_bytes(), 0x0800);
assert_eq!(ad.kind(), ExtentKind::NotRecordedAllocated);
assert_eq!(ad.location, LbAddr { block: 0x30, partition: 0 });
assert_eq!(LongAd::parse(&buf, 5), None);
}
#[test]
fn short_ad_splits_length_and_type() {
let buf = [0x00_u8, 0x10, 0x00, 0xC0, 0x40, 0x00, 0x00, 0x00];
let ad = ShortAd::parse(&buf, 0).expect("short_ad");
assert_eq!(ad.length_bytes(), 0x1000);
assert_eq!(ad.kind(), ExtentKind::NextExtent);
assert_eq!(ad.position, 0x40);
assert_eq!(ShortAd::parse(&buf, 1), None);
}
#[test]
fn small_parsers_are_none_until_fully_present() {
let buf = [0x11_u8; 16];
for len in 0..8 {
assert_eq!(
ExtentAd::parse(buf.get(..len).unwrap_or_default(), 0),
None,
"extent_ad {len}"
);
assert_eq!(
ShortAd::parse(buf.get(..len).unwrap_or_default(), 0),
None,
"short_ad {len}"
);
}
for len in 0..6 {
assert_eq!(LbAddr::parse(buf.get(..len).unwrap_or_default(), 0), None, "lb_addr {len}");
}
for len in 0..10 {
assert_eq!(LongAd::parse(buf.get(..len).unwrap_or_default(), 0), None, "long_ad {len}");
}
assert!(ExtentAd::parse(&buf, 0).is_some());
assert!(LbAddr::parse(&buf, 0).is_some());
assert!(LongAd::parse(&buf, 0).is_some());
assert!(ShortAd::parse(&buf, 0).is_some());
assert_eq!(ExtentAd::parse(&buf, usize::MAX), None);
assert_eq!(LbAddr::parse(&buf, usize::MAX), None);
assert_eq!(LongAd::parse(&buf, usize::MAX), None);
assert_eq!(ShortAd::parse(&buf, usize::MAX), None);
}
#[test]
fn extent_kind_covers_all_four_types() {
assert_eq!(ExtentKind::from_raw(0x0000_0000), ExtentKind::RecordedAllocated);
assert_eq!(ExtentKind::from_raw(0x4000_0000), ExtentKind::NotRecordedAllocated);
assert_eq!(ExtentKind::from_raw(0x8000_0000), ExtentKind::NotRecordedNotAllocated);
assert_eq!(ExtentKind::from_raw(0xC000_0000), ExtentKind::NextExtent);
assert_eq!(ExtentKind::from_raw(EXTENT_LENGTH_MASK), ExtentKind::RecordedAllocated);
}
proptest! {
#[test]
fn le_readers_never_panic(buf in any::<Vec<u8>>(), off in any::<usize>()) {
prop_assert_eq!(u8_at(&buf, off).is_some(), off < buf.len());
prop_assert_eq!(u16_le(&buf, off).is_some(), off.checked_add(2).is_some_and(|e| e <= buf.len()));
prop_assert_eq!(u32_le(&buf, off).is_some(), off.checked_add(4).is_some_and(|e| e <= buf.len()));
prop_assert_eq!(u64_le(&buf, off).is_some(), off.checked_add(8).is_some_and(|e| e <= buf.len()));
}
#[test]
fn u32_le_matches_from_le_bytes(prefix in any::<Vec<u8>>(), chunk in any::<[u8; 4]>()) {
let off = prefix.len();
let mut buf = prefix;
buf.extend_from_slice(&chunk);
prop_assert_eq!(u32_le(&buf, off), Some(u32::from_le_bytes(chunk)));
}
#[test]
fn tag_parse_never_panics(buf in any::<Vec<u8>>(), off in any::<usize>()) {
let parsed = Tag::parse(&buf, off);
prop_assert!(parsed.is_some() || parsed.is_none());
}
#[test]
fn long_short_length_is_low_30_bits(raw in any::<u32>(), pos in any::<u32>()) {
let mut buf = raw.to_le_bytes().to_vec();
buf.extend_from_slice(&pos.to_le_bytes());
buf.extend_from_slice(&[0_u8; 8]);
let short = ShortAd::parse(&buf, 0).expect("short_ad");
let long = LongAd::parse(&buf, 0).expect("long_ad");
prop_assert_eq!(short.length_bytes(), raw & EXTENT_LENGTH_MASK);
prop_assert_eq!(long.length_bytes(), raw & EXTENT_LENGTH_MASK);
prop_assert!(short.length_bytes() <= EXTENT_LENGTH_MASK);
}
}
}