use alloc::vec::Vec;
use crate::encoding::block_input::BlockInput;
use crate::encoding::{CompressionLevel, HistoryBuf, Matcher, Sequence};
pub const MIN_MATCH: u32 = 3;
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ExternalSequence {
pub offset: u32,
pub lit_len: u32,
pub match_len: u32,
pub rep: u32,
}
impl ExternalSequence {
pub const fn delimiter(lit_len: u32) -> Self {
Self {
offset: 0,
lit_len,
match_len: 0,
rep: 0,
}
}
pub const fn is_delimiter(&self) -> bool {
self.offset == 0 && self.match_len == 0
}
}
pub const fn sequence_bound(src_size: usize) -> usize {
(src_size / MIN_MATCH as usize + 1)
+ (src_size / crate::common::MIN_MAX_BLOCK_SIZE as usize + 1)
}
pub const fn producer_parse_heap_bytes(block_size: usize) -> usize {
sequence_bound(block_size) * core::mem::size_of::<ExternalSequence>()
}
pub fn merge_block_delimiters(sequences: &mut [ExternalSequence]) -> usize {
let mut kept = 0;
for index in 0..sequences.len() {
let sequence = sequences[index];
if sequence.is_delimiter() {
if let Some(next) = sequences.get_mut(index + 1) {
next.lit_len = next.lit_len.wrapping_add(sequence.lit_len);
}
} else {
sequences[kept] = sequence;
kept += 1;
}
}
kept
}
pub(crate) fn plain_offset(code: u32, lit_len: u32, history: &mut [u32; 3]) -> u32 {
if code > 3 {
let offset = code - 3;
history[2] = history[1];
history[1] = history[0];
history[0] = offset;
return offset;
}
let index = if lit_len == 0 { code } else { code - 1 };
let offset = match index {
0 => history[0],
1 => history[1],
2 => history[2],
_ => history[0].wrapping_sub(1),
};
if index > 0 {
if index > 1 {
history[2] = history[1];
}
history[1] = history[0];
history[0] = offset;
}
offset
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SequenceError {
LengthMismatch,
MatchTooShort {
index: usize,
},
OffsetTooFar {
index: usize,
},
ZeroOffset {
index: usize,
},
BlockTooLarge,
MatchMismatch {
index: usize,
},
}
impl core::fmt::Display for SequenceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::LengthMismatch => f.write_str("the sequences do not cover the input"),
Self::MatchTooShort { index } => write!(f, "sequence {index} matches too few bytes"),
Self::OffsetTooFar { index } => write!(f, "sequence {index} copies from too far back"),
Self::ZeroOffset { index } => write!(f, "sequence {index} matches at offset 0"),
Self::BlockTooLarge => f.write_str("a block is larger than the block size"),
Self::MatchMismatch { index } => {
write!(f, "sequence {index} copies bytes the input does not hold")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SequenceError {}
pub fn validate_parse(
sequences: &[ExternalSequence],
src_len: usize,
window: u64,
dict_len: usize,
explicit_delimiters: bool,
max_block: usize,
) -> Result<(), SequenceError> {
let mut position: u64 = 0;
let mut block_start: u64 = 0;
for (index, sequence) in sequences.iter().enumerate() {
position += u64::from(sequence.lit_len);
if sequence.is_delimiter() {
if explicit_delimiters {
if position - block_start > max_block as u64 {
return Err(SequenceError::BlockTooLarge);
}
block_start = position;
}
continue;
}
if sequence.offset == 0 {
return Err(SequenceError::ZeroOffset { index });
}
if sequence.match_len < MIN_MATCH {
return Err(SequenceError::MatchTooShort { index });
}
let reach = if position > window {
window
} else {
position + dict_len as u64
};
if u64::from(sequence.offset) > reach {
return Err(SequenceError::OffsetTooFar { index });
}
position += u64::from(sequence.match_len);
if position > src_len as u64 {
return Err(SequenceError::LengthMismatch);
}
}
let covered = if explicit_delimiters {
position == src_len as u64
} else {
position <= src_len as u64
};
if !covered {
return Err(SequenceError::LengthMismatch);
}
Ok(())
}
pub fn parse_reproduces(
sequences: &[ExternalSequence],
src: &[u8],
dict: &[u8],
) -> Result<(), SequenceError> {
let mut position = 0usize;
for (index, sequence) in sequences.iter().enumerate() {
position = position
.checked_add(sequence.lit_len as usize)
.ok_or(SequenceError::LengthMismatch)?;
if sequence.is_delimiter() {
continue;
}
let offset = sequence.offset as usize;
let len = sequence.match_len as usize;
let end = position
.checked_add(len)
.filter(|&end| end <= src.len())
.ok_or(SequenceError::LengthMismatch)?;
let target = &src[position..end];
let from_dict = if offset > position {
(offset - position).min(len)
} else {
0
};
if from_dict > 0 {
let start = dict
.len()
.checked_sub(offset - position)
.ok_or(SequenceError::OffsetTooFar { index })?;
if dict[start..start + from_dict] != target[..from_dict] {
return Err(SequenceError::MatchMismatch { index });
}
}
if len > from_dict {
let from = position + from_dict - offset;
if src[from..from + (len - from_dict)] != target[from_dict..] {
return Err(SequenceError::MatchMismatch { index });
}
}
position = end;
}
Ok(())
}
pub struct RebuiltSource<'a> {
sequences: core::slice::Iter<'a, ExternalSequence>,
literals: &'a [u8],
dict: &'a [u8],
window: usize,
lit_at: usize,
lit_left: usize,
match_left: usize,
offset: usize,
produced: usize,
history: Vec<u8>,
len: usize,
}
impl<'a> RebuiltSource<'a> {
pub fn new(
sequences: &'a [ExternalSequence],
literals: &'a [u8],
dict: &'a [u8],
window: usize,
) -> Result<Self, SequenceError> {
let mut position = 0usize;
let mut lits = 0usize;
for (index, sequence) in sequences.iter().enumerate() {
let lit_len = sequence.lit_len as usize;
lits = lits
.checked_add(lit_len)
.filter(|lits| *lits <= literals.len())
.ok_or(SequenceError::LengthMismatch)?;
position += lit_len;
if sequence.match_len == 0 {
continue;
}
let offset = sequence.offset as usize;
if offset == 0 {
return Err(SequenceError::ZeroOffset { index });
}
let past_reach = if position > window {
offset > window
} else {
position
.checked_add(dict.len())
.is_some_and(|reach| offset > reach)
};
if past_reach {
return Err(SequenceError::OffsetTooFar { index });
}
position = position
.checked_add(sequence.match_len as usize)
.ok_or(SequenceError::LengthMismatch)?;
}
let len = position
.checked_add(literals.len() - lits)
.ok_or(SequenceError::LengthMismatch)?;
Ok(Self {
sequences: sequences.iter(),
literals,
dict,
window,
lit_at: 0,
lit_left: 0,
match_left: 0,
offset: 0,
produced: 0,
history: Vec::new(),
len,
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
fn copy_match(&mut self, want: usize) {
let mut left = want;
while left > 0 {
let take = if self.offset > self.produced {
let back = self.offset - self.produced;
let from = self.dict.len() - back;
let take = left.min(back);
self.history
.extend_from_slice(&self.dict[from..from + take]);
take
} else {
let from = self.history.len() - self.offset;
let take = left.min(self.offset);
self.history.extend_from_within(from..from + take);
take
};
self.produced += take;
left -= take;
}
self.match_left -= want;
}
}
impl crate::io::Read for RebuiltSource<'_> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, crate::io::Error> {
let mut filled = 0;
while filled < buf.len() {
let room = buf.len() - filled;
let start = self.history.len();
if self.lit_left > 0 {
let n = self.lit_left.min(room);
self.history
.extend_from_slice(&self.literals[self.lit_at..self.lit_at + n]);
self.lit_at += n;
self.lit_left -= n;
self.produced += n;
} else if self.match_left > 0 {
self.copy_match(self.match_left.min(room));
} else if let Some(sequence) = self.sequences.next() {
self.lit_left = sequence.lit_len as usize;
self.match_left = sequence.match_len as usize;
self.offset = sequence.offset as usize;
continue;
} else if self.lit_at < self.literals.len() {
self.lit_left = self.literals.len() - self.lit_at;
continue;
} else {
break;
}
let n = self.history.len() - start;
buf[filled..filled + n].copy_from_slice(&self.history[start..]);
filled += n;
}
let keep = if self.match_left > 0 {
self.window.max(self.offset)
} else {
self.window
};
if keep
.checked_mul(2)
.is_some_and(|limit| self.history.len() > limit)
{
let drop = self.history.len() - keep;
self.history.drain(..drop);
}
Ok(filled)
}
}
pub struct SequenceReplay<'s> {
sequences: &'s [ExternalSequence],
next: usize,
lit_taken: u32,
match_taken: u32,
input: BlockInput,
window_size: u64,
dictionary_priming: bool,
delimited: bool,
}
impl<'s> SequenceReplay<'s> {
pub fn new(sequences: &'s [ExternalSequence], window_size: u64) -> Self {
Self {
sequences,
next: 0,
lit_taken: 0,
match_taken: 0,
input: BlockInput::default(),
window_size,
dictionary_priming: false,
delimited: sequences.iter().any(ExternalSequence::is_delimiter),
}
}
pub fn with_dictionary(mut self) -> Self {
self.dictionary_priming = true;
self
}
fn skip(&mut self, len: usize) {
self.walk(len, &mut |_| {});
}
fn walk(&mut self, len: usize, emit: &mut dyn FnMut(Sequence)) {
let mut remaining = len as u64;
let mut literals = 0usize;
while remaining > 0 {
let Some(sequence) = self.sequences.get(self.next) else {
break;
};
let lits_left = sequence.lit_len - self.lit_taken;
let take = u64::from(lits_left).min(remaining) as u32;
self.lit_taken += take;
literals += take as usize;
remaining -= u64::from(take);
if self.lit_taken < sequence.lit_len {
break;
}
let match_left = sequence.match_len - self.match_taken;
let take = u64::from(match_left).min(remaining) as u32;
if take > 0 {
if take >= MIN_MATCH {
emit(Sequence::Triple {
literal_len: literals,
offset: sequence.offset as usize,
match_len: take as usize,
});
literals = 0;
} else {
literals += take as usize;
}
self.match_taken += take;
remaining -= u64::from(take);
}
if self.match_taken == sequence.match_len {
self.next += 1;
self.lit_taken = 0;
self.match_taken = 0;
}
}
literals += remaining as usize;
if literals > 0 {
emit(Sequence::Literals { len: literals });
}
}
}
impl Matcher for SequenceReplay<'_> {
fn get_last_space(&mut self) -> &[u8] {
self.input.last_block()
}
fn fill_in_place(
&mut self,
capacity: usize,
fill: &mut dyn FnMut(&mut HistoryBuf) -> (usize, bool),
) -> (usize, bool) {
self.input.fill(capacity, fill)
}
fn uncommitted_input(&self) -> &[u8] {
self.input.uncommitted()
}
fn commit_filled(&mut self, len: usize) {
self.input.commit(len);
}
fn skip_matching(&mut self) {
let len = self.input.last_block().len();
self.skip(len);
}
fn start_matching(&mut self, mut handle_sequence: impl FnMut(Sequence)) {
let len = self.input.last_block().len();
self.walk(len, &mut handle_sequence);
}
fn reset(&mut self, _level: CompressionLevel) {
self.input.clear();
self.next = 0;
self.lit_taken = 0;
self.match_taken = 0;
}
fn supports_dictionary_priming(&self) -> bool {
self.dictionary_priming
}
fn window_size(&self) -> u64 {
self.window_size
}
fn fixed_block_len(&self) -> Option<usize> {
if !self.delimited {
return None;
}
let mut len = 0u64;
let (mut lit_taken, mut match_taken) = (self.lit_taken, self.match_taken);
for sequence in &self.sequences[self.next..] {
len += u64::from(sequence.lit_len - lit_taken)
+ u64::from(sequence.match_len - match_taken);
(lit_taken, match_taken) = (0, 0);
if sequence.is_delimiter() && len > 0 {
return usize::try_from(len).ok();
}
}
None
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SequenceProducerError;
pub trait SequenceProducer {
fn produce(
&mut self,
block: &[u8],
window_size: u64,
out: &mut Vec<ExternalSequence>,
) -> Result<(), SequenceProducerError>;
}
#[cfg(test)]
mod tests;