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 = data[ip] as usize;
ip += 1;
if ctrl < 32 {
let count = ctrl + 1;
if ip + count > data.len() {
return Err(Error::InvalidFormat(
"lzf: literal run exceeds input".into(),
));
}
output.extend_from_slice(&data[ip..ip + count]);
ip += count;
} else {
let mut length = (ctrl >> 5) as usize;
let mut offset = ((ctrl & 0x1f) as usize) << 8;
if length == 7 {
if ip >= data.len() {
return Err(Error::InvalidFormat(
"lzf: unexpected end in long match".into(),
));
}
length += data[ip] as usize;
ip += 1;
}
length += 2;
if ip >= data.len() {
return Err(Error::InvalidFormat(
"lzf: unexpected end reading offset".into(),
));
}
offset += data[ip] as usize + 1;
ip += 1;
if offset > output.len() {
return Err(Error::InvalidFormat(format!(
"lzf: back-reference offset {} exceeds output size {}",
offset,
output.len()
)));
}
let start = output.len() - offset;
for i in 0..length {
let byte = output[start + i];
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: output length mismatch"),
"unexpected error: {err}"
);
}
}