#![cfg(feature = "lzfse")]
use compcol::lzfse::{Decoder, Encoder, Lzfse};
use compcol::{Algorithm, Decoder as _, Encoder as _, Error, Status};
fn make_uncompressed_block(payload: &[u8]) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(b"bvx-");
b.extend_from_slice(&(payload.len() as u32).to_le_bytes());
b.extend_from_slice(payload);
b
}
fn eos() -> Vec<u8> {
b"bvx$".to_vec()
}
fn make_lzvn_block(decoded_len: u32, payload: &[u8]) -> Vec<u8> {
let mut b = Vec::new();
b.extend_from_slice(b"bvxn");
b.extend_from_slice(&decoded_len.to_le_bytes());
b.extend_from_slice(&(payload.len() as u32).to_le_bytes());
b.extend_from_slice(payload);
b
}
fn lzvn_payload_literals_small(text: &[u8]) -> Vec<u8> {
assert!(text.len() <= 15);
let mut p = Vec::new();
p.push(0xE0 | (text.len() as u8)); p.extend_from_slice(text);
p.push(0x06);
p.extend_from_slice(&[0; 7]);
p
}
fn lzvn_payload_literals(b: u8, n: usize) -> Vec<u8> {
let mut out = Vec::new();
let mut remaining = n;
while remaining > 0 {
let chunk = remaining.min(15);
out.push(0xE0 | (chunk as u8));
for _ in 0..chunk {
out.push(b);
}
remaining -= chunk;
}
out.push(0x06);
out.extend_from_slice(&[0; 7]);
out
}
fn drive_to_end(dec: &mut Decoder, input: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let mut buf = vec![0u8; 256];
let mut consumed = 0;
while consumed < input.len() {
let (p, status) = dec.decode(&input[consumed..], &mut buf).unwrap();
consumed += p.consumed;
out.extend_from_slice(&buf[..p.written]);
match status {
Status::OutputFull => continue,
Status::InputEmpty => break,
Status::StreamEnd => {
return out;
}
}
}
loop {
let (p, status) = dec.finish(&mut buf).unwrap();
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
out
}
#[test]
fn algorithm_name_is_lzfse() {
assert_eq!(<Lzfse as Algorithm>::NAME, "lzfse");
}
#[test]
fn lzfse_algorithm_factory_produces_codec() {
let _enc = <Lzfse as Algorithm>::encoder();
let _dec = <Lzfse 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_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 pure_eos_stream_decodes_to_empty() {
let stream = eos();
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"");
}
const HELLO_WORLD: &[u8] = b"hello world";
#[test]
fn uncompressed_hello_world() {
let mut stream = make_uncompressed_block(HELLO_WORLD);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, HELLO_WORLD);
}
#[test]
fn uncompressed_empty_payload() {
let mut stream = make_uncompressed_block(b"");
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"");
}
#[test]
fn multiple_uncompressed_blocks() {
let mut stream = make_uncompressed_block(b"hello ");
stream.extend_from_slice(&make_uncompressed_block(b"world"));
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"hello world");
}
#[test]
fn uncompressed_one_byte_at_a_time() {
let mut stream = make_uncompressed_block(HELLO_WORLD);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let mut out = Vec::new();
let mut buf = [0u8; 16];
let mut consumed = 0;
while consumed < stream.len() {
let (p, status) = dec
.decode(&stream[consumed..consumed + 1], &mut buf)
.unwrap();
consumed += p.consumed;
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
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, HELLO_WORLD);
}
#[test]
fn lzvn_hello_world_literals_only() {
let payload = lzvn_payload_literals_small(HELLO_WORLD);
let mut stream = make_lzvn_block(HELLO_WORLD.len() as u32, &payload);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, HELLO_WORLD);
}
#[test]
fn lzvn_literal_then_sml_d_match() {
let mut payload = Vec::new();
payload.push(0xE6); payload.extend_from_slice(b"ABCDEF");
payload.push(0x00); payload.push(0x03); payload.push(0x06); payload.extend_from_slice(&[0; 7]);
let mut stream = make_lzvn_block(9, &payload);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"ABCDEFDEF");
}
#[test]
fn lzvn_sml_m_reuses_previous_distance() {
let mut payload = Vec::new();
payload.push(0xE4); payload.extend_from_slice(b"ABCD");
payload.push(0x00); payload.push(0x02); payload.push(0xF4); payload.push(0x06); payload.extend_from_slice(&[0; 7]);
let mut stream = make_lzvn_block(11, &payload);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"ABCDCDCDCDC");
}
#[test]
fn lzvn_100_capital_a() {
let expected = vec![b'A'; 100];
let payload = lzvn_payload_literals(b'A', 100);
let mut stream = make_lzvn_block(100u32, &payload);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, expected);
}
#[test]
fn lzvn_one_byte_at_a_time() {
let payload = lzvn_payload_literals_small(HELLO_WORLD);
let mut stream = make_lzvn_block(HELLO_WORLD.len() as u32, &payload);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let mut out = Vec::new();
let mut buf = [0u8; 16];
let mut consumed = 0;
while consumed < stream.len() {
let (p, status) = dec
.decode(&stream[consumed..consumed + 1], &mut buf)
.unwrap();
consumed += p.consumed;
out.extend_from_slice(&buf[..p.written]);
if matches!(status, Status::StreamEnd) {
break;
}
}
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, HELLO_WORLD);
}
#[test]
fn bvx2_block_returns_unsupported() {
let mut stream = b"bvx2".to_vec();
stream.extend_from_slice(&[0u8; 32]);
let mut dec = Decoder::new();
let mut buf = [0u8; 256];
let r = dec.decode(&stream, &mut buf);
assert!(
matches!(r, Err(Error::Unsupported)),
"expected Unsupported on bvx2 block, got {:?}",
r
);
}
#[test]
fn truncated_magic_does_not_panic() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let (p, status) = dec.decode(b"bvx", &mut buf).unwrap();
assert_eq!(p.consumed, 3);
assert_eq!(p.written, 0);
assert!(matches!(status, Status::InputEmpty));
let r = dec.finish(&mut buf);
assert_eq!(r, Err(Error::UnexpectedEnd));
}
#[test]
fn truncated_uncompressed_payload_does_not_panic() {
let mut stream = b"bvx-".to_vec();
stream.extend_from_slice(&10u32.to_le_bytes());
stream.extend_from_slice(b"abc");
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let _ = dec.decode(&stream, &mut buf);
let r = dec.finish(&mut buf);
assert!(matches!(r, Err(Error::UnexpectedEnd) | Err(Error::Corrupt)));
}
#[test]
fn garbage_magic_rejected() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
let r = dec.decode(b"GARB", &mut buf);
assert_eq!(r, Err(Error::BadHeader));
}
#[test]
fn reset_returns_to_await_magic() {
let mut stream = make_uncompressed_block(HELLO_WORLD);
stream.extend_from_slice(&eos());
let mut dec = Decoder::new();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, HELLO_WORLD);
dec.reset();
let out2 = drive_to_end(&mut dec, &stream);
assert_eq!(out2, HELLO_WORLD);
}
#[test]
fn reset_after_error_recovers() {
let mut dec = Decoder::new();
let mut buf = [0u8; 16];
assert_eq!(dec.decode(b"GARB", &mut buf), Err(Error::BadHeader));
assert!(dec.decode(b"x", &mut buf).is_err());
dec.reset();
let stream = eos();
let out = drive_to_end(&mut dec, &stream);
assert_eq!(out, b"");
}
#[cfg(feature = "factory")]
mod factory {
use compcol::factory;
#[test]
fn lookup_lzfse_encoder_and_decoder() {
assert!(factory::encoder_by_name("lzfse").is_some());
assert!(factory::decoder_by_name("lzfse").is_some());
}
#[test]
fn lookup_unknown() {
assert!(factory::encoder_by_name("not-a-real-lzfse").is_none());
assert!(factory::decoder_by_name("not-a-real-lzfse").is_none());
}
#[test]
fn names_contains_lzfse() {
assert!(factory::names().contains(&"lzfse"));
}
#[test]
fn boxed_encoder_is_unsupported() {
use compcol::Error;
let mut enc = factory::encoder_by_name("lzfse").unwrap();
let mut out = [0u8; 16];
assert_eq!(
enc.encode(b"hello", &mut out).unwrap_err(),
Error::Unsupported
);
}
#[test]
fn extension_is_lzfse() {
assert_eq!(factory::extension("lzfse"), Some("lzfse"));
}
}