#![macro_use]
use std::cmp;
use std::ops::{Range, RangeFrom};
use std::iter::{self, Iterator};
use std::slice::Iter;
use std::fmt;
use input_buffer::InputBuffer;
use matching::longest_match;
#[cfg(test)]
use lzvalue::{LZValue, LZType};
use huffman_table;
use chained_hash_table::{ChainedHashTable, update_hash};
#[cfg(test)]
use compression_options::{HIGH_MAX_HASH_CHECKS, HIGH_LAZY_IF_LESS_THAN};
use output_writer::{BufferStatus, DynamicWriter};
use compress::Flush;
use rle::process_chunk_greedy_rle;
const MAX_MATCH: usize = huffman_table::MAX_MATCH as usize;
const MIN_MATCH: usize = huffman_table::MIN_MATCH as usize;
const NO_RLE: u16 = 43212;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MatchingType {
Greedy,
Lazy,
}
impl fmt::Display for MatchingType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MatchingType::Greedy => write!(f, "Greedy matching"),
MatchingType::Lazy => write!(f, "Lazy matching"),
}
}
}
pub struct LZ77State {
hash_table: ChainedHashTable,
is_first_window: bool,
is_last_block: bool,
overlap: usize,
current_block_input_bytes: u64,
max_hash_checks: u16,
lazy_if_less_than: u16,
matching_type: MatchingType,
match_state: ChunkState,
bytes_to_hash: usize,
was_synced: bool,
}
impl LZ77State {
pub fn new(
max_hash_checks: u16,
lazy_if_less_than: u16,
matching_type: MatchingType,
) -> LZ77State {
LZ77State {
hash_table: ChainedHashTable::new(),
is_first_window: true,
is_last_block: false,
overlap: 0,
current_block_input_bytes: 0,
max_hash_checks: max_hash_checks,
lazy_if_less_than: lazy_if_less_than,
matching_type: matching_type,
match_state: ChunkState::new(),
bytes_to_hash: 0,
was_synced: false,
}
}
pub fn reset(&mut self) {
self.hash_table.reset();
self.is_first_window = true;
self.is_last_block = false;
self.overlap = 0;
self.current_block_input_bytes = 0;
self.match_state = ChunkState::new();
self.bytes_to_hash = 0
}
pub fn set_last(&mut self) {
self.is_last_block = true;
}
pub fn is_last_block(&self) -> bool {
self.is_last_block
}
pub fn current_block_input_bytes(&self) -> u64 {
self.current_block_input_bytes
}
pub fn reset_input_bytes(&mut self) {
self.current_block_input_bytes = 0;
}
pub fn pending_byte(&self) -> bool {
self.match_state.add
}
pub fn pending_byte_as_num(&self) -> usize {
if self.match_state.add {
1
} else {
0
}
}
}
const DEFAULT_WINDOW_SIZE: usize = 32768;
#[derive(Debug)]
pub enum ProcessStatus {
Ok,
BufferFull(usize),
}
#[derive(Debug)]
pub struct ChunkState {
current_length: u16,
current_distance: u16,
prev_byte: u8,
cur_byte: u8,
add: bool,
}
impl ChunkState {
pub fn new() -> ChunkState {
ChunkState {
current_length: 0,
current_distance: 0,
prev_byte: 0,
cur_byte: 0,
add: false,
}
}
}
pub fn buffer_full(position: usize) -> ProcessStatus {
ProcessStatus::BufferFull(position)
}
fn process_chunk(
data: &[u8],
iterated_data: &Range<usize>,
mut match_state: &mut ChunkState,
hash_table: &mut ChainedHashTable,
writer: &mut DynamicWriter,
max_hash_checks: u16,
lazy_if_less_than: usize,
matching_type: MatchingType,
) -> (usize, ProcessStatus) {
let avoid_rle = if cfg!(test) {
lazy_if_less_than == NO_RLE as usize
} else {
false
};
match matching_type {
MatchingType::Greedy => {
process_chunk_greedy(data, iterated_data, hash_table, writer, max_hash_checks)
}
MatchingType::Lazy => {
if max_hash_checks > 0 || avoid_rle {
process_chunk_lazy(
data,
iterated_data,
&mut match_state,
hash_table,
writer,
max_hash_checks,
lazy_if_less_than,
)
} else {
process_chunk_greedy_rle(data, iterated_data, writer)
}
}
}
}
fn add_to_hash_table(
bytes_to_add: usize,
insert_it: &mut iter::Zip<RangeFrom<usize>, Iter<u8>>,
hash_it: &mut Iter<u8>,
hash_table: &mut ChainedHashTable,
) {
let taker = insert_it.by_ref().take(bytes_to_add);
let mut hash_taker = hash_it.by_ref().take(bytes_to_add);
let mut hash = hash_table.current_hash();
for (ipos, _) in taker {
if let Some(&i_hash_byte) = hash_taker.next() {
hash = update_hash(hash, i_hash_byte);
hash_table.add_with_hash(ipos, hash);
}
}
hash_table.set_hash(hash);
}
macro_rules! write_literal{
($w:ident, $byte:expr, $pos:expr) => {
let b_status = $w.write_literal($byte);
if let BufferStatus::Full = b_status {
return (0, buffer_full($pos));
}
};
}
#[inline]
fn match_too_far(match_len: usize, match_dist: usize) -> bool {
const TOO_FAR: usize = 8 * 1024;
match_len == MIN_MATCH && match_dist > TOO_FAR
}
fn create_iterators<'a>(
data: &'a [u8],
iterated_data: &Range<usize>,
) -> (
usize,
iter::Zip<RangeFrom<usize>, Iter<'a, u8>>,
Iter<'a, u8>,
) {
let end = cmp::min(data.len(), iterated_data.end);
let start = iterated_data.start;
let current_chunk = &data[start..end];
let insert_it = (start..).zip(current_chunk.iter());
let hash_it = {
let hash_start = if data.len() - start > 2 {
start + 2
} else {
data.len()
};
(&data[hash_start..]).iter()
};
(end, insert_it, hash_it)
}
fn process_chunk_lazy(
data: &[u8],
iterated_data: &Range<usize>,
state: &mut ChunkState,
mut hash_table: &mut ChainedHashTable,
writer: &mut DynamicWriter,
max_hash_checks: u16,
lazy_if_less_than: usize,
) -> (usize, ProcessStatus) {
let (end, mut insert_it, mut hash_it) = create_iterators(data, iterated_data);
const NO_LENGTH: u16 = 0;
let mut prev_length = state.current_length;
let mut prev_distance = state.current_distance;
state.current_length = 0;
state.current_distance = 0;
let mut overlap = 0;
let mut ignore_next = prev_length as usize >= lazy_if_less_than;
state.prev_byte = state.cur_byte;
while let Some((position, &b)) = insert_it.next() {
state.cur_byte = b;
if let Some(&hash_byte) = hash_it.next() {
hash_table.add_hash_value(position, hash_byte);
if !ignore_next {
let (mut match_len, match_dist) = {
let max_hash_checks = if prev_length >= 32 {
max_hash_checks >> 2
} else {
max_hash_checks
};
longest_match(
data,
hash_table,
position,
prev_length as usize,
max_hash_checks,
)
};
if match_too_far(match_len, match_dist) {
match_len = NO_LENGTH as usize;
};
if match_len >= lazy_if_less_than {
ignore_next = true;
}
state.current_length = match_len as u16;
state.current_distance = match_dist as u16;
} else {
state.current_length = NO_LENGTH;
state.current_distance = 0;
ignore_next = false;
};
if prev_length >= state.current_length && prev_length >= MIN_MATCH as u16 {
let b_status =
writer.write_length_distance(prev_length as u16, prev_distance as u16);
let bytes_to_add = prev_length - 2;
add_to_hash_table(
bytes_to_add as usize,
&mut insert_it,
&mut hash_it,
&mut hash_table,
);
if position + prev_length as usize > end {
overlap = position + prev_length as usize - end - 1;
};
state.add = false;
state.current_length = 0;
state.current_distance = 0;
if let BufferStatus::Full = b_status {
return (overlap, buffer_full(position + prev_length as usize - 1));
}
ignore_next = false;
} else if state.add {
write_literal!(writer, state.prev_byte, position + 1);
} else {
state.add = true
}
prev_length = state.current_length;
prev_distance = state.current_distance;
state.prev_byte = b;
} else {
if prev_length >= MIN_MATCH as u16 {
let b_status =
writer.write_length_distance(prev_length as u16, prev_distance as u16);
state.current_length = 0;
state.current_distance = 0;
state.add = false;
if let BufferStatus::Full = b_status {
return (0, buffer_full(end));
} else {
return (0, ProcessStatus::Ok);
}
};
if state.add {
state.add = false;
write_literal!(writer, state.prev_byte, position + 1);
};
write_literal!(writer, b, position + 1);
}
}
(overlap, ProcessStatus::Ok)
}
fn process_chunk_greedy(
data: &[u8],
iterated_data: &Range<usize>,
mut hash_table: &mut ChainedHashTable,
writer: &mut DynamicWriter,
max_hash_checks: u16,
) -> (usize, ProcessStatus) {
let (end, mut insert_it, mut hash_it) = create_iterators(data, iterated_data);
const NO_LENGTH: usize = 0;
let mut overlap = 0;
while let Some((position, &b)) = insert_it.next() {
if let Some(&hash_byte) = hash_it.next() {
hash_table.add_hash_value(position, hash_byte);
let (match_len, match_dist) =
{ longest_match(data, hash_table, position, NO_LENGTH, max_hash_checks) };
if match_len >= MIN_MATCH as usize && !match_too_far(match_len, match_dist) {
let b_status = writer.write_length_distance(match_len as u16, match_dist as u16);
let bytes_to_add = match_len - 1;
add_to_hash_table(
bytes_to_add,
&mut insert_it,
&mut hash_it,
&mut hash_table,
);
if position + match_len > end {
overlap = position + match_len - end;
};
if let BufferStatus::Full = b_status {
return (overlap, buffer_full(position + match_len));
}
} else {
write_literal!(writer, b, position + 1);
}
} else {
write_literal!(writer, b, position + 1);
}
}
(overlap, ProcessStatus::Ok)
}
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
pub enum LZ77Status {
NeedInput,
EndBlock,
Finished,
}
#[cfg(test)]
pub fn lz77_compress_block_finish(
data: &[u8],
state: &mut LZ77State,
buffer: &mut InputBuffer,
mut writer: &mut DynamicWriter,
) -> (usize, LZ77Status) {
let (consumed, status, _) =
lz77_compress_block(data, state, buffer, &mut writer, Flush::Finish);
(consumed, status)
}
pub fn lz77_compress_block(
data: &[u8],
state: &mut LZ77State,
buffer: &mut InputBuffer,
mut writer: &mut DynamicWriter,
flush: Flush,
) -> (usize, LZ77Status, usize) {
let window_size = DEFAULT_WINDOW_SIZE;
let finish = flush == Flush::Finish || flush == Flush::Sync;
let sync = flush == Flush::Sync;
let mut current_position = 0;
let mut status = LZ77Status::EndBlock;
let mut add_initial = true;
if state.was_synced {
if buffer.current_end() > 2 {
let pos_add = buffer.current_end() - 2;
for (n, &b) in data.iter().take(2).enumerate() {
state.hash_table.add_hash_value(n + pos_add, b);
}
add_initial = false;
}
state.was_synced = false;
}
let mut remaining_data = buffer.add_data(data);
loop {
let pending_previous = state.pending_byte_as_num();
assert!(writer.buffer_length() <= (window_size * 2));
if state.is_first_window {
if buffer.current_end() >= (window_size * 2) + MAX_MATCH || finish {
if buffer.get_buffer().len() >= 2 && add_initial &&
state.current_block_input_bytes == 0
{
let b = buffer.get_buffer();
state.hash_table.add_initial_hash_values(b[0], b[1]);
add_initial = false;
}
let first_chunk_end = cmp::min(window_size, buffer.current_end());
let start = state.overlap;
let (overlap, p_status) = process_chunk(
buffer.get_buffer(),
&(start..first_chunk_end),
&mut state.match_state,
&mut state.hash_table,
&mut writer,
state.max_hash_checks,
state.lazy_if_less_than as usize,
state.matching_type,
);
state.overlap = overlap;
state.bytes_to_hash = overlap;
if let ProcessStatus::BufferFull(written) = p_status {
state.overlap = if overlap > 0 { overlap } else { written };
status = LZ77Status::EndBlock;
current_position = written - state.pending_byte_as_num();
state.current_block_input_bytes +=
(written - start + overlap + pending_previous -
state.pending_byte_as_num()) as u64;
break;
}
state.current_block_input_bytes +=
(first_chunk_end - start + overlap + pending_previous -
state.pending_byte_as_num()) as u64;
if first_chunk_end >= buffer.current_end() && finish {
current_position = first_chunk_end - state.pending_byte_as_num();
if !sync {
state.set_last();
state.is_first_window = false;
} else {
state.overlap = first_chunk_end;
state.was_synced = true;
}
debug_assert!(
!state.pending_byte(),
"Bug! Ended compression wit a pending byte!"
);
status = LZ77Status::Finished;
break;
}
state.is_first_window = false;
} else {
status = LZ77Status::NeedInput;
break;
}
} else if buffer.current_end() >= (window_size * 2) + MAX_MATCH || finish {
if buffer.current_end() >= window_size + 2 {
for (n, &h) in buffer.get_buffer()[window_size + 2..]
.iter()
.enumerate()
.take(state.bytes_to_hash)
{
state.hash_table.add_hash_value(window_size + n, h);
}
state.bytes_to_hash = 0;
}
let start = window_size + state.overlap;
let end = cmp::min(window_size * 2, buffer.current_end());
let (overlap, p_status) = process_chunk(
buffer.get_buffer(),
&(start..end),
&mut state.match_state,
&mut state.hash_table,
&mut writer,
state.max_hash_checks,
state.lazy_if_less_than as usize,
state.matching_type,
);
state.bytes_to_hash = overlap;
if let ProcessStatus::BufferFull(written) = p_status {
state.current_block_input_bytes +=
(written - start + pending_previous - state.pending_byte_as_num()) as u64;
state.overlap = if overlap > 0 {
if state.max_hash_checks > 0 {
state.hash_table.slide(window_size);
}
remaining_data = buffer.slide(remaining_data.unwrap_or(&[]));
overlap
} else {
written - window_size
};
current_position = written - state.pending_byte_as_num();
break;
}
state.current_block_input_bytes +=
(end - start + overlap + pending_previous - state.pending_byte_as_num()) as u64;
state.overlap = overlap;
if remaining_data.is_none() && finish && end == buffer.current_end() {
current_position = buffer.current_end();
debug_assert!(
!state.pending_byte(),
"Bug! Ended compression wit a pending byte!"
);
if !sync {
state.set_last();
} else {
state.overlap = buffer.current_end() - window_size;
state.was_synced = true;
}
status = LZ77Status::Finished;
break;
} else {
if state.max_hash_checks > 0 {
state.hash_table.slide(window_size);
}
remaining_data = buffer.slide(remaining_data.unwrap_or(&[]));
}
} else {
status = LZ77Status::NeedInput;
break;
}
}
(
data.len() - remaining_data.unwrap_or(&[]).len(),
status,
current_position,
)
}
#[cfg(test)]
pub fn decompress_lz77(input: &[LZValue]) -> Vec<u8> {
decompress_lz77_with_backbuffer(input, &[])
}
#[cfg(test)]
pub fn decompress_lz77_with_backbuffer(input: &[LZValue], back_buffer: &[u8]) -> Vec<u8> {
let mut output = Vec::new();
for p in input {
match p.value() {
LZType::Literal(l) => output.push(l),
LZType::StoredLengthDistance(l, d) => {
let d = d as usize;
let prev_output_len = output.len();
let consumed = if d > output.len() {
let into_back_buffer = d - output.len();
assert!(
into_back_buffer <= back_buffer.len(),
"ERROR: Attempted to refer to a match in non-existing data!\
into_back_buffer: {}, back_buffer len {}, d {}, l {:?}",
into_back_buffer,
back_buffer.len(),
d,
l
);
let start = back_buffer.len() - into_back_buffer;
let end = cmp::min(back_buffer.len(), start + l.actual_length() as usize);
output.extend_from_slice(&back_buffer[start..end]);
end - start
} else {
0
};
let start = prev_output_len.saturating_sub(d);
let mut n = 0;
while n < (l.actual_length() as usize).saturating_sub(consumed) {
let b = output[start + n];
output.push(b);
n += 1;
}
}
}
}
output
}
#[cfg(test)]
pub struct TestStruct {
state: LZ77State,
buffer: InputBuffer,
writer: DynamicWriter,
}
#[cfg(test)]
impl TestStruct {
fn new() -> TestStruct {
TestStruct::with_config(
HIGH_MAX_HASH_CHECKS,
HIGH_LAZY_IF_LESS_THAN,
MatchingType::Lazy,
)
}
fn with_config(
max_hash_checks: u16,
lazy_if_less_than: u16,
matching_type: MatchingType,
) -> TestStruct {
TestStruct {
state: LZ77State::new(max_hash_checks, lazy_if_less_than, matching_type),
buffer: InputBuffer::empty(),
writer: DynamicWriter::new(),
}
}
fn compress_block(&mut self, data: &[u8], flush: bool) -> (usize, LZ77Status, usize) {
lz77_compress_block(
data,
&mut self.state,
&mut self.buffer,
&mut self.writer,
if flush { Flush::Finish } else { Flush::None },
)
}
}
#[cfg(test)]
pub fn lz77_compress(data: &[u8]) -> Option<Vec<LZValue>> {
lz77_compress_conf(
data,
HIGH_MAX_HASH_CHECKS,
HIGH_LAZY_IF_LESS_THAN,
MatchingType::Lazy,
)
}
#[cfg(test)]
pub fn lz77_compress_conf(
data: &[u8],
max_hash_checks: u16,
lazy_if_less_than: u16,
matching_type: MatchingType,
) -> Option<Vec<LZValue>> {
let mut test_boxed = Box::new(TestStruct::with_config(
max_hash_checks,
lazy_if_less_than,
matching_type,
));
let mut out = Vec::<LZValue>::with_capacity(data.len() / 3);
{
let test = test_boxed.as_mut();
let mut slice = data;
while !test.state.is_last_block {
let bytes_written = lz77_compress_block_finish(
slice,
&mut test.state,
&mut test.buffer,
&mut test.writer,
).0;
slice = &slice[bytes_written..];
out.extend(test.writer.get_buffer());
test.writer.clear();
}
}
Some(out)
}
#[cfg(test)]
mod test {
use super::*;
use lzvalue::{LZValue, LZType, lit, ld};
use chained_hash_table::WINDOW_SIZE;
use compression_options::DEFAULT_LAZY_IF_LESS_THAN;
use test_utils::get_test_data;
use output_writer::MAX_BUFFER_LENGTH;
fn print_output(input: &[LZValue]) {
let mut output = vec![];
for l in input {
match l.value() {
LZType::Literal(l) => output.push(l),
LZType::StoredLengthDistance(l, d) => {
output.extend(format!("<L {}>", l.actual_length()).into_bytes());
output.extend(format!("<D {}>", d).into_bytes())
}
}
}
println!("\"{}\"", String::from_utf8(output).unwrap());
}
#[test]
fn compress_short() {
let test_bytes = String::from("Deflate late").into_bytes();
let res = lz77_compress(&test_bytes).unwrap();
let decompressed = decompress_lz77(&res);
assert_eq!(test_bytes, decompressed);
assert_eq!(*res.last().unwrap(), LZValue::length_distance(4, 5));
}
#[test]
fn compress_long() {
let input = get_test_data();
let compressed = lz77_compress(&input).unwrap();
assert!(compressed.len() < input.len());
let decompressed = decompress_lz77(&compressed);
println!("compress_long length: {}", input.len());
for (n, (&a, &b)) in input.iter().zip(decompressed.iter()).enumerate() {
if a != b {
println!("First difference at {}, input: {}, output: {}", n, a, b);
break;
}
}
assert_eq!(input.len(), decompressed.len());
assert!(&decompressed == &input);
}
#[test]
fn lazy() {
let data = b"nba badger nbadger";
let compressed = lz77_compress(data).unwrap();
let test = compressed[compressed.len() - 1];
if let LZType::StoredLengthDistance(l, _) = test.value() {
assert_eq!(l.actual_length(), 6);
} else {
print_output(&compressed);
panic!();
}
}
fn roundtrip(data: &[u8]) {
let compressed = super::lz77_compress(&data).unwrap();
let decompressed = decompress_lz77(&compressed);
assert!(decompressed == data);
}
#[test]
#[allow(unused)]
fn exact_window_size() {
use std::io::Write;
let mut data = vec![0; WINDOW_SIZE];
roundtrip(&data);
{
data.write(&[22; WINDOW_SIZE]);
}
roundtrip(&data);
{
data.write(&[55; WINDOW_SIZE]);
}
roundtrip(&data);
}
#[test]
fn border() {
use chained_hash_table::WINDOW_SIZE;
let mut data = vec![35; WINDOW_SIZE];
data.extend(b"Test");
let compressed = super::lz77_compress(&data).unwrap();
assert!(compressed.len() < data.len());
let decompressed = decompress_lz77(&compressed);
assert_eq!(decompressed.len(), data.len());
assert!(decompressed == data);
}
#[test]
fn border_multiple_blocks() {
use chained_hash_table::WINDOW_SIZE;
let mut data = vec![0; (WINDOW_SIZE * 2) + 50];
data.push(1);
let compressed = super::lz77_compress(&data).unwrap();
assert!(compressed.len() < data.len());
let decompressed = decompress_lz77(&compressed);
assert_eq!(decompressed.len(), data.len());
assert!(decompressed == data);
}
#[test]
fn compress_block_status() {
use input_buffer::InputBuffer;
let data = b"Test data data";
let mut writer = DynamicWriter::new();
let mut buffer = InputBuffer::empty();
let mut state = LZ77State::new(4096, DEFAULT_LAZY_IF_LESS_THAN, MatchingType::Lazy);
let status = lz77_compress_block_finish(data, &mut state, &mut buffer, &mut writer);
assert_eq!(status.1, LZ77Status::Finished);
assert!(&buffer.get_buffer()[..data.len()] == data);
assert_eq!(buffer.current_end(), data.len());
}
#[test]
fn compress_block_multiple_windows() {
use input_buffer::InputBuffer;
use output_writer::DynamicWriter;
let data = get_test_data();
assert!(data.len() > (WINDOW_SIZE * 2) + super::MAX_MATCH);
let mut writer = DynamicWriter::new();
let mut buffer = InputBuffer::empty();
let mut state = LZ77State::new(0, DEFAULT_LAZY_IF_LESS_THAN, MatchingType::Lazy);
let (bytes_consumed, status) =
lz77_compress_block_finish(&data, &mut state, &mut buffer, &mut writer);
assert_eq!(
buffer.get_buffer().len(),
(WINDOW_SIZE * 2) + super::MAX_MATCH
);
assert_eq!(status, LZ77Status::EndBlock);
let buf_len = buffer.get_buffer().len();
assert!(buffer.get_buffer()[..] == data[..buf_len]);
writer.clear();
let (_, status) = lz77_compress_block_finish(
&data[bytes_consumed..],
&mut state,
&mut buffer,
&mut writer,
);
assert_eq!(status, LZ77Status::EndBlock);
}
#[test]
fn multiple_inputs() {
let data = b"Badger badger bababa test data 25 asfgestghresjkgh";
let comp1 = lz77_compress(data).unwrap();
let comp2 = {
const SPLIT: usize = 25;
let first_part = &data[..SPLIT];
let second_part = &data[SPLIT..];
let mut state = TestStruct::new();
let (bytes_written, status, _) = state.compress_block(first_part, false);
assert_eq!(bytes_written, first_part.len());
assert_eq!(status, LZ77Status::NeedInput);
let (bytes_written, status, _) = state.compress_block(second_part, true);
assert_eq!(bytes_written, second_part.len());
assert_eq!(status, LZ77Status::Finished);
Vec::from(state.writer.get_buffer())
};
assert!(comp1 == comp2);
}
#[test]
fn buffer_fill() {
let data = get_test_data();
buffer_test_literals(&data);
buffer_test_last_bytes(MatchingType::Greedy, &data);
buffer_test_last_bytes(MatchingType::Lazy, &data);
buffer_test_match(MatchingType::Greedy);
buffer_test_match(MatchingType::Lazy);
buffer_test_add_end(&data);
}
fn buffer_test_literals(data: &[u8]) {
let mut state = TestStruct::with_config(0, NO_RLE, MatchingType::Lazy);
let (bytes_consumed, status, position) = state.compress_block(&data, false);
assert_eq!(status, LZ77Status::EndBlock);
assert!(bytes_consumed <= (WINDOW_SIZE * 2) + MAX_MATCH);
assert_eq!(state.writer.get_buffer().len(), MAX_BUFFER_LENGTH);
assert_eq!(position, state.writer.get_buffer().len());
assert_eq!(
state.state.current_block_input_bytes() as usize,
MAX_BUFFER_LENGTH
);
state.state.reset_input_bytes();
let mut out = decompress_lz77(state.writer.get_buffer());
state.writer.clear();
assert_eq!(state.writer.get_buffer().len(), 0);
assert!(data[..out.len()] == out[..]);
let _ = state.compress_block(&data[bytes_consumed..], false);
assert!(state.writer.get_buffer().len() > 0);
assert_eq!(
state.state.current_block_input_bytes() as usize,
MAX_BUFFER_LENGTH
);
out.extend_from_slice(&decompress_lz77(state.writer.get_buffer()));
assert!(data[..out.len()] == out[..]);
}
fn buffer_test_last_bytes(matching_type: MatchingType, data: &[u8]) {
const BYTES_USED: usize = MAX_BUFFER_LENGTH;
assert!(
&data[..BYTES_USED] ==
&decompress_lz77(&lz77_compress_conf(
&data[..BYTES_USED],
0,
NO_RLE,
matching_type,
).unwrap())
[..]
);
assert!(
&data[..BYTES_USED + 1] ==
&decompress_lz77(&lz77_compress_conf(
&data[..BYTES_USED + 1],
0,
NO_RLE,
matching_type,
).unwrap())
[..]
);
}
fn buffer_test_match(matching_type: MatchingType) {
let mut state = TestStruct::with_config(1, 0, matching_type);
for _ in 0..MAX_BUFFER_LENGTH - 4 {
assert!(state.writer.write_literal(0) == BufferStatus::NotFull);
}
state.compress_block(&[1, 2, 3, 1, 2, 3, 4], true);
assert!(*state.writer.get_buffer().last().unwrap() == LZValue::length_distance(3, 3));
}
fn buffer_test_add_end(_data: &[u8]) {
}
#[test]
fn test_decompress_with_backbuffer() {
let bb = vec![5; WINDOW_SIZE];
let lz_compressed = [lit(2), lit(4), ld(4, 20), lit(1), lit(1), ld(5, 10)];
let dec = decompress_lz77_with_backbuffer(&lz_compressed, &bb);
assert!(dec == [2, 4, 5, 5, 5, 5, 1, 1, 5, 5, 2, 4, 5]);
}
}
#[cfg(all(test, feature = "benchmarks"))]
mod bench {
use test_std::Bencher;
use test_utils::get_test_data;
use super::lz77_compress;
#[bench]
fn test_file_zlib_lz77_only(b: &mut Bencher) {
let test_data = get_test_data();
b.iter(|| lz77_compress(&test_data));
}
}