use crate::Result;
#[derive(Debug, Clone)]
pub struct SdsEntryHeader {
pub hash: u32,
pub security_id: u32,
pub offset_in_sds: u64,
pub size: u32,
}
impl SdsEntryHeader {
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < 0x14 {
return Err(crate::Error::InvalidImage(
"ntfs: $Secure:$SDS entry header truncated".into(),
));
}
Ok(Self {
hash: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
security_id: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
offset_in_sds: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
size: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
})
}
}
#[derive(Debug, Clone)]
pub struct SiiEntry {
pub security_id: u32,
pub sds_offset: u64,
pub sds_size: u32,
}
pub fn walk_sii_node(buf: &[u8]) -> Result<Vec<SiiEntry>> {
let mut out = Vec::new();
let mut cursor = 0usize;
while cursor + 16 <= buf.len() {
let data_offset = u16::from_le_bytes(buf[cursor..cursor + 2].try_into().unwrap()) as usize;
let data_size =
u16::from_le_bytes(buf[cursor + 2..cursor + 4].try_into().unwrap()) as usize;
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 > buf.len() {
break;
}
let is_last = flags & 0x02 != 0;
if !is_last && key_len >= 4 && data_size >= 0x14 {
let key_start = cursor + 16;
let security_id = u32::from_le_bytes(buf[key_start..key_start + 4].try_into().unwrap());
let data_start = cursor + data_offset;
if data_start + data_size <= buf.len() {
let hdr = SdsEntryHeader::parse(&buf[data_start..data_start + data_size])?;
out.push(SiiEntry {
security_id,
sds_offset: hdr.offset_in_sds,
sds_size: hdr.size,
});
let _ = security_id;
}
}
cursor += entry_len;
if is_last {
break;
}
}
Ok(out)
}
#[derive(Clone)]
pub struct UpcaseTable {
table: Vec<u16>,
}
impl UpcaseTable {
pub fn identity() -> Self {
let mut table = Vec::with_capacity(0x10000);
for i in 0..=0xFFFFu16 {
table.push(i);
}
Self { table }
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let mut table = Vec::with_capacity(0x10000);
for i in 0..0x10000usize {
let start = i * 2;
if start + 2 <= bytes.len() {
table.push(u16::from_le_bytes([bytes[start], bytes[start + 1]]));
} else {
table.push(i as u16);
}
}
Self { table }
}
pub fn fold_unit(&self, c: u16) -> u16 {
self.table[c as usize]
}
pub fn fold_str(&self, s: &str) -> Vec<u16> {
s.encode_utf16().map(|u| self.fold_unit(u)).collect()
}
pub fn equals_ignore_case(&self, a: &str, b: &str) -> bool {
let mut au = a.encode_utf16();
let mut bu = b.encode_utf16();
loop {
match (au.next(), bu.next()) {
(None, None) => return true,
(Some(x), Some(y)) => {
if self.fold_unit(x) != self.fold_unit(y) {
return false;
}
}
_ => return false,
}
}
}
}
impl std::fmt::Debug for UpcaseTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UpcaseTable({} entries)", self.table.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identity_folds_self() {
let t = UpcaseTable::identity();
assert_eq!(t.fold_unit(0x0061), 0x0061);
assert!(t.equals_ignore_case("abc", "abc"));
assert!(!t.equals_ignore_case("abc", "abd"));
}
#[test]
fn ascii_uppercase_table() {
let mut bytes = Vec::with_capacity(0x10000 * 2);
for i in 0..0x10000u32 {
let v = if (0x61..=0x7A).contains(&i) {
i as u16 - 0x20
} else {
i as u16
};
bytes.extend_from_slice(&v.to_le_bytes());
}
let t = UpcaseTable::from_bytes(&bytes);
assert_eq!(t.fold_unit(b'a' as u16), b'A' as u16);
assert!(t.equals_ignore_case("HELLO.TXT", "hello.txt"));
assert!(!t.equals_ignore_case("hello", "world"));
}
#[test]
fn sds_header_parse() {
let mut buf = vec![0u8; 0x14];
buf[0..4].copy_from_slice(&0xDEAD_BEEFu32.to_le_bytes());
buf[4..8].copy_from_slice(&7u32.to_le_bytes());
buf[8..16].copy_from_slice(&0x4000u64.to_le_bytes());
buf[16..20].copy_from_slice(&0x100u32.to_le_bytes());
let h = SdsEntryHeader::parse(&buf).unwrap();
assert_eq!(h.hash, 0xDEAD_BEEF);
assert_eq!(h.security_id, 7);
assert_eq!(h.offset_in_sds, 0x4000);
assert_eq!(h.size, 0x100);
}
}