use super::{
DecodeError, DecodeStreamConfig, Decompressor, MemberMode, OutputSize,
core::{Input, Output, Stop},
};
use crate::{Window, dictionary::DictionaryRef};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum DecodeOperation {
#[default]
Process,
Finish,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecoderStatus {
NeedsInput,
NeedsOutput,
Finished,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecodeProgress {
pub consumed: usize,
pub produced: usize,
pub status: DecoderStatus,
}
#[derive(Debug, thiserror::Error)]
#[error("{error}")]
pub struct DecodeFailure {
#[source]
pub error: DecodeError,
pub consumed: usize,
pub produced: usize,
}
impl DecodeFailure {
pub fn into_error(self) -> DecodeError {
self.error
}
}
#[derive(Debug)]
pub struct DecoderSession<'d, 'dict> {
decoder: &'d mut Decompressor,
stream: DecodeStreamConfig,
total_in: u64,
total_out: u64,
members: u64,
window: Option<Window>,
final_end: u64,
finishing: bool,
boundary: bool,
finished: bool,
failed: bool,
dictionary: Option<DictionaryRef<'dict>>,
}
impl<'d, 'dict> DecoderSession<'d, 'dict> {
pub(super) fn declared_remaining(&self) -> usize {
usize::try_from(self.decoder.workspace.declared_remaining()).unwrap_or(usize::MAX)
}
pub(super) fn start(
decoder: &'d mut Decompressor,
stream: DecodeStreamConfig,
dictionary: Option<DictionaryRef<'dict>>,
) -> Result<Self, DecodeError> {
if decoder.active {
return Err(DecodeError::AbandonedSession);
}
if let (OutputSize::Exact(expected), Some(limit)) = (
stream.output_size(),
decoder.config.limits().max_output_bytes(),
) && expected > limit
{
return Err(DecodeError::OutputLimitExceeded { limit });
}
if decoder
.config
.limits()
.max_workspace_bytes()
.is_some_and(|limit| decoder.retained_bytes() > limit)
{
decoder.recover();
}
decoder.workspace.reset(decoder.config);
decoder.active = true;
Ok(Self {
decoder,
stream,
total_in: 0,
total_out: 0,
members: 0,
window: None,
final_end: 0,
finishing: false,
boundary: false,
finished: false,
failed: false,
dictionary,
})
}
}
impl DecoderSession<'_, '_> {
pub fn process(
&mut self,
input: &[u8],
output: &mut [u8],
operation: DecodeOperation,
) -> Result<DecodeProgress, DecodeFailure> {
self.process_inner(input, output, operation, None)
}
fn process_inner(
&mut self,
input: &[u8],
output: &mut [u8],
operation: DecodeOperation,
collect: Option<usize>,
) -> Result<DecodeProgress, DecodeFailure> {
let invalid = |error| DecodeFailure {
error,
consumed: 0,
produced: 0,
};
if self.failed {
return Err(invalid(DecodeError::InvalidState));
}
if self.finished {
return Ok(DecodeProgress {
consumed: 0,
produced: 0,
status: DecoderStatus::Finished,
});
}
let Some(end) = self.total_in.checked_add(input.len() as u64) else {
self.failed = true;
return Err(invalid(DecodeError::SizeOverflow));
};
if self.finishing {
if operation != DecodeOperation::Finish || end != self.final_end {
self.failed = true;
return Err(invalid(DecodeError::InvalidState));
}
} else if operation == DecodeOperation::Finish {
self.final_end = end;
self.finishing = true;
}
let config = self.decoder.config;
let limits = config.limits();
let mut input = Input::new(input, self.total_in, limits.max_input_bytes());
let mut output = Output {
collect,
bytes: output,
produced: 0,
total_before: self.total_out,
limit: limits.max_output_bytes(),
exact: self.stream.output_size(),
};
let result = (|| loop {
if self.boundary {
if config.member_mode() == MemberMode::Single
|| (self.finishing && input.consumed == input.bytes.len())
{
if let OutputSize::Exact(expected) = self.stream.output_size() {
let actual = self.total_out + output.produced as u64;
if actual != expected {
return Err(DecodeError::OutputSizeMismatch { expected, actual });
}
}
self.finished = true;
return Ok(DecoderStatus::Finished);
}
if input.consumed == input.bytes.len() {
return Ok(DecoderStatus::NeedsInput);
}
self.decoder.workspace.reset(config);
self.boundary = false;
}
let outcome = self.decoder.workspace.run(
self.decoder.backend,
&mut input,
&mut output,
config,
self.dictionary,
);
if let Some(window) = self.decoder.workspace.window {
self.window = Some(window);
}
match outcome? {
Stop::Input if self.finishing => {
return Err(DecodeError::UnexpectedEndOfInput);
}
Stop::Input => return Ok(DecoderStatus::NeedsInput),
Stop::Output => return Ok(DecoderStatus::NeedsOutput),
Stop::Member => {
self.members = self
.members
.checked_add(1)
.ok_or(DecodeError::SizeOverflow)?;
self.boundary = true;
}
}
})();
self.total_in += input.consumed as u64;
self.total_out += output.produced as u64;
match result {
Ok(status) => Ok(DecodeProgress {
consumed: input.consumed,
produced: output.produced,
status,
}),
Err(error) => {
self.failed = true;
Err(DecodeFailure {
error,
consumed: input.consumed,
produced: output.produced,
})
}
}
}
pub(super) fn collect(&mut self, input: &[u8]) -> Result<DecodeProgress, DecodeFailure> {
let capacity = self.window.map_or(0, |window| {
usize::try_from(1u64 << window.bits()).unwrap_or(usize::MAX)
});
self.process_inner(input, &mut [], DecodeOperation::Finish, Some(capacity))
}
pub(super) fn take_collected(&mut self) -> alloc::vec::Vec<u8> {
self.decoder.workspace.take_collected()
}
pub(super) fn collected(&self) -> &[u8] {
self.decoder.workspace.collected()
}
pub const fn is_finished(&self) -> bool {
self.finished
}
pub const fn total_in(&self) -> u64 {
self.total_in
}
pub const fn total_out(&self) -> u64 {
self.total_out
}
pub const fn members_decoded(&self) -> u64 {
self.members
}
pub const fn window(&self) -> Option<Window> {
self.window
}
}
impl Drop for DecoderSession<'_, '_> {
fn drop(&mut self) {
self.decoder.active = false;
self.decoder.workspace.reset(self.decoder.config);
self.decoder.trim(self.decoder.retention());
}
}