const MAX_DECOMPRESSED_LEN: usize = 512 * 1024 * 1024;
pub fn decompress(input: &[u8], expected_len: usize) -> crate::Result<Vec<u8>> {
if expected_len > MAX_DECOMPRESSED_LEN {
return Err(crate::Error::Parse(format!(
"LZF: expected decompressed length {} exceeds cap of {}",
expected_len, MAX_DECOMPRESSED_LEN
)));
}
let mut out = vec![0u8; expected_len];
let mut iidx = 0;
let mut oidx = 0;
let in_len = input.len();
while iidx < in_len && oidx < expected_len {
let mut ctrl = input[iidx] as usize;
iidx += 1;
if ctrl < (1 << 5) {
ctrl += 1;
if iidx + ctrl > in_len || oidx + ctrl > expected_len {
return Err(crate::Error::Parse(
"LZF: literal run out of bounds".to_string(),
));
}
out[oidx..oidx + ctrl].copy_from_slice(&input[iidx..iidx + ctrl]);
iidx += ctrl;
oidx += ctrl;
} else {
let mut length = ctrl >> 5;
let mut ref_idx = oidx
.checked_sub(((ctrl & 0x1f) << 8) + 1)
.ok_or_else(|| crate::Error::Parse("LZF: invalid back reference".to_string()))?;
if length == 7 {
if iidx >= in_len {
return Err(crate::Error::Parse(
"LZF: truncated length byte".to_string(),
));
}
length += input[iidx] as usize;
iidx += 1;
}
if iidx >= in_len {
return Err(crate::Error::Parse(
"LZF: truncated reference byte".to_string(),
));
}
ref_idx = ref_idx
.checked_sub(input[iidx] as usize)
.ok_or_else(|| crate::Error::Parse("LZF: invalid back reference".to_string()))?;
iidx += 1;
length += 2;
if oidx + length > expected_len {
return Err(crate::Error::Parse(
"LZF: match run exceeds output bounds".to_string(),
));
}
#[allow(clippy::explicit_counter_loop)]
for _ in 0..length {
out[oidx] = out[ref_idx];
oidx += 1;
ref_idx += 1;
}
}
}
if oidx != expected_len {
return Err(crate::Error::Parse(format!(
"LZF: decompressed length {} != expected {}",
oidx, expected_len
)));
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_literals(data: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
for chunk in data.chunks(32) {
out.push((chunk.len() - 1) as u8);
out.extend_from_slice(chunk);
}
out
}
#[test]
fn literal_only_roundtrip() {
let original: Vec<u8> = (0..100).map(|i| (i * 7) as u8).collect();
let encoded = encode_literals(&original);
let decoded = decompress(&encoded, original.len()).unwrap();
assert_eq!(decoded, original);
}
#[test]
fn empty_input_roundtrip() {
let decoded = decompress(&[], 0).unwrap();
assert!(decoded.is_empty());
}
#[test]
fn back_reference_decodes_repeated_run() {
let encoded = [0u8, b'a', 192, 0];
let decoded = decompress(&encoded, 9).unwrap();
assert_eq!(decoded, vec![b'a'; 9]);
}
#[test]
fn literal_run_out_of_bounds_is_an_error() {
let err = decompress(&[5], 6).unwrap_err();
assert!(err.to_string().contains("literal run out of bounds"));
}
#[test]
fn back_reference_before_any_output_is_an_error() {
let err = decompress(&[192, 0], 5).unwrap_err();
assert!(err.to_string().contains("invalid back reference"));
}
#[test]
fn truncated_extended_length_byte_is_an_error() {
let err = decompress(&[0, b'x', 224], 10).unwrap_err();
assert!(err.to_string().contains("truncated length byte"));
}
#[test]
fn truncated_distance_byte_is_an_error() {
let err = decompress(&[0, b'x', 32], 10).unwrap_err();
assert!(err.to_string().contains("truncated reference byte"));
}
#[test]
fn match_run_exceeding_output_bounds_is_an_error() {
let encoded = [0u8, b'a', 192, 0];
let err = decompress(&encoded, 5).unwrap_err();
assert!(err.to_string().contains("match run exceeds output bounds"));
}
#[test]
fn short_input_leaves_length_mismatch_error() {
let err = decompress(&[0, b'a'], 5).unwrap_err();
assert!(err
.to_string()
.contains("decompressed length 1 != expected 5"));
}
#[test]
fn rejects_expected_len_over_cap() {
let err = decompress(&[], MAX_DECOMPRESSED_LEN + 1).unwrap_err();
assert!(err.to_string().contains("exceeds cap"));
}
}