#[cfg(test)]
mod tests;
use crate::error::{WhisperError, WhisperResult};
pub const BLOCK_SIZE: usize = 64 * 1024;
pub const MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024;
#[allow(dead_code)]
pub const LZ4_MAGIC: u32 = 0x184D_2204;
#[derive(Debug)]
pub struct Decompressor {
pub(crate) buffer: Vec<u8>,
}
impl Decompressor {
#[must_use]
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(BLOCK_SIZE),
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: Vec::with_capacity(capacity),
}
}
pub fn decompress_block(
&mut self,
compressed: &[u8],
decompressed_size: usize,
) -> WhisperResult<&[u8]> {
if decompressed_size > MAX_BLOCK_SIZE {
return Err(WhisperError::Format(format!(
"decompressed size {decompressed_size} exceeds maximum {MAX_BLOCK_SIZE}"
)));
}
self.buffer.clear();
self.buffer.reserve(decompressed_size);
let mut src_pos = 0;
let src = compressed;
while self.buffer.len() < decompressed_size {
Self::require_bytes(src, src_pos, 1)?;
let token = src[src_pos];
src_pos += 1;
let literal_len = (token >> 4) as usize;
let match_len_base = (token & 0x0F) as usize;
let literal_len = Self::read_extended_length(src, &mut src_pos, literal_len)?;
Self::require_bytes(src, src_pos, literal_len)?;
self.buffer
.extend_from_slice(&src[src_pos..src_pos + literal_len]);
src_pos += literal_len;
if self.buffer.len() >= decompressed_size {
break;
}
Self::require_bytes(src, src_pos, 2)?;
let offset = u16::from_le_bytes([src[src_pos], src[src_pos + 1]]) as usize;
src_pos += 2;
if offset == 0 || offset > self.buffer.len() {
return Err(WhisperError::Format(format!(
"invalid offset {} (buffer length {})",
offset,
self.buffer.len()
)));
}
let match_len = Self::read_extended_length(src, &mut src_pos, match_len_base)? + 4;
let match_start = self.buffer.len() - offset;
for i in 0..match_len {
let byte = self.buffer[match_start + (i % offset)];
self.buffer.push(byte);
}
}
self.buffer.truncate(decompressed_size);
Ok(&self.buffer)
}
fn require_bytes(src: &[u8], pos: usize, n: usize) -> WhisperResult<()> {
if pos + n > src.len() {
return Err(WhisperError::Format(
"unexpected end of compressed data".into(),
));
}
Ok(())
}
fn read_extended_length(src: &[u8], pos: &mut usize, base: usize) -> WhisperResult<usize> {
let mut len = base;
if base == 15 {
loop {
let &byte = src.get(*pos).ok_or_else(|| {
WhisperError::Format("unexpected end reading extended length".into())
})?;
*pos += 1;
len += byte as usize;
if byte != 255 {
break;
}
}
}
Ok(len)
}
pub fn store_uncompressed(&mut self, data: &[u8]) -> WhisperResult<&[u8]> {
self.buffer.clear();
self.buffer.extend_from_slice(data);
Ok(&self.buffer)
}
#[must_use]
pub fn capacity(&self) -> usize {
self.buffer.capacity()
}
#[must_use]
pub fn len(&self) -> usize {
self.buffer.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn reset(&mut self) {
self.buffer.clear();
}
}
impl Default for Decompressor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) struct Compressor {
pub(crate) buffer: Vec<u8>,
}
#[cfg(test)]
impl Default for Compressor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl Compressor {
#[must_use]
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(BLOCK_SIZE),
}
}
pub fn compress_block(&mut self, data: &[u8]) -> WhisperResult<&[u8]> {
self.buffer.clear();
let mut pos = 0;
let mut literal_start = 0;
while pos < data.len() {
let (match_offset, match_len) = Self::find_match(data, pos);
if match_len >= 4 {
let literals = &data[literal_start..pos];
self.encode_sequence(literals, match_offset, match_len)?;
pos += match_len;
literal_start = pos;
} else {
pos += 1;
}
}
if literal_start < data.len() {
let remaining = &data[literal_start..];
self.encode_literals_only(remaining)?;
}
Ok(&self.buffer)
}
pub fn compress_store(&mut self, data: &[u8]) -> WhisperResult<&[u8]> {
self.buffer.clear();
let mut pos = 0;
while pos < data.len() {
let chunk_size = std::cmp::min(data.len() - pos, MAX_BLOCK_SIZE);
let chunk = &data[pos..pos + chunk_size];
self.encode_literals_only(chunk)?;
pos += chunk_size;
}
Ok(&self.buffer)
}
fn find_match(data: &[u8], pos: usize) -> (usize, usize) {
let window_size = std::cmp::min(pos, 65535);
let max_match = std::cmp::min(data.len() - pos, 255 + 15 + 4);
let mut best_offset = 0;
let mut best_len = 0;
for offset in 1..=window_size {
let match_start = pos - offset;
let mut len = 0;
while pos + len < data.len()
&& len < max_match
&& data[match_start + (len % offset)] == data[pos + len]
{
len += 1;
}
if len >= 4 && len > best_len {
best_offset = offset;
best_len = len;
}
}
(best_offset, best_len)
}
fn encode_sequence(
&mut self,
literals: &[u8],
offset: usize,
match_len: usize,
) -> WhisperResult<()> {
let literal_len = literals.len();
let match_len_base = match_len.saturating_sub(4);
let lit_nibble = std::cmp::min(literal_len, 15);
let match_nibble = std::cmp::min(match_len_base, 15);
let token = ((lit_nibble as u8) << 4) | (match_nibble as u8);
self.buffer.push(token);
if literal_len >= 15 {
self.encode_extended_length(literal_len - 15);
}
self.buffer.extend_from_slice(literals);
self.buffer
.extend_from_slice(&(offset as u16).to_le_bytes());
if match_len_base >= 15 {
self.encode_extended_length(match_len_base - 15);
}
Ok(())
}
fn encode_literals_only(&mut self, literals: &[u8]) -> WhisperResult<()> {
let literal_len = literals.len();
let lit_nibble = std::cmp::min(literal_len, 15);
let token = (lit_nibble as u8) << 4;
self.buffer.push(token);
if literal_len >= 15 {
self.encode_extended_length(literal_len - 15);
}
self.buffer.extend_from_slice(literals);
Ok(())
}
fn encode_extended_length(&mut self, mut extra: usize) {
while extra >= 255 {
self.buffer.push(255);
extra -= 255;
}
self.buffer.push(extra as u8);
}
}