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 reset(&mut self) {
self.head.fill(NIL);
}
#[inline]
pub(crate) fn insert(&mut self, buffer: &[u8], pos: usize) {
if pos + 4 > buffer.len() || pos >= BUFFER_SIZE {
return;
}
let h = hash4_at(buffer, pos);
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)> {
let buf_len = buffer.len();
if pos + MIN_MATCH > buf_len {
return None;
}
let h = hash4_at(buffer, pos);
let idx = (h as usize) & (HASH_SIZE - 1);
let max_dist = WINDOW_SIZE.min(pos);
let max_len = MAX_MATCH.min(buf_len - pos);
if max_len < MIN_MATCH {
return None;
}
let nice = params.nice_match.min(max_len);
let chain_cap = params.max_chain;
let target = &buffer[pos..pos + max_len];
let mut best_len: usize = 0;
let mut best_dist: usize = 0;
let mut best_tail: u8 = 0;
let prev = &self.prev[..];
let head = &self.head[..];
let mut cur = head[idx];
let mut steps = 0usize;
while cur != NIL && steps < chain_cap {
let cur_pos = cur as usize;
if cur_pos >= pos {
cur = prev[cur_pos];
steps += 1;
continue;
}
let dist = pos - cur_pos;
if dist > max_dist {
break;
}
if best_len > 0 && buffer[cur_pos + best_len] != best_tail {
cur = prev[cur_pos];
steps += 1;
continue;
}
let cand = &buffer[cur_pos..cur_pos + max_len];
let mut len = 0usize;
while len + 8 <= max_len {
let a = u64::from_le_bytes([
cand[len],
cand[len + 1],
cand[len + 2],
cand[len + 3],
cand[len + 4],
cand[len + 5],
cand[len + 6],
cand[len + 7],
]);
let b = u64::from_le_bytes([
target[len],
target[len + 1],
target[len + 2],
target[len + 3],
target[len + 4],
target[len + 5],
target[len + 6],
target[len + 7],
]);
let diff = a ^ b;
if diff != 0 {
len += (diff.trailing_zeros() / 8) as usize;
break;
}
len += 8;
}
while len < max_len && cand[len] == target[len] {
len += 1;
}
if len >= MIN_MATCH && len > best_len {
best_len = len;
best_dist = dist;
if best_len >= nice {
break;
}
if best_len < max_len {
best_tail = target[best_len];
}
}
cur = 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()
}
}
#[inline]
fn hash4_at(buffer: &[u8], pos: usize) -> u32 {
debug_assert!(pos + 4 <= buffer.len());
let b = &buffer[pos..pos + 4];
let v = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
v.wrapping_mul(0x9E37_79B1) >> (32 - HASH_BITS)
}