use std::collections::HashMap;
use std::collections::hash_map::Entry;
pub const MAX_CODE_BITS: u8 = 12;
struct BitWriter {
out: Vec<u8>,
bits: u32,
count: u8,
}
impl BitWriter {
fn new() -> BitWriter {
BitWriter {
out: Vec::new(),
bits: 0,
count: 0,
}
}
fn write(&mut self, code: u16, width: u8) {
self.bits |= u32::from(code) << self.count;
self.count += width;
while self.count >= 8 {
self.out.push((self.bits & 0xFF) as u8);
self.bits >>= 8;
self.count -= 8;
}
}
fn finish(mut self) -> Vec<u8> {
if self.count > 0 {
self.out.push((self.bits & 0xFF) as u8);
}
self.out
}
}
pub fn compress(symbols: &[u8], min_code_bits: u8) -> Vec<u8> {
let clear = 1u16 << min_code_bits;
let end = clear + 1;
let mut bits = BitWriter::new();
let mut width = min_code_bits + 1;
let mut dict: HashMap<(u16, u8), u16> = HashMap::new();
let mut next = end + 1;
bits.write(clear, width);
let mut symbols = symbols.iter().copied();
let Some(first) = symbols.next() else {
bits.write(end, width);
return bits.finish();
};
let mut prefix = u16::from(first);
for symbol in symbols {
match dict.entry((prefix, symbol)) {
Entry::Occupied(known) => {
prefix = *known.get();
continue;
}
Entry::Vacant(slot) => {
bits.write(prefix, width);
if next < 1 << MAX_CODE_BITS {
slot.insert(next);
next += 1;
if next > (1u16 << width) && width < MAX_CODE_BITS {
width += 1;
}
prefix = u16::from(symbol);
continue;
}
}
}
bits.write(clear, width);
dict.clear();
next = end + 1;
width = min_code_bits + 1;
prefix = u16::from(symbol);
}
bits.write(prefix, width);
bits.write(end, width);
bits.finish()
}
const MAX_OUTPUT: usize = 1 << 22;
pub fn decompress(data: &[u8], min_code_bits: u8) -> Option<Vec<u8>> {
decode(data, min_code_bits).map(|(out, _)| out)
}
pub(crate) fn decode(data: &[u8], min_code_bits: u8) -> Option<(Vec<u8>, usize)> {
let clear = 1u16 << min_code_bits;
let end = clear + 1;
let mut width = min_code_bits + 1;
let mut table: Vec<Vec<u8>> = Vec::new();
let reset = |table: &mut Vec<Vec<u8>>| {
table.clear();
for index in 0..=u16::from(u8::MAX) {
table.push(vec![index as u8]);
if index == clear - 1 {
break;
}
}
table.push(Vec::new()); table.push(Vec::new()); };
reset(&mut table);
let (mut acc, mut count, mut at) = (0u32, 0u8, 0usize);
let mut out = Vec::new();
let mut prev: Option<u16> = None;
let mut restarts = 0usize;
let mut started = false;
loop {
while count < width && at < data.len() {
acc |= u32::from(data[at]) << count;
count += 8;
at += 1;
}
if count < width {
break;
}
let code = (acc & ((1u32 << width) - 1)) as u16;
acc >>= width;
count -= width;
if code == clear {
if started {
restarts += 1;
}
started = true;
reset(&mut table);
width = min_code_bits + 1;
prev = None;
continue;
}
if code == end {
break;
}
let entry = if usize::from(code) < table.len() {
table[usize::from(code)].clone()
} else {
if usize::from(code) != table.len() {
return None;
}
let prefix = table.get(usize::from(prev?))?;
let mut entry = prefix.clone();
entry.push(*entry.first()?);
entry
};
let first = *entry.first()?;
if out.len() + entry.len() > MAX_OUTPUT {
return None;
}
out.extend_from_slice(&entry);
if let Some(prev) = prev {
let mut new = table.get(usize::from(prev))?.clone();
new.push(first);
table.push(new);
if table.len() >= 1 << width && width < MAX_CODE_BITS {
width += 1;
}
}
prev = Some(code);
}
Some((out, restarts))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trips() {
let cases: Vec<Vec<u8>> = vec![
vec![],
vec![3],
vec![0; 500],
(0..4000u32).map(|i| (i % 16) as u8).collect(),
(0..9000u32).map(|i| ((i * i) % 16) as u8).collect(),
];
for symbols in cases {
let encoded = compress(&symbols, 4);
let decoded = decompress(&encoded, 4);
assert_eq!(decoded.as_ref(), Some(&symbols), "{} bytes", symbols.len());
}
let text = b"the tide is out, the tide is out, the tide is out".to_vec();
assert_eq!(decompress(&compress(&text, 8), 8), Some(text));
}
#[test]
fn survives_a_full_dictionary() {
let mut rng = crate::sim::Pcg32::new(0x1ADE_C0DE, 0x9971);
let symbols: Vec<u8> = (0..3 * 144 * 112)
.map(|_| (rng.next_u32() % 16) as u8)
.collect();
let encoded = compress(&symbols, 4);
let (decoded, restarts) = decode(&encoded, 4).expect("our own stream decodes");
assert!(
restarts >= 2,
"expected the dictionary to fill and reset; it reset {restarts} times"
);
assert_eq!(decoded, symbols, "the two sides restarted out of step");
}
#[test]
fn round_trips_at_every_code_width() {
let mut rng = crate::sim::Pcg32::new(7, 3);
for bits in 1..=8u8 {
let alphabet = 1u32 << bits;
let symbols: Vec<u8> = (0..5000)
.map(|_| (rng.next_u32() % alphabet) as u8)
.collect();
let min_code_bits = bits.max(2);
let encoded = compress(&symbols, min_code_bits);
let decoded = decompress(&encoded, min_code_bits);
assert_eq!(decoded.as_ref(), Some(&symbols), "{bits}-bit alphabet");
}
}
#[test]
fn nonsense_is_refused_rather_than_followed() {
assert_eq!(decompress(&[0xFF, 0xFF, 0xFF, 0xFF], 8), None);
assert_eq!(
decompress(&nine_bit_stream(&[256, 65, 300, 257]), 8),
None,
"a code past the table is refused"
);
for seed in 0..400u32 {
let junk: Vec<u8> = (0..40u32)
.map(|i| (seed.wrapping_mul(2_654_435_761).wrapping_add(i * 97) >> 5) as u8)
.collect();
let _ = decompress(&junk, 8);
}
}
fn nine_bit_stream(codes: &[u32]) -> Vec<u8> {
let mut bits = Vec::new();
for code in codes {
for bit in 0..9 {
bits.push((code >> bit) & 1 == 1);
}
}
bits.chunks(8)
.map(|byte| {
byte.iter()
.enumerate()
.fold(0u8, |acc, (i, &b)| acc | (u8::from(b) << i))
})
.collect()
}
#[test]
fn the_entry_being_built_may_be_named_ahead_of_time() {
assert_eq!(
decompress(&nine_bit_stream(&[256, 65, 258, 257]), 8),
Some(vec![65, 65, 65])
);
let back = decompress(&compress(&[65, 65, 65], 8), 8);
assert_eq!(back, Some(vec![65, 65, 65]));
}
#[test]
fn a_stream_that_names_more_than_it_may_is_refused() {
fn pack(codes: &[(u16, u8)]) -> Vec<u8> {
let (mut out, mut bits, mut count) = (Vec::new(), 0u32, 0u8);
for &(code, width) in codes {
bits |= u32::from(code) << count;
count += width;
while count >= 8 {
out.push((bits & 0xFF) as u8);
bits >>= 8;
count -= 8;
}
}
if count > 0 {
out.push((bits & 0xFF) as u8);
}
out
}
let (mut codes, mut width, mut entries) = (Vec::new(), 9u8, 258usize);
codes.push((1u16 << 8, width)); codes.push((0, width));
for code in 258..=(1u16 << MAX_CODE_BITS) - 1 {
codes.push((code, width));
entries += 1;
if entries >= 1 << width && width < MAX_CODE_BITS {
width += 1;
}
}
let bomb = pack(&codes);
assert!(
bomb.len() < 8 * 1024,
"{} bytes should be asking for megabytes",
bomb.len()
);
let asked: usize = (2..=(1 << MAX_CODE_BITS) - 257).sum();
assert!(
asked > MAX_OUTPUT,
"{asked} bytes is not a bomb against a {MAX_OUTPUT}-byte cap"
);
assert_eq!(decompress(&bomb, 8), None, "the cap is what stops it");
let honest = compress(&vec![7u8; 4096], 8);
assert_eq!(decompress(&honest, 8), Some(vec![7u8; 4096]));
}
}