use std::io::{Cursor,Read,Write,Seek};
use crate::DYNERR;
use crate::lzss_huff;
use crate::lzw;
pub fn crc16(crc_seed: u16, buf: &[u8]) -> u16
{
let mut crc: u16 = crc_seed;
for i in 0..buf.len() {
crc ^= (buf[i] as u16) << 8;
for _bit in 0..8 {
crc = (crc << 1) ^ match crc & 0x8000 { 0 => 0, _ => 0xa097 };
}
}
crc
}
pub const TD_V1_OPTIONS: lzw::Options = lzw::Options {
header_bits: 16,
header_divisor: 4,
in_offset: 12,
out_offset: 12,
chunk_size: 4096,
min_symbol: 0,
max_symbol: 255,
clear_code: None,
stop_code: None,
min_code_width: 12,
max_code_width: 12,
ord: crate::BitOrder::Lsb0,
max_file_size: 3_000_000
};
pub const TD_V2_OPTIONS: lzss_huff::Options = lzss_huff::Options {
header: false,
in_offset: 12,
out_offset: 12,
window_size: 4096,
threshold: 2,
lookahead: 60,
precursor: b' ',
max_file_size: 3_000_000
};
pub fn expand<R,W>(compressed_in: &mut R, expanded_out: &mut W) -> Result<(u64,u64),DYNERR>
where R: Read + Seek, W: Write + Seek {
let mut td_header: [u8;12] = [0;12];
compressed_in.read_exact(&mut td_header)?;
if &td_header[0..2] != "td".as_bytes() {
return Err(Box::new(crate::Error::FileFormatMismatch))
}
let crc = u16::to_le_bytes(crc16(0,&td_header[0..10]));
if crc!=td_header[10..12] {
return Err(Box::new(crate::Error::BadChecksum))
}
td_header[0..2].copy_from_slice("TD".as_bytes());
let crc = u16::to_le_bytes(crc16(0,&td_header[0..10]));
td_header[10..12].copy_from_slice(&crc);
expanded_out.write_all(&td_header)?;
if td_header[4] < 20 {
let (in_size,out_size) = lzw::expand(compressed_in,expanded_out,&TD_V1_OPTIONS)?;
Ok((in_size+td_header.len() as u64,out_size+td_header.len() as u64))
} else {
let (in_size,out_size) = lzss_huff::expand(compressed_in,expanded_out,&TD_V2_OPTIONS)?;
Ok((in_size+td_header.len() as u64,out_size+td_header.len() as u64))
}
}
pub fn compress<R,W>(expanded_in: &mut R, compressed_out: &mut W) -> Result<(u64,u64),DYNERR>
where R: Read + Seek, W: Write + Seek {
let mut td_header: [u8;12] = [0;12];
expanded_in.read_exact(&mut td_header)?;
if &td_header[0..2] != "TD".as_bytes() {
return Err(Box::new(crate::Error::FileFormatMismatch))
}
let crc = u16::to_le_bytes(crc16(0,&td_header[0..10]));
if crc!=td_header[10..12] {
return Err(Box::new(crate::Error::BadChecksum))
}
td_header[0..2].copy_from_slice("td".as_bytes());
let crc = u16::to_le_bytes(crc16(0,&td_header[0..10]));
td_header[10..12].copy_from_slice(&crc);
compressed_out.write_all(&td_header)?;
if td_header[4] < 20 {
let (in_size,out_size) = lzw::compress(expanded_in,compressed_out,&TD_V1_OPTIONS)?;
Ok((in_size+td_header.len() as u64,out_size+td_header.len() as u64))
} else {
let (in_size,out_size) = lzss_huff::compress(expanded_in,compressed_out,&TD_V2_OPTIONS)?;
Ok((in_size+td_header.len() as u64,out_size+td_header.len() as u64))
}
}
pub fn compress_slice(slice: &[u8]) -> Result<Vec<u8>,DYNERR> {
let mut src = Cursor::new(slice);
let mut ans: Cursor<Vec<u8>> = Cursor::new(Vec::new());
compress(&mut src,&mut ans)?;
Ok(ans.into_inner())
}
pub fn expand_slice(slice: &[u8]) -> Result<Vec<u8>,DYNERR> {
let mut src = Cursor::new(slice);
let mut ans: Cursor<Vec<u8>> = Cursor::new(Vec::new());
expand(&mut src,&mut ans)?;
Ok(ans.into_inner())
}
#[test]
fn compression_works() {
let mut normal_header = "TD0123456789".as_bytes().to_vec();
let normal_data = "I am Sam. Sam I am. I do not like this Sam I am.\n".as_bytes().to_vec();
let crc = u16::to_le_bytes(crc16(0,&normal_header[0..10]));
normal_header[10..12].copy_from_slice(&crc);
let mut advanced_header = "td0123456789".as_bytes().to_vec();
let advanced_data = "EA EB 3D BF 9C 4E FE 1E 16 EA 34 09 1C 0D C0 8C 02 FC 3F 77 3F 57 20 17 7F 1F 5F BF C6 AB 7F A5 AF FE 4C 39 96";
let crc = u16::to_le_bytes(crc16(0,&advanced_header[0..10]));
advanced_header[10..12].copy_from_slice(&crc);
let test_data = [normal_header,normal_data].concat();
let compressed = compress_slice(&test_data).expect("compression failed");
let expected = [advanced_header,hex::decode(advanced_data.replace(" ","")).unwrap()].concat();
assert_eq!(compressed,expected);
}
#[test]
fn invertibility() {
let mut test_data = "TD0123456789I am Sam. Sam I am. I do not like this Sam I am.\n".as_bytes().to_vec();
let crc = u16::to_le_bytes(crc16(0,&test_data[0..10]));
test_data[10..12].copy_from_slice(&crc);
let compressed = compress_slice(&test_data).expect("compression failed");
let expanded = expand_slice(&compressed).expect("expansion failed");
assert_eq!(test_data.to_vec(),expanded);
}