use super::tables::*;
use super::Error;
type BitBuf = usize;
const WORDBYTES: usize = size_of::<BitBuf>();
const BITBUF_NBITS: u32 = BitBuf::BITS;
const MAX_BITSLEFT: u32 = BITBUF_NBITS - 1;
const CONSUMABLE_NBITS: u32 = MAX_BITSLEFT - 7;
const FASTLOOP_PRELOADABLE_NBITS: u32 = BITBUF_NBITS;
const PRELOAD_SLACK: u32 = FASTLOOP_PRELOADABLE_NBITS - MAX_BITSLEFT;
const fn can_consume(n: u32) -> bool {
CONSUMABLE_NBITS >= n
}
const fn can_consume_and_then_preload(consume: u32, preload: u32) -> bool {
CONSUMABLE_NBITS >= consume && FASTLOOP_PRELOADABLE_NBITS >= consume + preload
}
const FASTLOOP_MAX_BYTES_WRITTEN: usize = 2 + MAX_MATCH_LEN + (5 * WORDBYTES) - 1;
const TAIL_MIN_OUT_ROOM: usize = 3;
const FASTLOOP_MAX_BYTES_READ: usize =
(MAX_BITSLEFT + (2 * LITLEN_TABLEBITS) + LENGTH_MAXBITS + OFFSET_MAXBITS).div_ceil(8) as usize
+ WORDBYTES;
const EXTRA_LITERALS: bool =
can_consume_and_then_preload(2 * LITLEN_TABLEBITS + LENGTH_MAXBITS, OFFSET_TABLEBITS)
&& can_consume_and_then_preload(
2 * LITLEN_TABLEBITS + MAX_LITLEN_CODEWORD_LEN,
LITLEN_TABLEBITS,
);
const OFFSET_MAX_REFILL_BITS: u32 = if OFFSET_MAXBITS - OFFSET_TABLEBITS > OFFSET_MAXFASTBITS {
OFFSET_MAXBITS - OFFSET_TABLEBITS
} else {
OFFSET_MAXFASTBITS
};
const DEFLATE_BLOCKTYPE_UNCOMPRESSED: BitBuf = 0;
const DEFLATE_BLOCKTYPE_STATIC_HUFFMAN: BitBuf = 1;
const DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN: BitBuf = 2;
pub(super) const PRECODE_LENS_PERMUTATION: [u8; NUM_PRECODE_SYMS] = [
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
];
#[inline(always)]
const fn bitmask(n: u32) -> BitBuf {
(1 << n) - 1
}
#[inline(always)]
fn entry_value(entry: u32, saved: BitBuf) -> usize {
(entry >> 16) as usize + ((saved & bitmask(entry as u8 as u32)) >> (entry >> 8) as u8)
}
#[derive(Clone, Copy, Debug)]
struct Bits {
buf: BitBuf,
left: u32,
}
impl Bits {
#[inline(always)]
fn avail(self) -> u32 {
self.left as u8 as u32
}
#[inline(always)]
fn peek(self, n: u32) -> usize {
self.buf & bitmask(n)
}
#[inline(always)]
fn consume(&mut self, n: u32) {
self.buf >>= n;
self.left = self.left.wrapping_sub(n);
}
#[inline(always)]
fn consume_entry(&mut self, entry: u32) {
self.buf >>= entry as u8;
self.left = self.left.wrapping_sub(entry);
}
#[inline(always)]
fn subtable_index(self, entry: u32) -> usize {
(entry >> 16) as usize + self.peek((entry >> 8) & 0x3F)
}
#[inline(always)]
fn refill(&mut self, word: BitBuf) -> usize {
self.buf |= word << (self.left as u8);
let held = ((self.left >> 3) & 7) as usize;
self.left |= MAX_BITSLEFT & !7;
held
}
}
#[derive(Clone, Copy, Debug)]
struct Cursor {
in_pos: usize,
out_pos: usize,
bits: Bits,
overread: usize,
}
impl Cursor {
const START: Cursor = Cursor {
in_pos: 0,
out_pos: 0,
bits: Bits { buf: 0, left: 0 },
overread: 0,
};
fn whole_bytes(self) -> usize {
(self.bits.avail() >> 3) as usize
}
fn consumed_overread(self) -> bool {
self.overread > self.whole_bytes()
}
fn align_to_byte(&mut self) {
self.in_pos -= self.whole_bytes() - self.overread;
self.overread = 0;
self.bits = Cursor::START.bits;
}
}
#[derive(Clone, Debug)]
pub(super) struct Resume {
at: Cursor,
in_block: Option<bool>,
stored_left: u16,
match_left: u16,
match_offset: u16,
need_input: bool,
}
impl Resume {
pub(super) const START: Resume = Resume {
at: Cursor::START,
in_block: None,
stored_left: 0,
match_left: 0,
match_offset: 0,
need_input: false,
};
fn new(mut at: Cursor, in_block: Option<bool>, need_input: bool) -> Self {
if !at.consumed_overread() {
let partial = at.bits.avail() & 7;
let buf = at.bits.peek(partial);
at.align_to_byte();
at.bits = Bits { buf, left: partial };
}
Resume {
at,
in_block,
need_input,
..Resume::START
}
}
pub(super) fn in_pos(&self) -> usize {
self.at.in_pos
}
pub(super) fn set_in_pos(&mut self, pos: usize) {
self.at.in_pos = pos;
}
pub(super) fn out_pos(&self) -> usize {
self.at.out_pos
}
pub(super) fn set_out_pos(&mut self, pos: usize) {
self.at.out_pos = pos;
}
pub(super) fn needs_input(&self) -> bool {
self.need_input
}
}
#[derive(Debug)]
pub(super) enum Status {
Done(usize, usize),
Full(Resume),
}
enum FastExit {
Margin,
EndOfBlock,
BadData,
}
#[inline(always)]
fn bad_offset<const WRITE: bool>(offset: usize, out_pos: usize) -> bool {
if WRITE {
out_pos.wrapping_sub(offset) >= out_pos
} else {
offset.wrapping_sub(1) >= out_pos
}
}
const FASTLOOP_IN_WINDOW: usize = if FASTLOOP_MAX_BYTES_READ > 4 * (WORDBYTES - 1) + WORDBYTES {
FASTLOOP_MAX_BYTES_READ
} else {
4 * (WORDBYTES - 1) + WORDBYTES
};
struct FastState<'a> {
input: &'a [u8],
output: &'a mut [u8],
out_len: usize,
in_pos: usize,
out_pos: usize,
bits: Bits,
}
impl<'a> FastState<'a> {
#[inline(always)]
fn new(input: &'a [u8], output: &'a mut [u8], out_len: usize, c: &Cursor) -> Self {
FastState {
input,
output,
out_len,
in_pos: c.in_pos,
out_pos: c.out_pos,
bits: c.bits,
}
}
#[inline(always)]
fn finish(self, c: &mut Cursor) {
c.in_pos = self.in_pos;
c.out_pos = self.out_pos;
c.bits = self.bits;
}
}
#[inline(always)]
fn fastloop<const WRITE: bool, const TAIL: bool>(t: &Tables, s: &mut FastState<'_>) -> FastExit {
let FastState {
input,
ref mut output,
out_len,
in_pos,
mut out_pos,
mut bits,
} = *s;
let mut saved_bitbuf: BitBuf;
let mut entry: u32;
let litlen_tablemask = bitmask(LITLEN_TABLEBITS);
let mut rest = &input[in_pos..];
macro_rules! refill {
() => {
let held = bits.refill(BitBuf::from_le_bytes(*rest.first_chunk().unwrap()));
rest = &rest[WORDBYTES - 1 - held..];
};
}
macro_rules! write_literal {
($e:expr) => {
let e: u32 = $e;
if WRITE {
output[out_pos] = (e >> 8) as u8;
}
out_pos += 1;
};
}
let exit = 'done: {
let out_len = if WRITE { output.len() } else { out_len };
let margin = if TAIL {
TAIL_MIN_OUT_ROOM - 1
} else {
FASTLOOP_MAX_BYTES_WRITTEN
};
let Some(out_end) = out_len.checked_sub(margin) else {
break 'done FastExit::Margin;
};
if rest.len() < FASTLOOP_IN_WINDOW || out_pos >= out_end {
break 'done FastExit::Margin;
}
refill!();
entry = t.litlen[bits.peek(LITLEN_TABLEBITS)];
loop {
if rest.len() < FASTLOOP_IN_WINDOW || out_pos >= out_end {
break 'done FastExit::Margin;
}
'next: {
saved_bitbuf = bits.buf;
bits.consume_entry(entry);
if entry & HUFFDEC_LITERAL != 0 {
if EXTRA_LITERALS {
let mut scratch = [0; 3];
let run: &mut [u8; 3] = if WRITE {
output[out_pos..].first_chunk_mut().unwrap()
} else {
&mut scratch
};
let mut n = 0;
macro_rules! write_literal {
($e:expr) => {
let e: u32 = $e;
run[n] = (e >> 8) as u8;
n += 1;
};
}
let lit = entry;
entry = t.litlen[bits.buf & litlen_tablemask];
saved_bitbuf = bits.buf;
bits.consume_entry(entry);
write_literal!(lit);
if entry & HUFFDEC_LITERAL != 0 {
let lit = entry;
entry = t.litlen[bits.buf & litlen_tablemask];
saved_bitbuf = bits.buf;
bits.consume_entry(entry);
write_literal!(lit);
if entry & HUFFDEC_LITERAL != 0 {
let lit = entry;
entry = t.litlen[bits.buf & litlen_tablemask];
refill!();
write_literal!(lit);
out_pos += n;
break 'next;
}
}
out_pos += n;
} else {
const _: () = assert!(can_consume_and_then_preload(
LITLEN_TABLEBITS,
LITLEN_TABLEBITS
));
let lit = entry;
entry = t.litlen[bits.buf & litlen_tablemask];
refill!();
write_literal!(lit);
break 'next;
}
}
if TAIL
&& (entry & HUFFDEC_SUBTABLE_POINTER != 0
|| (entry & HUFFDEC_EXCEPTIONAL == 0
&& entry_value(entry, saved_bitbuf) > out_len - out_pos))
{
bits.buf = saved_bitbuf;
bits.left = bits.left.wrapping_add(entry);
break 'done FastExit::Margin;
}
if entry & HUFFDEC_EXCEPTIONAL != 0 {
if entry & HUFFDEC_END_OF_BLOCK != 0 {
break 'done if entry & HUFFDEC_INVALID != 0 {
FastExit::BadData
} else {
FastExit::EndOfBlock
};
}
entry = t.litlen[bits.subtable_index(entry)];
saved_bitbuf = bits.buf;
bits.consume_entry(entry);
if !can_consume_and_then_preload(MAX_LITLEN_CODEWORD_LEN, LITLEN_TABLEBITS)
|| !can_consume_and_then_preload(LENGTH_MAXBITS, OFFSET_TABLEBITS)
{
refill!();
}
if entry & HUFFDEC_LITERAL != 0 {
let lit = entry;
entry = t.litlen[bits.buf & litlen_tablemask];
refill!();
write_literal!(lit);
break 'next;
}
if entry & HUFFDEC_END_OF_BLOCK != 0 {
break 'done FastExit::EndOfBlock;
}
}
let length = entry_value(entry, saved_bitbuf);
const _: () = assert!(can_consume_and_then_preload(
LENGTH_MAXFASTBITS,
OFFSET_TABLEBITS
));
entry = t.offset[bits.peek(OFFSET_TABLEBITS)];
if can_consume_and_then_preload(OFFSET_MAXBITS, LITLEN_TABLEBITS) {
if entry & HUFFDEC_EXCEPTIONAL != 0 {
if bits.avail() < OFFSET_MAXBITS + LITLEN_TABLEBITS - PRELOAD_SLACK {
refill!();
}
bits.consume(OFFSET_TABLEBITS);
entry = t.offset[bits.subtable_index(entry)];
} else if bits.avail() < OFFSET_MAXFASTBITS + LITLEN_TABLEBITS - PRELOAD_SLACK {
refill!();
}
} else {
refill!();
if entry & HUFFDEC_EXCEPTIONAL != 0 {
bits.consume(OFFSET_TABLEBITS);
entry = t.offset[bits.subtable_index(entry)];
refill!();
const _: () = assert!(can_consume(OFFSET_MAXBITS - OFFSET_TABLEBITS));
} else {
const _: () = assert!(can_consume(OFFSET_MAXFASTBITS));
}
}
saved_bitbuf = bits.buf;
bits.consume_entry(entry);
let offset = entry_value(entry, saved_bitbuf);
if bad_offset::<WRITE>(offset, out_pos) {
break 'done FastExit::BadData;
}
if !can_consume_and_then_preload(OFFSET_MAX_REFILL_BITS, LITLEN_TABLEBITS)
&& bits.avail() < LITLEN_TABLEBITS - PRELOAD_SLACK
{
refill!();
}
entry = t.litlen[bits.buf & litlen_tablemask];
refill!();
if WRITE {
if TAIL {
copy_match_slow(output, out_pos, offset, length);
} else {
copy_match(output, out_pos, offset, length);
}
}
out_pos += length;
}
}
};
(s.in_pos, s.out_pos, s.bits) = (input.len() - rest.len(), out_pos, bits);
exit
}
const MATCH_SPAN: usize = MAX_MATCH_LEN + 5 * WORDBYTES - 1;
#[inline(always)]
fn copy_match(output: &mut [u8], dst: usize, offset: usize, length: usize) {
let length = length.min(MAX_MATCH_LEN);
let w = &mut output[dst - offset..dst + MATCH_SPAN];
let mut i = 0;
macro_rules! copy_word {
($k:expr) => {
let x = *w[i + $k..].first_chunk::<WORDBYTES>().unwrap();
*w[offset + i + $k..].first_chunk_mut().unwrap() = x;
};
}
if offset >= WORDBYTES {
loop {
for k in 0..5 {
copy_word!(k * WORDBYTES);
}
i += 5 * WORDBYTES;
if i >= length {
break;
}
}
} else if offset == 1 {
let b = w[0];
loop {
w[1 + i..][..4 * WORDBYTES].fill(b);
i += 4 * WORDBYTES;
if i >= length {
break;
}
}
} else {
loop {
copy_word!(0);
copy_word!(offset);
i += 2 * offset;
if i >= length {
break;
}
}
}
}
#[inline(always)]
fn copy_match_slow(output: &mut [u8], dst: usize, offset: usize, length: usize) {
if length <= 16 {
for i in dst..dst + length {
output[i] = output[i - offset];
}
return;
}
let src = dst - offset;
let end = dst + length;
let mut pos = dst;
while pos < end {
let n = (pos - src).min(end - pos);
output.copy_within(src..src + n, pos);
pos += n;
}
}
#[inline]
pub(super) fn run<const RESUMABLE: bool, const WRITE: bool>(
t: &mut Tables,
input: &[u8],
output: &mut [u8],
out_len: usize,
resume: Option<&Resume>,
more_input: bool,
) -> Result<Status, Error> {
#[cfg(all(target_arch = "x86_64", not(target_feature = "bmi2"), feature = "std"))]
{
if !cfg!(sirocco_no_bmi2) && std::is_x86_feature_detected!("bmi2") {
return unsafe {
run_bmi2::<RESUMABLE, WRITE>(t, input, output, out_len, resume, more_input)
};
}
}
decompress::<RESUMABLE, WRITE>(
t,
input,
output,
out_len,
resume,
more_input,
|t, s, tail| {
if WRITE && tail {
fastloop_default::<WRITE, true>(t, s)
} else {
fastloop_default::<WRITE, false>(t, s)
}
},
)
}
#[inline(never)]
fn fastloop_default<const WRITE: bool, const TAIL: bool>(
t: &Tables,
s: &mut FastState<'_>,
) -> FastExit {
fastloop::<WRITE, TAIL>(t, s)
}
#[cfg(all(target_arch = "x86_64", not(target_feature = "bmi2"), feature = "std"))]
#[target_feature(enable = "bmi2")]
fn run_bmi2<const RESUMABLE: bool, const WRITE: bool>(
t: &mut Tables,
input: &[u8],
output: &mut [u8],
out_len: usize,
resume: Option<&Resume>,
more_input: bool,
) -> Result<Status, Error> {
decompress::<RESUMABLE, WRITE>(
t,
input,
output,
out_len,
resume,
more_input,
|t, s, tail| {
if WRITE && tail {
fastloop_bmi2::<WRITE, true>(t, s)
} else {
fastloop_bmi2::<WRITE, false>(t, s)
}
},
)
}
#[cfg(all(target_arch = "x86_64", not(target_feature = "bmi2"), feature = "std"))]
#[target_feature(enable = "bmi2")]
#[inline(never)]
fn fastloop_bmi2<const WRITE: bool, const TAIL: bool>(
t: &Tables,
s: &mut FastState<'_>,
) -> FastExit {
fastloop::<WRITE, TAIL>(t, s)
}
#[inline(always)]
fn decompress<const RESUMABLE: bool, const WRITE: bool>(
t: &mut Tables,
input: &[u8],
output: &mut [u8],
out_len: usize,
resume: Option<&Resume>,
more_input: bool,
fastloop: impl Fn(&Tables, &mut FastState<'_>, bool) -> FastExit,
) -> Result<Status, Error> {
use Error::*;
let resume = if RESUMABLE { resume } else { None };
let out_len = if WRITE { output.len() } else { out_len };
let more_input = RESUMABLE && more_input;
let mut c = resume.map_or(Cursor::START, |r| r.at);
assert!(c.in_pos <= input.len() && c.out_pos <= out_len);
let mut resume_in_block = resume.and_then(|r| r.in_block);
let mut resume_stored = resume.map_or(0, |r| r.stored_left);
if let Some(r) = resume.filter(|r| r.match_left > 0) {
let (left, offset) = (usize::from(r.match_left), usize::from(r.match_offset));
if offset > c.out_pos {
return Err(BadData);
}
let n = left.min(out_len - c.out_pos);
if WRITE {
copy_match_slow(output, c.out_pos, offset, n);
}
c.out_pos += n;
if n < left {
return Ok(Status::Full(Resume {
at: c,
match_left: (left - n) as u16,
..r.clone()
}));
}
}
macro_rules! pause {
($at:expr, $in_block:expr, $need_input:expr) => {
return Ok(Status::Full(Resume::new($at, $in_block, $need_input)))
};
}
macro_rules! pause_stored {
($left:expr, $is_final_block:expr, $need_input:expr) => {
return Ok(Status::Full(Resume {
stored_left: $left as u16,
..Resume::new(c, Some($is_final_block), $need_input)
}))
};
}
macro_rules! full {
($at:expr, $in_block:expr) => {{
if c.consumed_overread() {
truncated!();
}
pause!($at, $in_block, false)
}};
}
let mut last = (c, resume_in_block);
macro_rules! truncated {
() => {{
if more_input {
pause!(last.0, last.1, true);
}
return Err(Truncated);
}};
}
macro_rules! bad {
() => {{
if c.consumed_overread() {
truncated!();
}
return Err(BadData);
}};
}
macro_rules! refill_bits {
() => {
if let Some(word) = input.get(c.in_pos..c.in_pos + WORDBYTES) {
let held = c
.bits
.refill(BitBuf::from_le_bytes(word.try_into().unwrap()));
c.in_pos += WORDBYTES - 1 - held;
} else {
while c.bits.avail() < CONSUMABLE_NBITS {
if let Some(&b) = input.get(c.in_pos) {
c.bits.buf |= (b as BitBuf) << c.bits.avail();
c.in_pos += 1;
} else {
c.overread += 1;
if c.overread > WORDBYTES {
bad!();
}
}
c.bits.left = c.bits.left.wrapping_add(8);
}
}
};
}
loop {
if more_input && c.consumed_overread() {
pause!(last.0, last.1, true);
}
last = (c, resume_in_block);
let is_final_block;
let mut stored = None;
if let Some(f) = resume_in_block.take() {
is_final_block = f;
if resume_stored > 0 {
stored = Some(usize::from(core::mem::take(&mut resume_stored)));
}
} else {
const _: () = assert!(can_consume(1 + 2 + 5 + 5 + 4));
refill_bits!();
is_final_block = c.bits.buf & 1 != 0;
let block_type = (c.bits.buf >> 1) & bitmask(2);
if block_type == DEFLATE_BLOCKTYPE_DYNAMIC_HUFFMAN {
let num_litlen_syms = 257 + ((c.bits.buf >> 3) & bitmask(5)) as usize;
let num_offset_syms = 1 + ((c.bits.buf >> 8) & bitmask(5)) as usize;
let num_explicit_precode_lens = 4 + ((c.bits.buf >> 13) & bitmask(4)) as usize;
c.bits.consume(17);
if num_litlen_syms > 286 || num_offset_syms > 30 {
bad!();
}
let (explicit, implicit) =
PRECODE_LENS_PERMUTATION.split_at(num_explicit_precode_lens);
for &sym in explicit {
if c.bits.avail() < 3 {
refill_bits!();
}
t.precode_lens[sym as usize] = c.bits.peek(3) as u8;
c.bits.consume(3);
}
for &sym in implicit {
t.precode_lens[sym as usize] = 0;
}
if !t.build_precode() {
bad!();
}
let num_lens = num_litlen_syms + num_offset_syms;
let lens = &mut t.lens;
let mut i = 0;
while i < num_lens {
if c.bits.avail() < MAX_PRE_CODEWORD_LEN + 7 {
refill_bits!();
}
const _: () = assert!(PRECODE_TABLEBITS == MAX_PRE_CODEWORD_LEN);
let entry = t.precode[c.bits.peek(MAX_PRE_CODEWORD_LEN)];
c.bits.consume_entry(entry);
let presym = entry >> 16;
if presym < 16 {
lens[i] = presym as u8;
i += 1;
} else if presym == 16 {
let rep_count = 3 + c.bits.peek(2);
c.bits.consume(2);
if i == 0 {
bad!();
}
let rep_val = lens[i - 1];
lens[i..i + 6].fill(rep_val);
i += rep_count;
} else if presym == 17 {
let rep_count = 3 + c.bits.peek(3);
c.bits.consume(3);
lens[i..i + 10].fill(0);
i += rep_count;
} else {
let rep_count = 11 + c.bits.peek(7);
c.bits.consume(7);
lens[i..i + rep_count].fill(0);
i += rep_count;
}
}
if i != num_lens {
bad!();
}
if lens[256] == 0 {
bad!();
}
if !t.build_dynamic(num_litlen_syms, num_offset_syms) {
bad!();
}
} else if block_type == DEFLATE_BLOCKTYPE_UNCOMPRESSED {
c.bits.consume(3);
if c.consumed_overread() {
bad!();
}
c.align_to_byte();
let Some(header) = input.get(c.in_pos..c.in_pos + 4) else {
truncated!();
};
let len = u16::from_le_bytes([header[0], header[1]]);
let nlen = u16::from_le_bytes([header[2], header[3]]);
if len != !nlen {
bad!();
}
let len = len as usize;
let data = input.get(c.in_pos + 4..c.in_pos + 4 + len);
match data {
Some(data) if len <= out_len - c.out_pos => {
if WRITE {
output[c.out_pos..c.out_pos + len].copy_from_slice(data);
}
c.in_pos += 4 + len;
c.out_pos += len;
if is_final_block {
break;
}
continue;
}
_ => {
c.in_pos += 4;
stored = Some(len);
}
}
} else {
if block_type != DEFLATE_BLOCKTYPE_STATIC_HUFFMAN {
bad!();
}
c.bits.consume(3);
t.load_static();
}
}
if let Some(left) = stored {
let n = left.min(input.len() - c.in_pos).min(out_len - c.out_pos);
if WRITE {
output[c.out_pos..c.out_pos + n].copy_from_slice(&input[c.in_pos..c.in_pos + n]);
}
c.in_pos += n;
c.out_pos += n;
if n < left {
if !more_input && c.in_pos == input.len() {
return Err(Truncated);
}
pause_stored!(left - n, is_final_block, c.out_pos < out_len);
}
if is_final_block {
break;
}
continue;
}
'block_done: {
let mut s = FastState::new(input, output, out_len, &c);
let near_end =
|s: &FastState<'_>| WRITE && s.out_len - s.out_pos < FASTLOOP_MAX_BYTES_WRITTEN;
let mut exit = if near_end(&s) {
FastExit::Margin
} else {
fastloop(t, &mut s, false)
};
if matches!(exit, FastExit::Margin) && near_end(&s) {
exit = fastloop(t, &mut s, true);
}
s.finish(&mut c);
match exit {
FastExit::EndOfBlock => break 'block_done,
FastExit::BadData => bad!(),
FastExit::Margin => {}
}
loop {
if more_input && c.consumed_overread() {
pause!(last.0, last.1, true);
}
let symbol_start = c;
if RESUMABLE {
last = (c, Some(is_final_block));
}
refill_bits!();
let mut entry = t.litlen[c.bits.peek(LITLEN_TABLEBITS)];
let mut saved_bitbuf = c.bits.buf;
c.bits.consume_entry(entry);
if entry & (HUFFDEC_LITERAL | HUFFDEC_SUBTABLE_POINTER) == HUFFDEC_SUBTABLE_POINTER
{
entry = t.litlen[c.bits.subtable_index(entry)];
saved_bitbuf = c.bits.buf;
c.bits.consume_entry(entry);
}
if entry & HUFFDEC_LITERAL != 0 {
if c.out_pos == out_len {
full!(symbol_start, Some(is_final_block));
}
if WRITE {
output[c.out_pos] = (entry >> 8) as u8;
}
c.out_pos += 1;
continue;
}
if entry & HUFFDEC_END_OF_BLOCK != 0 {
if entry & HUFFDEC_INVALID != 0 {
bad!();
}
break 'block_done;
}
let length = entry_value(entry, saved_bitbuf);
if !can_consume(LENGTH_MAXBITS + OFFSET_MAXBITS) {
refill_bits!();
}
entry = t.offset[c.bits.peek(OFFSET_TABLEBITS)];
if entry & HUFFDEC_EXCEPTIONAL != 0 {
c.bits.consume(OFFSET_TABLEBITS);
entry = t.offset[c.bits.subtable_index(entry)];
if !can_consume(OFFSET_MAXBITS) {
refill_bits!();
}
}
let offset = entry_value(entry, c.bits.buf);
c.bits.consume_entry(entry);
if offset == 0 {
bad!();
}
let room = out_len - c.out_pos;
if room == 0 {
full!(symbol_start, Some(is_final_block));
}
if bad_offset::<WRITE>(offset, c.out_pos) {
bad!();
}
if length > room {
if c.consumed_overread() {
truncated!();
}
if WRITE {
copy_match_slow(output, c.out_pos, offset, room);
}
c.out_pos += room;
return Ok(Status::Full(Resume {
match_left: (length - room) as u16,
match_offset: offset as u16,
..Resume::new(c, Some(is_final_block), false)
}));
}
if WRITE {
copy_match_slow(output, c.out_pos, offset, length);
}
c.out_pos += length;
}
}
if is_final_block {
break;
}
}
if c.consumed_overread() {
bad!();
}
c.align_to_byte();
Ok(Status::Done(c.in_pos, c.out_pos))
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn copy_match_slow_matches_byte_copy() {
let start: Vec<u8> = (0..300u32).map(|i| (i * 7 + 3) as u8).collect();
for offset in 1..=70 {
for length in 3..=MAX_MATCH_LEN {
let dst = 100;
let mut want = start.clone();
want.resize(dst + length + 8, 0);
let mut got = want.clone();
for i in dst..dst + length {
want[i] = want[i - offset];
}
copy_match_slow(&mut got, dst, offset, length);
assert_eq!(got, want, "offset {offset}, length {length}");
}
}
}
}