pub fn decompress_unit(src: &[u8], dst: &mut [u8]) -> crate::Result<usize> {
let mut src_cursor = 0usize;
let mut dst_cursor = 0usize;
while src_cursor + 2 <= src.len() && dst_cursor < dst.len() {
let header = u16::from_le_bytes([src[src_cursor], src[src_cursor + 1]]);
if header == 0 {
break;
}
src_cursor += 2;
let is_compressed = header & 0x8000 != 0;
let chunk_len = (header & 0x0FFF) as usize + 1;
if src_cursor + chunk_len > src.len() {
return Err(crate::Error::InvalidImage(format!(
"ntfs: LZNT1 chunk length {chunk_len} oversteps source ({} remaining)",
src.len() - src_cursor
)));
}
let chunk = &src[src_cursor..src_cursor + chunk_len];
src_cursor += chunk_len;
if !is_compressed {
let take = chunk_len.min(dst.len() - dst_cursor);
dst[dst_cursor..dst_cursor + take].copy_from_slice(&chunk[..take]);
dst_cursor += take;
continue;
}
let chunk_start_in_dst = dst_cursor;
let mut chunk_in = 0usize;
while chunk_in < chunk.len() && dst_cursor < dst.len() {
let flags = chunk[chunk_in];
chunk_in += 1;
for bit in 0..8u8 {
if chunk_in >= chunk.len() || dst_cursor >= dst.len() {
break;
}
let is_back_ref = flags & (1 << bit) != 0;
if !is_back_ref {
dst[dst_cursor] = chunk[chunk_in];
chunk_in += 1;
dst_cursor += 1;
} else {
if chunk_in + 2 > chunk.len() {
return Err(crate::Error::InvalidImage(
"ntfs: LZNT1 back-ref token truncated".into(),
));
}
let tok = u16::from_le_bytes([chunk[chunk_in], chunk[chunk_in + 1]]);
chunk_in += 2;
let emitted_in_chunk = (dst_cursor - chunk_start_in_dst) as u32;
let u = bit_allocator_u(emitted_in_chunk);
let length_bits = 16 - u;
let length_mask = (1u16 << length_bits) - 1;
let length = (tok & length_mask) as usize + 3;
let offset = (tok >> length_bits) as usize + 1;
if offset > dst_cursor - chunk_start_in_dst {
return Err(crate::Error::InvalidImage(format!(
"ntfs: LZNT1 back-ref offset {offset} predates chunk start"
)));
}
let src_pos_start = dst_cursor - offset;
let take = length.min(dst.len() - dst_cursor);
for i in 0..take {
dst[dst_cursor + i] = dst[src_pos_start + i];
}
dst_cursor += take;
}
}
}
}
let emitted = dst_cursor;
for b in &mut dst[emitted..] {
*b = 0;
}
Ok(emitted)
}
fn bit_allocator_u(emitted: u32) -> u32 {
let mut u = 4u32;
let mut threshold = 1u32 << 4; while emitted >= threshold && u < 12 {
u += 1;
threshold <<= 1;
}
u
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decompresses_literal_chunk() {
let mut src = vec![0x03, 0x30]; src.extend_from_slice(b"ABCD");
src.extend_from_slice(&[0u8, 0u8]);
let mut dst = vec![0u8; 16];
let n = decompress_unit(&src, &mut dst).unwrap();
assert_eq!(n, 4);
assert_eq!(&dst[..4], b"ABCD");
assert_eq!(dst[4..], [0u8; 12]);
}
#[test]
fn decompresses_all_literal_compressed_chunk() {
let chunk_payload = vec![0x00u8, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H'];
let chunk_len_minus_1 = chunk_payload.len() as u16 - 1;
let header = 0xB000u16 | chunk_len_minus_1;
let mut src = header.to_le_bytes().to_vec();
src.extend_from_slice(&chunk_payload);
src.extend_from_slice(&[0u8, 0u8]);
let mut dst = vec![0u8; 16];
let n = decompress_unit(&src, &mut dst).unwrap();
assert_eq!(n, 8);
assert_eq!(&dst[..8], b"ABCDEFGH");
}
#[test]
fn decompresses_back_reference() {
let chunk_payload = vec![0x08u8, b'A', b'B', b'C', 0x00, 0x20];
let chunk_len_minus_1 = chunk_payload.len() as u16 - 1;
let header = 0xB000u16 | chunk_len_minus_1;
let mut src = header.to_le_bytes().to_vec();
src.extend_from_slice(&chunk_payload);
src.extend_from_slice(&[0u8, 0u8]);
let mut dst = vec![0u8; 16];
let n = decompress_unit(&src, &mut dst).unwrap();
assert_eq!(n, 6);
assert_eq!(&dst[..6], b"ABCABC");
}
#[test]
fn back_reference_self_overlap() {
let chunk_payload = vec![0x02u8, b'X', 0x02, 0x00];
let chunk_len_minus_1 = chunk_payload.len() as u16 - 1;
let header = 0xB000u16 | chunk_len_minus_1;
let mut src = header.to_le_bytes().to_vec();
src.extend_from_slice(&chunk_payload);
src.extend_from_slice(&[0u8, 0u8]);
let mut dst = vec![0u8; 16];
let n = decompress_unit(&src, &mut dst).unwrap();
assert_eq!(n, 6);
assert_eq!(&dst[..6], b"XXXXXX");
}
#[test]
fn bit_allocator_clamps() {
assert_eq!(bit_allocator_u(0), 4);
assert_eq!(bit_allocator_u(15), 4);
assert_eq!(bit_allocator_u(16), 5);
assert_eq!(bit_allocator_u(31), 5);
assert_eq!(bit_allocator_u(32), 6);
assert_eq!(bit_allocator_u(4095), 12);
assert_eq!(bit_allocator_u(8192), 12); }
}