#![cfg(feature = "ppmd")]
use compcol::ppmd::{Decoder, Encoder, Ppmd};
use compcol::{Algorithm, Decoder as _, Encoder as _, Error, Status};
fn make_header(order: u8, mem_mb: u8, restoration: u8, len: u64) -> Vec<u8> {
let mut h = Vec::with_capacity(11);
h.push(order);
h.push(mem_mb);
h.push(restoration);
h.extend_from_slice(&len.to_le_bytes());
h
}
fn drive_to_end(dec: &mut Decoder, input: &[u8]) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
let mut spin = 0;
loop {
let (p, status) = dec.decode(&input[consumed..], &mut buf)?;
consumed += p.consumed;
out.extend_from_slice(&buf[..p.written]);
match status {
Status::OutputFull => {}
Status::InputEmpty => {
if consumed >= input.len() && p.written == 0 {
break;
}
}
Status::StreamEnd => return Ok(out),
}
if p.consumed == 0 && p.written == 0 {
break;
}
spin += 1;
if spin > 100_000 {
panic!(
"decoder spin (consumed={}, out_len={})",
consumed,
out.len()
);
}
}
loop {
let (p, status) = dec.finish(&mut buf)?;
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
Ok(out)
}
const MAX_FREQ: u32 = 124;
struct OrderZeroModel {
states: Vec<(u8, u32)>,
summ_freq: u32,
}
impl OrderZeroModel {
fn new() -> Self {
let states: Vec<(u8, u32)> = (0..=255).map(|s| (s as u8, 1)).collect();
Self {
states,
summ_freq: 256 + 1,
}
}
fn find(&self, sym: u8) -> usize {
self.states
.iter()
.position(|&(s, _)| s == sym)
.expect("order-0")
}
fn encode_lookup(&self, sym: u8) -> (u32, u32, u32) {
let mut acc = 0u32;
for &(s, f) in &self.states {
if s == sym {
return (acc, f, self.summ_freq);
}
acc += f;
}
unreachable!()
}
fn update(&mut self, sym: u8) {
let i = self.find(sym);
let new_f = self.states[i].1 + 4;
if new_f > MAX_FREQ {
self.rescale();
return;
}
self.states[i].1 = new_f;
self.summ_freq += 4;
if i > 0 && new_f > self.states[i - 1].1 {
self.states.swap(i, i - 1);
}
}
fn rescale(&mut self) {
let mut new_summ = 0u32;
for s in &mut self.states {
s.1 = ((s.1 + 1) >> 1).max(1);
new_summ += s.1;
}
self.summ_freq = new_summ;
}
}
struct RangeEnc {
low: u64,
range: u32,
cache_size: u32,
cache: u8,
out: Vec<u8>,
}
impl RangeEnc {
fn new() -> Self {
Self {
low: 0,
range: 0xFFFF_FFFF,
cache_size: 1,
cache: 0,
out: Vec::new(),
}
}
fn encode(&mut self, start: u32, size: u32, total: u32) {
self.range /= total;
self.low = self.low.wrapping_add(start as u64 * self.range as u64);
self.range = self.range.wrapping_mul(size);
self.normalize();
}
fn normalize(&mut self) {
while self.range < (1 << 24) {
self.shift_low();
self.range <<= 8;
}
}
fn shift_low(&mut self) {
if (self.low as u32) < 0xFF00_0000 || self.low >> 32 != 0 {
let mut temp = self.cache;
loop {
self.out.push(temp.wrapping_add((self.low >> 32) as u8));
temp = 0xFF;
self.cache_size -= 1;
if self.cache_size == 0 {
break;
}
}
self.cache = (self.low as u32 >> 24) as u8;
}
self.cache_size += 1;
self.low = (self.low << 8) & 0xFFFF_FFFF;
}
fn flush(mut self) -> Vec<u8> {
for _ in 0..5 {
self.shift_low();
}
self.out
}
}
fn encode_payload(input: &[u8]) -> Vec<u8> {
let mut m = OrderZeroModel::new();
let mut e = RangeEnc::new();
for &b in input {
let (start, size, total) = m.encode_lookup(b);
e.encode(start, size, total);
m.update(b);
}
let mut bytes = e.flush();
if bytes.first() != Some(&0) {
bytes.insert(0, 0);
}
bytes
}
fn make_stream(input: &[u8], order: u8) -> Vec<u8> {
let mut s = make_header(order, 1, 0, input.len() as u64);
s.extend_from_slice(&encode_payload(input));
s
}
#[test]
fn algorithm_name_is_ppmd() {
assert_eq!(<Ppmd as Algorithm>::NAME, "ppmd");
}
#[test]
fn ppmd_algorithm_factory_produces_codec() {
let _enc = <Ppmd as Algorithm>::encoder();
let _dec = <Ppmd as Algorithm>::decoder();
}
#[test]
fn decoder_new_does_not_panic() {
let _ = Decoder::new();
}
#[test]
fn encoder_encode_is_unsupported() {
let mut enc = Encoder::new();
let mut out = [0u8; 16];
assert_eq!(
enc.encode(b"hello", &mut out).unwrap_err(),
Error::Unsupported
);
}
#[test]
fn encoder_finish_is_unsupported() {
let mut enc = Encoder::new();
let mut out = [0u8; 16];
assert_eq!(enc.finish(&mut out).unwrap_err(), Error::Unsupported);
}
#[test]
fn encoder_reset_is_a_noop() {
let mut enc = Encoder::new();
enc.reset();
let mut out = [0u8; 4];
assert_eq!(enc.encode(b"x", &mut out).unwrap_err(), Error::Unsupported);
}
#[test]
fn rt_empty() {
let stream = make_stream(b"", 4);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, b"");
}
#[test]
fn rt_single_byte() {
let stream = make_stream(b"A", 4);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, b"A");
}
#[test]
fn rt_hello_world() {
let stream = make_stream(b"hello world", 6);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, b"hello world");
}
#[test]
fn rt_64k_repeating() {
let mut payload = Vec::with_capacity(64 * 1024);
let pattern = b"the quick brown fox jumps over the lazy dog ";
while payload.len() < 64 * 1024 {
payload.extend_from_slice(pattern);
}
payload.truncate(64 * 1024);
let stream = make_stream(&payload, 8);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, payload);
}
#[test]
fn rt_mixed_corpus() {
let mut payload = Vec::new();
payload.extend_from_slice(b"ASCII prefix.\n");
payload.extend((0u8..=255u8).cycle().take(4096));
payload.extend_from_slice(b"\nASCII suffix.");
let stream = make_stream(&payload, 4);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, payload);
}
#[test]
fn streaming_one_byte_at_a_time() {
let payload = b"the quick brown fox";
let stream = make_stream(payload, 4);
let mut dec = Decoder::new();
let mut out = Vec::new();
let mut buf = [0u8; 128];
let mut consumed = 0;
let mut spin = 0;
while consumed < stream.len() {
let take = (stream.len() - consumed).min(1);
let (p, status) = dec
.decode(&stream[consumed..consumed + take], &mut buf)
.unwrap();
consumed += p.consumed;
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
return assert_eq!(out, payload);
}
spin += 1;
if spin > 100_000 {
panic!("streaming spin");
}
}
loop {
let (p, status) = dec.finish(&mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) || p.written == 0 {
break;
}
}
assert_eq!(out, payload);
}
#[test]
fn truncated_header_returns_input_empty() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let (p, status) = dec.decode(&[4, 1, 0, 0, 0], &mut buf).unwrap();
assert_eq!(p.consumed, 5);
assert!(matches!(status, Status::InputEmpty));
let r = dec.finish(&mut buf);
assert_eq!(r, Err(Error::UnexpectedEnd));
}
#[test]
fn header_order_too_small_is_bad_header() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let stream = make_header(1, 1, 0, 0);
let r = dec.decode(&stream, &mut buf);
assert_eq!(r, Err(Error::BadHeader));
}
#[test]
fn header_order_too_large_is_bad_header() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let stream = make_header(17, 1, 0, 0);
let r = dec.decode(&stream, &mut buf);
assert_eq!(r, Err(Error::BadHeader));
}
#[test]
fn header_zero_mem_is_bad_header() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let stream = make_header(4, 0, 0, 0);
let r = dec.decode(&stream, &mut buf);
assert_eq!(r, Err(Error::BadHeader));
}
#[test]
fn header_bad_restoration_is_bad_header() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let stream = make_header(4, 1, 9, 0);
let r = dec.decode(&stream, &mut buf);
assert_eq!(r, Err(Error::BadHeader));
}
#[test]
fn payload_first_byte_must_be_zero() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let mut stream = make_header(4, 1, 0, 8);
stream.extend_from_slice(&[0xFF; 16]);
let r = dec.decode(&stream, &mut buf);
assert_eq!(r, Err(Error::Corrupt));
}
#[test]
fn truncated_payload_returns_unexpected_end_on_finish() {
let payload = b"hello there ppmd world";
let stream = make_stream(payload, 4);
let truncated = &stream[..stream.len() - 3];
let mut dec = Decoder::new();
let mut buf = [0u8; 256];
let _ = dec.decode(truncated, &mut buf);
let r = dec.finish(&mut buf);
assert!(matches!(r, Err(Error::UnexpectedEnd) | Err(Error::Corrupt)));
}
#[test]
fn garbage_after_header_does_not_panic() {
let mut stream = make_header(4, 1, 0, 16);
stream.extend_from_slice(&[0u8; 4]);
stream.extend_from_slice(&[0xAA; 64]); let mut dec = Decoder::new();
let mut buf = [0u8; 32];
let _ = dec.decode(&stream, &mut buf);
let _ = dec.finish(&mut buf);
}
#[test]
fn reset_returns_to_header_phase() {
let stream = make_stream(b"reset test", 4);
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, b"reset test");
dec.reset();
let out2 = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out2, b"reset test");
}
#[test]
fn reset_after_error_recovers() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let r = dec.decode(&[99, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], &mut buf);
assert_eq!(r, Err(Error::BadHeader));
assert!(dec.decode(b"x", &mut buf).is_err());
dec.reset();
let stream = make_stream(b"", 4);
let out = drive_to_end(&mut dec, &stream).unwrap();
assert_eq!(out, b"");
}
#[cfg(feature = "factory")]
mod factory {
use compcol::factory;
#[test]
fn lookup_ppmd_encoder_and_decoder() {
assert!(factory::encoder_by_name("ppmd").is_some());
assert!(factory::decoder_by_name("ppmd").is_some());
}
#[test]
fn lookup_unknown() {
assert!(factory::encoder_by_name("not-ppmd").is_none());
assert!(factory::decoder_by_name("not-ppmd").is_none());
}
#[test]
fn names_contains_ppmd() {
assert!(factory::names().contains(&"ppmd"));
}
#[test]
fn boxed_encoder_is_unsupported() {
use compcol::Error;
let mut enc = factory::encoder_by_name("ppmd").unwrap();
let mut out = [0u8; 16];
assert_eq!(
enc.encode(b"hello", &mut out).unwrap_err(),
Error::Unsupported
);
}
#[test]
fn extension_is_ppmd() {
assert_eq!(factory::extension("ppmd"), Some("ppmd"));
}
}