#![cfg(feature = "sit13")]
use compcol::sit13::{Decoder, DecoderConfig, Encoder, Sit13};
use compcol::{Algorithm, Decoder as _, Encoder as _, Error, Status};
fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in data {
crc ^= b as u16;
for _ in 0..8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
crc
}
fn be16(d: &[u8], o: usize) -> u16 {
u16::from_be_bytes([d[o], d[o + 1]])
}
fn be32(d: &[u8], o: usize) -> usize {
u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) as usize
}
fn decode_fork(payload: &[u8], ulen: usize) -> Result<Vec<u8>, Error> {
let mut dec = Sit13::decoder_with(DecoderConfig::with_len(ulen));
let mut out = vec![0u8; ulen];
let mut written = 0usize;
let mut consumed = 0usize;
loop {
let (p, s) = dec.decode(&payload[consumed..], &mut out[written..])?;
consumed += p.consumed;
written += p.written;
match s {
Status::StreamEnd => break,
Status::InputEmpty => break,
Status::OutputFull => {
if written >= out.len() {
break;
}
}
}
if p.consumed == 0 && p.written == 0 {
break;
}
}
loop {
let (p, s) = dec.finish(&mut out[written..])?;
written += p.written;
if matches!(s, Status::StreamEnd) {
break;
}
if p.written == 0 {
break;
}
}
out.truncate(written);
Ok(out)
}
fn walk_forks(data: &[u8]) -> Vec<(u8, &[u8], usize, u16)> {
assert_eq!(&data[0..4], b"SIT!", "bad magic");
assert_eq!(&data[10..14], b"rLau", "bad rLau tag");
const HDR: usize = 112;
let mut forks = Vec::new();
let mut off = 22usize;
while off + HDR <= data.len() {
let rmeth = data[off];
let dmeth = data[off + 1];
if matches!(rmeth, 0x20 | 0x21) || matches!(dmeth, 0x20 | 0x21) {
off += HDR;
continue;
}
let rlen = be32(data, off + 84);
let dlen = be32(data, off + 88);
let rclen = be32(data, off + 92);
let dclen = be32(data, off + 96);
let rcrc = be16(data, off + 100);
let dcrc = be16(data, off + 102);
let data_off = off + HDR;
let res = &data[data_off..data_off + rclen];
let dat = &data[data_off + rclen..data_off + rclen + dclen];
if rmeth & 0x0F == 13 {
forks.push((rmeth & 0x0F, res, rlen, rcrc));
}
if dmeth & 0x0F == 13 {
forks.push((dmeth & 0x0F, dat, dlen, dcrc));
}
off = data_off + rclen + dclen;
}
forks
}
#[test]
fn bundled_sit_all_method13_forks_pass_crc() {
let data = include_bytes!("fixtures/sit13/sample.sit");
let forks = walk_forks(data);
assert_eq!(forks.len(), 7, "expected 7 method-13 forks in the fixture");
let mut pass = 0usize;
for (i, &(_method, payload, ulen, crc)) in forks.iter().enumerate() {
let decoded = decode_fork(payload, ulen)
.unwrap_or_else(|e| panic!("fork {i} failed to decode: {e:?}"));
assert_eq!(decoded.len(), ulen, "fork {i} wrong length");
let got = crc16(&decoded);
assert_eq!(
got, crc,
"fork {i} CRC mismatch: got {got:#06x} want {crc:#06x}"
);
pass += 1;
}
assert_eq!(pass, 7);
}
#[test]
fn algorithm_name_is_sit13() {
assert_eq!(<Sit13 as Algorithm>::NAME, "sit13");
}
#[test]
fn algorithm_factory_constructs_codec() {
let _enc = <Sit13 as Algorithm>::encoder();
let _dec = <Sit13 as Algorithm>::decoder();
}
#[test]
fn decoder_constructors_do_not_panic() {
let _ = Decoder::new();
let _ = Decoder::with_len(0);
let _ = Decoder::with_len(1234);
let _ = Decoder::default();
let _ = Sit13::decoder_with(DecoderConfig::default());
let _ = Sit13::decoder_with(DecoderConfig::with_len(99));
}
#[test]
fn encoder_encode_is_unsupported() {
let mut enc = Encoder::new();
let mut out = [0u8; 32];
assert_eq!(enc.encode(b"hello", &mut out), Err(Error::Unsupported));
}
#[test]
fn encoder_finish_is_unsupported() {
let mut enc = Encoder::new();
let mut out = [0u8; 32];
assert_eq!(enc.finish(&mut out), Err(Error::Unsupported));
}
#[test]
fn empty_fork_decodes_to_empty() {
let decoded = decode_fork(&[], 0).expect("empty fork should decode");
assert!(decoded.is_empty());
}
#[test]
fn empty_member_decodes_to_empty_via_finish() {
let mut dec = Decoder::with_len(0);
let mut out = [0u8; 16];
let (p, status) = dec.finish(&mut out).unwrap();
assert_eq!(p.written, 0);
assert_eq!(status, Status::StreamEnd);
let (p2, status2) = dec.finish(&mut out).unwrap();
assert_eq!(p2.written, 0);
assert_eq!(status2, Status::StreamEnd);
}
#[test]
fn truncation_is_clean_error() {
let err = decode_fork(&[0x00], 64).expect_err("truncated stream must error");
assert!(
matches!(err, Error::UnexpectedEnd | Error::Corrupt),
"unexpected error variant: {err:?}"
);
}
#[test]
fn illegal_control_byte_is_corrupt() {
for ctrl in [0x60u8, 0x70, 0xF0, 0xFF] {
let err = decode_fork(&[ctrl, 0, 0, 0], 16).expect_err("illegal control byte must error");
assert_eq!(err, Error::Corrupt, "control {ctrl:#x}");
}
}
#[test]
fn poisoned_after_error_then_corrupt() {
let mut dec = Decoder::with_len(16);
let mut out = [0u8; 16];
let _ = dec.decode(&[0xF0, 0, 0, 0], &mut out);
let err = dec.finish(&mut out).unwrap_err();
assert_eq!(err, Error::Corrupt);
}
#[test]
fn reset_restores_empty_member_semantics() {
let mut dec = Decoder::with_len(0);
let mut out = [0u8; 16];
let (_p, status) = dec.finish(&mut out).unwrap();
assert_eq!(status, Status::StreamEnd);
dec.reset();
let (p, status) = dec.finish(&mut out).unwrap();
assert_eq!(p.written, 0);
assert_eq!(status, Status::StreamEnd);
}
#[cfg(feature = "factory")]
#[test]
fn factory_resolves_sit13() {
use compcol::factory::{decoder_by_name, encoder_by_name, names};
assert!(names().contains(&"sit13"));
let mut enc = encoder_by_name("sit13").expect("encoder registered");
let mut out = [0u8; 16];
assert_eq!(enc.encode(b"x", &mut out), Err(Error::Unsupported));
let _dec = decoder_by_name("sit13").expect("decoder registered");
}