#![cfg(feature = "deflate")]
use compcol::deflate::{Decoder, Encoder};
use compcol::{Decoder as _, Encoder as _, Error, Flush, Status};
fn drive_encode<E: compcol::Encoder>(enc: &mut E, input: &[u8], out: &mut Vec<u8>) {
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
while consumed < input.len() {
let (p, status) = enc.encode(&input[consumed..], &mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
match status {
Status::InputEmpty => break,
Status::OutputFull => continue,
Status::StreamEnd => panic!("encode returned StreamEnd"),
}
}
}
fn drive_flush<E: compcol::Encoder>(enc: &mut E, mode: Flush, out: &mut Vec<u8>) {
let mut buf = vec![0u8; 4096];
loop {
let (p, status) = enc.flush(&mut buf, mode).unwrap();
out.extend_from_slice(&buf[..p.written]);
match status {
Status::InputEmpty => break,
Status::OutputFull => continue,
Status::StreamEnd => panic!("flush returned StreamEnd"),
}
}
}
fn drive_finish<E: compcol::Encoder>(enc: &mut E, out: &mut Vec<u8>) {
let mut buf = vec![0u8; 4096];
loop {
let (p, status) = enc.finish(&mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
assert!(p.written > 0 || matches!(status, Status::OutputFull));
}
}
fn decode_all(encoded: &[u8]) -> Result<Vec<u8>, Error> {
let mut dec = Decoder::new();
let mut out = Vec::new();
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
while consumed < encoded.len() {
let (p, status) = dec.decode(&encoded[consumed..], &mut buf)?;
out.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
match status {
Status::StreamEnd => break,
Status::InputEmpty => break,
Status::OutputFull => continue,
}
}
loop {
let (p, _status) = dec.decode(&[], &mut buf)?;
out.extend_from_slice(&buf[..p.written]);
if p.written == 0 {
break;
}
}
Ok(out)
}
fn decode_partial(encoded: &[u8]) -> Result<Vec<u8>, Error> {
let mut dec = Decoder::new();
let mut out = Vec::new();
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
while consumed < encoded.len() {
let (p, status) = dec.decode(&encoded[consumed..], &mut buf)?;
out.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
match status {
Status::StreamEnd => return Ok(out),
Status::InputEmpty => break,
Status::OutputFull => continue,
}
}
loop {
let (p, _status) = dec.decode(&[], &mut buf)?;
out.extend_from_slice(&buf[..p.written]);
if p.written == 0 {
break;
}
}
Ok(out)
}
#[test]
fn deflate_three_chunks_with_sync_flushes() {
let a: Vec<u8> = "AAAA".repeat(64).into_bytes(); let b: Vec<u8> = "BBBB".repeat(64).into_bytes();
let c: Vec<u8> = "CCCC".repeat(64).into_bytes();
let mut enc = Encoder::new();
let mut wire = Vec::new();
drive_encode(&mut enc, &a, &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
let after_a = wire.len();
drive_encode(&mut enc, &b, &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
let after_b = wire.len();
drive_encode(&mut enc, &c, &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
drive_finish(&mut enc, &mut wire);
let full = decode_all(&wire).expect("decode full stream");
let mut expected = Vec::new();
expected.extend_from_slice(&a);
expected.extend_from_slice(&b);
expected.extend_from_slice(&c);
assert_eq!(full, expected);
let prefix_a = decode_partial(&wire[..after_a]).expect("decode prefix A");
assert_eq!(prefix_a, a, "first-sync prefix should yield A");
let prefix_ab = decode_partial(&wire[..after_b]).expect("decode prefix AB");
let mut ab = Vec::new();
ab.extend_from_slice(&a);
ab.extend_from_slice(&b);
assert_eq!(prefix_ab, ab, "second-sync prefix should yield A++B");
}
#[test]
fn deflate_history_preserved_across_sync() {
let mut history = Vec::with_capacity(16 * 1024);
for i in 0..(16 * 1024) {
history.push((i & 0xff) as u8);
}
let mut enc = Encoder::new();
let mut wire = Vec::new();
drive_encode(&mut enc, &history, &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
let after_sync = wire.len();
drive_encode(&mut enc, &history, &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
drive_finish(&mut enc, &mut wire);
let post_flush_bytes = wire.len() - after_sync;
assert!(
post_flush_bytes * 10 < history.len(),
"post-flush portion {} bytes is too large vs history {} bytes — history not preserved across Sync",
post_flush_bytes,
history.len()
);
let decoded = decode_all(&wire).expect("decode full stream");
let mut expected = Vec::new();
expected.extend_from_slice(&history);
expected.extend_from_slice(&history);
assert_eq!(decoded, expected);
}
#[test]
fn deflate_full_flush_resets_history() {
let mut sentinel = Vec::with_capacity(4096);
for i in 0..4096u32 {
let v = ((i.wrapping_mul(2654435761)) >> 24) as u8;
sentinel.push(v);
}
let mut enc_full = Encoder::new();
let mut wire_full = Vec::new();
drive_encode(&mut enc_full, &sentinel, &mut wire_full);
drive_flush(&mut enc_full, Flush::Full, &mut wire_full);
let after_full_sync = wire_full.len();
drive_encode(&mut enc_full, &sentinel, &mut wire_full);
drive_flush(&mut enc_full, Flush::Sync, &mut wire_full);
drive_finish(&mut enc_full, &mut wire_full);
let post_full_bytes = wire_full.len() - after_full_sync;
let mut enc_sync = Encoder::new();
let mut wire_sync = Vec::new();
drive_encode(&mut enc_sync, &sentinel, &mut wire_sync);
drive_flush(&mut enc_sync, Flush::Sync, &mut wire_sync);
let after_sync_sync = wire_sync.len();
drive_encode(&mut enc_sync, &sentinel, &mut wire_sync);
drive_flush(&mut enc_sync, Flush::Sync, &mut wire_sync);
drive_finish(&mut enc_sync, &mut wire_sync);
let post_sync_bytes = wire_sync.len() - after_sync_sync;
assert!(
post_full_bytes > post_sync_bytes * 4,
"post-Full ({} bytes) should be much larger than post-Sync ({} bytes) — Full flush did not reset history",
post_full_bytes,
post_sync_bytes
);
let mut expected = Vec::new();
expected.extend_from_slice(&sentinel);
expected.extend_from_slice(&sentinel);
let decoded_full = decode_all(&wire_full).expect("decode Full wire");
assert_eq!(decoded_full, expected);
let decoded_sync = decode_all(&wire_sync).expect("decode Sync wire");
assert_eq!(decoded_sync, expected);
}
#[test]
fn deflate_flush_handles_tiny_output_buffer() {
let mut enc = Encoder::new();
let mut wire = Vec::new();
drive_encode(&mut enc, b"hello world", &mut wire);
let mut buf = [0u8; 1];
loop {
let (p, status) = enc.flush(&mut buf, Flush::Sync).unwrap();
wire.extend_from_slice(&buf[..p.written]);
match status {
Status::InputEmpty => break,
Status::OutputFull => continue,
Status::StreamEnd => panic!("flush returned StreamEnd"),
}
}
drive_finish(&mut enc, &mut wire);
let decoded = decode_all(&wire).expect("decode tiny-buffer wire");
assert_eq!(decoded, b"hello world");
}
#[cfg(feature = "rle")]
#[test]
fn rle_flush_is_default_noop() {
use compcol::rle;
let mut enc = rle::Encoder::new();
let mut buf = [0u8; 16];
let (p, status) = enc.flush(&mut buf, Flush::Sync).unwrap();
assert_eq!(p.consumed, 0);
assert_eq!(p.written, 0);
assert!(matches!(status, Status::InputEmpty));
let (p, status) = enc.flush(&mut buf, Flush::Full).unwrap();
assert_eq!(p.consumed, 0);
assert_eq!(p.written, 0);
assert!(matches!(status, Status::InputEmpty));
}
#[cfg(feature = "zlib")]
#[test]
fn zlib_flush_round_trips_without_trailer() {
use compcol::zlib;
let mut enc = zlib::Encoder::new();
let mut wire = Vec::new();
drive_encode(&mut enc, b"chunk one ", &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
let after_first_flush = wire.len();
drive_encode(&mut enc, b"chunk two ", &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
drive_encode(&mut enc, b"chunk three", &mut wire);
drive_finish(&mut enc, &mut wire);
let mut dec = zlib::Decoder::new();
let mut out = Vec::new();
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
while consumed < wire.len() {
let (p, status) = dec.decode(&wire[consumed..], &mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
if matches!(status, Status::StreamEnd) {
break;
}
if matches!(status, Status::InputEmpty) {
break;
}
}
assert_eq!(out, b"chunk one chunk two chunk three");
let mut probe = zlib::Decoder::new();
let mut probe_out = vec![0u8; 4096];
let mut probe_consumed = 0;
while probe_consumed < after_first_flush {
let (p, _status) = probe
.decode(&wire[probe_consumed..after_first_flush], &mut probe_out)
.unwrap();
probe_consumed += p.consumed;
if p.consumed == 0 && p.written == 0 {
break;
}
}
let finish_result = probe.finish(&mut probe_out);
assert!(
finish_result.is_err(),
"zlib decoder finish on a sync-flushed prefix must error \
(no trailer) — got Ok, meaning flush emitted a trailer"
);
}
#[cfg(feature = "gzip")]
#[test]
fn gzip_flush_round_trips_without_trailer() {
use compcol::gzip;
let mut enc = gzip::Encoder::new();
let mut wire = Vec::new();
drive_encode(&mut enc, b"alpha ", &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
let after_first_flush = wire.len();
drive_encode(&mut enc, b"beta ", &mut wire);
drive_flush(&mut enc, Flush::Sync, &mut wire);
drive_encode(&mut enc, b"gamma", &mut wire);
drive_finish(&mut enc, &mut wire);
let mut dec = gzip::Decoder::new();
let mut out = Vec::new();
let mut buf = vec![0u8; 4096];
let mut consumed = 0;
while consumed < wire.len() {
let (p, status) = dec.decode(&wire[consumed..], &mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
if matches!(status, Status::StreamEnd | Status::InputEmpty) {
break;
}
}
assert_eq!(out, b"alpha beta gamma");
let mut probe = gzip::Decoder::new();
let mut probe_out = vec![0u8; 4096];
let mut probe_consumed = 0;
while probe_consumed < after_first_flush {
let (p, _status) = probe
.decode(&wire[probe_consumed..after_first_flush], &mut probe_out)
.unwrap();
probe_consumed += p.consumed;
if p.consumed == 0 && p.written == 0 {
break;
}
}
let finish_result = probe.finish(&mut probe_out);
assert!(
finish_result.is_err(),
"gzip decoder finish on a sync-flushed prefix must error \
(no trailer) — got Ok, meaning flush emitted a trailer"
);
}
#[test]
fn deflate_flush_with_no_pending_input() {
let mut enc = Encoder::new();
let mut wire = Vec::new();
drive_flush(&mut enc, Flush::Sync, &mut wire);
assert!(!wire.is_empty(), "flush should emit the sync marker");
drive_encode(&mut enc, b"after", &mut wire);
drive_finish(&mut enc, &mut wire);
let decoded = decode_all(&wire).expect("decode empty-flush wire");
assert_eq!(decoded, b"after");
}