use std::cell::RefCell;
const SLOTS: usize = 1 << 16;
const MAX_LIT: usize = 1 << 5;
const MAX_OFF: usize = 1 << 13;
const MAX_REF: usize = (1 << 8) + (1 << 3);
struct Table {
slots: Box<[u32]>,
touched: Vec<u32>,
}
thread_local! {
static TABLE: RefCell<Table> = RefCell::new(Table {
slots: vec![0u32; SLOTS].into_boxed_slice(),
touched: Vec::new(),
});
}
#[inline]
const fn slot(hval: u32) -> usize {
((hval >> 8).wrapping_sub(hval.wrapping_mul(5)) & (SLOTS as u32 - 1)) as usize
}
pub(crate) fn pack(data: &[u8]) -> Option<Vec<u8>> {
if data.len() <= 4 {
return None;
}
TABLE.with(|table| {
let mut table = table.borrow_mut();
let Table { slots, touched } = &mut *table;
for &at in touched.iter() {
slots[at as usize] = 0;
}
touched.clear();
compress(data, slots, touched)
})
}
#[allow(clippy::needless_range_loop)]
fn compress(data: &[u8], slots: &mut [u32], touched: &mut Vec<u32>) -> Option<Vec<u8>> {
let n = data.len();
let room = n - 4;
let mut out = vec![0u8; room];
let mut op = 0usize;
let mut lit = 0usize;
let mut ip = 0usize;
op += 1;
let mut hval = (u32::from(data[0]) << 8) | u32::from(data[1]);
while ip + 2 < n {
hval = (hval << 8) | u32::from(data[ip + 2]);
let at = slot(hval);
let reference = slots[at] as usize;
if slots[at] == 0 {
touched.push(at as u32);
}
slots[at] = ip as u32;
let off = ip.wrapping_sub(reference).wrapping_sub(1);
if off < MAX_OFF
&& reference > 0
&& data[reference + 2] == data[ip + 2]
&& data[reference] == data[ip]
&& data[reference + 1] == data[ip + 1]
{
let mut len = 2usize;
let maxlen = (n - ip - len).min(MAX_REF);
if op + 3 + 1 >= room && op - usize::from(lit == 0) + 3 + 1 >= room {
return None;
}
out[op - lit - 1] = (lit as u8).wrapping_sub(1);
op -= usize::from(lit == 0);
let mut stopped = false;
if maxlen > 16 {
for _ in 0..16 {
len += 1;
if data[reference + len] != data[ip + len] {
stopped = true;
break;
}
}
}
if !stopped {
loop {
len += 1;
if !(len < maxlen && data[reference + len] == data[ip + len]) {
break;
}
}
}
len -= 2;
ip += 1;
if len < 7 {
out[op] = ((off >> 8) as u8) + ((len as u8) << 5);
op += 1;
} else {
out[op] = ((off >> 8) as u8) + (7 << 5);
op += 1;
out[op] = (len - 7) as u8;
op += 1;
}
out[op] = off as u8;
op += 1;
lit = 0;
op += 1;
ip += len + 1;
if ip + 2 >= n {
break;
}
ip -= 2;
hval = (u32::from(data[ip]) << 8) | u32::from(data[ip + 1]);
for _ in 0..2 {
hval = (hval << 8) | u32::from(data[ip + 2]);
let at = slot(hval);
if slots[at] == 0 {
touched.push(at as u32);
}
slots[at] = ip as u32;
ip += 1;
}
} else {
if op >= room {
return None;
}
lit += 1;
out[op] = data[ip];
op += 1;
ip += 1;
if lit == MAX_LIT {
out[op - lit - 1] = (lit - 1) as u8;
lit = 0;
op += 1;
}
}
}
if op + 3 > room {
return None;
}
while ip < n {
lit += 1;
out[op] = data[ip];
op += 1;
ip += 1;
if lit == MAX_LIT {
out[op - lit - 1] = (lit - 1) as u8;
lit = 0;
op += 1;
}
}
out[op - lit - 1] = (lit as u8).wrapping_sub(1);
op -= usize::from(lit == 0);
out.truncate(op);
Some(out)
}
pub(crate) fn unpack(packed: &[u8], plain: usize) -> Option<Vec<u8>> {
let mut out = Vec::with_capacity(plain.min(1 << 20));
let mut i = 0;
while i < packed.len() {
let ctrl = usize::from(packed[i]);
i += 1;
if ctrl < 32 {
let run = ctrl + 1;
let end = i.checked_add(run)?;
if end > packed.len() || out.len() + run > plain {
return None;
}
out.extend_from_slice(&packed[i..end]);
i = end;
} else {
let mut run = ctrl >> 5;
if run == 7 {
run += usize::from(*packed.get(i)?);
i += 1;
}
let back = ((ctrl & 0x1f) << 8) + usize::from(*packed.get(i)?) + 1;
i += 1;
let run = run + 2;
if back > out.len() || out.len() + run > plain {
return None;
}
let from = out.len() - back;
for at in from..from + run {
out.push(out[at]);
}
}
}
(out.len() == plain).then_some(out)
}
#[cfg(test)]
mod tests {
use super::{pack, unpack};
#[test]
fn unpacks_a_literal_run() {
assert_eq!(
unpack(&[3, b'a', b'b', b'c', b'd'], 4).as_deref(),
Some(&b"abcd"[..])
);
}
#[test]
fn unpacks_an_overlapping_reference() {
let packed = [0u8, b'a', 3 << 5, 0];
assert_eq!(unpack(&packed, 6).as_deref(), Some(&b"aaaaaa"[..]));
}
#[test]
fn refuses_a_reference_to_nothing() {
assert_eq!(unpack(&[(3 << 5), 0], 5), None);
assert_eq!(unpack(&[3, b'a'], 4), None);
}
#[test]
fn packs_a_long_run_the_way_liblzf_does() {
let packed = pack(&[b'a'; 200]).expect("a run of one byte compresses");
assert_eq!(packed, [1, b'a', b'a', 0xe0, 187, 0, 1, b'a', b'a']);
assert_eq!(unpack(&packed, 200).as_deref(), Some(&[b'a'; 200][..]));
}
#[test]
fn refuses_what_does_not_compress() {
let mut data = Vec::new();
for n in 0..=255u8 {
data.push(n);
}
assert_eq!(pack(&data), None);
assert_eq!(pack(b""), None);
assert_eq!(pack(b"abcd"), None);
}
#[test]
fn every_length_comes_back_the_way_it_went_in() {
let alphabet = b"abcdefgh";
for n in 0..400usize {
let mut data = Vec::with_capacity(n);
for i in 0..n {
data.push(alphabet[(i / 3) % alphabet.len()]);
}
if let Some(packed) = pack(&data) {
assert!(packed.len() < data.len(), "{n} did not get smaller");
assert_eq!(unpack(&packed, n).as_deref(), Some(&data[..]), "{n}");
}
}
}
#[test]
fn the_same_input_packs_the_same_way_twice() {
let one = vec![b'x'; 100];
let two = b"the quick brown fox jumps over the lazy dog, the quick brown fox".to_vec();
let first = pack(&one);
pack(&two);
let again = pack(&one);
assert_eq!(first, again);
}
#[test]
fn a_mixed_value_round_trips() {
let mut data = Vec::new();
for n in 0..500u32 {
data.extend_from_slice(b"field");
data.extend_from_slice(n.to_string().as_bytes());
data.extend_from_slice(b":value:");
data.extend_from_slice(&[b'z'; 7]);
}
let packed = pack(&data).expect("this compresses");
assert!(packed.len() * 4 < data.len());
assert_eq!(unpack(&packed, data.len()).as_deref(), Some(&data[..]));
}
}