mod error;
pub use error::DecompressError;
use std::io::Read;
#[derive(Clone)]
pub struct Settings {
pub classic_mode: bool,
pub max_output_size: usize,
}
impl Default for Settings {
fn default() -> Self {
Self {
classic_mode: false,
max_output_size: usize::MAX,
}
}
}
struct Context<'a> {
source: &'a mut dyn Read,
settings: Settings,
last_offset: usize,
bit_value: u16,
}
impl<'a> Context<'a> {
pub fn new(source: &'a mut dyn Read, settings: Settings) -> Self {
Self {
source,
settings,
last_offset: 1,
bit_value: 0x8000,
}
}
fn next_step(&mut self, state: State, output: &mut Vec<u8>) -> Result<State, DecompressError> {
match state {
State::CopyLiterals => {
let length = self
.read_interlaced_elias_gamma(false)?
.min(self.settings.max_output_size - output.len());
for _ in 0..length {
let literal = self.read_byte()?;
output.push(literal);
}
if self.read_bit()? {
Ok(State::CopyFromNewOffset)
} else {
Ok(State::CopyFromLastOffset)
}
}
State::CopyFromLastOffset => {
let length = self.read_interlaced_elias_gamma(false)?;
self.write_bytes(self.last_offset, length, output)?;
if self.read_bit()? {
Ok(State::CopyFromNewOffset)
} else {
Ok(State::CopyLiterals)
}
}
State::CopyFromNewOffset => {
let high = self.read_interlaced_elias_gamma(!self.settings.classic_mode)?;
if high == 256 {
return Ok(State::Done);
}
let second_byte = self.read_byte()?;
let offset = (high << 7)
.checked_sub((second_byte >> 1) as usize)
.ok_or(DecompressError::InvalidInput)?;
self.last_offset = offset;
self.bit_value = (self.bit_value >> 1) | ((second_byte as u16) << 15);
let length = self
.read_interlaced_elias_gamma(false)?
.checked_add(1)
.ok_or(DecompressError::InvalidInput)?;
self.write_bytes(offset, length, output)?;
if self.read_bit()? {
Ok(State::CopyFromNewOffset)
} else {
Ok(State::CopyLiterals)
}
}
State::Done => Ok(State::Done),
}
}
fn read_byte(&mut self) -> Result<u8, DecompressError> {
let mut buf = [0u8];
if self.source.read(&mut buf)? == 0 {
Err(DecompressError::TruncatedInput)
} else {
Ok(buf[0])
}
}
fn read_bit(&mut self) -> Result<bool, DecompressError> {
if self.bit_value == 0x8000 {
self.bit_value = ((self.read_byte()? as u16) << 8) | 0x80;
}
let bit = self.bit_value & 0x8000 != 0;
self.bit_value <<= 1;
Ok(bit)
}
fn read_interlaced_elias_gamma(&mut self, inverted: bool) -> Result<usize, DecompressError> {
let mut value = 1;
while !self.read_bit()? {
value = (value << 1) | (self.read_bit()? ^ inverted) as usize;
}
Ok(value)
}
fn write_bytes(
&self,
offset: usize,
length: usize,
output: &mut Vec<u8>,
) -> Result<(), DecompressError> {
if offset == 0 {
return Err(DecompressError::InvalidInput);
}
let Some(s) = output.len().checked_sub(offset) else {
return Err(DecompressError::InvalidInput);
};
let length = length.min(self.settings.max_output_size - output.len());
output.reserve(length);
for i in 0..length {
output.push(output[s + i]);
}
Ok(())
}
}
#[derive(Debug)]
enum State {
CopyLiterals,
CopyFromLastOffset,
CopyFromNewOffset,
Done,
}
pub fn decompress(source: &mut dyn Read) -> Result<Vec<u8>, DecompressError> {
decompress_with_settings(source, Settings::default())
}
pub fn decompress_with_settings(
source: &mut dyn Read,
settings: Settings,
) -> Result<Vec<u8>, DecompressError> {
let mut context = Context::new(source, settings.clone());
let mut output = Vec::new();
let mut state = State::CopyLiterals;
while output.len() < settings.max_output_size {
state = context.next_step(state, &mut output)?;
if let State::Done = state {
break;
}
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decompress_to_expected() {
let source = [
0x1fu8, 0x41, 0x42, 0x52, 0x41, 0x20, 0xf6, 0xab, 0x43, 0x44, 0xf5, 0xf2, 0x55, 0x58,
];
let result = decompress(&mut source.as_ref()).unwrap();
assert_eq!(&result, b"ABRA ABRACADABRA");
}
#[test]
fn empty_input() {
let source: &[u8] = &[];
let result = decompress(&mut source.as_ref());
let Err(DecompressError::TruncatedInput) = result else {
panic!("Unexpected {result:?}");
};
}
#[test]
fn invalid_input_offset_0() {
let source = [
149, 0, 0, 0, 255, 255, 255, 255, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85,
85, 85, 170, 0,
];
let result = decompress(&mut source.as_ref());
let Err(DecompressError::InvalidInput) = result else {
panic!("Expected InvalidOffset, got {result:?}");
};
}
#[test]
fn max_output_size_heeded_when_copying_literals() {
const MAX: usize = 4;
#[rustfmt::skip]
let source = [
0b01_01_1_1_01,
1,
2,
3,
4,
5,
6,
7,
0b01_01_01_01, 0b01_01_01_1_0, ];
let data = decompress_with_settings(
&mut source.as_ref(),
Settings {
max_output_size: MAX,
..Default::default()
},
)
.unwrap();
assert_eq!(data, &[1, 2, 3, 4]);
}
}