use zeroize::Zeroizing;
use super::bits::BitWriter;
use super::inflate::{DISTANCE_BASE, DISTANCE_EXTRA, LENGTH_BASE, LENGTH_EXTRA};
const SPAN: usize = 65_535;
const WINDOW: usize = 32_768;
const MIN_MATCH: usize = 3;
const MAX_MATCH: usize = 258;
const MAX_CHAIN: usize = 128;
const HASH_BITS: u32 = 15;
const STORED_OVERHEAD: usize = 6;
type Token = u32;
const MATCH: Token = 1 << 31;
pub(super) fn compress(input: &[u8]) -> Zeroizing<Vec<u8>> {
let spans = input.len().div_ceil(SPAN).max(1);
let bound = input.len() + spans * STORED_OVERHEAD + 1;
let mut out = Zeroizing::new(Vec::with_capacity(bound));
let mut matcher = Matcher::new();
let mut tokens: Zeroizing<Vec<Token>> =
Zeroizing::new(Vec::with_capacity(SPAN.min(input.len())));
{
let mut w = BitWriter::new(&mut out);
let mut start = 0;
loop {
let end = (start + SPAN).min(input.len());
let last = end == input.len();
tokens.clear();
matcher.tokenize(input, start, end, &mut tokens);
let fixed_bits = 3 + tokens.iter().map(|&t| fixed_token_bits(t)).sum::<u64>() + 7;
let padding = u64::from((8 - (w.bit_offset() + 3) % 8) % 8);
let stored_bits = 3 + padding + 32 + 8 * (end - start) as u64;
if fixed_bits < stored_bits {
fixed_block(&mut w, last, &tokens);
} else {
stored_block(&mut w, last, &input[start..end]);
}
if last {
break;
}
start = end;
}
w.finish();
}
debug_assert!(out.len() <= bound);
out
}
fn stored_block(w: &mut BitWriter<'_>, last: bool, data: &[u8]) {
let len = data.len() as u16;
w.bits(u32::from(last), 1);
w.bits(0b00, 2);
w.align_to_byte();
w.bits(u32::from(len), 16);
w.bits(u32::from(!len), 16);
w.bytes(data);
}
fn fixed_block(w: &mut BitWriter<'_>, last: bool, tokens: &[Token]) {
w.bits(u32::from(last), 1);
w.bits(0b01, 2);
for &token in tokens {
if token & MATCH == 0 {
let (code, len) = fixed_literal_code(token);
w.code(code, len);
continue;
}
let length = ((token >> 16) & 0x1FF) as usize;
let distance = (token & 0xFFFF) as usize;
let (index, extra) = length_symbol(length);
let (code, len) = fixed_literal_code(257 + index as u32);
w.code(code, len);
w.bits(extra, u32::from(LENGTH_EXTRA[index]));
let (index, extra) = distance_symbol(distance);
w.code(index as u32, 5);
w.bits(extra, u32::from(DISTANCE_EXTRA[index]));
}
let (code, len) = fixed_literal_code(256);
w.code(code, len);
}
fn fixed_literal_code(symbol: u32) -> (u32, u32) {
match symbol {
0..=143 => (0b0011_0000 + symbol, 8),
144..=255 => (0b1_1001_0000 + symbol - 144, 9),
256..=279 => (symbol - 256, 7),
_ => (0b1100_0000 + symbol - 280, 8),
}
}
fn fixed_token_bits(token: Token) -> u64 {
if token & MATCH == 0 {
return u64::from(fixed_literal_code(token).1);
}
let (index, _) = length_symbol(((token >> 16) & 0x1FF) as usize);
let (dindex, _) = distance_symbol((token & 0xFFFF) as usize);
u64::from(fixed_literal_code(257 + index as u32).1)
+ u64::from(LENGTH_EXTRA[index])
+ 5
+ u64::from(DISTANCE_EXTRA[dindex])
}
fn length_symbol(length: usize) -> (usize, u32) {
let index = LENGTH_BASE
.iter()
.rposition(|&base| usize::from(base) <= length)
.unwrap_or(0);
(index, (length - usize::from(LENGTH_BASE[index])) as u32)
}
fn distance_symbol(distance: usize) -> (usize, u32) {
let index = DISTANCE_BASE
.iter()
.rposition(|&base| usize::from(base) <= distance)
.unwrap_or(0);
(index, (distance - usize::from(DISTANCE_BASE[index])) as u32)
}
struct Matcher {
head: Zeroizing<Vec<usize>>,
prev: Zeroizing<Vec<usize>>,
}
impl Matcher {
fn new() -> Self {
Self {
head: Zeroizing::new(vec![0; 1 << HASH_BITS]),
prev: Zeroizing::new(vec![0; WINDOW]),
}
}
fn hash(bytes: [u8; 3]) -> usize {
let v = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]);
(v.wrapping_mul(0x9E37_79B1) >> (32 - HASH_BITS)) as usize
}
fn prefix(input: &[u8], pos: usize) -> Option<[u8; 3]> {
match input.get(pos..pos + MIN_MATCH)? {
&[a, b, c] => Some([a, b, c]),
_ => None,
}
}
fn insert(&mut self, input: &[u8], pos: usize) {
let Some(prefix) = Self::prefix(input, pos) else {
return;
};
let h = Self::hash(prefix);
self.prev[pos % WINDOW] = self.head[h];
self.head[h] = pos + 1;
}
fn longest_match(&self, input: &[u8], pos: usize, limit: usize) -> Option<(usize, usize)> {
if limit < MIN_MATCH {
return None;
}
let prefix = Self::prefix(input, pos)?;
let mut candidate = self.head[Self::hash(prefix)];
let mut best: Option<(usize, usize)> = None;
for _ in 0..MAX_CHAIN {
let Some(at) = candidate.checked_sub(1) else {
break;
};
let distance = match pos.checked_sub(at) {
Some(d) if d > 0 && d <= WINDOW => d,
_ => break,
};
let length = input[at..]
.iter()
.zip(&input[pos..pos + limit])
.take_while(|(a, b)| a == b)
.count();
if length >= MIN_MATCH && best.is_none_or(|(l, _)| length > l) {
best = Some((length, distance));
if length == limit {
break;
}
}
candidate = self.prev[at % WINDOW];
}
best
}
fn tokenize(&mut self, input: &[u8], start: usize, end: usize, tokens: &mut Vec<Token>) {
let mut pos = start;
while pos < end {
let limit = (end - pos).min(MAX_MATCH);
match self.longest_match(input, pos, limit) {
Some((length, distance)) => {
tokens.push(MATCH | ((length as u32) << 16) | distance as u32);
for p in pos..pos + length {
self.insert(input, p);
}
pos += length;
}
None => {
tokens.push(Token::from(input[pos]));
self.insert(input, pos);
pos += 1;
}
}
}
}
}