use alloc::boxed::Box;
pub const MIN_MATCH: usize = 3;
pub const MAX_MATCH: usize = 4096;
const HASH_BITS: u32 = 15;
const HASH_SIZE: usize = 1 << HASH_BITS;
const NIL: u32 = u32::MAX;
#[derive(Clone, Copy, Debug)]
pub struct Match {
pub length: usize,
pub distance: usize,
}
pub struct MatchFinder {
head: Box<[u32; HASH_SIZE]>,
prev: Vec<u32>,
}
use alloc::vec;
use alloc::vec::Vec;
fn hash4(b: &[u8]) -> u32 {
let v = (b[0] as u32) | ((b[1] as u32) << 8) | ((b[2] as u32) << 16) | ((b[3] as u32) << 24);
v.wrapping_mul(0x9E37_79B1) >> (32 - HASH_BITS)
}
impl MatchFinder {
pub fn new(buffer_len: usize) -> Self {
Self {
head: Box::new([NIL; HASH_SIZE]),
prev: vec![NIL; buffer_len.max(1)],
}
}
#[allow(dead_code)]
pub fn reset(&mut self) {
for h in self.head.iter_mut() {
*h = NIL;
}
for p in self.prev.iter_mut() {
*p = NIL;
}
}
pub fn resize_for(&mut self, buffer_len: usize) {
self.prev.clear();
self.prev.resize(buffer_len.max(1), NIL);
for h in self.head.iter_mut() {
*h = NIL;
}
}
pub fn insert(&mut self, buffer: &[u8], pos: usize) {
if pos + 4 > buffer.len() {
return;
}
let h = hash4(&buffer[pos..pos + 4]) as usize;
self.prev[pos] = self.head[h];
self.head[h] = pos as u32;
}
pub fn find_match(
&self,
buffer: &[u8],
pos: usize,
window: usize,
max_chain: usize,
nice_match: usize,
) -> Option<Match> {
if pos + MIN_MATCH > buffer.len() {
return None;
}
if pos + 4 > buffer.len() {
return None;
}
let h = hash4(&buffer[pos..pos + 4]) as usize;
let max_dist = window.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[h];
let mut steps = 0usize;
while cur != NIL && steps < 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 = 0;
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 >= nice_match {
break;
}
}
cur = self.prev[cur_pos];
steps += 1;
}
if best_len >= MIN_MATCH {
Some(Match {
length: best_len,
distance: best_dist,
})
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_simple_match() {
let data = b"abcdefg__abcdefg__"; let mut mf = MatchFinder::new(data.len());
for pos in 0..(data.len().saturating_sub(3)) {
mf.insert(data, pos);
if pos == 9 {
let m = mf.find_match(data, 9, data.len(), 16, 64).unwrap();
assert!(m.length >= 7);
assert_eq!(m.distance, 9);
}
}
}
#[test]
fn rejects_short_match() {
let data = b"abcXabd";
let mut mf = MatchFinder::new(data.len());
mf.insert(data, 0);
mf.insert(data, 1);
mf.insert(data, 2);
mf.insert(data, 3);
let m = mf.find_match(data, 4, data.len(), 16, 64);
assert!(m.is_none());
}
}