#![cfg(feature = "lzx")]
use compcol::lzx::{Decoder, Encoder, Lzx};
use compcol::{Algorithm, Decoder as _, Encoder as _, Error, Status};
fn encode_chunked(input: &[u8], in_chunk: usize, out_chunk: usize) -> Vec<u8> {
let mut enc = Encoder::new();
let mut encoded = Vec::new();
let mut buf = vec![0u8; out_chunk.max(1)];
let mut i = 0;
while i < input.len() {
let end = (i + in_chunk).min(input.len());
let chunk = &input[i..end];
let mut consumed = 0;
while consumed < chunk.len() {
let (p, status) = enc.encode(&chunk[consumed..], &mut buf).unwrap();
encoded.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
match status {
Status::InputEmpty | Status::StreamEnd => break,
Status::OutputFull => continue,
}
}
i = end;
}
loop {
let (p, status) = enc.finish(&mut buf).unwrap();
encoded.extend_from_slice(&buf[..p.written]);
match status {
Status::StreamEnd => break,
Status::OutputFull | Status::InputEmpty => {
if p.written == 0 {
panic!("encoder finish stalled");
}
}
}
}
encoded
}
fn decode_chunked(encoded: &[u8], in_chunk: usize, out_chunk: usize) -> Vec<u8> {
let mut dec = Decoder::new();
let mut decoded = Vec::new();
let mut buf = vec![0u8; out_chunk.max(1)];
let mut i = 0;
while i < encoded.len() {
let end = (i + in_chunk).min(encoded.len());
let chunk = &encoded[i..end];
let mut consumed = 0;
while consumed < chunk.len() {
let (p, status) = dec.decode(&chunk[consumed..], &mut buf).unwrap();
decoded.extend_from_slice(&buf[..p.written]);
consumed += p.consumed;
match status {
Status::InputEmpty | Status::StreamEnd => break,
Status::OutputFull => continue,
}
}
i = end;
}
loop {
let (p, status) = dec.finish(&mut buf).unwrap();
decoded.extend_from_slice(&buf[..p.written]);
match status {
Status::StreamEnd => break,
Status::OutputFull | Status::InputEmpty => {
if p.written == 0 {
panic!("decoder finish stalled (decoded {} so far)", decoded.len());
}
}
}
}
decoded
}
fn round_trip(input: &[u8]) {
let encoded = encode_chunked(input, input.len().max(1), input.len().max(64) * 2 + 64);
let decoded = decode_chunked(&encoded, encoded.len().max(1), input.len().max(1));
assert_eq!(decoded, input, "round-trip mismatch");
}
#[test]
fn name_is_lzx() {
assert_eq!(<Lzx as Algorithm>::NAME, "lzx");
}
#[test]
fn empty_input() {
round_trip(&[]);
}
#[test]
fn hello_world() {
round_trip(b"hello, world!");
}
#[test]
fn single_byte() {
round_trip(&[0x42]);
}
#[test]
fn two_bytes() {
round_trip(&[0xAA, 0xBB]);
}
fn lorem(target_bytes: usize) -> Vec<u8> {
let base: &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, \
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ";
let mut out = Vec::with_capacity(target_bytes + base.len());
while out.len() < target_bytes {
out.extend_from_slice(base);
}
out.truncate(target_bytes);
out
}
#[test]
fn lorem_4kib() {
round_trip(&lorem(4 * 1024));
}
#[test]
fn lorem_16kib() {
round_trip(&lorem(16 * 1024));
}
#[test]
fn lorem_40kib_crosses_chunk() {
round_trip(&lorem(40 * 1024));
}
#[test]
fn pseudo_random_8kib() {
let mut data = Vec::with_capacity(8 * 1024);
let mut x: u32 = 0xCAFE_BABE;
while data.len() < 8 * 1024 {
x = x.wrapping_mul(1103515245).wrapping_add(12345);
data.push((x >> 16) as u8);
}
round_trip(&data);
}
#[test]
fn one_byte_streaming_round_trip() {
let input = b"streaming, one byte at a time, both directions.".to_vec();
let encoded = encode_chunked(&input, 1, 1);
let decoded = decode_chunked(&encoded, 1, 1);
assert_eq!(decoded, input);
}
#[test]
fn invalid_window_bits_in_header() {
let bad: &[u8] = &[9, 0, 0, 0, 0];
let mut dec = Decoder::new();
let mut out = [0u8; 16];
let err = dec.decode(bad, &mut out).unwrap_err();
assert_eq!(err, Error::Unsupported);
}
#[test]
fn truncated_stream_after_header() {
let header: &[u8] = &[15, 10, 0, 0, 0]; let mut dec = Decoder::new();
let mut out = [0u8; 16];
let (p, _status) = dec.decode(header, &mut out).unwrap();
assert_eq!(p.consumed, 5);
assert_eq!(p.written, 0);
let err = dec.finish(&mut out).unwrap_err();
assert_eq!(err, Error::UnexpectedEnd);
}
#[test]
fn invalid_block_type_zero() {
let block_size: u32 = 10;
let header28: u32 = ((block_size >> 8) & 0xFFFF) << 8 | (block_size & 0xFF);
let padded32 = header28 << 4;
let w0 = ((padded32 >> 16) & 0xFFFF) as u16;
let w1 = (padded32 & 0xFFFF) as u16;
let mut bytes: Vec<u8> = Vec::new();
bytes.extend_from_slice(&[15, 10, 0, 0, 0]);
bytes.push((w0 & 0xFF) as u8);
bytes.push((w0 >> 8) as u8);
bytes.push((w1 & 0xFF) as u8);
bytes.push((w1 >> 8) as u8);
let mut dec = Decoder::new();
let mut out = [0u8; 32];
let res = dec.decode(&bytes, &mut out);
assert_eq!(res, Err(Error::InvalidBlockType));
}
#[test]
fn fixture_uncompressed_block_layout() {
let encoded = encode_chunked(b"hi", 2, 64);
let expected: &[u8] = &[
15, 0x02, 0, 0, 0, 0x00, 0x30, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x69, ];
assert_eq!(encoded, expected);
}
#[test]
fn fixture_round_trip_decoder_only() {
let fixture: &[u8] = &[
15, 0x02, 0, 0, 0, 0x00, 0x30, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, b'h', b'i',
];
let out = decode_chunked(fixture, 4, 4);
assert_eq!(out, b"hi");
}
#[test]
fn fixture_multi_block() {
let n = 80 * 1024;
let data = lorem(n);
let encoded = encode_chunked(&data, n, n * 2 + 1024);
assert!(encoded.len() >= data.len() + 53);
assert!(encoded.len() <= data.len() + 256);
let decoded = decode_chunked(&encoded, encoded.len() / 7 + 1, 4096);
assert_eq!(decoded, data);
}
#[test]
fn encoder_reset_round_trip() {
let mut enc = Encoder::new();
let mut buf = vec![0u8; 1024];
for input in [b"abc".as_ref(), b"defgh".as_ref(), b"".as_ref()] {
enc.reset();
let mut encoded = Vec::new();
let (p, _status) = enc.encode(input, &mut buf).unwrap();
encoded.extend_from_slice(&buf[..p.written]);
assert_eq!(p.consumed, input.len());
loop {
let (p, status) = enc.finish(&mut buf).unwrap();
encoded.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
let decoded = decode_chunked(&encoded, encoded.len().max(1), 1024);
assert_eq!(decoded, input);
}
}
#[test]
fn decoder_reset_round_trip() {
let mut dec = Decoder::new();
for input in [b"abc".as_ref(), b"longer message here".as_ref()] {
let encoded = encode_chunked(input, input.len().max(1), 1024);
dec.reset();
let mut decoded = Vec::new();
let mut buf = vec![0u8; 1024];
let mut i = 0;
while i < encoded.len() {
let (p, status) = dec.decode(&encoded[i..], &mut buf).unwrap();
decoded.extend_from_slice(&buf[..p.written]);
i += p.consumed;
match status {
Status::InputEmpty | Status::StreamEnd => break,
Status::OutputFull => continue,
}
}
loop {
let (p, status) = dec.finish(&mut buf).unwrap();
decoded.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
assert_eq!(decoded, input);
}
}
#[cfg(feature = "factory")]
mod factory {
use compcol::Status;
use compcol::factory;
#[test]
fn lookup_known() {
assert!(factory::encoder_by_name("lzx").is_some());
assert!(factory::decoder_by_name("lzx").is_some());
}
#[test]
fn lookup_unknown() {
assert!(factory::encoder_by_name("does-not-exist").is_none());
assert!(factory::decoder_by_name("does-not-exist").is_none());
}
#[test]
fn names_contains_lzx() {
assert!(factory::names().contains(&"lzx"));
}
#[test]
fn boxed_round_trip() {
let mut enc = factory::encoder_by_name("lzx").unwrap();
let mut dec = factory::decoder_by_name("lzx").unwrap();
let input = b"hello hello hello";
let mut buf = vec![0u8; 256];
let mut encoded = Vec::new();
let (p, _) = enc.encode(input, &mut buf).unwrap();
encoded.extend_from_slice(&buf[..p.written]);
assert_eq!(p.consumed, input.len());
loop {
let (pf, status) = enc.finish(&mut buf).unwrap();
encoded.extend_from_slice(&buf[..pf.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
let mut decoded = Vec::new();
let mut i = 0;
while i < encoded.len() {
let (pd, status) = dec.decode(&encoded[i..], &mut buf).unwrap();
decoded.extend_from_slice(&buf[..pd.written]);
i += pd.consumed;
match status {
Status::InputEmpty | Status::StreamEnd => break,
Status::OutputFull => continue,
}
}
loop {
let (pdf, status) = dec.finish(&mut buf).unwrap();
decoded.extend_from_slice(&buf[..pdf.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
assert_eq!(decoded, input);
}
}