use crate::error::{Error, Result};
pub fn decompress(data: &[u8], expected_size: usize) -> Result<Vec<u8>> {
let mut output = Vec::with_capacity(expected_size);
let mut ip = 0;
while ip < data.len() {
let ctrl = usize::from(data[ip]);
ip += 1;
if ctrl < 32 {
let count = ctrl + 1;
let literal_end = ip.checked_add(count).ok_or_else(|| {
Error::InvalidFormat("lzf: literal run input offset overflow".into())
})?;
if literal_end > data.len() {
return Err(Error::InvalidFormat(
"lzf: literal run exceeds input".into(),
));
}
if output
.len()
.checked_add(count)
.is_none_or(|len| len > expected_size)
{
return Err(Error::InvalidFormat(format!(
"lzf: literal run exceeds expected output size {expected_size}"
)));
}
output.extend_from_slice(&data[ip..literal_end]);
ip = literal_end;
} else {
let mut length = ctrl >> 5;
let mut offset = (ctrl & 0x1f) << 8;
if length == 7 {
if ip >= data.len() {
return Err(Error::InvalidFormat(
"lzf: unexpected end in long match".into(),
));
}
length += usize::from(data[ip]);
ip += 1;
}
length += 2;
if ip >= data.len() {
return Err(Error::InvalidFormat(
"lzf: unexpected end reading offset".into(),
));
}
offset += usize::from(data[ip]) + 1;
ip += 1;
if offset > output.len() {
return Err(Error::InvalidFormat(format!(
"lzf: back-reference offset {} exceeds output size {}",
offset,
output.len()
)));
}
if output
.len()
.checked_add(length)
.is_none_or(|len| len > expected_size)
{
return Err(Error::InvalidFormat(format!(
"lzf: back-reference exceeds expected output size {expected_size}"
)));
}
let start = output.len() - offset;
for i in 0..length {
let src = start.checked_add(i).ok_or_else(|| {
Error::InvalidFormat("lzf: back-reference source offset overflow".into())
})?;
let byte = *output.get(src).ok_or_else(|| {
Error::InvalidFormat("lzf: back-reference source out of range".into())
})?;
output.push(byte);
}
}
}
if output.len() != expected_size {
return Err(Error::InvalidFormat(format!(
"lzf: output length mismatch: expected {expected_size}, got {}",
output.len()
)));
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lzf_literal_only() {
let compressed = vec![0x04, b'H', b'e', b'l', b'l', b'o'];
let result = decompress(&compressed, 5).unwrap();
assert_eq!(result, b"Hello");
}
#[test]
fn test_lzf_with_backref() {
let compressed = vec![0x02, b'a', b'b', b'c', 0x20, 0x02];
let result = decompress(&compressed, 6).unwrap();
assert_eq!(result, b"abcabc");
}
#[test]
fn test_lzf_rejects_output_size_mismatch() {
let compressed = vec![0x04, b'H', b'e', b'l', b'l', b'o'];
let err = decompress(&compressed, 4).unwrap_err();
assert!(
err.to_string()
.contains("lzf: literal run exceeds expected output size 4"),
"unexpected error: {err}"
);
}
#[test]
fn test_lzf_rejects_backref_past_expected_output_size() {
let compressed = vec![0x00, b'a', 0x20, 0x00];
let err = decompress(&compressed, 2).unwrap_err();
assert!(
err.to_string()
.contains("lzf: back-reference exceeds expected output size 2"),
"unexpected error: {err}"
);
}
#[test]
fn test_lzf_rejects_literal_run_past_input() {
let compressed = vec![0x04, b'H', b'e'];
let err = decompress(&compressed, 5).unwrap_err();
assert!(
err.to_string().contains("lzf: literal run exceeds input"),
"unexpected error: {err}"
);
}
}