use alloc::vec;
use alloc::vec::Vec;
use super::block::{
DISTANCE_MAX, FIELD_MAX, LAST_LITERALS, MF_LIMIT, MIN_LENGTH, MIN_MATCH, compress_bound, count,
push_last_literals, push_length, read16, read32, read64,
};
use super::optimal;
const HASH_LOG: u32 = 15;
const MID_HASH_LOG: u32 = HASH_LOG - 1;
const MID_HASH_SIZE: usize = 8;
const LEVEL_DEFAULT: i32 = 9;
const LEVEL_MAX: i32 = 12;
const PREFIX_IDX: u32 = 64 * 1024;
const OPTIMAL_ML: isize = (FIELD_MAX as isize - 1) + MIN_MATCH as isize;
#[derive(Copy, Clone, Eq, PartialEq)]
pub(super) enum Strategy {
Mid,
Chain,
Optimal,
}
#[derive(Copy, Clone)]
pub(super) struct LevelParams {
pub(super) strategy: Strategy,
pub(super) searches: i32,
pub(super) target_length: usize,
}
pub(super) const fn level_params(level: i32) -> LevelParams {
let level = if level < 1 { LEVEL_DEFAULT } else { level };
let level = if level > LEVEL_MAX { LEVEL_MAX } else { level };
let (strategy, searches, target_length) = match level {
0..=2 => (Strategy::Mid, 2, 16),
3 => (Strategy::Chain, 4, 16),
4 => (Strategy::Chain, 8, 16),
5 => (Strategy::Chain, 16, 16),
6 => (Strategy::Chain, 32, 16),
7 => (Strategy::Chain, 64, 16),
8 => (Strategy::Chain, 128, 16),
9 => (Strategy::Chain, 256, 16),
10 => (Strategy::Optimal, 96, 64),
11 => (Strategy::Optimal, 512, 128),
_ => (Strategy::Optimal, 16_384, optimal::OPT_NUM),
};
LevelParams {
strategy,
searches,
target_length,
}
}
#[derive(Copy, Clone, Default)]
pub(super) struct Match {
pub(super) off: i32,
pub(super) len: i32,
pub(super) back: i32,
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum Repeat {
Untested,
Not,
Confirmed,
}
const fn hash_ptr(sequence: u32) -> usize {
(sequence.wrapping_mul(2_654_435_761) >> (MIN_MATCH as u32 * 8 - HASH_LOG)) as usize
}
const fn mid_hash4(sequence: u32) -> usize {
(sequence.wrapping_mul(2_654_435_761) >> (32 - MID_HASH_LOG)) as usize
}
const fn mid_hash7(value: u64) -> usize {
((value << 8).wrapping_mul(58_295_818_150_454_627) >> (64 - MID_HASH_LOG)) as usize
}
const fn nb_common_bytes32(value: u32) -> i32 {
(value.leading_zeros() >> 3) as i32
}
fn count_back(src: &[u8], ip: usize, matched: usize, i_min: usize, m_min: usize) -> i32 {
let mut back = 0i32;
let min = (i_min as isize - ip as isize).max(m_min as isize - matched as isize) as i32;
while back - min > 3 {
let a = (ip as isize + back as isize - 4) as usize;
let b = (matched as isize + back as isize - 4) as usize;
let value = read32(src, a) ^ read32(src, b);
if value != 0 {
return back - nb_common_bytes32(value);
}
back -= 4;
}
while back > min
&& src[(ip as isize + back as isize - 1) as usize]
== src[(matched as isize + back as isize - 1) as usize]
{
back -= 1;
}
back
}
fn count_pattern(src: &[u8], mut ip: usize, end: usize, pattern32: u32) -> usize {
let start = ip;
let pattern = u64::from(pattern32) + (u64::from(pattern32) << 32);
while ip + 7 < end {
let diff = read64(src, ip) ^ pattern;
if diff == 0 {
ip += 8;
continue;
}
ip += (diff.trailing_zeros() >> 3) as usize;
return ip - start;
}
let mut pattern_byte = pattern;
while ip < end && src[ip] == pattern_byte as u8 {
ip += 1;
pattern_byte >>= 8;
}
ip - start
}
fn reverse_count_pattern(src: &[u8], mut ip: usize, low: usize, pattern: u32) -> usize {
let start = ip;
while ip >= low + 4 {
if read32(src, ip - 4) != pattern {
break;
}
ip -= 4;
}
let bytes = pattern.to_le_bytes();
let mut index = 3usize;
while ip > low {
if src[ip - 1] != bytes[index] {
break;
}
ip -= 1;
index = index.saturating_sub(1);
}
start - ip
}
const fn protect_dict_end(dict_limit: u32, match_index: u32) -> bool {
dict_limit.wrapping_sub(1).wrapping_sub(match_index) >= 3
}
pub(super) fn encode_sequence(
out: &mut Vec<u8>,
src: &[u8],
ip: &mut usize,
anchor: &mut usize,
match_length: i32,
offset: i32,
) {
let length = *ip - *anchor;
let token = out.len();
if length >= FIELD_MAX {
out.push((FIELD_MAX << 4) as u8);
push_length(out, length - FIELD_MAX);
} else {
out.push((length << 4) as u8);
}
out.extend_from_slice(&src[*anchor..*ip]);
out.extend_from_slice(&(offset as u16).to_le_bytes());
let mut length = (match_length as usize) - MIN_MATCH;
if length >= FIELD_MAX {
out[token] += FIELD_MAX as u8;
length -= FIELD_MAX;
while length >= 510 {
out.push(255);
out.push(255);
length -= 510;
}
if length >= 255 {
length -= 255;
out.push(255);
}
out.push(length as u8);
} else {
out[token] += length as u8;
}
*ip += match_length as usize;
*anchor = *ip;
}
pub(super) struct Hc<'a> {
pub(super) src: &'a [u8],
hash_table: Vec<u32>,
chain_table: Vec<u16>,
next_to_update: u32,
}
impl<'a> Hc<'a> {
pub(super) fn new(src: &'a [u8]) -> Self {
Self {
src,
hash_table: vec![0; 1 << HASH_LOG],
chain_table: vec![0; 1 << 16],
next_to_update: PREFIX_IDX,
}
}
fn delta(&self, position: u32) -> u32 {
u32::from(self.chain_table[usize::from(position as u16)])
}
fn insert(&mut self, ip: usize) {
let target = ip as u32 + PREFIX_IDX;
let mut idx = self.next_to_update;
while idx < target {
let h = hash_ptr(read32(self.src, (idx - PREFIX_IDX) as usize));
let mut delta = idx.wrapping_sub(self.hash_table[h]) as usize;
if delta > DISTANCE_MAX {
delta = DISTANCE_MAX;
}
self.chain_table[usize::from(idx as u16)] = delta as u16;
self.hash_table[h] = idx;
idx += 1;
}
self.next_to_update = target;
}
#[expect(clippy::too_many_arguments, reason = "mirrors the reference signature")]
pub(super) fn insert_and_get_wider_match(
&mut self,
ip: usize,
i_low: usize,
i_high: usize,
mut longest: i32,
max_attempts: i32,
pattern_analysis: bool,
chain_swap: bool,
) -> Match {
let src = self.src;
let ip_index = ip as u32 + PREFIX_IDX;
let within_start_distance = PREFIX_IDX + (DISTANCE_MAX as u32 + 1) > ip_index;
let lowest_match_index = if within_start_distance {
PREFIX_IDX
} else {
ip_index - DISTANCE_MAX as u32
};
let look_back = (ip - i_low) as i32;
let mut nb_attempts = max_attempts;
let mut match_chain_pos = 0u32;
let pattern = read32(src, ip);
let mut repeat = Repeat::Untested;
let mut src_pattern_length = 0usize;
let mut offset = 0i32;
let mut s_back = 0i32;
self.insert(ip);
let mut match_index = self.hash_table[hash_ptr(pattern)];
while match_index >= lowest_match_index && nb_attempts > 0 {
let mut match_length = 0i32;
nb_attempts -= 1;
let match_ptr = (match_index - PREFIX_IDX) as usize;
let a = i_low + longest as usize - 1;
let b = match_ptr as isize - look_back as isize + longest as isize - 1;
if b >= 0
&& (b as usize) + 2 <= src.len()
&& read16(src, a) == read16(src, b as usize)
&& read32(src, match_ptr) == pattern
{
let back = if look_back != 0 {
count_back(src, ip, match_ptr, i_low, 0)
} else {
0
};
match_length = MIN_MATCH as i32
+ count(src, ip + MIN_MATCH, match_ptr + MIN_MATCH, i_high) as i32;
match_length -= back;
if match_length > longest {
longest = match_length;
offset = (ip_index - match_index) as i32;
s_back = back;
}
}
if chain_swap && match_length == longest && match_index + longest as u32 <= ip_index {
let mut distance_to_next_match = 1u32;
let end = longest - MIN_MATCH as i32 + 1;
let mut step;
let mut accel = 1i32 << 4;
let mut pos = 0i32;
while pos < end {
let candidate_dist = self.delta(match_index.wrapping_add(pos as u32));
step = accel >> 4;
accel += 1;
if candidate_dist > distance_to_next_match {
distance_to_next_match = candidate_dist;
match_chain_pos = pos as u32;
accel = 1 << 4;
}
pos += step;
}
if distance_to_next_match > 1 {
if distance_to_next_match > match_index {
break;
}
match_index -= distance_to_next_match;
continue;
}
}
let dist_next_match = self.delta(match_index);
if pattern_analysis && dist_next_match == 1 && match_chain_pos == 0 {
let match_candidate_idx = match_index - 1;
if repeat == Repeat::Untested {
if ((pattern & 0xFFFF) == (pattern >> 16))
& ((pattern & 0xFF) == (pattern >> 24))
{
repeat = Repeat::Confirmed;
src_pattern_length = count_pattern(src, ip + 4, i_high, pattern) + 4;
} else {
repeat = Repeat::Not;
}
}
if repeat == Repeat::Confirmed
&& match_candidate_idx >= lowest_match_index
&& protect_dict_end(PREFIX_IDX, match_candidate_idx)
{
let match_ptr = (match_candidate_idx - PREFIX_IDX) as usize;
if read32(src, match_ptr) == pattern {
let forward_pattern_length =
count_pattern(src, match_ptr + 4, i_high, pattern) + 4;
let mut back_length = reverse_count_pattern(src, match_ptr, 0, pattern);
back_length = (match_candidate_idx
- match_candidate_idx
.wrapping_sub(back_length as u32)
.max(lowest_match_index))
as usize;
let current_segment_length = back_length + forward_pattern_length;
if current_segment_length >= src_pattern_length
&& forward_pattern_length <= src_pattern_length
{
let new_match_index = match_candidate_idx
.wrapping_add(forward_pattern_length as u32)
.wrapping_sub(src_pattern_length as u32);
match_index = if protect_dict_end(PREFIX_IDX, new_match_index) {
new_match_index
} else {
PREFIX_IDX
};
} else {
let new_match_index =
match_candidate_idx.wrapping_sub(back_length as u32);
if protect_dict_end(PREFIX_IDX, new_match_index) {
match_index = new_match_index;
if look_back == 0 {
let max_ml = current_segment_length.min(src_pattern_length);
if (longest as usize) < max_ml {
if ip + PREFIX_IDX as usize - match_index as usize
> DISTANCE_MAX
{
break;
}
longest = max_ml as i32;
offset = (ip_index - match_index) as i32;
}
let dist_to_next_pattern = self.delta(match_index);
if dist_to_next_pattern > match_index {
break;
}
match_index -= dist_to_next_pattern;
}
} else {
match_index = PREFIX_IDX;
}
}
continue;
}
}
}
match_index -= self.delta(match_index.wrapping_add(match_chain_pos));
}
Match {
len: longest,
off: offset,
back: s_back,
}
}
fn find_best_match(
&mut self,
ip: usize,
limit: usize,
attempts: i32,
pattern_analysis: bool,
) -> Match {
self.insert_and_get_wider_match(
ip,
ip,
limit,
MIN_MATCH as i32 - 1,
attempts,
pattern_analysis,
false,
)
}
fn compress_hash_chain(&mut self, out: &mut Vec<u8>, max_attempts: i32) {
let src = self.src;
let n = src.len();
let pattern_analysis = max_attempts > 128;
let mut ip = 0isize;
let mut anchor = 0usize;
if n >= MIN_LENGTH {
let mflimit = (n - MF_LIMIT) as isize;
let matchlimit = n - LAST_LITERALS;
let nomatch = Match::default();
let mut start0;
let mut start2 = 0isize;
let mut start3;
let mut m0;
let mut m1;
let mut m2;
let mut m3;
macro_rules! encode {
($len:expr, $off:expr) => {{
let mut p = ip as usize;
encode_sequence(out, src, &mut p, &mut anchor, $len, $off);
ip = p as isize;
}};
}
macro_rules! encode_then_jump {
($len:expr, $off:expr) => {{
let mut p = ip as usize;
encode_sequence(out, src, &mut p, &mut anchor, $len, $off);
}};
}
'main: while ip <= mflimit {
m1 = self.find_best_match(ip as usize, matchlimit, max_attempts, pattern_analysis);
if m1.len < MIN_MATCH as i32 {
ip += 1;
continue;
}
start0 = ip;
m0 = m1;
'search2: loop {
if ip + m1.len as isize <= mflimit {
start2 = ip + m1.len as isize - 2;
m2 = self.insert_and_get_wider_match(
start2 as usize,
ip as usize,
matchlimit,
m1.len,
max_attempts,
pattern_analysis,
false,
);
start2 += m2.back as isize;
} else {
m2 = nomatch;
}
if m2.len <= m1.len {
encode!(m1.len, m1.off);
continue 'main;
}
if start0 < ip && start2 < ip + m0.len as isize {
ip = start0;
m1 = m0;
}
if start2 - ip < 3 {
ip = start2;
m1 = m2;
continue 'search2;
}
'search3: loop {
if start2 - ip < OPTIMAL_ML {
let mut new_ml = m1.len as isize;
if new_ml > OPTIMAL_ML {
new_ml = OPTIMAL_ML;
}
if ip + new_ml > start2 + m2.len as isize - MIN_MATCH as isize {
new_ml = start2 - ip + m2.len as isize - MIN_MATCH as isize;
}
let correction = new_ml - (start2 - ip);
if correction > 0 {
start2 += correction;
m2.len -= correction as i32;
}
}
if start2 + m2.len as isize <= mflimit {
start3 = start2 + m2.len as isize - 3;
m3 = self.insert_and_get_wider_match(
start3 as usize,
start2 as usize,
matchlimit,
m2.len,
max_attempts,
pattern_analysis,
false,
);
start3 += m3.back as isize;
} else {
m3 = nomatch;
start3 = 0;
}
if m3.len <= m2.len {
if start2 < ip + m1.len as isize {
m1.len = (start2 - ip) as i32;
}
encode_then_jump!(m1.len, m1.off);
ip = start2;
encode!(m2.len, m2.off);
continue 'main;
}
if start3 < ip + m1.len as isize + 3 {
if start3 >= ip + m1.len as isize {
if start2 < ip + m1.len as isize {
let correction = (ip + m1.len as isize - start2) as i32;
start2 += correction as isize;
m2.len -= correction;
if m2.len < MIN_MATCH as i32 {
start2 = start3;
m2 = m3;
}
}
encode_then_jump!(m1.len, m1.off);
ip = start3;
m1 = m3;
start0 = start2;
m0 = m2;
continue 'search2;
}
start2 = start3;
m2 = m3;
continue 'search3;
}
if start2 < ip + m1.len as isize {
if start2 - ip < OPTIMAL_ML {
if m1.len as isize > OPTIMAL_ML {
m1.len = OPTIMAL_ML as i32;
}
if ip + m1.len as isize
> start2 + m2.len as isize - MIN_MATCH as isize
{
m1.len = (start2 - ip) as i32 + m2.len - MIN_MATCH as i32;
}
let correction = m1.len - (start2 - ip) as i32;
if correction > 0 {
start2 += correction as isize;
m2.len -= correction;
}
} else {
m1.len = (start2 - ip) as i32;
}
}
encode_then_jump!(m1.len, m1.off);
ip = start2;
m1 = m2;
start2 = start3;
m2 = m3;
continue 'search3;
}
}
}
}
push_last_literals(out, src, anchor);
}
fn compress_mid(&mut self, out: &mut Vec<u8>) {
let src = self.src;
let n = src.len();
let mut anchor = 0usize;
if n >= MIN_LENGTH {
let mut ip = 0usize;
let mflimit = n - MF_LIMIT;
let matchlimit = n - LAST_LITERALS;
let ilimit_idx = ((n - MID_HASH_SIZE) as u32).wrapping_add(PREFIX_IDX);
let half = 1usize << MID_HASH_LOG;
macro_rules! add8 {
($at:expr, $idx:expr) => {
self.hash_table[half + mid_hash7(read64(src, $at))] = $idx
};
}
macro_rules! add4 {
($at:expr, $idx:expr) => {
self.hash_table[mid_hash4(read32(src, $at))] = $idx
};
}
while ip <= mflimit {
let ip_index = ip as u32 + PREFIX_IDX;
let mut match_length = 0usize;
let mut match_distance = 0usize;
let mut found = false;
{
let h8 = mid_hash7(read64(src, ip));
let pos8 = self.hash_table[half + h8];
self.hash_table[half + h8] = ip_index;
if (ip_index.wrapping_sub(pos8) as usize) <= DISTANCE_MAX && pos8 >= PREFIX_IDX
{
let match_ptr = (pos8 - PREFIX_IDX) as usize;
match_length = count(src, ip, match_ptr, matchlimit);
if match_length >= MIN_MATCH {
match_distance = (ip_index - pos8) as usize;
found = true;
}
}
}
if !found {
let h4 = mid_hash4(read32(src, ip));
let pos4 = self.hash_table[h4];
self.hash_table[h4] = ip_index;
if (ip_index.wrapping_sub(pos4) as usize) <= DISTANCE_MAX && pos4 >= PREFIX_IDX
{
let match_ptr = (pos4 - PREFIX_IDX) as usize;
match_length = count(src, ip, match_ptr, matchlimit);
if match_length >= MIN_MATCH {
let h8 = mid_hash7(read64(src, ip + 1));
let pos8 = self.hash_table[half + h8];
let m2_distance = (ip_index + 1).wrapping_sub(pos8) as usize;
match_distance = (ip_index - pos4) as usize;
if m2_distance <= DISTANCE_MAX && pos8 >= PREFIX_IDX && ip < mflimit {
let m2_ptr = (pos8 - PREFIX_IDX) as usize;
let ml2 = count(src, ip + 1, m2_ptr, matchlimit);
if ml2 > match_length {
self.hash_table[half + h8] = ip_index + 1;
ip += 1;
match_length = ml2;
match_distance = m2_distance;
}
}
found = true;
}
}
}
if !found {
ip += 1 + ((ip - anchor) >> 9);
continue;
}
while ip > anchor
&& ip > match_distance
&& src[ip - 1] == src[ip - match_distance - 1]
{
ip -= 1;
match_length += 1;
}
add8!(ip + 1, ip_index + 1);
add8!(ip + 2, ip_index + 2);
add4!(ip + 1, ip_index + 1);
encode_sequence(
out,
src,
&mut ip,
&mut anchor,
match_length as i32,
match_distance as i32,
);
let end_match_idx = ip as u32 + PREFIX_IDX;
let pos_m2 = end_match_idx - 2;
if pos_m2 < ilimit_idx {
if ip > 5 {
add8!(ip - 5, end_match_idx - 5);
}
add8!(ip - 3, end_match_idx - 3);
add8!(ip - 2, end_match_idx - 2);
add4!(ip - 2, end_match_idx - 2);
add4!(ip - 1, end_match_idx - 1);
}
}
}
push_last_literals(out, src, anchor);
}
}
pub(super) fn compress(src: &[u8], level: i32) -> Vec<u8> {
let params = level_params(level);
let mut out = Vec::with_capacity(compress_bound(src.len()));
let mut hc = Hc::new(src);
match params.strategy {
Strategy::Mid => hc.compress_mid(&mut out),
Strategy::Chain => hc.compress_hash_chain(&mut out, params.searches),
Strategy::Optimal => {
optimal::compress(&mut hc, &mut out, params, level >= LEVEL_MAX);
}
}
out
}