#![cfg(feature = "rar5")]
use compcol::rar5::{Decoder, Encoder, Rar5};
use compcol::{Algorithm, Decoder as _, Encoder as _, Error, Status};
const FIXTURE_AAA: &[u8] = &[
0xc0, 0x97, 0x0d, 0x02, 0x3f, 0xd3, 0x1f, 0xf1, 0x5e, 0x7f, 0x49, 0x81, 0xa9, 0xbf, 0x15, 0x00,
];
const FIXTURE_AAA_UNPACK: u64 = 201;
const FIXTURE_ABCABC: &[u8] = &[
0xc2, 0x88, 0x10, 0x33, 0x23, 0xfc, 0x32, 0xff, 0x32, 0xf0, 0x3f, 0xd5, 0x22, 0x12, 0xca, 0xee,
0xe3, 0x4f, 0xc0,
];
const FIXTURE_ABCABC_UNPACK: u64 = 301;
const FIXTURE_E8: &[u8] = &[
0xc0, 0xcf, 0x55, 0x03, 0x40, 0x04, 0x23, 0xf7, 0x44, 0x2d, 0x2f, 0x24, 0x69, 0xd6, 0x60, 0x8d,
0x85, 0x41, 0x82, 0x4e, 0x7d, 0x8b, 0xcc, 0xff, 0x88, 0x38, 0x85, 0x84, 0xaa, 0x38, 0x45, 0x09,
0x3e, 0x38, 0xa6, 0xa2, 0x72, 0x7a, 0x82, 0x8a, 0x92, 0x9a, 0xa2, 0xaa, 0xb2, 0xba, 0xc2, 0xca,
0xd2, 0xda, 0xe2, 0xea, 0xf2, 0xfb, 0x03, 0x0b, 0x13, 0x1b, 0x23, 0x2b, 0x33, 0x3b, 0x43, 0x4b,
0x53, 0x5b, 0x63, 0x6b, 0x73, 0x7b, 0x83, 0x8b, 0x93, 0x9b, 0xa3, 0xab, 0xb3, 0xbb, 0xc3, 0xcb,
0xd3, 0xdb, 0xe3, 0xec, 0x97, 0xef, 0xee, 0x80,
];
const FIXTURE_E8_UNPACK: u64 = 1506;
fn decode_once(comp: &[u8], unpack: u64, window: usize) -> Result<Vec<u8>, Error> {
let mut dec = Decoder::with_unpack_size_and_window(unpack, window);
let mut out = vec![0u8; unpack as usize];
let mut total = 0usize;
let (p, status) = dec.decode(comp, &mut out[total..])?;
total += p.written;
if !matches!(status, Status::StreamEnd) {
loop {
if total >= out.len() {
break;
}
let (p, status) = dec.decode(&[], &mut out[total..])?;
total += p.written;
if matches!(status, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
}
loop {
if total >= out.len() {
let mut scratch = [0u8; 16];
let (pf, status) = dec.finish(&mut scratch)?;
if pf.written != 0 {
out.extend_from_slice(&scratch[..pf.written]);
total += pf.written;
}
if matches!(status, Status::StreamEnd) {
break;
}
if pf.written == 0 {
break;
}
continue;
}
let (pf, status) = dec.finish(&mut out[total..])?;
total += pf.written;
if matches!(status, Status::StreamEnd) {
break;
}
if pf.written == 0 {
break;
}
}
out.truncate(total);
Ok(out)
}
fn decode_chunked(
comp: &[u8],
unpack: u64,
window: usize,
in_chunk: usize,
out_chunk: usize,
) -> Result<Vec<u8>, Error> {
let mut dec = Decoder::with_unpack_size_and_window(unpack, window);
let mut decoded: Vec<u8> = Vec::with_capacity(unpack as usize);
let mut buf = vec![0u8; out_chunk.max(1)];
let mut i = 0usize;
while i < comp.len() {
let end = (i + in_chunk).min(comp.len());
let chunk = &comp[i..end];
let mut consumed = 0;
while consumed < chunk.len() {
let (p, status) = dec.decode(&chunk[consumed..], &mut buf)?;
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.decode(&[], &mut buf)?;
decoded.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
}
loop {
let (p, status) = dec.finish(&mut buf)?;
decoded.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
Ok(decoded)
}
#[test]
fn algorithm_name_is_rar5() {
assert_eq!(<Rar5 as Algorithm>::NAME, "rar5");
}
#[test]
fn rar5_algorithm_factory_produces_codec() {
let _enc = <Rar5 as Algorithm>::encoder();
let _dec = <Rar5 as Algorithm>::decoder();
}
#[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_no_op() {
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 empty_input_with_zero_unpack_size_finishes_cleanly() {
let mut dec = Decoder::with_unpack_size_and_window(0, 128 * 1024);
let mut out = [0u8; 16];
let (p, status) = dec.finish(&mut out).unwrap();
assert_eq!(p.written, 0);
assert!(
matches!(status, Status::StreamEnd),
"expected StreamEnd on empty finish, got {:?}",
status
);
}
#[test]
fn decode_aaa_fixture() {
let out = decode_once(FIXTURE_AAA, FIXTURE_AAA_UNPACK, 128 * 1024).expect("decode");
let mut expected = vec![b'A'; 200];
expected.push(b'\n');
assert_eq!(out, expected);
}
#[test]
fn decode_abcabc_fixture() {
let out = decode_once(FIXTURE_ABCABC, FIXTURE_ABCABC_UNPACK, 128 * 1024).expect("decode");
let mut expected = Vec::new();
for _ in 0..100 {
expected.extend_from_slice(b"abc");
}
expected.push(b'\n');
assert_eq!(out, expected);
}
#[test]
fn decode_e8_filter_fixture() {
let out = decode_once(FIXTURE_E8, FIXTURE_E8_UNPACK, 128 * 1024).expect("decode");
let mut expected = Vec::with_capacity(FIXTURE_E8_UNPACK as usize);
for i in 0..50u32 {
expected.extend_from_slice(&[0u8; 20]);
expected.push(0xE8);
expected.extend_from_slice(&(0x100u32 + i).to_le_bytes());
}
expected.extend_from_slice(&[0x90u8; 256]);
assert_eq!(out.len(), expected.len());
assert_eq!(
out, expected,
"E8 filter round-trip must match the original input byte-for-byte"
);
}
#[test]
fn decode_aaa_chunked_one_byte_at_a_time() {
let out = decode_chunked(FIXTURE_AAA, FIXTURE_AAA_UNPACK, 128 * 1024, 1, 64).expect("decode");
let mut expected = vec![b'A'; 200];
expected.push(b'\n');
assert_eq!(out, expected);
}
#[test]
fn decode_aaa_with_small_output_buffer() {
let out = decode_chunked(
FIXTURE_AAA,
FIXTURE_AAA_UNPACK,
128 * 1024,
FIXTURE_AAA.len(),
32,
)
.expect("decode");
let mut expected = vec![b'A'; 200];
expected.push(b'\n');
assert_eq!(out, expected);
}
#[test]
fn truncated_input_returns_error_or_no_output() {
let mut dec = Decoder::with_unpack_size_and_window(201, 128 * 1024);
let mut out = [0u8; 256];
let (p, _status) = dec.decode(&FIXTURE_AAA[..1], &mut out).unwrap();
assert_eq!(
p.written, 0,
"no output should be produced from a 1-byte input"
);
let mut tail = [0u8; 16];
let err = dec.finish(&mut tail).unwrap_err();
assert_eq!(err, Error::UnexpectedEnd);
}
#[test]
fn invalid_block_header_checksum_is_rejected() {
let mut bad = FIXTURE_AAA.to_vec();
bad[2] ^= 0xFF;
let mut dec = Decoder::with_unpack_size_and_window(201, 128 * 1024);
let mut out = [0u8; 256];
let r = dec.decode(&bad, &mut out);
assert!(matches!(
r,
Err(Error::BadHeader) | Err(Error::Corrupt) | Err(Error::InvalidHuffmanTree)
));
}
#[test]
fn decoder_does_not_panic_on_garbage() {
let garbage: Vec<u8> = (0..512u32).map(|i| (i ^ (i >> 3)) as u8).collect();
let mut dec = Decoder::with_unpack_size_and_window(1024, 128 * 1024);
let mut out = [0u8; 1024];
let _ = dec.decode(&garbage, &mut out);
}
#[test]
fn reset_clears_state() {
let mut dec = Decoder::with_unpack_size_and_window(FIXTURE_AAA_UNPACK, 128 * 1024);
let mut out = vec![0u8; FIXTURE_AAA_UNPACK as usize];
let (p, _status) = dec.decode(FIXTURE_AAA, &mut out).unwrap();
let mut total = p.written;
while total < out.len() {
let (p, status) = dec.decode(&[], &mut out[total..]).unwrap();
total += p.written;
if matches!(status, Status::StreamEnd) || p.written == 0 {
break;
}
}
loop {
let mut scratch = [0u8; 16];
let (pf, status) = dec.finish(&mut scratch).unwrap();
let _ = pf;
if matches!(status, Status::StreamEnd) || pf.written == 0 {
break;
}
}
dec.reset();
let mut out2 = vec![0u8; FIXTURE_AAA_UNPACK as usize];
let (p, _status) = dec.decode(FIXTURE_AAA, &mut out2).unwrap();
let mut total2 = p.written;
while total2 < out2.len() {
let (p, status) = dec.decode(&[], &mut out2[total2..]).unwrap();
total2 += p.written;
if matches!(status, Status::StreamEnd) || p.written == 0 {
break;
}
}
let mut expected = vec![b'A'; 200];
expected.push(b'\n');
assert_eq!(&out2[..total2], expected.as_slice());
}
#[cfg(feature = "factory")]
mod factory {
use compcol::Error;
use compcol::factory;
#[test]
fn lookup_rar5_encoder_and_decoder() {
assert!(factory::encoder_by_name("rar5").is_some());
assert!(factory::decoder_by_name("rar5").is_some());
}
#[test]
fn lookup_unknown() {
assert!(factory::encoder_by_name("not-a-real-rar5").is_none());
assert!(factory::decoder_by_name("not-a-real-rar5").is_none());
}
#[test]
fn names_contains_rar5() {
assert!(factory::names().contains(&"rar5"));
}
#[test]
fn boxed_encoder_is_unsupported() {
let mut enc = factory::encoder_by_name("rar5").unwrap();
let mut out = [0u8; 16];
assert_eq!(
enc.encode(b"hello", &mut out).unwrap_err(),
Error::Unsupported
);
}
}