use alloc::boxed::Box;
pub(crate) const MIN_MATCH: usize = 4;
pub(crate) const MAX_MATCH: usize = 4096;
pub(crate) const WINDOW_SIZE: usize = 65_520;
const HASH_BITS: u32 = 15;
const HASH_SIZE: usize = 1 << HASH_BITS;
const NIL: u32 = u32::MAX;
pub(crate) const BUFFER_SIZE: usize = 65_536;
#[derive(Debug, Clone, Copy)]
pub(crate) struct FinderParams {
pub max_chain: usize,
pub nice_match: usize,
}
pub(crate) struct MatchFinder {
head: Box<[u32; HASH_SIZE]>,
prev: Box<[u32; BUFFER_SIZE]>,
}
impl MatchFinder {
pub(crate) fn new() -> Self {
Self {
head: Box::new([NIL; HASH_SIZE]),
prev: Box::new([NIL; BUFFER_SIZE]),
}
}
pub(crate) fn insert(&mut self, buffer: &[u8], pos: usize) {
if pos + 4 > buffer.len() || pos >= BUFFER_SIZE {
return;
}
let h = hash4(
buffer[pos],
buffer[pos + 1],
buffer[pos + 2],
buffer[pos + 3],
);
let idx = (h as usize) & (HASH_SIZE - 1);
self.prev[pos] = self.head[idx];
self.head[idx] = pos as u32;
}
pub(crate) fn find_match(
&self,
buffer: &[u8],
pos: usize,
params: FinderParams,
) -> Option<(usize, usize)> {
if pos + MIN_MATCH > buffer.len() {
return None;
}
let h = hash4(
buffer[pos],
buffer[pos + 1],
buffer[pos + 2],
buffer[pos + 3],
);
let idx = (h as usize) & (HASH_SIZE - 1);
let max_dist = WINDOW_SIZE.min(pos);
let max_len = MAX_MATCH.min(buffer.len() - pos);
if max_len < MIN_MATCH {
return None;
}
let mut best_len: usize = 0;
let mut best_dist: usize = 0;
let mut cur = self.head[idx];
let mut steps = 0usize;
while cur != NIL && steps < params.max_chain {
let cur_pos = cur as usize;
if cur_pos >= pos {
cur = self.prev[cur_pos];
steps += 1;
continue;
}
let dist = pos - cur_pos;
if dist > max_dist {
break;
}
if best_len > 0
&& best_len < max_len
&& buffer[cur_pos + best_len] != buffer[pos + best_len]
{
cur = self.prev[cur_pos];
steps += 1;
continue;
}
let mut len = 0usize;
while len < max_len && buffer[cur_pos + len] == buffer[pos + len] {
len += 1;
}
if len >= MIN_MATCH && len > best_len {
best_len = len;
best_dist = dist;
if best_len >= params.nice_match {
break;
}
}
cur = self.prev[cur_pos];
steps += 1;
}
if best_len >= MIN_MATCH {
Some((best_len, best_dist))
} else {
None
}
}
}
impl Default for MatchFinder {
fn default() -> Self {
Self::new()
}
}
fn hash4(b0: u8, b1: u8, b2: u8, b3: u8) -> u32 {
let v = (b0 as u32) | ((b1 as u32) << 8) | ((b2 as u32) << 16) | ((b3 as u32) << 24);
v.wrapping_mul(0x9E37_79B1) >> (32 - HASH_BITS)
}