#![allow(clippy::indexing_slicing)]
use bitvec::{field::BitField, order::Msb0, slice::BitSlice, view::BitView};
#[derive(Debug)]
pub enum DecompressError {
InvalidSrcSize,
InvalidDstSize,
MalformedSrcData,
}
#[derive(Debug)]
pub enum DecompressionAlgorithm {
UefiDecompress,
TianoDecompress,
}
pub fn decompress_into_with_algo(
src: &[u8],
dst: &mut [u8],
algo: DecompressionAlgorithm,
) -> Result<(), DecompressError> {
if src.len() < 8 {
Err(DecompressError::InvalidSrcSize)?;
}
let compressed_size = u32::from_le_bytes(src[0..4].try_into().unwrap()) as usize;
if compressed_size > src.len() {
Err(DecompressError::InvalidSrcSize)?;
}
let orig_size = u32::from_le_bytes(src[4..8].try_into().unwrap()) as usize;
if orig_size == 0 {
return Ok(());
}
if orig_size != dst.len() {
Err(DecompressError::InvalidDstSize)?;
}
let mut dst_idx = 0;
for result in CodeIterator::new(&src[8..], algo) {
match result {
Ok(symbol) => match symbol {
CodeSymbol::OrigChar(char) => {
dst[dst_idx] = char;
dst_idx += 1;
}
CodeSymbol::StrPointer(offset, len) => {
let start = dst_idx
.checked_sub(offset)
.and_then(|x| x.checked_sub(1))
.ok_or(DecompressError::MalformedSrcData)?;
for src in start..start + len {
dst[dst_idx] = dst[src];
dst_idx += 1;
if dst_idx == dst.len() {
break;
}
}
}
},
Err(err) => Err(err)?,
}
if dst_idx == dst.len() {
break;
}
}
Ok(())
}
enum CodeSymbol {
OrigChar(u8),
StrPointer(usize, usize),
}
const NC: usize = 510;
const CBIT: usize = 9;
const CTABLE_BITSIZE: usize = 12;
const NT: usize = 19;
const TBIT: usize = 5;
const PTABLE_BITSIZE: usize = 8;
const MAXNP: usize = 31;
const NPT: usize = [NT, MAXNP][(NT < MAXNP) as usize];
struct CodeIterator<'a> {
src: &'a BitSlice<u8, Msb0>,
src_index: usize,
is_error: bool,
remaining_block_size: usize,
left: [u16; 2 * NC - 1],
right: [u16; 2 * NC - 1],
c_len: [u8; NC],
pt_len: [u8; NPT],
c_table: [u16; 1 << CTABLE_BITSIZE],
pt_table: [u16; 1 << PTABLE_BITSIZE],
p_bit: usize,
}
impl<'a> CodeIterator<'a> {
fn new(src: &'a [u8], algo: DecompressionAlgorithm) -> Self {
Self {
src: src.view_bits::<Msb0>(),
src_index: 0,
is_error: false,
remaining_block_size: 0,
left: [0u16; 2 * NC - 1],
right: [0u16; 2 * NC - 1],
c_len: [0u8; NC],
pt_len: [0u8; NPT],
c_table: [0u16; 4096],
pt_table: [0u16; 256],
p_bit: match algo {
DecompressionAlgorithm::UefiDecompress => 4,
DecompressionAlgorithm::TianoDecompress => 5,
},
}
}
fn pop_bits(&mut self, count: usize) -> Result<&BitSlice<u8, Msb0>, DecompressError> {
if let Some(bitslice) = self.src.get(self.src_index..self.src_index + count) {
self.src_index += count;
Ok(bitslice)
} else {
Err(DecompressError::MalformedSrcData)
}
}
fn peek_bits(&self, count: usize) -> Result<&BitSlice<u8, Msb0>, DecompressError> {
if let Some(bitslice) = self.src.get(self.src_index..self.src_index + count) {
Ok(bitslice)
} else {
Err(DecompressError::MalformedSrcData)
}
}
fn read_pt_len(&mut self, num_symbols: usize, num_bits: usize, extra: bool) -> Result<(), DecompressError> {
assert!(num_symbols <= NPT);
let count = self.pop_bits(num_bits)?.load_be::<usize>();
if count == 0 {
let char_c = self.pop_bits(num_bits)?.load_be::<u16>();
self.pt_table.fill(char_c);
self.pt_len[..num_symbols].fill(0);
Ok(())
} else {
let mut idx = 0;
while idx < count && idx < NPT {
let mut code_len = self.pop_bits(3)?.load_be::<u8>();
if code_len == 7 {
loop {
let bit = self.pop_bits(1)?[0];
if bit {
code_len += 1;
} else {
break;
}
}
}
self.pt_len[idx] = code_len;
idx += 1;
if extra && idx == 3 {
let zero_count = self.pop_bits(2)?.load_be::<usize>();
self.pt_len[idx..idx + zero_count].fill(0);
idx += zero_count;
}
}
if idx > num_symbols {
Err(DecompressError::MalformedSrcData)?;
}
self.pt_len[idx..num_symbols].fill(0);
Self::build_huffman_table(
num_symbols,
&self.pt_len,
PTABLE_BITSIZE,
&mut self.pt_table,
&mut self.left,
&mut self.right,
)
}
}
fn read_c_len(&mut self) -> Result<(), DecompressError> {
let count = self.pop_bits(CBIT)?.load_be::<usize>();
if count == 0 {
let symbol = self.pop_bits(CBIT)?.load_be::<u16>();
self.c_len.fill(0);
self.c_table.fill(symbol);
Ok(())
} else {
let mut idx = 0;
while idx < count {
let mut symbol = self.pt_table[self.peek_bits(PTABLE_BITSIZE)?.load_be::<usize>()];
if symbol as usize >= NT {
let mut mask_idx = PTABLE_BITSIZE;
loop {
let bit_buff = self.peek_bits(mask_idx + 1)?;
if bit_buff[mask_idx] {
symbol = self.right[symbol as usize];
} else {
symbol = self.left[symbol as usize];
}
mask_idx += 1;
if (symbol as usize) < NT {
break;
}
}
}
self.pop_bits(self.pt_len[symbol as usize] as usize)?;
if symbol <= 2 {
if symbol == 0 {
symbol = 1;
} else if symbol == 1 {
symbol = self.pop_bits(4)?.load_be::<u16>() + 3;
} else if symbol == 2 {
symbol = self.pop_bits(CBIT)?.load_be::<u16>() + 20;
}
for _ in 0..symbol {
if idx >= self.c_len.len() {
Err(DecompressError::MalformedSrcData)?;
}
self.c_len[idx] = 0;
idx += 1;
}
} else {
if idx >= self.c_len.len() {
Err(DecompressError::MalformedSrcData)?;
}
self.c_len[idx] = (symbol - 2) as u8;
idx += 1;
}
}
self.c_len[idx..NC].fill(0);
Self::build_huffman_table(
NC,
&self.c_len,
CTABLE_BITSIZE,
&mut self.c_table,
&mut self.left,
&mut self.right,
)
}
}
fn decode_position(&mut self) -> Result<usize, DecompressError> {
let bit_buffer = self.peek_bits(PTABLE_BITSIZE)?;
let mut val = self.pt_table[bit_buffer.load_be::<usize>()] as usize;
if val >= MAXNP {
let mut mask_idx = PTABLE_BITSIZE;
loop {
let bit_buffer = self.peek_bits(mask_idx + 1)?;
if bit_buffer[mask_idx] {
val = self.right[val] as usize;
} else {
val = self.left[val] as usize;
}
mask_idx += 1;
if val < MAXNP {
break;
}
}
}
self.pop_bits(self.pt_len[val] as usize)?;
if val > 1 {
val = (1 << (val - 1)) + self.pop_bits(val - 1)?.load_be::<usize>();
}
Ok(val)
}
fn build_huffman_table(
num_symbols: usize,
bit_lengths: &[u8],
table_bits: usize,
table: &mut [u16],
left: &mut [u16],
right: &mut [u16],
) -> Result<(), DecompressError> {
assert!(table_bits <= 16);
let mut count = [0u16; 17];
for idx in 0..num_symbols {
if bit_lengths[idx] > 16 {
Err(DecompressError::MalformedSrcData)?;
}
count[bit_lengths[idx] as usize] += 1;
}
let mut start = [0u16; 18];
for idx in 1..=16 {
let word_of_start = start[idx];
let word_of_count = count[idx] << (16 - idx);
start[idx + 1] = word_of_start.wrapping_add(word_of_count);
}
if start[17] != 0 {
Err(DecompressError::MalformedSrcData)?;
}
let extended_bits = 16 - table_bits;
let mut weight = [0; 17];
for idx in 1..=table_bits {
start[idx] >>= extended_bits;
weight[idx] = 1 << (table_bits - idx);
}
for (idx, w) in weight.iter_mut().enumerate().skip(table_bits + 1) {
*w = 1 << (16 - idx)
}
let idx = start[table_bits + 1] >> extended_bits;
if idx != 0 {
let idx_3 = 1 << table_bits;
if idx < idx_3 {
table[idx as usize..idx_3 as usize].fill(0);
}
}
enum TablePointer {
Table(usize),
Left(usize),
Right(usize),
}
impl TablePointer {
fn set(&self, table: &mut [u16], left: &mut [u16], right: &mut [u16], val: u16) {
match self {
TablePointer::Table(idx) => table[*idx] = val,
TablePointer::Left(idx) => left[*idx] = val,
TablePointer::Right(idx) => right[*idx] = val,
}
}
fn get(&self, table: &mut [u16], left: &mut [u16], right: &mut [u16]) -> u16 {
match self {
TablePointer::Table(idx) => table[*idx],
TablePointer::Left(idx) => left[*idx],
TablePointer::Right(idx) => right[*idx],
}
}
}
let mut next_avail_node = num_symbols;
let mask = 1 << (15 - table_bits);
for (char, sym_bit_len) in bit_lengths.iter().enumerate().take(num_symbols) {
let sym_bit_len = *sym_bit_len as usize;
if sym_bit_len == 0 {
continue;
}
if sym_bit_len > 16 {
Err(DecompressError::MalformedSrcData)?;
}
let next_code = start[sym_bit_len].wrapping_add(weight[sym_bit_len]);
if sym_bit_len <= table_bits {
if start[sym_bit_len] >= next_code || next_code > 1 << table_bits {
Err(DecompressError::MalformedSrcData)?;
}
for idx in start[sym_bit_len]..next_code {
table[idx as usize] = char.try_into().expect("symbol count too large");
}
} else {
let mut symbol_bitstring = start[sym_bit_len];
let mut pointer = TablePointer::Table((symbol_bitstring >> extended_bits) as usize);
let mut idx = sym_bit_len - table_bits;
while idx != 0 {
if pointer.get(table, left, right) == 0 && next_avail_node < (2 * NC - 1) {
pointer.set(table, left, right, next_avail_node.try_into().expect("symbol count too large"));
right[next_avail_node] = 0;
left[next_avail_node] = 0;
next_avail_node += 1;
}
if pointer.get(table, left, right) < (2 * NC - 1) as u16 {
if symbol_bitstring & mask != 0 {
pointer = TablePointer::Right(pointer.get(table, left, right) as usize);
} else {
pointer = TablePointer::Left(pointer.get(table, left, right) as usize);
}
}
symbol_bitstring <<= 1;
idx -= 1;
}
pointer.set(table, left, right, char.try_into().expect("symbol count too large"));
}
start[sym_bit_len] = next_code;
}
Ok(())
}
}
impl Iterator for CodeIterator<'_> {
type Item = Result<CodeSymbol, DecompressError>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_error {
return None;
}
if self.remaining_block_size == 0 {
self.remaining_block_size = match self.pop_bits(16) {
Ok(bits) => bits.load_be::<u16>() as usize,
Err(err) => {
self.is_error = true;
return Some(Err(err));
}
};
if let Err(err) = self.read_pt_len(NT, TBIT, true) {
self.is_error = true;
return Some(Err(err));
}
if let Err(err) = self.read_c_len() {
self.is_error = true;
return Some(Err(err));
}
if let Err(err) = self.read_pt_len(MAXNP, self.p_bit, false) {
self.is_error = true;
return Some(Err(err));
}
}
self.remaining_block_size -= 1;
let bit_buff = match self.peek_bits(CTABLE_BITSIZE) {
Ok(buff) => buff,
Err(err) => {
self.is_error = true;
return Some(Err(err));
}
};
let mut decode_idx = self.c_table[bit_buff.load_be::<usize>()] as usize;
if decode_idx >= NC {
let mut mask_idx = CTABLE_BITSIZE;
loop {
let bit_buff = match self.peek_bits(mask_idx + 1) {
Ok(buff) => buff,
Err(err) => {
self.is_error = true;
return Some(Err(err));
}
};
if bit_buff[mask_idx] {
decode_idx = self.right[decode_idx] as usize;
} else {
decode_idx = self.left[decode_idx] as usize;
}
mask_idx += 1;
if decode_idx < NC {
break;
};
}
}
if let Err(err) = self.pop_bits(self.c_len[decode_idx] as usize) {
self.is_error = true;
return Some(Err(err));
}
if decode_idx < 256 {
Some(Ok(CodeSymbol::OrigChar(decode_idx as u8)))
} else {
let len = decode_idx - (0x100 - 3);
let pos = match self.decode_position() {
Ok(pos) => pos,
Err(err) => {
self.is_error = true;
return Some(Err(err));
}
};
Some(Ok(CodeSymbol::StrPointer(pos, len)))
}
}
}
#[cfg(test)]
mod test {
extern crate std;
use std::{fs::File, io::Read, iter::zip, println, time, vec, vec::Vec};
use super::decompress_into_with_algo;
macro_rules! test_collateral {
($fname:expr) => {
concat!(env!("CARGO_MANIFEST_DIR"), "/test_resources/", $fname)
};
}
#[test]
fn uefi_decompress_should_produce_expected_buffer() {
let mut compressed_file =
File::open(test_collateral!("uefi_compressed.bin")).expect("failed to open test file");
let mut compressed_buffer = Vec::new();
compressed_file.read_to_end(&mut compressed_buffer).expect("failed to read test file");
let mut uncompressed_file =
File::open(test_collateral!("uefi_uncompressed.bin")).expect("failed to open test file");
let mut uncompressed_buffer = Vec::new();
uncompressed_file.read_to_end(&mut uncompressed_buffer).expect("failed to read test file");
let mut test_buffer = vec![0u8; uncompressed_buffer.len()];
decompress_into_with_algo(&compressed_buffer, &mut test_buffer, super::DecompressionAlgorithm::UefiDecompress)
.unwrap();
assert_eq!(test_buffer.len(), uncompressed_buffer.len());
for (idx, (test, reference)) in zip(test_buffer, uncompressed_buffer).enumerate() {
assert!(test == reference, "mismatch at idx: {:}, expected {:#x} != {:#x} actual", idx, reference, test);
}
}
#[test]
fn tiano_decompress_should_produce_expected_buffer() {
let mut compressed_file =
File::open(test_collateral!("tiano_compressed.bin")).expect("failed to open test file");
let mut compressed_buffer = Vec::new();
compressed_file.read_to_end(&mut compressed_buffer).expect("failed to read test file");
let mut uncompressed_file =
File::open(test_collateral!("tiano_uncompressed.bin")).expect("failed to open test file");
let mut uncompressed_buffer = Vec::new();
uncompressed_file.read_to_end(&mut uncompressed_buffer).expect("failed to read test file");
let mut test_buffer = vec![0u8; uncompressed_buffer.len()];
decompress_into_with_algo(&compressed_buffer, &mut test_buffer, super::DecompressionAlgorithm::TianoDecompress)
.unwrap();
assert_eq!(test_buffer.len(), uncompressed_buffer.len());
for (idx, (test, reference)) in zip(test_buffer, uncompressed_buffer).enumerate() {
assert!(test == reference, "mismatch at idx: {:}, expected {:#x} != {:#x} actual", idx, reference, test);
}
}
#[test]
fn decompress_with_original_size_of_zero_should_return_zero_sized_buffer() {
let mut compressed_buffer = [0x0; 16];
compressed_buffer[0] = 0x08;
let mut uefi_uncompressed = Vec::new();
assert!(
decompress_into_with_algo(
&compressed_buffer,
&mut uefi_uncompressed,
super::DecompressionAlgorithm::UefiDecompress
)
.is_ok()
);
assert_eq!(uefi_uncompressed.len(), 0);
let mut tiano_uncompressed = Vec::new();
assert!(
decompress_into_with_algo(
&compressed_buffer,
&mut tiano_uncompressed,
super::DecompressionAlgorithm::TianoDecompress
)
.is_ok()
);
assert_eq!(tiano_uncompressed.len(), 0);
}
#[test]
fn fuzz_testing_should_fail_gracefully() {
const FUZZ_COUNT: usize = 100;
let mut compressed_file =
File::open(test_collateral!("uefi_compressed.bin")).expect("failed to open test file");
let mut compressed_buffer = Vec::new();
compressed_file.read_to_end(&mut compressed_buffer).expect("failed to read test file");
let mut uncompressed_file =
File::open(test_collateral!("uefi_uncompressed.bin")).expect("failed to open test file");
let mut uncompressed_buffer = Vec::new();
uncompressed_file.read_to_end(&mut uncompressed_buffer).expect("failed to read test file");
let uncompressed_len = uncompressed_buffer.len();
for _ in 0..FUZZ_COUNT {
let mut fuzz_buffer = compressed_buffer.clone();
let fuzz_time = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_micros() as usize;
let fuzz_idx = fuzz_time % fuzz_buffer.len();
println!("fuzz_idx: {:} before: {:#x}", fuzz_idx, fuzz_buffer[fuzz_idx]);
fuzz_buffer[fuzz_idx] ^= 0xff;
println!("fuzz_idx: {:} after: {:#x}", fuzz_idx, fuzz_buffer[fuzz_idx]);
let mut test_buffer = vec![0u8; uncompressed_len];
let _ = decompress_into_with_algo(
&fuzz_buffer,
&mut test_buffer,
super::DecompressionAlgorithm::UefiDecompress,
);
}
}
}