use alloc::vec::Vec;
use super::driver::Encoder;
use super::fast::FastEncoder;
use super::rfc9841::context::SharedContextInner;
use crate::compressor::session::{EncoderStatus, Operation, Progress};
use crate::compressor::{BrotliCompressError, BrotliResult};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(super) enum Phase {
Open,
Flushed,
Finished,
Failed,
}
#[derive(Debug)]
pub(super) struct StreamState {
pub(super) phase: Phase,
limit: usize,
flint: bool,
}
pub(super) enum Destination<'a> {
Slice(&'a mut [u8]),
Append(&'a mut Vec<u8>),
}
pub(super) struct Output<'a> {
destination: Destination<'a>,
produced: usize,
}
impl<'a> Output<'a> {
pub(super) const fn new(destination: Destination<'a>) -> Self {
Self {
destination,
produced: 0,
}
}
fn append(&mut self, bytes: &[u8]) -> usize {
let count = match &mut self.destination {
Destination::Slice(output) => {
let count = bytes.len().min(output.len() - self.produced);
output[self.produced..self.produced + count].copy_from_slice(&bytes[..count]);
count
}
Destination::Append(output) => {
output.extend_from_slice(bytes);
bytes.len()
}
};
self.produced += count;
count
}
}
pub(super) struct Buffers<'a> {
pub(super) staging: &'a mut Vec<u8>,
pub(super) pending: &'a mut Vec<u8>,
pub(super) served: &'a mut usize,
pub(super) allow_pending: bool,
}
impl Buffers<'_> {
fn drain(&mut self, output: &mut Output<'_>) -> bool {
*self.served += output.append(&self.pending[*self.served..]);
if *self.served < self.pending.len() {
return false;
}
self.pending.clear();
*self.served = 0;
true
}
}
struct Delivery<'a, 'o> {
output: &'a mut Output<'o>,
pending: &'a mut Vec<u8>,
served: &'a mut usize,
allow_pending: bool,
}
impl Delivery<'_, '_> {
fn encode(
&mut self,
encoder: &mut Encoder,
attached: Option<&SharedContextInner>,
input: &[u8],
operation: Operation,
) -> BrotliResult<()> {
if operation != Operation::Flush
&& let Encoder::Fast(fast) = encoder
&& let Destination::Slice(output) = &mut self.output.destination
{
let tail = &mut output[self.output.produced..];
if tail.len() >= FastEncoder::fragment_reserve(input.len())? {
self.output.produced +=
fast.encode_block_into(input, operation == Operation::Finish, tail)?;
return Ok(());
}
}
if operation != Operation::Flush
&& let Encoder::Fast(fast) = encoder
&& let Destination::Append(output) = &mut self.output.destination
{
self.output.produced +=
fast.encode_block_append(input, operation == Operation::Finish, output)?;
return Ok(());
}
let bytes = match operation {
Operation::Flush => encoder.flush_block(input, attached)?,
_ => encoder.encode_block_with(input, operation == Operation::Finish, attached)?,
};
let direct = self.output.append(bytes);
if direct < bytes.len() {
if !self.allow_pending {
return Err(BrotliCompressError::OutputTooSmall);
}
self.pending.clear();
self.pending.extend_from_slice(&bytes[direct..]);
*self.served = 0;
}
Ok(())
}
}
impl StreamState {
pub(super) const fn new(limit: usize, flint: bool) -> Self {
Self {
phase: Phase::Open,
limit,
flint,
}
}
pub(super) fn process(
&mut self,
encoder: &mut Encoder,
attached: Option<&SharedContextInner>,
mut buffers: Buffers<'_>,
input: &[u8],
mut output: Output<'_>,
operation: Operation,
) -> BrotliResult<Progress> {
let mut consumed = 0;
let status = loop {
if !buffers.drain(&mut output) {
break EncoderStatus::NeedsOutput;
}
if self.phase == Phase::Finished {
break EncoderStatus::Finished;
}
if self.phase == Phase::Flushed
&& consumed == input.len()
&& operation == Operation::Flush
{
break EncoderStatus::NeedsInput;
}
let limit = if self.flint { 2 } else { self.limit };
let take = (limit - buffers.staging.len()).min(input.len() - consumed);
let at_end = consumed + take == input.len();
let restart = self.flint
&& buffers.staging.len() + take == 2
&& (!at_end || operation != Operation::Finish);
if at_end && operation == Operation::Process && !restart {
buffers
.staging
.extend_from_slice(&input[consumed..consumed + take]);
consumed += take;
if take != 0 {
self.phase = Phase::Open;
}
break EncoderStatus::NeedsInput;
}
let block_operation = if restart {
Operation::Flush
} else if at_end {
operation
} else {
Operation::Process
};
let block = if buffers.staging.is_empty() {
&input[consumed..consumed + take]
} else {
buffers
.staging
.extend_from_slice(&input[consumed..consumed + take]);
buffers.staging.as_slice()
};
let outcome = Delivery {
output: &mut output,
pending: buffers.pending,
served: buffers.served,
allow_pending: buffers.allow_pending,
}
.encode(encoder, attached, block, block_operation);
if let Err(error) = outcome {
self.phase = Phase::Failed;
return Err(error);
}
consumed += take;
buffers.staging.clear();
self.phase = match block_operation {
Operation::Finish => Phase::Finished,
Operation::Flush if !restart => Phase::Flushed,
_ => Phase::Open,
};
if restart {
self.flint = false;
}
};
Ok(Progress {
consumed,
produced: output.produced,
status,
})
}
}
pub(super) fn finish(
encoder: &mut Encoder,
attached: Option<&SharedContextInner>,
input: &[u8],
destination: Destination<'_>,
) -> BrotliResult<usize> {
let mut state = StreamState::new(encoder.block_size_limit(), false);
let mut staging = Vec::new();
let mut pending = Vec::new();
let mut served = 0;
state
.process(
encoder,
attached,
Buffers {
staging: &mut staging,
pending: &mut pending,
served: &mut served,
allow_pending: false,
},
input,
Output::new(destination),
Operation::Finish,
)
.map(|progress| progress.produced)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pending_output_is_delivered_once_and_empty_destinations_preserve_it() {
let mut staging = Vec::new();
let mut pending = b"0123456789".to_vec();
let mut served = 0;
let mut buffers = Buffers {
staging: &mut staging,
pending: &mut pending,
served: &mut served,
allow_pending: true,
};
assert!(!buffers.drain(&mut Output::new(Destination::Slice(&mut []))));
assert_eq!(*buffers.served, 0);
for (expected, drained) in [(b"0123".as_slice(), false), (b"4567", false), (b"89", true)] {
let mut bytes = [0; 4];
let mut output = Output::new(Destination::Slice(&mut bytes));
assert_eq!(buffers.drain(&mut output), drained);
assert_eq!(output.produced, expected.len());
assert_eq!(&bytes[..expected.len()], expected);
}
assert!(buffers.pending.is_empty());
assert_eq!(*buffers.served, 0);
assert!(buffers.drain(&mut Output::new(Destination::Slice(&mut []))));
}
}