use crate::block::DecompressError;
use crate::block::MINMATCH;
use crate::sink::Sink;
use crate::sink::SliceSink;
#[allow(unused_imports)]
use alloc::vec;
#[allow(unused_imports)]
use alloc::vec::Vec;
#[inline]
fn read_integer(input: &[u8], input_pos: &mut usize) -> Result<u32, DecompressError> {
let mut n: u32 = 0;
loop {
let extra: u8 = *input
.get(*input_pos)
.ok_or(DecompressError::ExpectedAnotherByte)?;
*input_pos += 1;
n += extra as u32;
if extra != 0xFF {
break;
}
}
Ok(n)
}
#[inline]
fn read_match_offset(input: &[u8], input_pos: &mut usize) -> Result<u16, DecompressError> {
let dst = input
.get(*input_pos..*input_pos + 2)
.ok_or(DecompressError::ExpectedAnotherByte)?;
*input_pos += 2;
let offset = u16::from_le_bytes(dst.try_into().unwrap());
if offset == 0 {
Err(DecompressError::OffsetZero)
} else {
Ok(offset)
}
}
const FIT_TOKEN_MASK_LITERAL: u8 = 0b00001111;
const FIT_TOKEN_MASK_MATCH: u8 = 0b11110000;
#[test]
fn check_token() {
assert!(!does_token_fit(15));
assert!(does_token_fit(14));
assert!(does_token_fit(114));
assert!(!does_token_fit(0b11110000));
assert!(does_token_fit(0b10110000));
}
#[inline]
fn does_token_fit(token: u8) -> bool {
!((token & FIT_TOKEN_MASK_LITERAL) == FIT_TOKEN_MASK_LITERAL
|| (token & FIT_TOKEN_MASK_MATCH) == FIT_TOKEN_MASK_MATCH)
}
#[inline]
pub(crate) fn decompress_internal<const USE_DICT: bool, S: Sink>(
input: &[u8],
output: &mut S,
ext_dict: &[u8],
) -> Result<usize, DecompressError> {
let mut input_pos = 0;
let initial_output_pos = output.pos();
let safe_input_pos = input
.len()
.saturating_sub(16 + 2 );
let mut safe_output_pos = output
.capacity()
.saturating_sub(16 + 18 );
if USE_DICT {
safe_output_pos = safe_output_pos.saturating_sub(17);
};
loop {
let token = *input
.get(input_pos)
.ok_or(DecompressError::ExpectedAnotherByte)?;
input_pos += 1;
if does_token_fit(token) && input_pos <= safe_input_pos && output.pos() < safe_output_pos {
let literal_length = (token >> 4) as usize;
let input: &[u8; 16] = input[input_pos..input_pos + 16].try_into().unwrap();
output.extend_from_slice_wild(input, literal_length);
input_pos += literal_length;
let offset = read_match_offset(input, &mut literal_length.clone())? as usize;
input_pos += 2;
let mut match_length = MINMATCH + (token & 0xF) as usize;
if USE_DICT && offset > output.pos() {
let copied = copy_from_dict(output, ext_dict, offset, match_length)?;
if copied == match_length {
continue;
}
match_length -= copied;
}
let (start, did_overflow) = output.pos().overflowing_sub(offset);
if did_overflow {
return Err(DecompressError::OffsetOutOfBounds);
}
if offset >= match_length {
output.extend_from_within(start, 18, match_length);
} else {
output.extend_from_within_overlapping(start, match_length)
}
continue;
}
let mut literal_length = (token >> 4) as usize;
if literal_length != 0 {
if literal_length == 15 {
literal_length += read_integer(input, &mut input_pos)? as usize;
}
if literal_length > input.len() - input_pos {
return Err(DecompressError::LiteralOutOfBounds);
}
if literal_length > output.capacity() - output.pos() {
return Err(DecompressError::OutputTooSmall {
expected: output.pos() + literal_length,
actual: output.capacity(),
});
}
output.extend_from_slice(&input[input_pos..input_pos + literal_length]);
input_pos += literal_length;
}
if input_pos >= input.len() {
break;
}
let offset = read_match_offset(input, &mut input_pos)? as usize;
let mut match_length = MINMATCH + (token & 0xF) as usize;
if match_length == MINMATCH + 15 {
match_length += read_integer(input, &mut input_pos)? as usize;
}
if output.pos() + match_length > output.capacity() {
return Err(DecompressError::OutputTooSmall {
expected: output.pos() + match_length,
actual: output.capacity(),
});
}
if USE_DICT && offset > output.pos() {
let copied = copy_from_dict(output, ext_dict, offset, match_length)?;
if copied == match_length {
continue;
}
match_length -= copied;
}
duplicate_slice(output, offset, match_length)?;
}
Ok(output.pos() - initial_output_pos)
}
#[inline]
fn copy_from_dict(
output: &mut impl Sink,
ext_dict: &[u8],
offset: usize,
match_length: usize,
) -> Result<usize, DecompressError> {
debug_assert!(offset > output.pos());
let (dict_offset, did_overflow) = ext_dict.len().overflowing_sub(offset - output.pos());
if did_overflow {
return Err(DecompressError::OffsetOutOfBounds);
}
let dict_match_length = match_length.min(ext_dict.len() - dict_offset);
let ext_match = &ext_dict[dict_offset..dict_offset + dict_match_length];
output.extend_from_slice(ext_match);
Ok(dict_match_length)
}
#[inline(always)] fn duplicate_slice(
output: &mut impl Sink,
offset: usize,
match_length: usize,
) -> Result<(), DecompressError> {
if match_length > offset {
duplicate_overlapping_slice(output, offset, match_length)?;
} else {
let (start, did_overflow) = output.pos().overflowing_sub(offset);
if did_overflow {
return Err(DecompressError::OffsetOutOfBounds);
}
match match_length {
0..=32 if output.pos() + 32 <= output.capacity() => {
output.extend_from_within(start, 32, match_length)
}
33..=64 if output.pos() + 64 <= output.capacity() => {
output.extend_from_within(start, 64, match_length)
}
_ => output.extend_from_within(start, match_length, match_length),
}
}
Ok(())
}
#[inline]
fn duplicate_overlapping_slice(
sink: &mut impl Sink,
offset: usize,
match_length: usize,
) -> Result<(), DecompressError> {
let (start, did_overflow) = sink.pos().overflowing_sub(offset);
if did_overflow {
return Err(DecompressError::OffsetOutOfBounds);
}
if offset == 1 {
let val = sink.byte_at(start);
sink.extend_with_fill(val, match_length);
} else {
sink.extend_from_within_overlapping(start, match_length);
}
Ok(())
}
#[inline]
pub fn decompress_into(input: &[u8], output: &mut [u8]) -> Result<usize, DecompressError> {
decompress_internal::<false, _>(input, &mut SliceSink::new(output, 0), b"")
}
#[inline]
pub fn decompress_into_with_dict(
input: &[u8],
output: &mut [u8],
ext_dict: &[u8],
) -> Result<usize, DecompressError> {
decompress_internal::<true, _>(input, &mut SliceSink::new(output, 0), ext_dict)
}
#[inline]
pub fn decompress_size_prepended(input: &[u8]) -> Result<Vec<u8>, DecompressError> {
let (uncompressed_size, input) = super::uncompressed_size(input)?;
decompress(input, uncompressed_size)
}
#[inline]
pub fn decompress(input: &[u8], min_uncompressed_size: usize) -> Result<Vec<u8>, DecompressError> {
let mut decompressed: Vec<u8> = vec![0; min_uncompressed_size];
let decomp_len =
decompress_internal::<false, _>(input, &mut SliceSink::new(&mut decompressed, 0), b"")?;
decompressed.truncate(decomp_len);
Ok(decompressed)
}
#[inline]
pub fn decompress_size_prepended_with_dict(
input: &[u8],
ext_dict: &[u8],
) -> Result<Vec<u8>, DecompressError> {
let (uncompressed_size, input) = super::uncompressed_size(input)?;
decompress_with_dict(input, uncompressed_size, ext_dict)
}
#[inline]
pub fn decompress_with_dict(
input: &[u8],
min_uncompressed_size: usize,
ext_dict: &[u8],
) -> Result<Vec<u8>, DecompressError> {
let mut decompressed: Vec<u8> = vec![0; min_uncompressed_size];
let decomp_len =
decompress_internal::<true, _>(input, &mut SliceSink::new(&mut decompressed, 0), ext_dict)?;
decompressed.truncate(decomp_len);
Ok(decompressed)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn all_literal() {
assert_eq!(decompress(&[0x30, b'a', b'4', b'9'], 3).unwrap(), b"a49");
}
#[test]
fn incomplete_input() {
assert!(matches!(
decompress(&[], 255),
Err(DecompressError::ExpectedAnotherByte)
));
assert!(matches!(
decompress(&[0xF0], 255),
Err(DecompressError::ExpectedAnotherByte)
));
assert!(matches!(
decompress(&[0x0F, 0], 255),
Err(DecompressError::ExpectedAnotherByte)
));
assert!(matches!(
decompress(&[0x0F, 1, 0], 255),
Err(DecompressError::ExpectedAnotherByte)
));
}
#[test]
fn offset_oob() {
assert!(matches!(
decompress(&[0x40, b'a', 1, 0], 4),
Err(DecompressError::LiteralOutOfBounds)
));
assert!(matches!(
decompress(&[0x20, b'a', b'a', 1, 0], 1),
Err(DecompressError::OutputTooSmall {
expected: 2,
actual: 1
})
));
assert!(matches!(
decompress(&[0x10, b'a', 1, 0], 4),
Err(DecompressError::OutputTooSmall {
expected: 5,
actual: 4
})
));
assert!(matches!(
decompress(
&[0x0E, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
256
),
Err(DecompressError::OffsetOutOfBounds)
));
assert!(matches!(
decompress_with_dict(
&[0x0E, 255, 0, 0x70, 0, 0, 0, 0, 0, 0, 0],
256,
&[0_u8; 250]
),
Err(DecompressError::OffsetOutOfBounds)
));
assert!(matches!(
decompress(&[0x0F, 1, 0, 1, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
Err(DecompressError::OffsetOutOfBounds)
));
assert!(matches!(
decompress(&[0x40, 0, 0, 0, 0, 255, 0, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
Err(DecompressError::OffsetOutOfBounds)
));
}
#[test]
fn offset_0() {
assert!(matches!(
decompress(&[0x0E, 0, 0, 0x70, 0, 0, 0, 0, 0, 0, 0], 256),
Err(DecompressError::OffsetZero)
));
}
}