use super::block_decoder::{self, BlockDecoder};
use super::buffer_backend::{BufferBackend, WILDCOPY_OVERLENGTH};
use super::decode_buffer::DecodeBuffer;
use super::errors::{
BlockHeaderReadError, DecodeBlockContentError, FrameHeaderError, ReadFrameHeaderError,
};
use super::frame;
use super::ringbuffer::RingBuffer;
use super::scratch::{ByteScratch, FSEScratch, HuffmanScratch, Workspace, WorkspaceRef};
use super::user_slice_buf::UserSliceBackend;
use crate::blocks::block::{BlockHeader, BlockType};
use crate::common::{MAGIC_NUM, MAX_DECODER_WINDOW_SIZE, MIN_WINDOW_SIZE};
use core::marker::PhantomData;
use core::ptr::NonNull;
pub const COMPRESSED_BLOCK_UNITS: u64 = (1 << crate::huff0::MAX_MAX_NUM_BITS)
+ (1 << super::sequence_section_decoder::LL_MAX_LOG)
+ (1 << super::sequence_section_decoder::ML_MAX_LOG)
+ (1 << super::sequence_section_decoder::OF_MAX_LOG);
const MAX_FRAME_HEADER: usize = 18;
const TABLE_ALIGN: usize = {
let fse = core::mem::align_of::<FSEScratch>();
let huf = core::mem::align_of::<HuffmanScratch>();
if fse > huf { fse } else { huf }
};
pub fn workspace_bytes(max_window_size: u64) -> Result<usize, WorkspaceConfigError> {
Layout::for_window(max_window_size).map(|layout| layout.total)
}
struct Layout {
huf_offset: usize,
tables_len: usize,
ring_cap: usize,
literals_len: usize,
staging_len: usize,
total: usize,
}
impl Layout {
fn for_window(max_window_size: u64) -> Result<Layout, WorkspaceConfigError> {
use WorkspaceConfigError::SizeOverflow;
if !(MIN_WINDOW_SIZE..=MAX_DECODER_WINDOW_SIZE).contains(&max_window_size) {
return Err(WorkspaceConfigError::WindowLimitOutOfRange {
requested: max_window_size,
});
}
let window = usize::try_from(max_window_size).map_err(|_| SizeOverflow)?;
let block_max = block_decoder::block_maximum(window);
let huf_offset = core::mem::size_of::<FSEScratch>()
.next_multiple_of(core::mem::align_of::<HuffmanScratch>());
let tables_len = huf_offset + core::mem::size_of::<HuffmanScratch>();
let ring_cap = window
.checked_add(2 * block_max)
.and_then(|n| n.checked_add(1))
.ok_or(SizeOverflow)?;
let literals_len = block_max + WILDCOPY_OVERLENGTH;
let staging_len = block_max;
let total = [
tables_len,
ring_cap,
WILDCOPY_OVERLENGTH,
literals_len,
staging_len,
]
.iter()
.try_fold(TABLE_ALIGN - 1, |sum, &part| sum.checked_add(part))
.ok_or(SizeOverflow)?;
Ok(Layout {
huf_offset,
tables_len,
ring_cap,
literals_len,
staging_len,
total,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeLimits {
pub max_window_size: u64,
pub max_output: u64,
}
impl DecodeLimits {
pub const fn new(max_window_size: u64) -> Self {
Self {
max_window_size,
max_output: u64::MAX,
}
}
pub const fn with_max_output(self, max_output: u64) -> Self {
Self { max_output, ..self }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WorkBudget {
remaining: u64,
}
impl WorkBudget {
pub const fn new(units: u64) -> Self {
Self { remaining: units }
}
pub const fn remaining(&self) -> u64 {
self.remaining
}
pub fn add(&mut self, units: u64) {
self.remaining = self.remaining.saturating_add(units);
}
fn charge(&mut self, units: u64) -> Result<(), WorkspaceDecodeError> {
match self.remaining.checked_sub(units) {
Some(left) => {
self.remaining = left;
Ok(())
}
None => Err(WorkspaceDecodeError::Limit(LimitExceeded::Work {
needed: units,
remaining: self.remaining,
})),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Progress {
pub consumed: usize,
pub produced: usize,
pub state: StepState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StepState {
NeedInput,
NeedOutput,
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WorkspaceConfigError {
WindowLimitOutOfRange {
requested: u64,
},
SizeOverflow,
WorkspaceTooSmall {
needed: usize,
provided: usize,
},
}
impl core::fmt::Display for WorkspaceConfigError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::WindowLimitOutOfRange { requested } => write!(
f,
"window limit {requested} lies outside {MIN_WINDOW_SIZE}..={MAX_DECODER_WINDOW_SIZE}"
),
Self::SizeOverflow => write!(f, "workspace size does not fit in usize"),
Self::WorkspaceTooSmall { needed, provided } => write!(
f,
"workspace of {provided} bytes is smaller than the {needed} the limits require"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WorkspaceConfigError {}
#[derive(Debug)]
#[non_exhaustive]
pub enum WorkspaceDecodeError {
Limit(LimitExceeded),
Malformed(MalformedInput),
Unsupported(Unsupported),
Truncated,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LimitExceeded {
Window {
requested: u64,
limit: u64,
},
Output {
limit: u64,
},
Work {
needed: u64,
remaining: u64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Unsupported {
Dictionary {
id: u32,
},
#[cfg(feature = "legacy")]
LegacyFrame {
version: crate::decoding::LegacyVersion,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum MalformedInput {
FrameHeader(ReadFrameHeaderError),
Window(FrameHeaderError),
BlockHeader(BlockHeaderReadError),
BlockTooLarge {
size: usize,
maximum: usize,
},
Block(DecodeBlockContentError),
ContentSize {
declared: u64,
produced: u64,
},
Checksum {
expected: u32,
calculated: u32,
},
}
impl core::fmt::Display for WorkspaceDecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Limit(limit) => match limit {
LimitExceeded::Window { requested, limit } => write!(
f,
"frame window of {requested} bytes exceeds the {limit}-byte limit"
),
LimitExceeded::Output { limit } => {
write!(f, "output exceeds the {limit}-byte limit")
}
LimitExceeded::Work { needed, remaining } => write!(
f,
"work of {needed} units exceeds the {remaining} remaining"
),
},
Self::Malformed(malformed) => match malformed {
MalformedInput::FrameHeader(e) => write!(f, "invalid frame header: {e}"),
MalformedInput::Window(e) => write!(f, "invalid frame window: {e}"),
MalformedInput::BlockHeader(e) => write!(f, "invalid block header: {e}"),
MalformedInput::BlockTooLarge { size, maximum } => write!(
f,
"block of {size} bytes exceeds the frame's block maximum of {maximum}"
),
MalformedInput::Block(e) => write!(f, "invalid block: {e}"),
MalformedInput::ContentSize { declared, produced } => write!(
f,
"frame declares {declared} bytes but its blocks produce {produced}"
),
MalformedInput::Checksum {
expected,
calculated,
} => write!(
f,
"content checksum {calculated:#010x} does not match the stored {expected:#010x}"
),
},
Self::Unsupported(Unsupported::Dictionary { id }) => {
write!(
f,
"frame needs dictionary {id}, which this decoder does not take"
)
}
#[cfg(feature = "legacy")]
Self::Unsupported(Unsupported::LegacyFrame { version }) => write!(
f,
"v0.{} frame, which this decoder does not read",
version.minor()
),
Self::Truncated => write!(f, "input ended in the middle of a frame"),
Self::Failed => write!(f, "an earlier step failed; reset the decoder"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WorkspaceDecodeError {}
struct SliceScratch<'w> {
buf: &'w mut [u8],
len: usize,
}
impl ByteScratch for SliceScratch<'_> {
#[inline(always)]
fn len(&self) -> usize {
self.len
}
#[inline(always)]
fn clear(&mut self) {
self.len = 0;
}
#[inline(always)]
fn truncate(&mut self, len: usize) {
self.len = self.len.min(len);
}
#[inline(always)]
fn reserve(&mut self, additional: usize) {
assert!(
self.len
.checked_add(additional)
.is_some_and(|end| end <= self.buf.len()),
"literals exceed the workspace region sized for a block"
);
}
#[inline(always)]
fn push(&mut self, byte: u8) {
if let Some(slot) = self.buf.get_mut(self.len) {
*slot = byte;
}
self.len += 1;
}
fn resize(&mut self, new_len: usize, value: u8) {
if new_len > self.len {
self.buf[self.len..new_len].fill(value);
}
self.len = new_len;
}
fn extend_from_slice(&mut self, bytes: &[u8]) {
let end = self.len + bytes.len();
self.buf[self.len..end].copy_from_slice(bytes);
self.len = end;
}
#[inline(always)]
fn as_mut_ptr(&mut self) -> *mut u8 {
self.buf.as_mut_ptr()
}
#[inline(always)]
unsafe fn set_len(&mut self, new_len: usize) {
assert!(new_len <= self.buf.len());
self.len = new_len;
}
#[inline(always)]
fn as_slice(&self) -> &[u8] {
&self.buf[..self.len.min(self.buf.len())]
}
#[inline(always)]
fn as_mut_slice(&mut self) -> &mut [u8] {
let len = self.len.min(self.buf.len());
&mut self.buf[..len]
}
}
enum Window<'w> {
Idle,
Ring(DecodeBuffer<RingBuffer>),
Linear(DecodeBuffer<UserSliceBackend<'w>>),
}
macro_rules! with_window {
($window:expr, $buffer:ident => $body:expr, idle => $idle:expr) => {
match $window {
Window::Ring($buffer) => $body,
Window::Linear($buffer) => $body,
Window::Idle => $idle,
}
};
}
fn produced_so_far<B: BufferBackend>(buffer: &DecodeBuffer<B>) -> u64 {
if B::INLINE_EXEC_MAINTAINS_OUTPUT_COUNTER {
buffer.total_output()
} else {
buffer.buffer_ref().tail() as u64
}
}
struct BlockScratch<'a, 'w, B: BufferBackend> {
huf: &'a mut HuffmanScratch,
fse: &'a mut FSEScratch,
buffer: &'a mut DecodeBuffer<B>,
offset_hist: &'a mut [u32; 3],
literals: &'a mut SliceScratch<'w>,
block_content: &'a mut SliceScratch<'w>,
}
impl<'w, B: BufferBackend> Workspace for BlockScratch<'_, 'w, B> {
type Backend = B;
type Bytes = SliceScratch<'w>;
fn split(&mut self) -> WorkspaceRef<'_, B, SliceScratch<'w>> {
WorkspaceRef {
huf: &mut *self.huf,
fse: &mut *self.fse,
buffer: &mut *self.buffer,
offset_hist: &mut *self.offset_hist,
literals_buffer: &mut *self.literals,
block_content_buffer: &mut *self.block_content,
}
}
}
fn run_block<B: BufferBackend>(
decoder: &mut BlockDecoder,
scratch: &mut BlockScratch<'_, '_, B>,
header: &BlockHeader,
mut content: &[u8],
) -> Result<u64, DecodeBlockContentError> {
let before = produced_so_far(scratch.buffer);
decoder.decode_block_content_from_slice(header, scratch, None, &mut content)?;
Ok(produced_so_far(scratch.buffer) - before)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Phase {
Magic,
Descriptor,
FrameHeader,
SkippableSize,
Skipping { remaining: u64 },
BlockHeader,
BlockBody(PendingBlock),
Checksum,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct PendingBlock {
last_block: bool,
block_type: BlockType,
decompressed_size: u32,
content_size: u32,
}
impl PendingBlock {
fn header(self) -> BlockHeader {
BlockHeader {
last_block: self.last_block,
block_type: self.block_type,
decompressed_size: self.decompressed_size,
content_size: self.content_size,
}
}
}
#[derive(Clone, Copy)]
struct FrameState {
declared_size: Option<u64>,
has_checksum: bool,
block_maximum: usize,
produced: u64,
}
pub struct WorkspaceDecoder<'w> {
huf: &'w mut HuffmanScratch,
fse: &'w mut FSEScratch,
offset_hist: [u32; 3],
literals: SliceScratch<'w>,
block_content: SliceScratch<'w>,
window: Window<'w>,
region: NonNull<u8>,
region_len: usize,
ring_cap: usize,
_region: PhantomData<&'w mut [u8]>,
staging: &'w mut [u8],
staged: usize,
block_decoder: BlockDecoder,
kernel: crate::cpu_kernel::CpuKernelTag,
limits: DecodeLimits,
phase: Phase,
header: [u8; MAX_FRAME_HEADER],
header_len: usize,
header_target: usize,
frame: FrameState,
blocks_done: bool,
stream_produced: u64,
failed: bool,
}
unsafe impl Send for WorkspaceDecoder<'_> {}
impl<'w> WorkspaceDecoder<'w> {
pub fn new(
workspace: &'w mut [u8],
limits: DecodeLimits,
) -> Result<Self, WorkspaceConfigError> {
let layout = Layout::for_window(limits.max_window_size)?;
let provided = workspace.len();
if provided < layout.total {
return Err(WorkspaceConfigError::WorkspaceTooSmall {
needed: layout.total,
provided,
});
}
let skip = workspace.as_ptr().align_offset(TABLE_ALIGN);
debug_assert!(skip < TABLE_ALIGN);
let rest = &mut workspace[skip..];
let (tables, rest) = rest.split_at_mut(layout.tables_len);
let (ring, rest) = rest.split_at_mut(layout.ring_cap + WILDCOPY_OVERLENGTH);
let (literals, rest) = rest.split_at_mut(layout.literals_len);
let staging = &mut rest[..layout.staging_len];
let tables = tables.as_mut_ptr();
let fse_ptr = tables.cast::<FSEScratch>();
let (fse, huf) = unsafe {
let huf_ptr = tables.add(layout.huf_offset).cast::<HuffmanScratch>();
FSEScratch::init_in_place(fse_ptr);
HuffmanScratch::init_in_place(huf_ptr);
(&mut *fse_ptr, &mut *huf_ptr)
};
let region_len = ring.len();
let region = NonNull::from(ring).cast::<u8>();
let kernel = crate::cpu_kernel::detect_cpu_kernel();
Ok(WorkspaceDecoder {
huf,
fse,
offset_hist: [1, 4, 8],
literals: SliceScratch {
buf: literals,
len: 0,
},
block_content: SliceScratch {
buf: &mut [],
len: 0,
},
window: Window::Idle,
region,
region_len,
ring_cap: layout.ring_cap,
_region: PhantomData,
staging,
staged: 0,
block_decoder: block_decoder::with_kernel(kernel),
kernel,
limits,
phase: Phase::Magic,
header: [0; MAX_FRAME_HEADER],
header_len: 0,
header_target: 4,
frame: FrameState {
declared_size: None,
has_checksum: false,
block_maximum: 0,
produced: 0,
},
blocks_done: false,
stream_produced: 0,
failed: false,
})
}
pub fn reset(&mut self) {
self.window = Window::Idle;
self.literals.clear();
self.staged = 0;
self.block_decoder = block_decoder::with_kernel(self.kernel);
self.phase = Phase::Magic;
self.header_len = 0;
self.header_target = 4;
self.blocks_done = false;
self.stream_produced = 0;
self.failed = false;
}
pub fn total_output(&self) -> u64 {
self.stream_produced
}
pub fn step(
&mut self,
input: &[u8],
input_is_final: bool,
output: &mut [u8],
work: &mut WorkBudget,
) -> Result<Progress, WorkspaceDecodeError> {
if self.failed {
return Err(WorkspaceDecodeError::Failed);
}
let result = self.step_inner(input, input_is_final, output, work);
if result.is_err() {
self.failed = true;
}
result
}
fn step_inner(
&mut self,
input: &[u8],
input_is_final: bool,
output: &mut [u8],
work: &mut WorkBudget,
) -> Result<Progress, WorkspaceDecodeError> {
let mut consumed = 0;
let mut produced = 0;
loop {
produced += self.deliver(&mut output[produced..]);
if self.pending() != 0 {
return Ok(Progress {
consumed,
produced,
state: StepState::NeedOutput,
});
}
let rest = &input[consumed..];
match self.phase {
Phase::Skipping { remaining } => {
if remaining == 0 {
self.expect_frame();
continue;
}
if rest.is_empty() {
return self.out_of_input(input_is_final, consumed, produced);
}
let take = rest
.len()
.min(usize::try_from(remaining).unwrap_or(usize::MAX));
work.charge(take as u64)?;
consumed += take;
self.phase = Phase::Skipping {
remaining: remaining - take as u64,
};
}
Phase::BlockBody(block) if block.block_type == BlockType::Raw => {
let remaining = block.content_size as usize - self.staged;
if remaining == 0 {
self.finish_block(block)?;
continue;
}
if rest.is_empty() {
return self.out_of_input(input_is_final, consumed, produced);
}
let take = rest.len().min(remaining);
work.charge(2 * take as u64)?;
self.push_raw(&rest[..take])?;
self.staged += take;
consumed += take;
}
Phase::BlockBody(block) => {
let size = block.content_size as usize;
if self.staged == 0 && rest.len() >= size {
work.charge(size as u64)?;
consumed += size;
self.decode_block(block, &rest[..size], work)?;
continue;
}
if rest.is_empty() {
return self.out_of_input(input_is_final, consumed, produced);
}
let take = rest.len().min(size - self.staged);
work.charge(take as u64)?;
self.staging[self.staged..self.staged + take].copy_from_slice(&rest[..take]);
self.staged += take;
consumed += take;
if self.staged == size {
self.staged = 0;
let staging = core::mem::take(&mut self.staging);
let decoded = self.decode_block(block, &staging[..size], work);
self.staging = staging;
decoded?;
}
}
_ => {
if self.header_len < self.header_target {
if rest.is_empty() {
return self.out_of_input(input_is_final, consumed, produced);
}
let take = rest.len().min(self.header_target - self.header_len);
work.charge(take as u64)?;
self.header[self.header_len..self.header_len + take]
.copy_from_slice(&rest[..take]);
self.header_len += take;
consumed += take;
}
if self.header_len == self.header_target {
self.header_complete()?;
}
}
}
}
}
fn out_of_input(
&self,
input_is_final: bool,
consumed: usize,
produced: usize,
) -> Result<Progress, WorkspaceDecodeError> {
if !input_is_final {
return Ok(Progress {
consumed,
produced,
state: StepState::NeedInput,
});
}
if self.phase == Phase::Magic && self.header_len == 0 {
return Ok(Progress {
consumed,
produced,
state: StepState::Done,
});
}
Err(WorkspaceDecodeError::Truncated)
}
fn pending(&self) -> usize {
with_window!(&self.window, buffer => buffer.undelivered(), idle => 0)
}
fn deliver(&mut self, output: &mut [u8]) -> usize {
if output.is_empty() {
return 0;
}
let blocks_done = self.blocks_done;
let drained = with_window!(&mut self.window, buffer => if blocks_done {
buffer.read_all(output)
} else {
buffer.read_reporting_pending(output).map(|(read, _)| read)
}, idle => Ok(0));
drained.unwrap_or(0)
}
fn expect_frame(&mut self) {
self.phase = Phase::Magic;
self.header_len = 0;
self.header_target = 4;
}
fn expect_header(&mut self, phase: Phase, target: usize) {
self.phase = phase;
self.header_target = target;
}
fn header_complete(&mut self) -> Result<(), WorkspaceDecodeError> {
use WorkspaceDecodeError::Malformed;
match self.phase {
Phase::Magic => {
let magic = u32::from_le_bytes([
self.header[0],
self.header[1],
self.header[2],
self.header[3],
]);
if (0x184D2A50..=0x184D2A5F).contains(&magic) {
self.expect_header(Phase::SkippableSize, 8);
} else if magic == MAGIC_NUM {
self.expect_header(Phase::Descriptor, 5);
} else {
#[cfg(feature = "legacy")]
if let Some(version) = crate::decoding::LegacyVersion::of(&magic.to_le_bytes())
{
return Err(WorkspaceDecodeError::Unsupported(
Unsupported::LegacyFrame { version },
));
}
return Err(Malformed(MalformedInput::FrameHeader(
ReadFrameHeaderError::BadMagicNumber(magic),
)));
}
}
Phase::SkippableSize => {
let length = u32::from_le_bytes([
self.header[4],
self.header[5],
self.header[6],
self.header[7],
]);
self.phase = Phase::Skipping {
remaining: u64::from(length),
};
}
Phase::Descriptor => {
let length = frame::measure_header_from_slice(&self.header[..5])
.map_err(|e| Malformed(MalformedInput::FrameHeader(e)))?;
self.expect_header(Phase::FrameHeader, length);
}
Phase::FrameHeader => self.start_frame()?,
Phase::BlockHeader => self.start_block()?,
Phase::Checksum => {
self.check_checksum()?;
self.expect_frame();
}
Phase::Skipping { .. } | Phase::BlockBody(_) => {
unreachable!("phases without a header buffer are handled by the step loop")
}
}
Ok(())
}
fn start_frame(&mut self) -> Result<(), WorkspaceDecodeError> {
use WorkspaceDecodeError::{Limit, Malformed};
let mut source = &self.header[..self.header_target];
let (header, _) = frame::read_frame_header_from_slice(&mut source, false)
.map_err(|e| Malformed(MalformedInput::FrameHeader(e)))?;
if let Some(id) = header.dictionary_id() {
return Err(WorkspaceDecodeError::Unsupported(Unsupported::Dictionary {
id,
}));
}
let window = header
.window_size()
.map_err(|e| Malformed(MalformedInput::Window(e)))?;
if window > self.limits.max_window_size {
return Err(Limit(LimitExceeded::Window {
requested: window,
limit: self.limits.max_window_size,
}));
}
let declared_size = header.fcs_declared().then(|| header.frame_content_size());
if let Some(declared) = declared_size
&& self
.stream_produced
.checked_add(declared)
.is_none_or(|total| total > self.limits.max_output)
{
return Err(Limit(LimitExceeded::Output {
limit: self.limits.max_output,
}));
}
let window = window as usize;
let has_checksum = header.descriptor.content_checksum_flag();
self.window = Window::Idle;
self.window = if header.descriptor.single_segment_flag() {
let region =
unsafe { core::slice::from_raw_parts_mut(self.region.as_ptr(), self.region_len) };
Window::Linear(DecodeBuffer::from_backend(
UserSliceBackend::from_slice(region),
window,
))
} else {
let ring = unsafe { RingBuffer::from_borrowed(self.region, self.ring_cap) };
Window::Ring(DecodeBuffer::from_backend(ring, window))
};
with_window!(&mut self.window, buffer => {
buffer.set_declared_content(declared_size);
#[cfg(feature = "hash")]
buffer.set_compute_hash(has_checksum);
}, idle => {});
self.offset_hist = [1, 4, 8];
self.literals.clear();
super::scratch::reset_entropy(self.huf, self.fse);
self.frame = FrameState {
declared_size,
has_checksum,
block_maximum: block_decoder::block_maximum(window),
produced: 0,
};
self.blocks_done = false;
self.header_len = 0;
self.expect_header(Phase::BlockHeader, 3);
Ok(())
}
fn start_block(&mut self) -> Result<(), WorkspaceDecodeError> {
use WorkspaceDecodeError::Malformed;
let (header, _) = self
.block_decoder
.read_block_header(&self.header[..3])
.map_err(|e| Malformed(MalformedInput::BlockHeader(e)))?;
let size = match header.block_type {
BlockType::RLE => header.decompressed_size,
_ => header.content_size,
} as usize;
let maximum = self.frame.block_maximum;
if size > maximum {
return Err(Malformed(MalformedInput::BlockTooLarge { size, maximum }));
}
self.header_len = 0;
self.staged = 0;
self.phase = Phase::BlockBody(PendingBlock {
last_block: header.last_block,
block_type: header.block_type,
decompressed_size: header.decompressed_size,
content_size: header.content_size,
});
Ok(())
}
fn decode_block(
&mut self,
block: PendingBlock,
content: &[u8],
work: &mut WorkBudget,
) -> Result<(), WorkspaceDecodeError> {
use WorkspaceDecodeError::{Limit, Malformed};
let output_bound = match block.block_type {
BlockType::Compressed => self.frame.block_maximum as u64 + COMPRESSED_BLOCK_UNITS,
_ => u64::from(block.decompressed_size),
};
work.charge(output_bound)?;
let output_limit = Limit(LimitExceeded::Output {
limit: self.limits.max_output,
});
if block.block_type != BlockType::Compressed
&& self
.stream_produced
.checked_add(u64::from(block.decompressed_size))
.is_none_or(|total| total > self.limits.max_output)
{
return Err(output_limit);
}
let header = block.header();
let produced = with_window!(&mut self.window, buffer => run_block(
&mut self.block_decoder,
&mut BlockScratch {
huf: &mut *self.huf,
fse: &mut *self.fse,
buffer,
offset_hist: &mut self.offset_hist,
literals: &mut self.literals,
block_content: &mut self.block_content,
},
&header,
content,
), idle => unreachable!("a block is decoded only inside a started frame"))
.map_err(|e| Malformed(MalformedInput::Block(e)))?;
self.stream_produced += produced;
self.frame.produced += produced;
if self.stream_produced > self.limits.max_output {
return Err(output_limit);
}
if let Some(declared) = self.frame.declared_size
&& self.frame.produced > declared
{
return Err(Malformed(MalformedInput::ContentSize {
declared,
produced: self.frame.produced,
}));
}
self.finish_block(block)
}
fn push_raw(&mut self, bytes: &[u8]) -> Result<(), WorkspaceDecodeError> {
use crate::cpu_kernel::{BaselineKernel, CpuKernelTag, ScalarKernel};
use WorkspaceDecodeError::{Limit, Malformed};
let len = bytes.len() as u64;
let stream_total = self.stream_produced + len;
if stream_total > self.limits.max_output {
return Err(Limit(LimitExceeded::Output {
limit: self.limits.max_output,
}));
}
let frame_total = self.frame.produced + len;
if let Some(declared) = self.frame.declared_size
&& frame_total > declared
{
return Err(Malformed(MalformedInput::ContentSize {
declared,
produced: frame_total,
}));
}
let scalar = self.kernel == CpuKernelTag::Scalar;
let pushed = with_window!(&mut self.window, buffer => if scalar {
buffer.try_push::<ScalarKernel>(bytes)
} else {
buffer.try_push::<BaselineKernel>(bytes)
}, idle => unreachable!("a block is decoded only inside a started frame"));
pushed.map_err(|_| {
Malformed(MalformedInput::Block(
DecodeBlockContentError::BackendOverflow {
step: BlockType::Raw,
},
))
})?;
self.stream_produced = stream_total;
self.frame.produced = frame_total;
Ok(())
}
fn finish_block(&mut self, block: PendingBlock) -> Result<(), WorkspaceDecodeError> {
if let Some(declared) = self.frame.declared_size
&& block.last_block
&& self.frame.produced != declared
{
return Err(WorkspaceDecodeError::Malformed(
MalformedInput::ContentSize {
declared,
produced: self.frame.produced,
},
));
}
self.staged = 0;
self.header_len = 0;
if !block.last_block {
self.expect_header(Phase::BlockHeader, 3);
} else {
self.blocks_done = true;
if self.frame.has_checksum {
self.expect_header(Phase::Checksum, 4);
} else {
self.expect_frame();
}
}
Ok(())
}
fn check_checksum(&mut self) -> Result<(), WorkspaceDecodeError> {
#[cfg(feature = "hash")]
{
use core::hash::Hasher;
let expected = u32::from_le_bytes([
self.header[0],
self.header[1],
self.header[2],
self.header[3],
]);
let calculated =
with_window!(&self.window, buffer => buffer.hash.finish(), idle => 0) as u32;
if expected != calculated {
return Err(WorkspaceDecodeError::Malformed(MalformedInput::Checksum {
expected,
calculated,
}));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests;