use super::*;
use alloc::vec;
use alloc::vec::Vec;
fn run_block(
data: &[u8],
hash_log: u32,
mls: u32,
) -> (Vec<(Vec<u8>, usize, usize)>, FastBlockResult) {
let mut table = FastHashTable::new(hash_log, mls);
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => {
tuples.push((literals.to_vec(), offset, match_len));
}
Sequence::Literals { literals } => {
tuples.push((literals.to_vec(), 0, 0));
}
};
let result = match mls {
4 => compress_block_fast::<4, false>(
data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
[0, 0],
2,
&mut handle,
),
5 => compress_block_fast::<5, false>(
data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
[0, 0],
2,
&mut handle,
),
_ => panic!("test helper only supports mls=4 and mls=5"),
};
let acct: usize = tuples
.iter()
.map(|(lits, _off, mlen)| lits.len() + mlen)
.sum::<usize>()
+ result.tail_literals_len;
assert_eq!(acct, data.len(), "kernel must account for every input byte",);
(tuples, result)
}
#[test]
fn short_input_reports_tail_without_emission() {
let data = [1u8, 2, 3, 4, 5];
let (tuples, result) = run_block(&data, 8, 4);
assert!(
tuples.is_empty(),
"kernel must NOT emit sequences for short inputs (got {tuples:?})",
);
assert_eq!(result.tail_literals_len, data.len());
}
#[test]
fn finds_long_repeat_in_simple_pattern() {
let mut data = Vec::new();
data.extend_from_slice(b"ABCDEFGHIJKLMNOP");
data.extend_from_slice(b"ABCDEFGHIJKLMNOP");
data.extend_from_slice(b"________");
let (tuples, _result) = run_block(&data, 12, 4);
let triple = tuples
.iter()
.find(|(_, _, m)| *m > 0)
.expect("kernel must emit at least one Triple for the repeated half");
assert!(
triple.2 >= 4,
"match_len must be ≥ MIN_MATCH=4 (got {})",
triple.2,
);
assert!(
triple.1 > 0,
"explicit-offset match must have offset > 0 (got {})",
triple.1,
);
}
fn run_block_with_rep(
data: &[u8],
hash_log: u32,
rep: [u32; 2],
) -> (Vec<(Vec<u8>, usize, usize)>, FastBlockResult) {
let mut table = FastHashTable::new(hash_log, 4);
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => tuples.push((literals.to_vec(), offset, match_len)),
Sequence::Literals { literals } => tuples.push((literals.to_vec(), 0, 0)),
};
let result = compress_block_fast::<4, false>(
data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
rep,
2,
&mut handle,
);
let acct: usize = tuples
.iter()
.map(|(lits, _off, mlen)| lits.len() + mlen)
.sum::<usize>()
+ result.tail_literals_len;
assert_eq!(acct, data.len(), "kernel must account for every input byte");
(tuples, result)
}
#[test]
fn repcode_match_emits_with_rep_offset_one() {
let data = vec![0x42u8; 64];
let (tuples, _) = run_block_with_rep(&data, 8, [1, 4]);
let rep_triple = tuples
.iter()
.find(|(_, off, m)| *off == 1 && *m > 0)
.unwrap_or_else(|| panic!("repcode Triple at offset=1 expected, got {tuples:?}"));
assert!(
rep_triple.2 >= 4,
"match_len must be ≥ MIN_MATCH=4 (got {})",
rep_triple.2,
);
assert!(
rep_triple.2 >= 32,
"uniform-byte rep extension must consume most of the buffer, got {}",
rep_triple.2,
);
}
#[test]
fn explicit_match_backward_extension_extends_by_marker_byte() {
let mut data: Vec<u8> = (0..15u8).collect();
data.push(b'Z');
data.extend_from_slice(b"AAAAAAAA");
for i in 0..8u8 {
data.push(0x80 + i);
}
data.push(b'Z');
data.extend_from_slice(b"AAAAAAAA");
for i in 0..16u8 {
data.push(0x40 + (i % 16));
}
let (tuples, _) = run_block_with_rep(&data, 12, [0, 0]);
let triple = tuples
.iter()
.find(|(_, _, m)| *m > 0)
.unwrap_or_else(|| panic!("expected an explicit-match Triple, got {tuples:?}"));
assert!(
triple.2 >= 5,
"expected match_len ≥ 5 from backward extension (got {})",
triple.2,
);
assert!(
!triple.0.ends_with(b"Z"),
"backward extension must consume the 'Z' marker (literals = {:?})",
triple.0,
);
}
#[test]
fn prefix_start_index_filter_rejects_below_window() {
let data = vec![0xAAu8; 64];
let mut table = FastHashTable::new(8, 4);
let h = unsafe { table.hash_ptr::<4>(data.as_ptr().add(1)) };
unsafe { table.put(h, 0) };
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => tuples.push((literals.to_vec(), offset, match_len)),
Sequence::Literals { literals } => tuples.push((literals.to_vec(), 0, 0)),
};
let _ = compress_block_fast::<4, false>(
&data,
0,
PrefixBounds {
prefix_start_index: 5,
window_low: 5,
},
&mut table,
[0, 0],
2,
&mut handle,
);
let mut anchor: usize = 0;
for (lits, off, m) in &tuples {
if *m > 0 {
let match_start = anchor + lits.len();
let match_src = match_start
.checked_sub(*off)
.expect("offset must not exceed match_start (would wrap)");
assert!(
match_src >= 5,
"match source {match_src} below prefix_start_index=5 \
(match_start={match_start}, offset={off})",
);
anchor = match_start + m;
} else {
anchor += lits.len();
}
}
}
#[test]
fn match_found_rejects_stale_entry_below_prefix_floor() {
let data = vec![0u8; 200];
let mut table = FastHashTable::new(8, 4);
let h = unsafe { table.hash_ptr::<4>(data.as_ptr().add(50)) };
unsafe { table.put(h, 5) };
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => tuples.push((literals.to_vec(), offset, match_len)),
Sequence::Literals { literals } => tuples.push((literals.to_vec(), 0, 0)),
};
let _ = compress_block_fast::<4, false>(
&data,
50,
PrefixBounds {
prefix_start_index: 50,
window_low: 50,
},
&mut table,
[0, 0],
2,
&mut handle,
);
for (_, off, m) in &tuples {
if *m > 0 {
assert!(
*off > 0 && *off <= data.len(),
"every emitted offset must reference an in-buffer backward position (got {off})",
);
}
}
}
#[test]
fn block_exactly_hash_read_size_emits_no_sequences() {
let data = [1u8, 2, 3, 4, 5, 6, 7, 8];
let (tuples, result) = run_block_with_rep(&data, 8, [0, 0]);
assert!(
tuples.is_empty(),
"exactly HASH_READ_SIZE bytes must produce no main-loop iterations",
);
assert_eq!(result.tail_literals_len, data.len());
}
#[test]
fn block_just_below_hash_read_size_emits_no_sequences() {
let data = [1u8, 2, 3, 4, 5, 6, 7];
let (tuples, result) = run_block_with_rep(&data, 8, [0, 0]);
assert!(tuples.is_empty());
assert_eq!(result.tail_literals_len, data.len());
}
#[test]
fn rep_offset_save_restore_when_out_of_range() {
let mut data = vec![0u8; 64];
let mut state = 0x1234_5678u32;
for byte in &mut data {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
*byte = state as u8;
}
let huge = 9999;
let mut table = FastHashTable::new(10, 4);
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => tuples.push((literals.to_vec(), offset, match_len)),
Sequence::Literals { literals } => tuples.push((literals.to_vec(), 0, 0)),
};
let result = compress_block_fast::<4, false>(
&data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
[huge, 7],
2,
&mut handle,
);
assert_eq!(
result.rep[0], huge,
"out-of-range rep_offset1 must be restored verbatim across the block",
);
assert_eq!(result.rep[1], 7, "rep_offset2 (also stashed) must restore");
}
#[test]
fn cmov_variant_matches_branch_variant_output() {
let mut data = alloc::vec::Vec::new();
for i in 0..512u32 {
data.push((i & 0xFF) as u8);
}
let tail = data[0..64].to_vec();
data.extend_from_slice(&tail);
let collect = |use_cmov: bool| -> alloc::vec::Vec<(alloc::vec::Vec<u8>, usize, usize)> {
let mut table = FastHashTable::new(12, 4);
let mut tuples = alloc::vec::Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => {
tuples.push((literals.to_vec(), offset, match_len));
}
Sequence::Literals { literals } => {
tuples.push((literals.to_vec(), 0, 0));
}
};
if use_cmov {
let _ = compress_block_fast::<4, true>(
&data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
[0, 0],
2,
&mut handle,
);
} else {
let _ = compress_block_fast::<4, false>(
&data,
0,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
&mut table,
[0, 0],
2,
&mut handle,
);
}
tuples
};
let out_branch = collect(false);
let out_cmov = collect(true);
assert_eq!(
out_branch, out_cmov,
"cmov and branch variants must emit identical sequences"
);
}
#[test]
fn cmov_variant_rejects_out_of_window_when_ip_equals_dummy() {
let mut data: alloc::vec::Vec<u8> = alloc::vec![0xAA; 32];
data[16] = 0x12;
data[17] = 0x34;
data[18] = 0x56;
data[19] = 0x78;
let base = data.as_ptr();
let ip_pos = 16usize;
let ip = unsafe { base.add(ip_pos) };
let branch_result = unsafe { match_found::<false>(ip, base, 4, 10) };
assert!(
!branch_result,
"branch variant must reject out-of-window match_idx"
);
let cmov_result = unsafe { match_found::<true>(ip, base, 4, 10) };
assert!(
!cmov_result,
"cmov variant must reject out-of-window match_idx even when \
ip bytes coincide with CMOV_DUMMY",
);
}
#[test]
fn borrowed_dict_kernel_reconstructs_via_dual_base() {
use crate::encoding::fastpath::FastpathKernel;
let hash_log = 12u32;
const MLS: u32 = 4;
let dict: Vec<u8> = (0u8..40).collect();
let mut inp: Vec<u8> = Vec::new();
inp.extend_from_slice(&dict[0..20]); inp.extend_from_slice(&dict[0..20]); inp.extend_from_slice(&dict[4..24]); inp.extend_from_slice(b"tail-literals-xyz");
let dict_end = dict.len();
let mut main_table = FastHashTable::new(hash_log, MLS);
let mut dict_table = FastHashTable::new(hash_log, MLS);
for pos in 0..=dict.len() - HASH_READ_SIZE {
let hat = unsafe { hash_ptr_raw::<MLS>(dict.as_ptr().add(pos), hash_log + DICT_TAG_BITS) };
unsafe {
dict_table.put(
hat >> DICT_TAG_BITS,
((pos as u32) << DICT_TAG_BITS) | (hat & DICT_TAG_MASK),
)
};
}
let mut tuples: Vec<(Vec<u8>, usize, usize)> = Vec::new();
let mut handle = |seq: Sequence<'_>| match seq {
Sequence::Triple {
literals,
offset,
match_len,
} => tuples.push((literals.to_vec(), offset, match_len)),
Sequence::Literals { literals } => tuples.push((literals.to_vec(), 0, 0)),
};
let result = compress_block_fast_dict_borrowed::<MLS, false>(
&inp,
&dict,
0,
inp.len(),
&mut main_table,
&dict_table,
PrefixBounds {
prefix_start_index: 1,
window_low: 0,
},
[0, 0],
2,
&mut handle,
FastpathKernel::Scalar,
);
let mut window = dict.clone();
let mut saw_dict_region_match = false;
for (literals, offset, match_len) in &tuples {
window.extend_from_slice(literals);
if *match_len > 0 {
let start = window.len() - offset;
if start < dict_end {
saw_dict_region_match = true;
}
for i in 0..*match_len {
let b = window[start + i];
window.push(b);
}
}
}
let tail_start = inp.len() - result.tail_literals_len;
window.extend_from_slice(&inp[tail_start..]);
assert_eq!(
&window[dict_end..],
&inp[..],
"borrowed dict kernel must reconstruct the input from the [dict][input] window",
);
assert!(
tuples.iter().any(|(_, _, m)| *m >= 4),
"expected at least one match Triple",
);
assert!(
saw_dict_region_match,
"expected at least one match reading from the dictionary region (dual-base path)",
);
}