use std::borrow::Cow;
use crate::io::Reader;
use crate::{Error, Result};
const FORMAT: &str = "LZ77";
pub fn decompress(bytes: &[u8]) -> Result<Vec<u8>> {
decompress_counting(bytes).map(|(out, _)| out)
}
pub fn decompress_if_compressed(bytes: &[u8]) -> Cow<'_, [u8]> {
match decompress_counting(bytes) {
Ok((out, read)) if !out.is_empty() && bytes.len() - read < 4 => Cow::Owned(out),
_ => Cow::Borrowed(bytes),
}
}
fn decompress_counting(bytes: &[u8]) -> Result<(Vec<u8>, usize)> {
let mut input = Reader::new(FORMAT, bytes);
let header = input.u32()?;
let kind = header as u8;
if kind != 0x10 && kind != 0x11 {
return Err(input.error_at(0, "not LZ77 data"));
}
let len = (header >> 8) as usize;
let mut out = Vec::with_capacity(len.min(bytes.len() * 9));
while out.len() < len {
let flags = input.u8()?;
for bit in (0..8).rev() {
if out.len() >= len {
break;
}
if flags >> bit & 1 == 0 {
out.push(input.u8()?);
continue;
}
let at = input.position();
let (length, distance) = reference(kind, &mut input)?;
let start = out
.len()
.checked_sub(distance)
.ok_or_else(|| Error::malformed(FORMAT, at as u64, "reference before the start"))?;
for index in start..start + length.min(len - out.len()) {
out.push(out[index]);
}
}
}
Ok((out, input.position()))
}
fn reference(kind: u8, input: &mut Reader) -> Result<(usize, usize)> {
let form = input.u8_at(input.position())? >> 4;
let (size, base) = match (kind, form) {
(0x10, _) => (2, 3),
(_, 0) => (3, 0x11),
(_, 1) => (4, 0x111),
_ => (2, 1),
};
let value = input
.take(size)?
.iter()
.fold(0, |value, &byte| value << 8 | byte as usize);
Ok(((value >> 12 & 0xFFFF) + base, (value & 0xFFF) + 1))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decompresses_literals_and_overlapping_references() {
let bytes = [0x10, 8, 0, 0, 0b0010_0000, b'a', b'b', 0x30, 0x01];
assert_eq!(decompress(&bytes).unwrap(), b"abababab");
}
#[test]
fn decompresses_every_long_reference_form() {
let len = 1 + 0x20 + 0x200 + 4u32;
let mut bytes = (0x11 | len << 8).to_le_bytes().to_vec();
bytes.extend([0b0111_0000, b'x']);
bytes.extend([0x00, (0x20 - 0x11) << 4, 0x00]);
bytes.extend([
0x10,
((0x200 - 0x111) >> 4) as u8,
((0x200 - 0x111) << 4) as u8,
0x00,
]);
bytes.extend([0x30, 0x00]);
assert_eq!(decompress(&bytes).unwrap(), vec![b'x'; len as usize]);
}
#[test]
fn rejects_corrupt_data() {
assert!(decompress(&[0x10, 4, 0, 0, 0x80, 0x00, 0x05]).is_err()); assert!(decompress(&[0x10, 8, 0, 0, 0x00, b'a']).is_err()); assert!(decompress(&[0x40, 1, 0, 0, 0, 0]).is_err());
}
#[test]
fn leaves_uncompressed_data_alone() {
let compressed = [0x10, 3, 0, 0, 0, b'a', b'b', b'c', 0];
assert_eq!(&*decompress_if_compressed(&compressed), b"abc");
assert!(matches!(
decompress_if_compressed(b"RGCN\xFF\xFE"),
Cow::Borrowed(_)
));
assert!(matches!(
decompress_if_compressed(&[0x10, 0, 0, 0, 0, 0, 0, 0]),
Cow::Borrowed(_)
));
let followed = [&compressed[..], &[0; 8]].concat();
assert_eq!(&*decompress_if_compressed(&followed), followed);
}
}