use crate::{Error, Result, Level};
const MIN_MATCH: usize = 4;
const HASH_LOG: usize = 12;
const HASH_TABLE_SIZE: usize = 1 << HASH_LOG; const MAX_DISTANCE: usize = 65535;
const LAST_LITERALS: usize = 5;
pub fn compress(input: &[u8]) -> Result<Vec<u8>> {
compress_with_level(input, Level::default())
}
pub fn compress_with_level(input: &[u8], level: Level) -> Result<Vec<u8>> {
match level {
Level::Fast => compress_fast(input),
Level::Balanced => compress_balanced(input),
Level::Best => compress_best(input),
}
}
fn compress_fast(input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(vec![0, 0, 0, 0]);
}
if input.len() > u32::MAX as usize {
return Err(Error::InputTooLarge {
size: input.len(),
max_size: u32::MAX as usize,
});
}
let mut output = Vec::with_capacity(input.len() + input.len() / 255 + 16);
output.extend_from_slice(&(input.len() as u32).to_le_bytes());
let mut hash_table: Vec<i32> = vec![-1; HASH_TABLE_SIZE];
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
let input_limit = if input_end > LAST_LITERALS {
input_end - LAST_LITERALS
} else {
0
};
while pos < input_limit {
if pos + MIN_MATCH <= input_end {
let hash = hash4(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let max_match = input_end - pos;
let len = count_match(&input[candidate_pos..], &input[pos..], max_match);
if len >= MIN_MATCH {
emit_sequence(&mut output, input, &mut anchor, pos, candidate_pos, len);
pos += len;
anchor = pos;
continue;
}
}
}
hash_table[hash] = pos as i32;
}
pos += 2; }
emit_final_literals(&mut output, input, anchor, input_end);
Ok(output)
}
fn compress_balanced(input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(vec![0, 0, 0, 0]); }
if input.len() > u32::MAX as usize {
return Err(Error::InputTooLarge {
size: input.len(),
max_size: u32::MAX as usize,
});
}
let mut output = Vec::with_capacity(input.len() + input.len() / 255 + 16);
output.extend_from_slice(&(input.len() as u32).to_le_bytes());
let mut hash_table: Vec<i32> = vec![-1; HASH_TABLE_SIZE];
let mut anchor = 0; let mut pos = 0;
let input_end = input.len();
let input_limit = if input_end > LAST_LITERALS {
input_end - LAST_LITERALS
} else {
0
};
while pos < input_limit {
let mut match_found = false;
let mut match_pos = 0;
let mut match_len = 0;
if pos + MIN_MATCH <= input_end {
let hash = hash4(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let max_match = input_end - pos;
let len = count_match(&input[candidate_pos..], &input[pos..], max_match);
if len >= MIN_MATCH {
match_found = true;
match_pos = candidate_pos;
match_len = len;
}
}
}
hash_table[hash] = pos as i32;
}
if match_found {
let literal_len = pos - anchor;
let lit_token = if literal_len >= 15 { 15 } else { literal_len };
let match_token = if match_len >= MIN_MATCH + 15 {
15
} else {
match_len - MIN_MATCH
};
output.push(((lit_token << 4) | match_token) as u8);
if literal_len >= 15 {
let mut remaining = literal_len - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[anchor..pos]);
let offset = (pos - match_pos) as u16;
output.extend_from_slice(&offset.to_le_bytes());
if match_len >= MIN_MATCH + 15 {
let mut remaining = match_len - MIN_MATCH - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
pos += match_len;
anchor = pos;
} else {
pos += 1;
}
}
let final_literals = input_end - anchor;
if final_literals > 0 {
let lit_token = if final_literals >= 15 { 15 } else { final_literals };
output.push((lit_token << 4) as u8);
if final_literals >= 15 {
let mut remaining = final_literals - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[anchor..]);
}
Ok(output)
}
fn compress_best(input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(vec![0, 0, 0, 0]);
}
if input.len() > u32::MAX as usize {
return Err(Error::InputTooLarge {
size: input.len(),
max_size: u32::MAX as usize,
});
}
let mut output = Vec::with_capacity(input.len() + input.len() / 255 + 16);
output.extend_from_slice(&(input.len() as u32).to_le_bytes());
let mut hash_table: Vec<i32> = vec![-1; HASH_TABLE_SIZE];
let mut anchor = 0;
let mut pos = 0;
let input_end = input.len();
let input_limit = if input_end > LAST_LITERALS {
input_end - LAST_LITERALS
} else {
0
};
while pos < input_limit {
let mut best_match_pos = 0;
let mut best_match_len = 0;
if pos + MIN_MATCH <= input_end {
let hash = hash4(&input[pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let max_match = input_end - pos;
let len = count_match(&input[candidate_pos..], &input[pos..], max_match);
if len >= MIN_MATCH {
best_match_pos = candidate_pos;
best_match_len = len;
}
}
}
hash_table[hash] = pos as i32;
}
if best_match_len > 0 && pos + 1 < input_limit {
let next_pos = pos + 1;
if next_pos + MIN_MATCH <= input_end {
let hash = hash4(&input[next_pos..]);
let candidate = hash_table[hash];
if candidate >= 0 {
let candidate_pos = candidate as usize;
let distance = next_pos - candidate_pos;
if distance > 0 && distance <= MAX_DISTANCE {
let max_match = input_end - next_pos;
let len = count_match(&input[candidate_pos..], &input[next_pos..], max_match);
if len > best_match_len + 2 {
pos += 1;
best_match_pos = candidate_pos;
best_match_len = len;
}
}
}
}
}
if best_match_len >= MIN_MATCH {
emit_sequence(&mut output, input, &mut anchor, pos, best_match_pos, best_match_len);
pos += best_match_len;
anchor = pos;
} else {
pos += 1;
}
}
emit_final_literals(&mut output, input, anchor, input_end);
Ok(output)
}
fn emit_sequence(
output: &mut Vec<u8>,
input: &[u8],
anchor: &mut usize,
pos: usize,
match_pos: usize,
match_len: usize,
) {
let literal_len = pos - *anchor;
let lit_token = if literal_len >= 15 { 15 } else { literal_len };
let match_token = if match_len >= MIN_MATCH + 15 {
15
} else {
match_len - MIN_MATCH
};
output.push(((lit_token << 4) | match_token) as u8);
if literal_len >= 15 {
let mut remaining = literal_len - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[*anchor..pos]);
let offset = (pos - match_pos) as u16;
output.extend_from_slice(&offset.to_le_bytes());
if match_len >= MIN_MATCH + 15 {
let mut remaining = match_len - MIN_MATCH - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
}
fn emit_final_literals(output: &mut Vec<u8>, input: &[u8], anchor: usize, input_end: usize) {
let final_literals = input_end - anchor;
if final_literals > 0 {
let lit_token = if final_literals >= 15 { 15 } else { final_literals };
output.push((lit_token << 4) as u8);
if final_literals >= 15 {
let mut remaining = final_literals - 15;
while remaining >= 255 {
output.push(255);
remaining -= 255;
}
output.push(remaining as u8);
}
output.extend_from_slice(&input[anchor..]);
}
}
pub fn decompress(input: &[u8]) -> Result<Vec<u8>> {
if input.len() < 4 {
return Err(Error::InvalidInput(
"Input too short, missing header".to_string(),
));
}
let original_size = u32::from_le_bytes([input[0], input[1], input[2], input[3]]) as usize;
if original_size == 0 {
return Ok(Vec::new()); }
let mut output = Vec::with_capacity(original_size);
let mut pos = 4;
while pos < input.len() {
if pos >= input.len() {
break;
}
let token = input[pos];
pos += 1;
let mut literal_len = (token >> 4) as usize;
let mut match_len = (token & 0x0F) as usize;
if literal_len == 15 {
loop {
if pos >= input.len() {
return Err(Error::CorruptedData(
"Unexpected end while reading literal length".to_string(),
));
}
let extra = input[pos] as usize;
pos += 1;
literal_len += extra;
if extra != 255 {
break;
}
}
}
if literal_len > 0 {
if pos + literal_len > input.len() {
return Err(Error::CorruptedData(format!(
"Literal overflow: need {} bytes at pos {}",
literal_len, pos
)));
}
output.extend_from_slice(&input[pos..pos + literal_len]);
pos += literal_len;
}
if pos >= input.len() || match_len == 0 {
continue; }
if pos + 2 > input.len() {
return Err(Error::CorruptedData(
"Unexpected end while reading match offset".to_string(),
));
}
let offset = u16::from_le_bytes([input[pos], input[pos + 1]]) as usize;
pos += 2;
if offset == 0 || offset > output.len() {
return Err(Error::CorruptedData(format!(
"Invalid match offset: {} (output len: {})",
offset,
output.len()
)));
}
match_len += MIN_MATCH;
if match_len == MIN_MATCH + 15 {
loop {
if pos >= input.len() {
return Err(Error::CorruptedData(
"Unexpected end while reading match length".to_string(),
));
}
let extra = input[pos] as usize;
pos += 1;
match_len += extra;
if extra != 255 {
break;
}
}
}
let match_start = output.len() - offset;
for i in 0..match_len {
let byte = output[match_start + i];
output.push(byte);
}
}
if output.len() != original_size {
return Err(Error::CorruptedData(format!(
"Size mismatch: expected {}, got {}",
original_size,
output.len()
)));
}
Ok(output)
}
#[inline]
fn hash4(data: &[u8]) -> usize {
if data.len() < 4 {
return 0;
}
let value = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
((value.wrapping_mul(2654435761)) >> (32 - HASH_LOG)) as usize
}
#[inline]
fn count_match(a: &[u8], b: &[u8], max_len: usize) -> usize {
let limit = a.len().min(b.len()).min(max_len);
let mut len = 0;
while len < limit && a[len] == b[len] {
len += 1;
}
len
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compress_decompress_simple() {
let data = b"Hello, World!";
let compressed = compress(data).unwrap();
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, &decompressed[..]);
}
#[test]
fn test_empty() {
let data = b"";
let compressed = compress(data).unwrap();
assert_eq!(compressed, vec![0, 0, 0, 0]);
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, &decompressed[..]);
}
#[test]
fn test_repetitive_data() {
let data = vec![b'A'; 1000];
let compressed = compress(&data).unwrap();
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
#[test]
fn test_random_data() {
let data: Vec<u8> = (0..1000).map(|i| (i * 17) as u8).collect();
let compressed = compress(&data).unwrap();
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
#[test]
fn test_all_zeros() {
let data = vec![0u8; 5000];
let compressed = compress(&data).unwrap();
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
#[test]
fn test_patterns() {
let patterns = vec![
b"abcabcabcabc".to_vec(),
b"The quick brown fox jumps over the lazy dog".to_vec(),
vec![1, 2, 3, 1, 2, 3, 1, 2, 3],
];
for data in patterns {
let compressed = compress(&data).unwrap();
let decompressed = decompress(&compressed).unwrap();
assert_eq!(data, decompressed);
}
}
}