use crate::Result;
use super::attribute::FileName;
pub(crate) const INDX_RECORD_MAGIC: &[u8; 4] = b"INDX";
pub const ENTRY_FLAG_HAS_CHILD: u32 = 0x01;
pub const ENTRY_FLAG_LAST: u32 = 0x02;
#[derive(Debug, Clone)]
pub struct IndexRootHeader {
pub indexed_attr_type: u32,
pub collation_rule: u32,
pub index_block_size: u32,
pub clusters_per_index_block: i8,
pub first_entry_offset: u32,
pub bytes_in_use: u32,
pub bytes_allocated: u32,
pub flags: u8,
pub header_offset: usize,
}
impl IndexRootHeader {
pub fn parse(value: &[u8]) -> Result<Self> {
if value.len() < 32 {
return Err(crate::Error::InvalidImage(
"ntfs: $INDEX_ROOT too short".into(),
));
}
let indexed_attr_type = u32::from_le_bytes(value[0..4].try_into().unwrap());
let collation_rule = u32::from_le_bytes(value[4..8].try_into().unwrap());
let index_block_size = u32::from_le_bytes(value[8..12].try_into().unwrap());
let clusters_per_index_block = value[12] as i8;
let header_offset = 16usize;
let first_entry_offset =
u32::from_le_bytes(value[header_offset..header_offset + 4].try_into().unwrap());
let bytes_in_use = u32::from_le_bytes(
value[header_offset + 4..header_offset + 8]
.try_into()
.unwrap(),
);
let bytes_allocated = u32::from_le_bytes(
value[header_offset + 8..header_offset + 12]
.try_into()
.unwrap(),
);
let flags = value[header_offset + 12];
Ok(Self {
indexed_attr_type,
collation_rule,
index_block_size,
clusters_per_index_block,
first_entry_offset,
bytes_in_use,
bytes_allocated,
flags,
header_offset,
})
}
pub fn has_index_allocation(&self) -> bool {
self.flags & 0x01 != 0
}
}
#[derive(Debug, Clone)]
pub struct IndexEntry {
pub file_ref: u64,
pub file_name: Option<FileName>,
pub child_vcn: Option<u64>,
}
pub fn walk_index_node(
buf: &[u8],
entries_start: usize,
bytes_in_use: usize,
mut visit: impl FnMut(&IndexEntry),
) -> Result<Vec<u64>> {
let end = entries_start
.checked_add(bytes_in_use)
.ok_or_else(|| crate::Error::InvalidImage("ntfs: index walk overflow".into()))?;
if end > buf.len() {
return Err(crate::Error::InvalidImage(
"ntfs: index node oversteps buffer".into(),
));
}
let mut cursor = entries_start;
let mut children = Vec::new();
while cursor + 16 <= end {
let file_ref = u64::from_le_bytes(buf[cursor..cursor + 8].try_into().unwrap());
let entry_len =
u16::from_le_bytes(buf[cursor + 8..cursor + 10].try_into().unwrap()) as usize;
let key_len =
u16::from_le_bytes(buf[cursor + 10..cursor + 12].try_into().unwrap()) as usize;
let flags = u32::from_le_bytes(buf[cursor + 12..cursor + 16].try_into().unwrap());
if entry_len < 16 || cursor + entry_len > end {
return Err(crate::Error::InvalidImage(format!(
"ntfs: index entry length {entry_len} oversteps node"
)));
}
let mut child_vcn = None;
if flags & ENTRY_FLAG_HAS_CHILD != 0 {
if entry_len < 8 {
return Err(crate::Error::InvalidImage(
"ntfs: index entry with child but too short".into(),
));
}
let vcn_off = cursor + entry_len - 8;
child_vcn = Some(u64::from_le_bytes(
buf[vcn_off..vcn_off + 8].try_into().unwrap(),
));
}
let is_last = flags & ENTRY_FLAG_LAST != 0;
let file_name = if !is_last && key_len > 0 {
let key_start = cursor + 16;
let key_end = key_start + key_len;
if key_end > cursor + entry_len {
return Err(crate::Error::InvalidImage(
"ntfs: index entry key oversteps entry".into(),
));
}
Some(FileName::parse(&buf[key_start..key_end])?)
} else {
None
};
let entry = IndexEntry {
file_ref,
file_name,
child_vcn,
};
if !is_last {
visit(&entry);
}
if let Some(vcn) = entry.child_vcn {
children.push(vcn);
}
cursor += entry_len;
if is_last {
break;
}
}
Ok(children)
}
#[derive(Debug, Clone)]
pub struct IndexBlockHeader {
pub first_entry_offset: u32,
pub bytes_in_use: u32,
pub bytes_allocated: u32,
pub flags: u8,
}
impl IndexBlockHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 0x28 {
return Err(crate::Error::InvalidImage(
"ntfs: INDX block too small".into(),
));
}
if &buf[0..4] != INDX_RECORD_MAGIC {
return Err(crate::Error::InvalidImage(format!(
"ntfs: bad INDX magic {:02x?}",
&buf[0..4]
)));
}
let header_off = 0x18usize;
let first_entry_offset =
u32::from_le_bytes(buf[header_off..header_off + 4].try_into().unwrap());
let bytes_in_use =
u32::from_le_bytes(buf[header_off + 4..header_off + 8].try_into().unwrap());
let bytes_allocated =
u32::from_le_bytes(buf[header_off + 8..header_off + 12].try_into().unwrap());
let flags = buf[header_off + 12];
Ok(Self {
first_entry_offset,
bytes_in_use,
bytes_allocated,
flags,
})
}
pub fn entries_start(&self) -> usize {
0x18 + self.first_entry_offset as usize
}
pub fn entries_end(&self) -> usize {
0x18 + self.first_entry_offset as usize + self.bytes_in_use as usize
- self.first_entry_offset as usize
}
pub fn entries_byte_len(&self) -> usize {
(self.bytes_in_use as usize).saturating_sub(16)
}
}