pub const OLECF_SIGNATURE: [u8; 8] = [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1];
pub const HEADER_SIZE: usize = 512;
pub const MINOR_VERSION: usize = 24;
pub const MAJOR_VERSION: usize = 26;
pub const BYTE_ORDER: usize = 28;
pub const SECTOR_SHIFT: usize = 30;
pub const MINI_SECTOR_SHIFT: usize = 32;
pub const NUM_DIR_SECTORS: usize = 40;
pub const NUM_FAT_SECTORS: usize = 44;
pub const FIRST_DIR_SECTOR: usize = 48;
pub const MINI_STREAM_CUTOFF: usize = 56;
pub const FIRST_MINIFAT_SECTOR: usize = 60;
pub const NUM_MINIFAT_SECTORS: usize = 64;
pub const FIRST_DIFAT_SECTOR: usize = 68;
pub const NUM_DIFAT_SECTORS: usize = 72;
pub const DIFAT_HEADER_OFFSET: usize = 76;
pub const DIFAT_HEADER_COUNT: usize = 109;
pub const BYTE_ORDER_LE: u16 = 0xFFFE;
pub const MINI_STREAM_CUTOFF_VALUE: u32 = 4096;
pub const MINI_SECTOR_SIZE: usize = 64;
pub const MINI_SECTOR_SHIFT_VALUE: u16 = 6;
pub const SECTOR_SHIFT_V3: u16 = 9;
pub const SECTOR_SHIFT_V4: u16 = 12;
pub const DIR_ENTRY_SIZE: usize = 128;
pub const FREESECT: u32 = 0xFFFF_FFFF;
pub const ENDOFCHAIN: u32 = 0xFFFF_FFFE;
pub const FATSECT: u32 = 0xFFFF_FFFD;
pub const DIFSECT: u32 = 0xFFFF_FFFC;
pub const MAXREGSECT: u32 = 0xFFFF_FFFA;
pub const NOSTREAM: u32 = 0xFFFF_FFFF;
pub const NAME: usize = 0;
pub const NAME_LEN: usize = 64;
pub const OBJECT_TYPE: usize = 66;
pub const COLOR: usize = 67;
pub const LEFT_SIBLING: usize = 68;
pub const RIGHT_SIBLING: usize = 72;
pub const CHILD: usize = 76;
pub const CLSID: usize = 80;
pub const STATE_BITS: usize = 96;
pub const CREATE_TIME: usize = 100;
pub const MODIFY_TIME: usize = 108;
pub const START_SECTOR: usize = 116;
pub const STREAM_SIZE: usize = 120;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum ObjectType {
Unknown = 0x00,
Storage = 0x01,
Stream = 0x02,
RootStorage = 0x05,
}
impl ObjectType {
#[must_use]
pub fn from_u8(value: u8) -> Option<ObjectType> {
match value {
0x00 => Some(ObjectType::Unknown),
0x01 => Some(ObjectType::Storage),
0x02 => Some(ObjectType::Stream),
0x05 => Some(ObjectType::RootStorage),
_ => None,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Color {
Red = 0x00,
Black = 0x01,
}
impl Color {
#[must_use]
pub fn from_u8(value: u8) -> Option<Color> {
match value {
0x00 => Some(Color::Red),
0x01 => Some(Color::Black),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signature_is_olecf_magic() {
assert_eq!(OLECF_SIGNATURE, [0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1]);
assert_eq!(u64::from_le_bytes(OLECF_SIGNATURE), 0xE11A_B1A1_E011_CFD0);
assert_eq!(HEADER_SIZE, 512);
}
#[test]
fn header_field_offsets() {
assert_eq!(MINOR_VERSION, 24);
assert_eq!(MAJOR_VERSION, 26);
assert_eq!(BYTE_ORDER, 28);
assert_eq!(SECTOR_SHIFT, 30);
assert_eq!(MINI_SECTOR_SHIFT, 32);
assert_eq!(NUM_DIR_SECTORS, 40);
assert_eq!(NUM_FAT_SECTORS, 44);
assert_eq!(FIRST_DIR_SECTOR, 48);
assert_eq!(MINI_STREAM_CUTOFF, 56);
assert_eq!(FIRST_MINIFAT_SECTOR, 60);
assert_eq!(NUM_MINIFAT_SECTORS, 64);
assert_eq!(FIRST_DIFAT_SECTOR, 68);
assert_eq!(NUM_DIFAT_SECTORS, 72);
assert_eq!(DIFAT_HEADER_OFFSET, 76);
assert_eq!(DIFAT_HEADER_COUNT, 109);
}
#[test]
fn structural_invariants() {
assert_eq!(BYTE_ORDER_LE, 0xFFFE);
assert_eq!(MINI_STREAM_CUTOFF_VALUE, 4096);
assert_eq!(MINI_SECTOR_SIZE, 64);
assert_eq!(MINI_SECTOR_SHIFT_VALUE, 6);
assert_eq!(1usize << MINI_SECTOR_SHIFT_VALUE, MINI_SECTOR_SIZE);
assert_eq!(SECTOR_SHIFT_V3, 9);
assert_eq!(SECTOR_SHIFT_V4, 12);
assert_eq!(1u32 << SECTOR_SHIFT_V3, 512);
assert_eq!(1u32 << SECTOR_SHIFT_V4, 4096);
assert_eq!(DIR_ENTRY_SIZE, 128);
}
#[test]
fn special_fat_sector_ids() {
assert_eq!(FREESECT, 0xFFFF_FFFF);
assert_eq!(ENDOFCHAIN, 0xFFFF_FFFE);
assert_eq!(FATSECT, 0xFFFF_FFFD);
assert_eq!(DIFSECT, 0xFFFF_FFFC);
assert_eq!(MAXREGSECT, 0xFFFF_FFFA);
assert_eq!(NOSTREAM, 0xFFFF_FFFF);
assert_eq!(NOSTREAM, FREESECT);
}
#[test]
fn directory_entry_offsets() {
assert_eq!(NAME, 0);
assert_eq!(NAME_LEN, 64);
assert_eq!(OBJECT_TYPE, 66);
assert_eq!(COLOR, 67);
assert_eq!(LEFT_SIBLING, 68);
assert_eq!(RIGHT_SIBLING, 72);
assert_eq!(CHILD, 76);
assert_eq!(CLSID, 80);
assert_eq!(STATE_BITS, 96);
assert_eq!(CREATE_TIME, 100);
assert_eq!(MODIFY_TIME, 108);
assert_eq!(START_SECTOR, 116);
assert_eq!(STREAM_SIZE, 120);
assert_eq!(STREAM_SIZE + 8, DIR_ENTRY_SIZE);
}
#[test]
fn object_type_round_trip() {
assert_eq!(ObjectType::from_u8(0x00), Some(ObjectType::Unknown));
assert_eq!(ObjectType::from_u8(0x01), Some(ObjectType::Storage));
assert_eq!(ObjectType::from_u8(0x02), Some(ObjectType::Stream));
assert_eq!(ObjectType::from_u8(0x05), Some(ObjectType::RootStorage));
assert_eq!(ObjectType::from_u8(0x03), None);
assert_eq!(ObjectType::Storage as u8, 0x01);
assert_eq!(ObjectType::RootStorage as u8, 0x05);
}
#[test]
fn color_round_trip() {
assert_eq!(Color::from_u8(0x00), Some(Color::Red));
assert_eq!(Color::from_u8(0x01), Some(Color::Black));
assert_eq!(Color::from_u8(0x02), None);
assert_eq!(Color::Red as u8, 0x00);
assert_eq!(Color::Black as u8, 0x01);
}
#[test]
fn sector_offset_formula() {
let off = |sid: u32, shift: u16| ((u64::from(sid) + 1) << shift);
assert_eq!(off(0, SECTOR_SHIFT_V3), 512);
assert_eq!(off(1, SECTOR_SHIFT_V3), 1024);
assert_eq!(0u64 << MINI_SECTOR_SHIFT_VALUE, 0);
assert_eq!(1u64 << MINI_SECTOR_SHIFT_VALUE, 64);
assert_eq!(3u64 << MINI_SECTOR_SHIFT_VALUE, 192);
}
}