use core::ptr;
use super::types::{
read_le16, wild_copy8, write32, DictDirective, DEC64TABLE, INC32TABLE, LASTLITERALS,
MATCH_SAFEGUARD_DISTANCE, MFLIMIT, MINMATCH, ML_BITS, ML_MASK, RUN_MASK, WILDCOPYLENGTH,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecompressError {
MalformedInput,
}
#[inline(always)]
fn output_error<T>() -> Result<T, DecompressError> {
Err(DecompressError::MalformedInput)
}
const RVL_ERROR: usize = usize::MAX;
#[inline(always)]
unsafe fn read_variable_length(
ip: &mut *const u8,
ilimit: *const u8,
initial_check: bool,
) -> usize {
let mut s: usize;
let mut length: usize = 0;
if initial_check && *ip >= ilimit {
return RVL_ERROR;
}
s = **ip as usize;
*ip = (*ip).add(1);
length += s;
if *ip > ilimit {
return RVL_ERROR;
}
if core::mem::size_of::<usize>() < 8 && length > usize::MAX / 2 {
return RVL_ERROR;
}
if s != 255 {
return length;
}
loop {
s = **ip as usize;
*ip = (*ip).add(1);
length += s;
if *ip > ilimit {
return RVL_ERROR;
}
if core::mem::size_of::<usize>() < 8 && length > usize::MAX / 2 {
return RVL_ERROR;
}
if s != 255 {
break;
}
}
length
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn decompress_generic(
src: *const u8,
dst: *mut u8,
src_size: usize,
output_size: usize,
partial_decoding: bool,
dict: DictDirective,
low_prefix: *const u8,
dict_start: *const u8, dict_size: usize,
) -> Result<usize, DecompressError> {
if src.is_null() || (output_size as isize) < 0 {
return output_error();
}
let mut ip: *const u8 = src;
let iend: *const u8 = src.add(src_size);
let mut op: *mut u8 = dst;
let oend: *mut u8 = dst.add(output_size);
let dict_end: *const u8 = if dict_start.is_null() {
ptr::null()
} else {
dict_start.add(dict_size)
};
let check_offset: bool = dict_size < 64 * 1024;
let short_iend: *const u8 = if src_size >= 16 {
iend.sub(14).sub(2)
} else {
src
};
let short_oend: *mut u8 = if output_size >= 32 {
oend.sub(14).sub(18)
} else {
dst
};
debug_assert!(low_prefix <= op as *const u8);
if output_size == 0 {
if partial_decoding {
return Ok(0);
}
return if src_size == 1 && *src == 0 {
Ok(0)
} else {
output_error()
};
}
if src_size == 0 {
return output_error();
}
'decode: loop {
debug_assert!(ip < iend);
let token: u8 = *ip;
ip = ip.add(1);
let mut lit_length: usize = (token >> ML_BITS as u8) as usize;
let offset: usize;
let match_ptr: *const u8;
let ml: usize;
if lit_length != RUN_MASK as usize
&& (ip < short_iend)
&& (op as *const u8 <= short_oend as *const u8)
{
ptr::copy_nonoverlapping(ip, op, 16);
op = op.add(lit_length);
ip = ip.add(lit_length);
ml = (token & ML_MASK as u8) as usize;
let off16 = read_le16(ip) as usize;
ip = ip.add(2);
let mp = (op as *const u8).wrapping_sub(off16);
if ml != ML_MASK as usize
&& off16 >= 8
&& (dict == DictDirective::WithPrefix64k || mp >= low_prefix)
{
ptr::copy_nonoverlapping(mp, op, 8);
ptr::copy_nonoverlapping(mp.add(8), op.add(8), 8);
ptr::copy_nonoverlapping(mp.add(16), op.add(16), 2);
op = op.add(ml + MINMATCH);
continue 'decode;
}
offset = off16;
match_ptr = mp;
} else {
if lit_length == RUN_MASK as usize {
let ilimit = if src_size >= RUN_MASK as usize {
iend.sub(RUN_MASK as usize)
} else {
src
};
let addl = read_variable_length(&mut ip, ilimit, true);
if addl == RVL_ERROR {
return output_error();
}
lit_length += addl;
if (op as usize).wrapping_add(lit_length) < op as usize {
return output_error();
}
if (ip as usize).wrapping_add(lit_length) < ip as usize {
return output_error();
}
}
let cpy: *mut u8 = op.add(lit_length);
let near_out_end = cpy > oend.sub(MFLIMIT);
let near_in_end = ip.add(lit_length) > iend.sub(2 + 1 + LASTLITERALS);
if near_out_end || near_in_end {
if partial_decoding {
let (lit_length, cpy) = if ip.add(lit_length) > iend {
let ll = iend as usize - ip as usize;
(ll, op.add(ll))
} else {
(lit_length, cpy)
};
let (lit_length, cpy) = if cpy > oend {
let ll = oend as usize - op as usize;
(ll, oend)
} else {
(lit_length, cpy)
};
ptr::copy(ip, op, lit_length);
ip = ip.add(lit_length);
op = cpy;
if !partial_decoding || cpy == oend || ip >= iend.sub(2) {
break 'decode;
}
} else {
if ip.add(lit_length) != iend || cpy > oend {
return output_error();
}
ptr::copy(ip, op, lit_length);
op = cpy;
break 'decode;
}
} else {
wild_copy8(op, ip, cpy);
ip = ip.add(lit_length);
op = cpy;
}
offset = read_le16(ip) as usize;
ip = ip.add(2);
match_ptr = (op as *const u8).wrapping_sub(offset);
ml = (token & ML_MASK as u8) as usize;
}
let mut ml_ext = ml;
if ml == ML_MASK as usize {
let ilimit = if src_size >= LASTLITERALS {
iend.sub(LASTLITERALS).add(1)
} else {
src
};
let addl = read_variable_length(&mut ip, ilimit, false);
if addl == RVL_ERROR {
return output_error();
}
ml_ext += addl;
if (op as usize).wrapping_add(ml_ext) < op as usize {
return output_error();
}
}
let match_length: usize = ml_ext + MINMATCH;
if check_offset && (match_ptr as usize).wrapping_add(dict_size) < low_prefix as usize {
return output_error();
}
if dict == DictDirective::UsingExtDict && (match_ptr as *const u8) < low_prefix {
debug_assert!(!dict_end.is_null());
let match_length = if op.add(match_length) > oend.sub(LASTLITERALS) {
if partial_decoding {
(oend as usize - op as usize).min(match_length)
} else {
return output_error();
}
} else {
match_length
};
let copy_size = low_prefix as usize - match_ptr as usize;
if match_length <= copy_size {
let dict_src = dict_end.sub(copy_size);
ptr::copy(dict_src, op, match_length);
op = op.add(match_length);
} else {
let rest_size = match_length - copy_size;
ptr::copy_nonoverlapping(dict_end.sub(copy_size), op, copy_size);
op = op.add(copy_size);
if rest_size > (op as usize - low_prefix as usize) {
let end_of_match: *mut u8 = op.add(rest_size);
let mut copy_from: *const u8 = low_prefix;
while op < end_of_match {
*op = *copy_from;
op = op.add(1);
copy_from = copy_from.add(1);
}
} else {
ptr::copy_nonoverlapping(low_prefix, op, rest_size);
op = op.add(rest_size);
}
}
continue 'decode;
}
debug_assert!(match_ptr >= low_prefix);
let cpy: *mut u8 = op.add(match_length);
if partial_decoding && cpy > oend.sub(MATCH_SAFEGUARD_DISTANCE) {
let mlen = (oend as usize - op as usize).min(match_length);
let match_end: *const u8 = match_ptr.add(mlen);
let copy_end: *mut u8 = op.add(mlen);
if match_end > op as *const u8 {
let mut mp = match_ptr;
while op < copy_end {
*op = *mp;
op = op.add(1);
mp = mp.add(1);
}
} else {
ptr::copy_nonoverlapping(match_ptr, op, mlen);
}
op = copy_end;
if op == oend {
break 'decode;
}
continue 'decode;
}
let mut mp: *const u8 = match_ptr;
if offset < 8 {
write32(op, 0);
*op = *mp;
*op.add(1) = *mp.add(1);
*op.add(2) = *mp.add(2);
*op.add(3) = *mp.add(3);
mp = mp.add(INC32TABLE[offset] as usize);
ptr::copy_nonoverlapping(mp, op.add(4), 4);
mp = mp.offset(-(DEC64TABLE[offset] as isize));
} else {
ptr::copy_nonoverlapping(mp, op, 8);
mp = mp.add(8);
}
op = op.add(8);
if cpy > oend.sub(MATCH_SAFEGUARD_DISTANCE) {
let o_copy_limit: *mut u8 = oend.sub(WILDCOPYLENGTH - 1);
if cpy > oend.sub(LASTLITERALS) {
return output_error();
}
if op < o_copy_limit {
wild_copy8(op, mp, o_copy_limit);
mp = mp.add(o_copy_limit as usize - op as usize);
op = o_copy_limit;
}
while op < cpy {
*op = *mp;
op = op.add(1);
mp = mp.add(1);
}
} else {
ptr::copy_nonoverlapping(mp, op, 8);
if match_length > 16 {
wild_copy8(op.add(8), mp.add(8), cpy);
}
}
op = cpy;
}
Ok(op as usize - dst as usize)
}
pub fn decompress_safe(src: &[u8], dst: &mut [u8]) -> Result<usize, DecompressError> {
if dst.is_empty() {
if src.len() == 1 && src[0] == 0 {
return Ok(0);
}
return output_error();
}
unsafe {
decompress_generic(
src.as_ptr(),
dst.as_mut_ptr(),
src.len(),
dst.len(),
false, DictDirective::NoDict,
dst.as_ptr(), ptr::null(), 0,
)
}
}
pub fn decompress_safe_partial(
src: &[u8],
dst: &mut [u8],
target_output_size: usize,
) -> Result<usize, DecompressError> {
let output_size = target_output_size.min(dst.len());
unsafe {
decompress_generic(
src.as_ptr(),
dst.as_mut_ptr(),
src.len(),
output_size,
true, DictDirective::NoDict,
dst.as_ptr(),
ptr::null(),
0,
)
}
}
pub fn decompress_safe_using_dict(
src: &[u8],
dst: &mut [u8],
dict: &[u8],
) -> Result<usize, DecompressError> {
if dict.is_empty() {
return decompress_safe(src, dst);
}
unsafe {
decompress_generic(
src.as_ptr(),
dst.as_mut_ptr(),
src.len(),
dst.len(),
false,
DictDirective::UsingExtDict,
dst.as_ptr(), dict.as_ptr(),
dict.len(),
)
}
}