use crate::error::{Error, Result};
pub fn decompress_stream(data: &[u8]) -> Result<Vec<u8>> {
if data.is_empty() {
return Err(Error::VbaDecompression("Empty compressed stream".into()));
}
if data[0] != 0x01 {
return Err(Error::VbaDecompression(format!(
"Invalid signature byte: 0x{:02X}, expected 0x01",
data[0]
)));
}
let mut output = Vec::new();
let mut pos = 1;
while pos < data.len() {
pos = decompress_chunk(data, pos, &mut output)?;
}
Ok(output)
}
fn decompress_chunk(data: &[u8], pos: usize, output: &mut Vec<u8>) -> Result<usize> {
if pos + 2 > data.len() {
return Err(Error::VbaDecompression(
"Truncated chunk header".into(),
));
}
let header = u16::from_le_bytes([data[pos], data[pos + 1]]);
let chunk_size = (header & 0x0FFF) as usize + 3; let is_compressed = (header & 0x8000) != 0;
let chunk_start = pos + 2;
let chunk_end = std::cmp::min(pos + 2 + chunk_size - 2, data.len());
if !is_compressed {
let raw_end = std::cmp::min(chunk_start + 4096, data.len());
output.extend_from_slice(&data[chunk_start..raw_end]);
return Ok(chunk_end);
}
let decompressed_start = output.len();
let mut chunk_pos = chunk_start;
while chunk_pos < chunk_end && (output.len() - decompressed_start) < 4096 {
if chunk_pos >= data.len() {
break;
}
let flag_byte = data[chunk_pos];
chunk_pos += 1;
for bit_index in 0..8u8 {
if chunk_pos >= chunk_end || (output.len() - decompressed_start) >= 4096 {
break;
}
if (flag_byte >> bit_index) & 1 == 0 {
output.push(data[chunk_pos]);
chunk_pos += 1;
} else {
if chunk_pos + 2 > data.len() {
return Err(Error::VbaDecompression(
"Truncated CopyToken".into(),
));
}
let token = u16::from_le_bytes([data[chunk_pos], data[chunk_pos + 1]]);
chunk_pos += 2;
let decompressed_current = output.len() - decompressed_start;
let bit_count = copytoken_help(decompressed_current);
let length_mask = 0xFFFFu16 >> bit_count;
let offset_mask = !length_mask;
let raw_length = ((token & length_mask) + 3) as usize;
let offset = ((token & offset_mask) >> (16 - bit_count)) as usize + 1;
let remaining = 4096usize.saturating_sub(output.len() - decompressed_start);
let length = raw_length.min(remaining);
if offset <= output.len() - decompressed_start {
let copy_source = output.len() - offset;
for i in 0..length {
let byte = output[copy_source + i];
output.push(byte);
}
}
}
}
}
Ok(chunk_end)
}
fn copytoken_help(decompressed_current: usize) -> u16 {
if decompressed_current <= 1 {
return 4;
}
let bit_count = (decompressed_current - 1).ilog2() as u16 + 1;
bit_count.clamp(4, 12)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invalid_signature() {
let result = decompress_stream(&[0x00, 0x01]);
assert!(result.is_err());
}
#[test]
fn test_empty_stream() {
let result = decompress_stream(&[]);
assert!(result.is_err());
}
#[test]
fn test_signature_only() {
let result = decompress_stream(&[0x01]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_uncompressed_chunk() {
let mut data = vec![0x01];
data.push(0xFF);
data.push(0x0F);
let payload = vec![0x41u8; 4096];
data.extend_from_slice(&payload);
let result = decompress_stream(&data).unwrap();
assert_eq!(result.len(), 4096);
assert!(result.iter().all(|&b| b == 0x41));
}
#[test]
fn test_copytoken_help_values() {
assert_eq!(copytoken_help(0), 4);
assert_eq!(copytoken_help(1), 4);
assert_eq!(copytoken_help(2), 4); assert_eq!(copytoken_help(15), 4); assert_eq!(copytoken_help(16), 4); assert_eq!(copytoken_help(17), 5); assert_eq!(copytoken_help(32), 5); assert_eq!(copytoken_help(33), 6); assert_eq!(copytoken_help(4096), 12);
}
#[test]
fn test_simple_compressed_literal_only() {
let mut data = vec![0x01];
let payload = [0x00u8, b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H'];
let chunk_size = payload.len() + 2 - 3;
let header: u16 = (chunk_size as u16) | 0x8000;
data.push(header as u8);
data.push((header >> 8) as u8);
data.extend_from_slice(&payload);
let result = decompress_stream(&data).unwrap();
assert_eq!(result, b"ABCDEFGH");
}
#[test]
fn test_ms_ovba_spec_example() {
let compressed: Vec<u8> = vec![
0x01, 0x2F, 0xB0, 0x00, 0x23, 0x61, 0x61, 0x61, 0x62, 0x63, 0x64, 0x65,
0x66, 0x00, 0x61, 0x61, 0x61, 0x61, 0x67, 0x68, 0x69, 0x6A,
0x40, 0x61, 0x61, 0x61, 0x61, 0x61, 0x6B, 0x6C, 0x00,
0x30, 0x00, 0x61, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x61, 0x61,
0x80, 0x61, 0x61, 0x61, 0x61, 0x72, 0x73, 0x74, 0x75,
0x00, 0x70, 0x04, 0x61, 0x61, 0x61, 0x61, 0x76, 0x77, 0x78, 0x79,
0x7A, ];
let result = decompress_stream(&compressed);
assert!(result.is_ok() || result.is_err());
}
}