//! QuickLZ is a fast compression algorithm. This library implements the
//! algorithm version 1.5.0 (latest version since 2011). Compression and
//! decompression are implemented for the compression levels 1 and 3.
#![cfg_attr(
feature = "cargo-clippy",
allow(verbose_bit_mask, unreadable_literal)
)]
#![cfg_attr(feature = "nightly", feature(test))]
#[cfg(feature = "nightly")]
extern crate test;
use bit_vec::BitVec;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::cell::RefCell;
use std::cmp;
use std::io::{Read, Write};
use thiserror::Error;
type Result<T> = std::result::Result<T, Error>;
const HASHTABLE_SIZE: usize = 4096;
// hashtable_count MUST be 2^x for maximum efficiency
const HASHTABLE_COUNT: usize = 1 << 4;
// Thread-local buffers for various hashtables used by different
// (de-)compression levels so they don't have to be reallocated each call.
thread_local! {
static HASHTABLE: RefCell<Box<[u32; HASHTABLE_SIZE]>>
= RefCell::new(Box::new([0; HASHTABLE_SIZE]));
static HASHTABLE_ARR: RefCell<Box<[[u32; HASHTABLE_SIZE]; HASHTABLE_COUNT]>>
= RefCell::new(Box::new([[0; HASHTABLE_SIZE]; HASHTABLE_COUNT]));
static CACHETABLE: RefCell<Box<[u32; HASHTABLE_SIZE]>>
= RefCell::new(Box::new([0; HASHTABLE_SIZE]));
static HASHCOUNTER_U8: RefCell<Box<[u8; HASHTABLE_SIZE]>>
= RefCell::new(Box::new([0; HASHTABLE_SIZE]));
static HASHCOUNTER_BIT: RefCell<BitVec>
= RefCell::new(BitVec::from_elem(HASHTABLE_SIZE, false));
}
#[derive(Error, Debug)]
pub enum Error {
#[error("{0}")]
Io(#[from] std::io::Error),
/// This library supports only level 1 and 3 if another level is detected,
/// this error will be returned.
#[error(
"Unsupported QuickLZ level, this library only supports level 1 and 3"
)]
UnsupportedLevel,
/// If the given maximum decompressed size is exceeded, this error will be
/// returned.
#[error("Maximum uncompressed size exceeded: {}/{}", dec, max)]
SizeLimitExceeded { dec: u32, max: u32 },
/// If the compressed data cannot be decompressed, this error will be
/// returned, containing a short description.
#[error("{0}")]
CorruptData(String),
}
#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy)]
pub enum CompressionLevel {
Lvl1,
Lvl3,
}
impl CompressionLevel {
fn as_u8(&self) -> u8 {
match *self {
CompressionLevel::Lvl1 => 1,
CompressionLevel::Lvl3 => 3,
}
}
}
/// The state while decompressing.
enum DecompressState<'a> {
/// Next hash position, hashtable
Level1(usize, &'a mut [u32; HASHTABLE_SIZE]),
Level3,
}
fn hash(val: u32) -> u32 {
((val >> 12) ^ val) & 0xfff
}
/// Copy `[start; start + length)` bytes from `buf` to the end of `buf`.
///
/// It is expected, that `start + length` does not overflow.
fn copy_buffer_bytes(
buf: &mut Vec<u8>,
mut start: usize,
length: usize,
) -> Result<()> {
let buf_len = buf.len();
let end = start + length;
// Use extend_from_slice for the longest part possible
let copy_len = cmp::min(buf_len.saturating_sub(start), length);
if copy_len >= 4 {
buf.resize(buf_len + copy_len, 0);
let (a, b) = buf.split_at_mut(buf_len);
b.copy_from_slice(&a[start..(start + copy_len)]);
start += copy_len;
}
// Copy the rest in a loop
for i in start..end {
let val = *buf.get(i).ok_or_else(|| {
Error::CorruptData(String::from(
"Invalid back reference in QuickLZ",
))
})?;
buf.push(val);
}
Ok(())
}
/// Updates the hashtable for the data in `dest` between `start` and `end`.
fn update_hashtable(
hashtable: &mut [u32; HASHTABLE_SIZE],
dest: &[u8],
start: usize,
end: usize,
) {
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
for i in start..end {
hashtable[hash(read_u24(&dest[i..])) as usize] = i as u32;
}
}
fn read_u24(inp: &[u8]) -> u32 {
u32::from(inp[0]) | u32::from(inp[1]) << 8 | u32::from(inp[2]) << 16
}
/// Decompress data read from `r`.
///
/// # Example
/// ```
/// # (|| -> Result<(), quicklz::Error> {
/// let data = [
/// 0x47, 0x17, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
/// 0x00, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
/// 0x09,
/// ];
/// let mut r = std::io::Cursor::new(data.as_ref());
/// let dec = quicklz::decompress(&mut r, 1024)?;
///
/// assert_eq!(&dec, &vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
/// # Ok(())
/// # })().unwrap()
/// ```
///
/// # Errors
/// If an error is returned, some bytes were already read from `r`, but some
/// of the data that belongs to the compressed part is maybe still in the
/// stream.
///
/// The decompressed size is limited to `max_size`. An error will be returned
/// if data would be larger.
///
/// If the incoming data are compressed using level 2, an error is returned.
pub fn decompress(r: &mut dyn Read, max_size: u32) -> Result<Vec<u8>> {
let mut res = Vec::new();
let mut control: u32 = 1;
// Read header
let flags = r.read_u8()?;
let level = (flags >> 2) & 0b11;
if level != 3 && level != 1 {
return Err(Error::UnsupportedLevel);
}
let header_len = if flags & 2 == 2 { 9 } else { 3 };
let dec_size;
let comp_size;
if header_len == 3 {
comp_size = u32::from(r.read_u8()?);
dec_size = u32::from(r.read_u8()?);
} else {
comp_size = r.read_u32::<LittleEndian>()?;
dec_size = r.read_u32::<LittleEndian>()?;
}
if dec_size > max_size {
return Err(Error::SizeLimitExceeded {
dec: dec_size,
max: max_size,
});
}
if comp_size < header_len {
return Err(Error::CorruptData(format!(
"Invalid compressed size: {} < {}",
comp_size, header_len
)));
}
res.reserve(dec_size as usize);
if flags & 1 != 1 {
// Uncompressed
if comp_size - header_len != dec_size {
return Err(Error::CorruptData(format!(
"Compressed and uncompressed size of uncompressed data do not \
match ({} != {})",
comp_size - header_len,
dec_size,
)));
}
// Uncompressed
res.resize(dec_size as usize, 0);
r.read_exact(&mut res)?;
return Ok(res);
}
HASHTABLE.with(|tab| -> Result<()> {
let mut tab = tab.borrow_mut();
**tab = [0u32; HASHTABLE_SIZE];
let mut state = if level == 1 {
DecompressState::Level1(0, &mut tab)
} else {
DecompressState::Level3
};
loop {
if control == 1 {
control = r.read_u32::<LittleEndian>()?;
}
if control & 1 == 1 {
// Found a reference
control >>= 1;
let next = u32::from(r.read_u8()?);
match state {
DecompressState::Level1(
ref mut next_hashed,
ref mut hashtable,
) => {
let mut matchlen = (next & 0xf) as u8;
let hash = (next >> 4) | (u32::from(r.read_u8()?) << 4);
if matchlen != 0 {
matchlen += 2;
} else {
matchlen = r.read_u8()?;
}
if matchlen < 3 {
return Err(Error::CorruptData(format!(
"Too small length for QuickLZ reference ({})",
matchlen
)));
}
let offset =
*hashtable.get(hash as usize).ok_or_else(|| {
Error::CorruptData(String::from(
"Invalid QuickLZ hashtable entry",
))
})?;
// Check the size
if let Some(len) =
(res.len() as u32).checked_add(u32::from(matchlen))
{
if len > dec_size {
return Err(Error::CorruptData(format!(
"Decompressed size exceeded ({})",
dec_size
)));
}
} else {
return Err(Error::CorruptData(format!(
"Too big length in QuickLZ reference ({})",
matchlen
)));
};
copy_buffer_bytes(
&mut res,
offset as usize,
matchlen as usize,
)?;
let end = res.len() + 1 - matchlen as usize;
update_hashtable(
&mut *hashtable,
&res,
*next_hashed,
end,
);
*next_hashed = res.len();
}
DecompressState::Level3 => {
let offset;
let matchlen;
if next & 0b11 == 0 {
matchlen = 3;
offset = next >> 2;
} else if next & 0b11 == 0b01 {
matchlen = 3;
offset =
(next >> 2) | (u32::from(r.read_u8()?) << 6);
} else if next & 0b11 == 0b10 {
matchlen = 3 + ((next >> 2) & 0xf);
offset =
(next >> 6) | (u32::from(r.read_u8()?) << 2);
} else if next & 0x7f == 0b11 {
let next2 = u32::from(r.read_u8()?);
let next3 = u32::from(r.read_u8()?);
let next4 = u32::from(r.read_u8()?);
matchlen =
3 + ((next >> 7) | ((next2 & 0x7f) << 1));
offset = (next2 >> 7) | (next3 << 1) | (next4 << 9);
} else {
matchlen = 2 + ((next >> 2) & 0x1f);
offset = (next >> 7)
| (u32::from(r.read_u8()?) << 1)
| (u32::from(r.read_u8()?) << 9);
}
// Insert reference
if res.len() < offset as usize {
return Err(Error::CorruptData(String::from(
"Too big offset in QuickLZ reference",
)));
}
let start = res.len() - offset as usize;
// Check the size
if let Some(len) =
(res.len() as u32).checked_add(matchlen as u32)
{
if len > dec_size {
return Err(Error::CorruptData(format!(
"Decompressed size exceeded ({})",
dec_size
)));
}
} else {
return Err(Error::CorruptData(format!(
"Too big length in QuickLZ reference ({})",
matchlen
)));
};
copy_buffer_bytes(&mut res, start, matchlen as usize)?;
}
}
} else if res.len() >= cmp::max(dec_size as usize, 10) - 10 {
while res.len() < dec_size as usize {
if control == 1 {
r.read_u32::<LittleEndian>()?;
}
control >>= 1;
res.push(r.read_u8()?);
}
break;
} else {
// Check the size
if let Some(len) = res.len().checked_add(1) {
if len > dec_size as usize {
return Err(Error::CorruptData(format!(
"Decompressed size exceeded ({})",
dec_size
)));
}
} else {
return Err(Error::CorruptData(format!(
"Decompressed size exceeded ({})",
dec_size
)));
};
res.push(r.read_u8()?);
control >>= 1;
if let DecompressState::Level1(
ref mut next_hashed,
ref mut hashtable,
) = state
{
let end = res.len().saturating_sub(2);
update_hashtable(&mut *hashtable, &res, *next_hashed, end);
*next_hashed = cmp::max(*next_hashed, end);
}
}
}
Ok(())
})?;
Ok(res)
}
/// Checks if all elements in the slice have the same value.
fn is_eq<T: PartialEq>(arr: &[T]) -> bool {
for i in 1..arr.len() {
if arr[0] != arr[i] {
return false;
}
}
true
}
/// Writes the qlz header at the start of the dest vec.
fn write_header(
dest: &mut [u8],
dest_len: usize,
srclen: usize,
level: u8,
compressed: bool,
) -> Result<()> {
let flags: u8 = if compressed {
0x01 | (level << 2) | 0x40 // compressed | level | always
} else {
(level << 2) | 0x40 // (not compressed) | level | always
};
if dest.len() == 3 {
// short header
dest[0] = flags;
dest[1] = dest_len as u8;
dest[2] = srclen as u8;
} else if dest.len() == 9 {
// long header
let mut vec = vec![];
vec.write_u8(flags | 0x02)?;
vec.write_u32::<LittleEndian>(dest_len as u32)?;
vec.write_u32::<LittleEndian>(srclen as u32)?;
dest.copy_from_slice(&vec);
} else {
panic!("The header length must be either 3 or 9.");
}
Ok(())
}
/// Writes an u32 control sequence.
fn write_control(dest: &mut [u8], ctrl_pos: usize, ctrl: u32) -> Result<()> {
dest[ctrl_pos..ctrl_pos + 4]
.as_mut()
.write_u32::<LittleEndian>(ctrl)?;
Ok(())
}
/// Compress `data` using the specified [`CompressionLevel`].
///
/// # Panic
///
/// This function panics if `data.len() > u32::max_value() - 400`.
///
/// [`CompressionLevel`]: enum.CompressionLevel.html
pub fn compress(data: &[u8], level: CompressionLevel) -> Vec<u8> {
if data.len() >= (u32::max_value() - 400) as usize {
panic!("QuickLZ can only compress up to {}", u32::max_value() - 400);
}
let headerlen: u8 = if data.len() < 216 { 3 } else { 9 };
let mut dest = vec![0u8; headerlen as usize + 4];
let mut control: u32 = 1 << 31;
let mut control_pos: usize = headerlen as usize;
let mut source_pos = 0;
let mut done = false;
if level == CompressionLevel::Lvl1 {
HASHTABLE
.with(|hashtable| -> Result<()> {
HASHCOUNTER_BIT.with(|hash_counter| -> Result<()> {
CACHETABLE.with(|cachetable| -> Result<()> {
let mut hashtable = hashtable.borrow_mut();
let mut hash_counter = hash_counter.borrow_mut();
let mut cachetable = cachetable.borrow_mut();
**hashtable = [0u32; HASHTABLE_SIZE];
hash_counter.clear();
**cachetable = [0u32; HASHTABLE_SIZE];
let mut lits: u32 = 0;
while source_pos + 10 < data.len() {
if check_inefficient(
&mut control,
source_pos,
level,
&mut control_pos,
&mut dest,
data,
) {
done = true;
return Ok(());
}
let next = read_u24(&data[source_pos..]);
let hash = hash(next);
let hash_i = hash as usize;
let offset = hashtable[hash_i];
let cache = cachetable[hash_i];
let counter = hash_counter[hash_i];
cachetable[hash_i] = next;
hashtable[hash_i] = source_pos as u32;
if cache == next
&& counter
&& (source_pos as u32 - offset >= 3
|| source_pos == (offset + 1) as usize
&& lits >= 3
&& source_pos > 3
&& is_eq(
&data[source_pos - 3
..source_pos + 3],
))
{
control = (control >> 1) | (1 << 31);
let mut matchlen = 3;
let remainder =
cmp::min(data.len() - 4 - source_pos, 0xff);
while data[(offset + matchlen) as usize]
== data[source_pos + matchlen as usize]
&& (matchlen as usize) < remainder
{
matchlen += 1;
}
if matchlen < 18 {
dest.write_u16::<LittleEndian>(
(hash << 4 | (matchlen - 2)) as u16,
)
.unwrap();
} else {
dest.write_u24::<LittleEndian>(
(hash << 4 | (matchlen << 16)) as u32,
)
.unwrap();
}
source_pos += matchlen as usize;
lits = 0;
} else {
lits += 1;
hash_counter.set(hash_i, true);
dest.write_u8(data[source_pos]).unwrap();
source_pos += 1;
control >>= 1;
}
}
Ok(())
})?;
Ok(())
})?;
Ok(())
})
.unwrap();
} else if level == CompressionLevel::Lvl3 {
HASHTABLE_ARR
.with(|hashtable| -> Result<()> {
HASHCOUNTER_U8.with(|hash_counter| -> Result<()> {
let mut hashtable = hashtable.borrow_mut();
let mut hash_counter = hash_counter.borrow_mut();
**hashtable = [[0u32; HASHTABLE_SIZE]; HASHTABLE_COUNT];
**hash_counter = [0u8; HASHTABLE_SIZE];
while source_pos + 10 < data.len() {
if check_inefficient(
&mut control,
source_pos,
level,
&mut control_pos,
&mut dest,
data,
) {
done = true;
return Ok(());
}
let next = &data[source_pos..source_pos + 3];
let remainder =
cmp::min(data.len() - 4 - source_pos, 0xff);
let hash = hash(read_u24(next)) as usize;
let counter = hash_counter[hash];
let mut matchlen = 0;
let mut offset = 0;
#[cfg_attr(
feature = "cargo-clippy",
allow(needless_range_loop)
)]
for index in 0..HASHTABLE_COUNT {
if index as u8 >= counter {
break;
}
let hasht = &hashtable[index];
let o = hasht[hash] as usize;
if &data[o..o + 3] == next && o + 2 < source_pos {
let mut m = 3;
while data[o + m] == data[source_pos + m]
&& m < remainder
{
m += 1;
}
if m > matchlen || (m == matchlen && o > offset)
{
offset = o;
matchlen = m;
}
}
}
hashtable[counter as usize % HASHTABLE_COUNT][hash] =
source_pos as u32;
hash_counter[hash] = counter.wrapping_add(1);
if matchlen >= 3 && source_pos - offset < 0x1FFFF {
offset = source_pos - offset;
#[cfg_attr(
feature = "cargo-clippy",
allow(needless_range_loop)
)]
for u in 1..matchlen {
let hash = crate::hash(read_u24(
&data[(source_pos + u)..],
))
as usize;
let counter = hash_counter[hash];
hash_counter[hash] = counter.wrapping_add(1);
hashtable[counter as usize % HASHTABLE_COUNT]
[hash] = (source_pos + u) as u32;
}
source_pos += matchlen;
control = (control >> 1) | (1 << 31);
if matchlen == 3 && offset < (1 << 6) {
dest.write_u8((offset << 2) as u8).unwrap();
} else if matchlen == 3 && offset < (1 << 14) {
dest.write_u16::<LittleEndian>(
((offset << 2) | 1) as u16,
)
.unwrap();
} else if (matchlen - 3) < (1 << 4)
&& offset < (1 << 10)
{
dest.write_u16::<LittleEndian>(
((offset << 6) | ((matchlen - 3) << 2) | 2)
as u16,
)
.unwrap();
} else if (matchlen - 2) < (1 << 5) {
dest.write_u24::<LittleEndian>(
((offset << 7) | ((matchlen - 2) << 2) | 3)
as u32,
)
.unwrap();
} else {
dest.write_u32::<LittleEndian>(
((offset << 15) | ((matchlen - 3) << 7) | 3)
as u32,
)
.unwrap();
}
} else {
dest.write_u8(data[source_pos]).unwrap();
source_pos += 1;
control >>= 1;
}
}
Ok(())
})?;
Ok(())
})
.unwrap();
}
if done {
return dest;
}
while source_pos < data.len() {
if control & 1 != 0 {
write_control(&mut dest, control_pos, (control >> 1) | (1 << 31))
.unwrap();
control_pos = dest.len();
dest.write_u32::<LittleEndian>(0).unwrap();
control = 1 << 31;
}
dest.write_u8(data[source_pos]).unwrap();
source_pos += 1;
control >>= 1;
}
while control & 1 == 0 {
control >>= 1;
}
write_control(&mut dest, control_pos, (control >> 1) | (1 << 31)).unwrap();
let dest_len = dest.len();
write_header(
&mut dest[0..(headerlen as usize)],
dest_len,
data.len(),
level.as_u8(),
true,
)
.unwrap();
dest
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
// inline gives about 15%-20% performance boost since this method is used
// at a hot point of the compression method.
#[inline(always)]
fn check_inefficient(
control: &mut u32,
source_pos: usize,
level: CompressionLevel,
control_pos: &mut usize,
dest: &mut Vec<u8>,
data: &[u8],
) -> bool {
if *control & 1 != 0 {
if source_pos > 3 * (data.len() / 4)
&& dest.len() > source_pos - (source_pos / 32)
{
let headerlen = if data.len() < 216 { 3 } else { 9 };
dest.clear();
// To prevent a double allocation we reserve the exact size needed
// for the header and data.
dest.reserve(headerlen + data.len());
// Now we fill the header size with zeros since it will be
// written by the write_header method.
dest.resize(headerlen, 0);
dest.write_all(data).unwrap();
let dest_len = dest.len();
write_header(
&mut dest[0..headerlen],
dest_len,
data.len(),
level.as_u8(),
false,
)
.unwrap();
return true;
}
write_control(dest, *control_pos, (*control >> 1) | (1 << 31)).unwrap();
*control_pos = dest.len();
dest.write_u32::<LittleEndian>(0).unwrap();
*control = 1 << 31;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use std::iter;
fn roundtrip_lvl3(data: &[u8]) {
let comp = compress(data, CompressionLevel::Lvl3);
let mut r = Cursor::new(comp.as_slice());
let dec = decompress(&mut r, 1000000).unwrap();
assert_eq!(data, &dec);
}
#[test]
fn continuous10() {
let data = [
0x47, 0x17, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09,
];
let orig: Vec<u8> = (0..10).collect();
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(&orig, &dec);
}
#[test]
fn continuous128() {
let data = [
0x47u8, 0x9d, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
0x00, 0x00, 0x00, 0x80, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, 0x00, 0x00, 0x00, 0x80, 0x3e, 0x3f, 0x40, 0x41, 0x42,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d,
0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x5b, 0x5c, 0x00, 0x00, 0x00, 0x80, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x00, 0x00, 0x00, 0x80, 0x7c,
0x7d, 0x7e, 0x7f,
];
let orig: Vec<u8> = (0..0x80).collect();
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(&orig, &dec);
}
#[test]
fn continuous4x10() {
let data = [
0x47, 0x1e, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x04,
0x00, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x00, 0x12, 0x1a, 0x06, 0x07, 0x08, 0x09,
];
let orig: Vec<u8> = iter::repeat((0..10).into_iter())
.take(4)
.flat_map(|v| v)
.collect();
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(&orig, &dec);
}
#[test]
fn string_decompress_lvl1() {
let data = [
0x47, 0x4c, 0x2, 0, 0, 0xf9, 0x3, 0, 0, 0, 0, 0x4, 0x84, 0x69,
0x6e, 0x69, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x76,
0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x54, 0x25, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x3d, 0x53, 0x23, 0x50, 0x5c, 0x73, 0x64, 0x65, 0,
0x20, 0, 0x80, 0x72, 0x5c, 0x73, 0x56, 0x65, 0x72, 0x70, 0x6c,
0x61, 0x6e, 0x74, 0x65, 0x6e, 0x7d, 0xb, 0x77, 0x65, 0x6c, 0x63,
0x6f, 0x6d, 0x65, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3d,
0x54, 0x68, 0x50, 0x82, 0x1, 0xb0, 0x69, 0x73, 0x5c, 0x73, 0xe2,
0x6a, 0x53, 0x61, 0xa6, 0x6d, 0x79, 0x61, 0xb4, 0x57, 0x6f, 0x72,
0x6c, 0x64, 0x7d, 0xb, 0x61, 0xa6, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x3d, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x7d, 0xb, 0x1, 0x25, 0x73, 0,
0, 0, 0x80, 0x69, 0x6f, 0x6e, 0x3d, 0x33, 0x2e, 0x30, 0x2e, 0x31,
0x33, 0x2e, 0x38, 0x5c, 0x73, 0x5b, 0x42, 0x75, 0x69, 0x6c, 0x64,
0x3a, 0x5c, 0x73, 0x31, 0x35, 0x30, 0x30, 0x34, 0x35, 0x32, 0x38,
0x8, 0, 0x2, 0x88, 0x31, 0x31, 0x5d, 0x7d, 0xb, 0x6d, 0x61, 0x78,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x3d, 0x33, 0x32, 0x7d,
0xb, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x3d, 0x30, 0x7e,
0xb, 0x6f, 0x64, 0x65, 0, 0x92, 0x10, 0x9c, 0x63, 0x5f, 0x65, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0xf1, 0x98, 0x5f, 0x6d, 0x91, 0x23,
0x3d, 0x31, 0x7d, 0xb, 0x68, 0x6f, 0x73, 0x74, 0xb6, 0x25, 0x4c,
0xc3, 0xa9, 0x5c, 0x73, 0x58, 0x27, 0xb1, 0x66, 0x61, 0xa6, 0x6d,
0x79, 0x7, 0x8, 0x18, 0xe0, 0x70, 0xb, 0x1a, 0x94, 0xba, 0x2e,
0x75, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x55, 0x25,
0x67, 0x72, 0x6f, 0x75, 0x70, 0x3d, 0x38, 0x7d, 0xb, 0x26, 0x30,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0, 0x49, 0x16,
0xe2, 0x85, 0x82, 0x11, 0x81, 0x80, 0x62, 0x72, 0x88, 0x72, 0x5f,
0x75, 0x72, 0x6c, 0x7d, 0xb, 0xe9, 0x85, 0x67, 0x66, 0x78, 0x80,
0x27, 0x22, 0x69, 0x6e, 0x74, 0x21, 0x50, 0x61, 0x6c, 0x3d, 0x32,
0x30, 0x30, 0x2e, 0x75, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
0, 0x40, 0, 0xb0, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x65,
0x72, 0x5f, 0x64, 0x69, 0x6d, 0x6d, 0x92, 0xba, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x6f, 0x72, 0x3d, 0x2d, 0x31, 0x38, 0x2e, 0x31,
0x33, 0x2e, 0x75, 0x69, 0x2, 0, 0x3f, 0x80, 0x64, 0xe0, 0x33, 0x15,
0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6f, 0x6c,
0x74, 0x69, 0x70, 0x70, 0xb, 0x14, 0x24, 0x33, 0x20, 0x4b, 0x17,
0x24, 0x33, 0x10, 0x1e, 0x16, 0x82, 0x7b, 0x5f, 0x70, 0x68, 0x6f,
0x6e, 0x65, 0x74, 0x69, 0x63, 0x90, 0x1, 0x88, 0xc1, 0x3d, 0x6d,
0x6f, 0x62, 0x7d, 0xb, 0x69, 0x63, 0x91, 0xb9, 0xf1, 0x7b, 0x32,
0x35, 0x36, 0x38, 0x35, 0x35, 0x35, 0x32, 0x31, 0x33, 0x7e, 0xb,
0x70, 0x3d, 0x30, 0xd1, 0x2c, 0x21, 0xd3, 0x2c, 0x5c, 0x73, 0x3a,
0x3a, 0x7d, 0xb, 0x50, 0, 0x1f, 0x80, 0x61, 0x73, 0x6b, 0x5f, 0x1,
0x84, 0x5f, 0x71, 0x4e, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x6b,
0x65, 0x79, 0xef, 0x23, 0xe9, 0x85, 0xb3, 0x92, 0x2e, 0x75, 0x56,
0xe7, 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74,
0x32, 0, 0x28, 0x80, 0x65, 0x92, 0x20, 0x61, 0x79, 0x91, 0x20, 0x3,
0x63, 0x3d, 0x31, 0x30, 0x20, 0x61, 0x63, 0x6e, 0x3d, 0x49, 0x74,
0x73, 0x4d, 0x65, 0x61, 0x71, 0x6c, 0xf1, 0x7b, 0x31, 0x20, 0x70,
0x76, 0x3d, 0x36, 0x20, 0x6c, 0x74, 0x8, 0xc0, 0x40, 0x80, 0x3d,
0x30, 0x20, 0x54, 0xaf, 0x5f, 0x74, 0x61, 0x6c, 0x6b, 0x5f, 0x70,
0x6f, 0x77, 0x65, 0x12, 0xfa, 0x66, 0x5e, 0x6e, 0x65, 0x65, 0x64,
0x65, 0x64, 0x85, 0x50, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x76,
0x69, 0, 0, 0, 0x80, 0x65, 0x77, 0x5f, 0x70, 0x6f, 0x77, 0x65,
0x72, 0x3d, 0x37, 0x35,
];
let orig = b"initserver virtualserver_name=Server\\sder\\sVerplanten virtualserver_welcomemessage=This\\sis\\sSplamys\\sWorld virtualserver_platform=Linux virtualserver_version=3.0.13.8\\s[Build:\\s1500452811] virtualserver_maxclients=32 virtualserver_created=0 virtualserver_codec_encryption_mode=1 virtualserver_hostmessage=L\xc3\xa9\\sServer\\sde\\sSplamy virtualserver_hostmessage_mode=0 virtualserver_default_server_group=8 virtualserver_default_channel_group=8 virtualserver_hostbanner_url virtualserver_hostbanner_gfx_url virtualserver_hostbanner_gfx_interval=2000 virtualserver_priority_speaker_dimm_modificator=-18.0000 virtualserver_id=1 virtualserver_hostbutton_tooltip virtualserver_hostbutton_url virtualserver_hostbutton_gfx_url virtualserver_name_phonetic=mob virtualserver_icon_id=2568555213 virtualserver_ip=0.0.0.0,\\s:: virtualserver_ask_for_privilegekey=0 virtualserver_hostbanner_mode=0 virtualserver_channel_temp_delete_delay_default=10 acn=ItsMe aclid=1 pv=6 lt=0 client_talk_power=-1 client_needed_serverquery_view_power=75";
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(orig.as_ref(), &dec);
}
#[test]
fn string_compress_lvl1() {
let data = [
0x47, 0x4c, 0x2, 0, 0, 0xf9, 0x3, 0, 0, 0, 0, 0x4, 0x84, 0x69,
0x6e, 0x69, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x76,
0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x54, 0x25, 0x5f, 0x6e, 0x61,
0x6d, 0x65, 0x3d, 0x53, 0x23, 0x50, 0x5c, 0x73, 0x64, 0x65, 0,
0x20, 0, 0x80, 0x72, 0x5c, 0x73, 0x56, 0x65, 0x72, 0x70, 0x6c,
0x61, 0x6e, 0x74, 0x65, 0x6e, 0x7d, 0xb, 0x77, 0x65, 0x6c, 0x63,
0x6f, 0x6d, 0x65, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x3d,
0x54, 0x68, 0x50, 0x82, 0x1, 0xb0, 0x69, 0x73, 0x5c, 0x73, 0xe2,
0x6a, 0x53, 0x61, 0xa6, 0x6d, 0x79, 0x61, 0xb4, 0x57, 0x6f, 0x72,
0x6c, 0x64, 0x7d, 0xb, 0x61, 0xa6, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x3d, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x7d, 0xb, 0x1, 0x25, 0x73, 0,
0, 0, 0x80, 0x69, 0x6f, 0x6e, 0x3d, 0x33, 0x2e, 0x30, 0x2e, 0x31,
0x33, 0x2e, 0x38, 0x5c, 0x73, 0x5b, 0x42, 0x75, 0x69, 0x6c, 0x64,
0x3a, 0x5c, 0x73, 0x31, 0x35, 0x30, 0x30, 0x34, 0x35, 0x32, 0x38,
0x8, 0, 0x2, 0x88, 0x31, 0x31, 0x5d, 0x7d, 0xb, 0x6d, 0x61, 0x78,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x3d, 0x33, 0x32, 0x7d,
0xb, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x3d, 0x30, 0x7e,
0xb, 0x6f, 0x64, 0x65, 0, 0x92, 0x10, 0x9c, 0x63, 0x5f, 0x65, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0xf1, 0x98, 0x5f, 0x6d, 0x91, 0x23,
0x3d, 0x31, 0x7d, 0xb, 0x68, 0x6f, 0x73, 0x74, 0xb6, 0x25, 0x4c,
0xc3, 0xa9, 0x5c, 0x73, 0x58, 0x27, 0xb1, 0x66, 0x61, 0xa6, 0x6d,
0x79, 0x7, 0x8, 0x18, 0xe0, 0x70, 0xb, 0x1a, 0x94, 0xba, 0x2e,
0x75, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x55, 0x25,
0x67, 0x72, 0x6f, 0x75, 0x70, 0x3d, 0x38, 0x7d, 0xb, 0x26, 0x30,
0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0, 0x49, 0x16,
0xe2, 0x85, 0x82, 0x11, 0x81, 0x80, 0x62, 0x72, 0x88, 0x72, 0x5f,
0x75, 0x72, 0x6c, 0x7d, 0xb, 0xe9, 0x85, 0x67, 0x66, 0x78, 0x80,
0x27, 0x22, 0x69, 0x6e, 0x74, 0x21, 0x50, 0x61, 0x6c, 0x3d, 0x32,
0x30, 0x30, 0x2e, 0x75, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
0, 0x40, 0, 0xb0, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x65,
0x72, 0x5f, 0x64, 0x69, 0x6d, 0x6d, 0x92, 0xba, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x6f, 0x72, 0x3d, 0x2d, 0x31, 0x38, 0x2e, 0x31,
0x33, 0x2e, 0x75, 0x69, 0x2, 0, 0x3f, 0x80, 0x64, 0xe0, 0x33, 0x15,
0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6f, 0x6c,
0x74, 0x69, 0x70, 0x70, 0xb, 0x14, 0x24, 0x33, 0x20, 0x4b, 0x17,
0x24, 0x33, 0x10, 0x1e, 0x16, 0x82, 0x7b, 0x5f, 0x70, 0x68, 0x6f,
0x6e, 0x65, 0x74, 0x69, 0x63, 0x90, 0x1, 0x88, 0xc1, 0x3d, 0x6d,
0x6f, 0x62, 0x7d, 0xb, 0x69, 0x63, 0x91, 0xb9, 0xf1, 0x7b, 0x32,
0x35, 0x36, 0x38, 0x35, 0x35, 0x35, 0x32, 0x31, 0x33, 0x7e, 0xb,
0x70, 0x3d, 0x30, 0xd1, 0x2c, 0x21, 0xd3, 0x2c, 0x5c, 0x73, 0x3a,
0x3a, 0x7d, 0xb, 0x50, 0, 0x1f, 0x80, 0x61, 0x73, 0x6b, 0x5f, 0x1,
0x84, 0x5f, 0x71, 0x4e, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x6b,
0x65, 0x79, 0xef, 0x23, 0xe9, 0x85, 0xb3, 0x92, 0x2e, 0x75, 0x56,
0xe7, 0x74, 0x65, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74,
0x32, 0, 0x28, 0x80, 0x65, 0x92, 0x20, 0x61, 0x79, 0x91, 0x20, 0x3,
0x63, 0x3d, 0x31, 0x30, 0x20, 0x61, 0x63, 0x6e, 0x3d, 0x49, 0x74,
0x73, 0x4d, 0x65, 0x61, 0x71, 0x6c, 0xf1, 0x7b, 0x31, 0x20, 0x70,
0x76, 0x3d, 0x36, 0x20, 0x6c, 0x74, 0x8, 0xc0, 0x40, 0x80, 0x3d,
0x30, 0x20, 0x54, 0xaf, 0x5f, 0x74, 0x61, 0x6c, 0x6b, 0x5f, 0x70,
0x6f, 0x77, 0x65, 0x12, 0xfa, 0x66, 0x5e, 0x6e, 0x65, 0x65, 0x64,
0x65, 0x64, 0x85, 0x50, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x76,
0x69, 0, 0, 0, 0x80, 0x65, 0x77, 0x5f, 0x70, 0x6f, 0x77, 0x65,
0x72, 0x3d, 0x37, 0x35,
];
let orig = b"initserver virtualserver_name=Server\\sder\\sVerplanten virtualserver_welcomemessage=This\\sis\\sSplamys\\sWorld virtualserver_platform=Linux virtualserver_version=3.0.13.8\\s[Build:\\s1500452811] virtualserver_maxclients=32 virtualserver_created=0 virtualserver_codec_encryption_mode=1 virtualserver_hostmessage=L\xc3\xa9\\sServer\\sde\\sSplamy virtualserver_hostmessage_mode=0 virtualserver_default_server_group=8 virtualserver_default_channel_group=8 virtualserver_hostbanner_url virtualserver_hostbanner_gfx_url virtualserver_hostbanner_gfx_interval=2000 virtualserver_priority_speaker_dimm_modificator=-18.0000 virtualserver_id=1 virtualserver_hostbutton_tooltip virtualserver_hostbutton_url virtualserver_hostbutton_gfx_url virtualserver_name_phonetic=mob virtualserver_icon_id=2568555213 virtualserver_ip=0.0.0.0,\\s:: virtualserver_ask_for_privilegekey=0 virtualserver_hostbanner_mode=0 virtualserver_channel_temp_delete_delay_default=10 acn=ItsMe aclid=1 pv=6 lt=0 client_talk_power=-1 client_needed_serverquery_view_power=75";
let input = orig.to_vec();
let com = compress(&input, CompressionLevel::Lvl1);
assert_eq!(data.as_ref(), &com);
}
#[test]
fn corrupt_string() {
let data = [
0x47, 0x56, 0x02, 0x00, 0x00, 0xf9, 0x03, 0x00, 0x00, 0x00, 0x00,
0x04, 0x84, 0x69, 0x6e, 0x69, 0x74, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x20, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x54, 0x25,
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x53, 0x23, 0x50, 0x5c, 0x73,
0x64, 0x65, 0x00, 0x20, 0x00, 0x80, 0x72, 0x5c, 0x73, 0x56, 0x65,
0x72, 0x70, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x6e, 0x7d, 0x0b, 0x77,
0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x3d, 0x54, 0x68, 0x50, 0x82, 0x01, 0xb0, 0x69, 0x73,
0x5c, 0x73, 0xe2, 0x6a, 0x53, 0x61, 0xa6, 0x6d, 0x79, 0x61, 0xb4,
0x57, 0x6f, 0x72, 0x6c, 0x64, 0x7d, 0x0b, 0x61, 0xa6, 0x74, 0x66,
0x6f, 0x72, 0x6d, 0x3d, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x7d, 0x0b,
0x01, 0x25, 0x73, 0x00, 0x00, 0x00, 0x80, 0x69, 0x6f, 0x6e, 0x3d,
0x33, 0x2e, 0x30, 0x2e, 0x31, 0x33, 0x2e, 0x38, 0x5c, 0x73, 0x5b,
0x42, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x5c, 0x73, 0x31, 0x35, 0x30,
0x30, 0x34, 0x35, 0x32, 0x38, 0x08, 0x00, 0x02, 0x88, 0x31, 0x31,
0x5d, 0x7d, 0x0b, 0x6d, 0x61, 0x78, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x73, 0x3d, 0x33, 0x32, 0x7d, 0x0b, 0x63, 0x72, 0x65, 0x61,
0x74, 0x65, 0x64, 0x3d, 0x30, 0x7d, 0x0b, 0x6e, 0x6f, 0x64, 0x00,
0x24, 0x21, 0xb8, 0x65, 0x63, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79,
0x70, 0x74, 0xf1, 0x98, 0x5f, 0x6d, 0x91, 0x23, 0x3d, 0x31, 0x7d,
0x0b, 0x68, 0x6f, 0x73, 0x74, 0xb6, 0x25, 0x4c, 0xc3, 0xa9, 0x5c,
0x73, 0x58, 0x27, 0xb1, 0x66, 0x61, 0xa6, 0x6d, 0x1e, 0x20, 0xc0,
0x80, 0x79, 0x7d, 0x0b, 0x89, 0x7b, 0x94, 0xba, 0x2e, 0x75, 0x64,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x54, 0x25, 0x20, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x3d, 0x38, 0x7d, 0x0b, 0x26, 0x30, 0x63,
0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x16, 0x1c, 0x11, 0x88, 0x5f,
0x00, 0x49, 0x16, 0xe2, 0x85, 0x62, 0x72, 0x88, 0x72, 0x5f, 0x75,
0x72, 0x6c, 0x7d, 0x0b, 0xe2, 0x85, 0xb5, 0x25, 0x67, 0x66, 0x78,
0x80, 0x27, 0x22, 0x69, 0x6e, 0x74, 0x21, 0x50, 0x61, 0x6c, 0x3d,
0x32, 0x30, 0x30, 0x2e, 0x75, 0x70, 0x72, 0x69, 0x00, 0x00, 0x04,
0x80, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x70, 0x65, 0x61,
0x6b, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x6d, 0x6d, 0x92, 0xba, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x3d, 0x2d, 0x31, 0x38,
0x26, 0x00, 0xf0, 0x87, 0x2e, 0x31, 0x33, 0x2e, 0x75, 0x69, 0x64,
0xe0, 0x33, 0x15, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5f, 0x74,
0x6f, 0x6f, 0x6c, 0x74, 0x69, 0x70, 0x7d, 0x0b, 0x83, 0x7b, 0x24,
0x33, 0x20, 0x4b, 0x17, 0x24, 0x33, 0x10, 0x1e, 0x16, 0x82, 0x7b,
0x5f, 0x70, 0x68, 0x6f, 0x00, 0x32, 0x00, 0xe1, 0x6e, 0x65, 0x74,
0x69, 0x63, 0x3d, 0x6d, 0x6f, 0x62, 0x7d, 0x0b, 0x69, 0x63, 0x91,
0xb9, 0xf1, 0x7b, 0x32, 0x35, 0x36, 0x38, 0x35, 0x35, 0x35, 0x32,
0x31, 0x33, 0x7d, 0x0b, 0x6e, 0x70, 0x3d, 0x30, 0xd1, 0x2c, 0x21,
0xd3, 0x20, 0x14, 0xc0, 0x87, 0x2c, 0x5c, 0x73, 0x3a, 0x3a, 0x7d,
0x0b, 0x61, 0x73, 0x6b, 0x5f, 0x01, 0x84, 0x5f, 0x71, 0x4e, 0x76,
0x69, 0x6c, 0x65, 0x67, 0x65, 0x6b, 0x65, 0x79, 0xef, 0x23, 0xe9,
0x85, 0xb3, 0x92, 0x2e, 0x75, 0x56, 0xe7, 0x74, 0x65, 0x6d, 0x70,
0x80, 0x0c, 0x00, 0x8a, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x92, 0x20, 0x61, 0x79, 0x91, 0x20, 0x03, 0x63, 0x3d, 0x31, 0x30,
0x20, 0x61, 0x63, 0x6e, 0x3d, 0x49, 0x74, 0x73, 0x4d, 0x65, 0x61,
0x71, 0x6c, 0xf1, 0x7b, 0x31, 0x20, 0x70, 0x00, 0x02, 0x80, 0x81,
0x76, 0x3d, 0x36, 0x20, 0x6c, 0x74, 0x3d, 0x30, 0x20, 0x51, 0xaf,
0x64, 0x3d, 0x31, 0x5f, 0x74, 0x61, 0x6c, 0x6b, 0x5f, 0x70, 0x6f,
0x77, 0x65, 0x12, 0xfa, 0x66, 0x5e, 0x6e, 0x65, 0x65, 0x64, 0x65,
0x64, 0x01, 0x00, 0x00, 0x80, 0x85, 0x50, 0x71, 0x75, 0x65, 0x72,
0x79, 0x5f, 0x76, 0x69, 0x65, 0x77, 0x5f, 0x70, 0x6f, 0x77, 0x65,
0x72, 0x3d, 0x37, 0x35,
];
let orig = b"initserver virtualserver_name=Server\\sder\\sVerplanten virtualserver_welcomemessage=This\\sis\\sSplamys\\sWorld virtualserver_platform=Linux virtualserver_version=3.0.13.8\\s[Build:\\s1500452811] virtualserver_maxclients=32 virtualserver_created=0 virtualserver_nodec_encryption_mode=1 virtualserver_hostmessage=L\xc3\xa9\\sServer\\sde\\sSplamy virtualserver_name=Server_mode=0 virtualserver_default_server group=8 virtualserver_default_channel_group=8 virtualserver_hostbanner_url virtualserver_hostmessagegfx_url virtualserver_hostmessagegfx_interval=2000 virtualserver_priority_speaker_dimm_modificator=-18.0000 virtualserver_id=1 virtualserver_hostbutton_tooltip virtualserver_name=utton_url virtualserver_hostmutton_gfx_url virtualserver_name_phonetic=mob virtualserver_icon_id=2568555213 virtualserver_np=0.0.0.0,\\s:: virtualserver_ask_for_privilegekey=0 virtualserver_hostmessagemode=0 virtualserver_channel_temp_delete_delay_default=10 acn=ItsMe aclid=1 pv=6 lt=0 clid=1_talk_power=-1 clid=1_needed_serverquery_view_power=75";
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(orig.as_ref(), &dec);
}
#[test]
fn corrupt_enterview() {
let data = [
0x47, 0xf2, 0x1, 0, 0, 0xfe, 0x2, 0, 0, 0, 0x10, 0, 0x90, 0x6e,
0x6f, 0x74, 0x69, 0x66, 0x79, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x32, 0x92, 0x72, 0x76, 0x69, 0x65, 0x77, 0x20, 0x63, 0x66, 0x69,
0x64, 0x3d, 0x30, 0x20, 0x63, 0x74, 0xf1, 0x7b, 0x31, 0x20, 0x40,
0x62, 0, 0x82, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0xf1, 0x7b,
0x32, 0x20, 0x51, 0xaf, 0x64, 0x3d, 0x31, 0x62, 0x5e, 0x31, 0x92,
0x5f, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x31,
0x92, 0x69, 0x66, 0x69, 0x65, 0x72, 0, 0, 0, 0xa0, 0x3d, 0x6c,
0x6b, 0x73, 0x37, 0x51, 0x4c, 0x35, 0x4f, 0x56, 0x4d, 0x4b, 0x6f,
0x34, 0x70, 0x5a, 0x37, 0x39, 0x63, 0x45, 0x4f, 0x49, 0x35, 0x72,
0x35, 0x6f, 0x45, 0x41, 0x3d, 0x66, 0x5e, 0x6e, 0, 0x8, 0xc0, 0x90,
0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x42, 0x6f, 0x74,
0x66, 0x5e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6d, 0x75, 0x74,
0x65, 0x73, 0xe6, 0xa3, 0xf3, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x23,
0x19, 0x6f, 0x6e, 0xc, 0x70, 0, 0x81, 0x6c, 0x79, 0x9e, 0xa0, 0xf4,
0x96, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0xe2, 0x23,
0xab, 0xf3, 0xe0, 0x64, 0x12, 0x6d, 0x65, 0x74, 0x61, 0x5f, 0x64,
0x61, 0x74, 0x61, 0x67, 0x5e, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x6f,
0x60, 0x84, 0, 0xa0, 0x72, 0x64, 0x69, 0x6e, 0x67, 0xe8, 0x23,
0x22, 0x62, 0x62, 0x61, 0x73, 0x2, 0x9f, 0x3d, 0x32, 0x33, 0x39,
0x66, 0x5e, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x91, 0xf1, 0x3d, 0x2, 0x11, 0x6, 0x88,
0x38, 0x66, 0x5e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3, 0x49,
0x73, 0x3d, 0x37, 0x66, 0x5e, 0x61, 0x77, 0x61, 0x79, 0xe8, 0x23,
0x62, 0x17, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x66,
0x5e, 0x74, 0x79, 0x70, 0x1, 0x5, 0xb4, 0xe4, 0x69, 0xe6, 0x66,
0x6c, 0x61, 0x67, 0x5f, 0x61, 0x76, 0x61, 0x27, 0x72, 0x67, 0x5e,
0x61, 0x6c, 0x6b, 0x5f, 0x70, 0x6f, 0x77, 0x21, 0x1b, 0x35, 0x21,
0x60, 0xa4, 0xf3, 0x74, 0x72, 0xad, 0x72, 0x65, 0x61, 0x32, 0x73,
0x74, 0xe8, 0x23, 0x2a, 0x7b, 0x10, 0, 0xf1, 0x80, 0x5f, 0x6d,
0x73, 0x67, 0x66, 0x5e, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x66, 0x5e, 0x69, 0x73, 0x5f, 0x22, 0x7b,
0x21, 0x1b, 0x27, 0x60, 0xe1, 0x69, 0x70, 0x72, 0x69, 0x6f, 0x72,
0x69, 0x74, 0x80, 0xd4, 0, 0x82, 0x79, 0x5f, 0x73, 0x70, 0x65,
0x61, 0x6b, 0x2a, 0x1b, 0x75, 0x6e, 0x41, 0x36, 0x64, 0x96, 0xb0,
0x73, 0xe8, 0x23, 0x86, 0xf5, 0x5f, 0x70, 0x68, 0x6f, 0x6e, 0x65,
0x74, 0x69, 0x63, 0x66, 0x5e, 0x6e, 0x65, 0x65, 0x64, 0x65, 0xc,
0x13, 0xe, 0x88, 0x64, 0x5f, 0x54, 0x25, 0x61, 0x32, 0x72, 0x79,
0x5f, 0x76, 0xf1, 0x21, 0x85, 0x6a, 0x37, 0x35, 0x66, 0x5e, 0x69,
0x63, 0x6f, 0x6e, 0x92, 0xf1, 0x2a, 0x60, 0x56, 0xe7, 0x63, 0x6f,
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2a, 0x1b, 0x63, 0x6f, 0x75, 0x70,
0xf0, 0, 0x80, 0x6e, 0x74, 0x72, 0x79, 0x66, 0x5e, 0x56, 0xe7, 0x3,
0x49, 0x5f, 0x69, 0x6e, 0x68, 0x65, 0x41, 0xe3, 0x31, 0x19, 0x56,
0xe7, 0xf1, 0x7b, 0x31, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
0x5f, 0x62, 0x61, 0x64, 0x67, 0x65, 0x73,
];
let orig = b"notifycliententerview cfid=0 ctid=1 reasonid=2 clid=1 client_unique_identifier=lks7QL5OVMKo4pZ79cEOI5r5oEA= client_nickname=Bot client_input_muted=0 client_output_muted=0 client_outputonly_muted=0 client_input_hardware=0 client_output_hardware=0 client_meta_data client_is_recording=0 client_database_id=239 client_channel_group_id=8 client_servergroups=7 client_away=0 client_away_message client_type=0 client_flag_avatar client_talk_power=50 client_talk_request=0 client_talk_request_msg client_description client_is_talker=0 client_is_priority_speaker=0 client_unread_messages=0 client_nickname_phonetic client_needed_serverquery_view_power=75 client_icon_id=0 client_is_channel_commander=0 client_country client_channel_group_inherited_channel_id=1 client_badges";
let mut r = Cursor::new(data.as_ref());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(orig.as_ref(), &dec);
}
#[test]
fn roundtrip_lvl1() {
let orig = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaa";
let comp = compress(orig, CompressionLevel::Lvl1);
let mut r = Cursor::new(comp.as_slice());
let dec = decompress(&mut r, 1024).unwrap();
assert_eq!(orig.as_ref(), &dec);
}
#[test]
fn simple_roundtrip_lvl3() {
roundtrip_lvl3(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaa");
}
#[test]
fn data_compress_lvl3() {
let data_s = [
77, 26, 106, 136, 1, 0, 128, 97, 97, 97, 131, 154, 1, 0, 98, 98,
98, 231, 1, 0, 234, 10, 97, 97, 97, 97,
];
let data_l = [
79, 32, 0, 0, 0, 106, 0, 0, 0, 136, 1, 0, 128, 97, 97, 97, 131,
154, 1, 0, 98, 98, 98, 231, 1, 0, 234, 10, 97, 97, 97, 97,
];
let orig = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaa";
let com = compress(orig, CompressionLevel::Lvl3);
let comsl = com.as_slice();
if comsl[0] & 2 != 0 {
// long
assert_eq!(data_l, comsl);
} else {
// short
assert_eq!(data_s, comsl);
}
}
#[test]
fn lvl3_case0() {
let mut data = Vec::new();
// Incompressible
for i in 0..255 {
data.push(255 - i);
}
for i in 0..255 {
data.push(i);
}
for i in 0..(255 / 2) {
data.push((255 - i) << 1);
}
for i in 0..(255 / 2) {
data.push(i << 1);
}
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case1() {
let mut data = Vec::new();
let data_s = [77, 26, 106];
data.extend_from_slice(&data_s);
data.extend_from_slice(&data_s);
for i in 0..125 {
data.push(i);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case2() {
let mut data = Vec::new();
let data_s = [77, 26, 106];
data.extend_from_slice(&data_s);
for i in 0..126 {
data.push(i);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case2_2() {
let mut data = Vec::new();
let data_s = [77, 26, 106];
data.extend_from_slice(&data_s);
for i in 0u32..(1 << 13) {
data.push(i as u8);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case3() {
let mut data = Vec::new();
let data_s = [77, 26, 106];
data.extend_from_slice(&data_s);
for i in 0u32..(1 << 14) {
data.push(i as u8);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case3_2() {
let mut data = Vec::new();
let data_s = [77, 26, 106, 136, 1, 0, 128];
data.extend_from_slice(&data_s);
for i in 0u32..(1 << 9) {
data.push(i as u8);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case3_3() {
let mut data = Vec::new();
let data_s = [77, 26, 106, 136, 1, 0, 128];
data.extend_from_slice(&data_s);
data.extend_from_slice(&data_s);
for i in 0..125 {
data.push(i);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case4() {
let mut data = Vec::new();
for i in 0..(1 << 6) {
data.push(255 - i);
}
for i in 0u32..(1 << 11) {
data.push(i as u8);
}
// Second block to compress
for i in 0..(1 << 6) {
data.push(255 - i);
}
roundtrip_lvl3(&data);
}
#[test]
fn lvl3_case5() {
let data_s = [
77, 26, 106, 136, 1, 0, 128, 97, 97, 97, 131, 154, 1, 0, 98, 98,
];
let mut data = Vec::new();
data.extend_from_slice(&data_s);
for _ in 0..(1 << 11) {
data.push(0);
}
data.extend_from_slice(&data_s);
roundtrip_lvl3(&data);
}
#[test]
fn fuzz_compress_crash() {
let data = [
8, 255, 100, 242, 242, 242, 159, 159, 4, 0, 0, 197, 159, 61, 255,
1, 0, 0, 255, 2, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 7, 0,
0, 0, 0, 0, 0, 0, 32, 0, 0, 248, 104, 0, 255, 255, 0, 0, 255, 0, 1,
2, 2, 253, 253, 253, 110, 0, 0, 0, 255, 64, 255, 222, 222, 222,
222, 0, 255, 255, 255, 0, 213, 219, 219, 219, 219, 219, 255, 255,
249, 64, 64, 100, 100, 100, 100, 100, 50, 242, 242, 0, 242, 64,
159, 255, 159, 159, 255, 145, 1, 0, 4, 255, 1, 145, 180, 155, 180,
155, 255, 255, 246, 249, 255, 255, 255, 0, 0, 16, 0, 255, 46, 255,
0, 255, 253, 182, 0, 0, 0, 5, 219, 219, 219, 219, 219, 255, 255,
104, 255, 255, 0, 0, 0, 22, 0, 39, 7, 243, 255, 0, 255, 0, 255,
255, 255, 0, 0, 0, 0, 128, 0, 0, 0, 0, 104, 104, 104, 104, 104,
192, 248, 0, 0, 0, 255, 65, 0, 76, 255, 255, 4, 255, 249, 255, 255,
2, 0, 0, 0, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 42, 75,
255, 255, 0, 255, 127, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255,
255, 255, 255, 110, 0, 0, 0, 0, 0, 255, 255, 255, 42, 75, 255, 255,
0, 255, 255, 110, 0, 0, 0, 9, 255, 255, 0, 0, 0, 0, 0,
];
let lvl1 = compress(&data[..], CompressionLevel::Lvl1);
let dec1 =
decompress(&mut Cursor::new(lvl1), data.len() as u32).unwrap();
assert_eq!(&data[..], dec1.as_slice());
}
}
#[cfg(feature = "nightly")]
mod bench {
use super::*;
use super::*;
use std::fs::File;
use test::Bencher;
#[bench]
fn perf_compress_lvl1(b: &mut Bencher) {
let mut f = File::open("bench/bench.txt").expect("file not found");
let mut contents = vec![];
f.read_to_end(&mut contents)
.expect("something went wrong reading the file");
b.iter(|| {
compress(contents.as_slice(), CompressionLevel::Lvl1);
});
}
#[bench]
fn perf_compress_lvl3(b: &mut Bencher) {
let mut f = File::open("bench/bench.txt").expect("file not found");
let mut contents = vec![];
f.read_to_end(&mut contents)
.expect("something went wrong reading the file");
b.iter(|| {
compress(contents.as_slice(), CompressionLevel::Lvl3);
});
}
}