mod entropy;
mod v05;
mod v06;
mod v07;
#[cfg(feature = "hash")]
use crate::decoding::ContentChecksum;
use crate::decoding::Dictionary;
use crate::decoding::FrameSizeError;
use crate::decoding::errors::LegacyFrameError as Error;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LegacyVersion {
V05,
V06,
V07,
}
impl LegacyVersion {
pub fn of(src: &[u8]) -> Option<LegacyVersion> {
let magic = u32::from_le_bytes(src.get(..4)?.try_into().ok()?);
match magic {
0xFD2F_B525 => Some(LegacyVersion::V05),
0xFD2F_B526 => Some(LegacyVersion::V06),
0xFD2F_B527 => Some(LegacyVersion::V07),
_ => None,
}
}
pub fn minor(self) -> u8 {
match self {
LegacyVersion::V05 => 5,
LegacyVersion::V06 => 6,
LegacyVersion::V07 => 7,
}
}
pub fn header_size(self, src: &[u8]) -> Option<usize> {
header_len(self, src).ok()
}
}
const BLOCK_SIZE_MAX: usize = 128 * 1024;
const STORED_BLOCK_MAX: usize = (1 << 19) - 1;
#[derive(Debug, Clone, Copy)]
struct BlockLimits {
compressed: usize,
stored: usize,
}
impl BlockLimits {
fn of(block_max: usize) -> BlockLimits {
if block_max >= BLOCK_SIZE_MAX {
BlockLimits {
compressed: BLOCK_SIZE_MAX,
stored: STORED_BLOCK_MAX,
}
} else {
BlockLimits {
compressed: block_max,
stored: block_max,
}
}
}
fn of_block(self, block: Block) -> usize {
match block {
Block::Compressed(_) => self.compressed,
Block::Raw(_) | Block::Rle(_) | Block::End => self.stored,
}
}
}
fn corrupt(reason: &'static str) -> Error {
Error::Corrupted { reason }
}
pub(crate) fn is_older_dictionary(raw: &[u8]) -> bool {
raw.first_chunk::<4>().is_some_and(|magic| {
matches!(
u32::from_le_bytes(*magic),
v05::DICT_MAGIC | v06::DICT_MAGIC
)
})
}
#[derive(Clone, Copy)]
pub(crate) enum LegacyDict<'a> {
None,
Parsed {
dict: &'a Dictionary,
header: &'a [u8],
},
}
impl<'a> LegacyDict<'a> {
pub(crate) fn of(handle: &'a crate::decoding::DictionaryHandle) -> LegacyDict<'a> {
LegacyDict::Parsed {
dict: handle.as_dict(),
header: handle.serialized_header(),
}
}
fn pieces(&self) -> (&'a [u8], &'a [u8]) {
match *self {
LegacyDict::None => (&[], &[]),
LegacyDict::Parsed { dict, header } => (header, &dict.dict_content),
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct History<'a> {
head: &'a [u8],
tail: &'a [u8],
}
#[derive(Clone)]
enum HistoryShape {
Content(core::ops::Range<usize>),
Whole,
}
impl HistoryShape {
fn of<'a>(&self, dict: LegacyDict<'a>) -> History<'a> {
let (head, tail) = dict.pieces();
match self {
HistoryShape::Content(range) => History {
head: &[],
tail: &tail[range.clone()],
},
HistoryShape::Whole => History { head, tail },
}
}
}
impl History<'_> {
fn len(&self) -> usize {
self.head.len() + self.tail.len()
}
fn copy_out<O: Output>(&self, from: usize, len: usize, out: &mut O) {
debug_assert!(from + len <= self.len());
if from >= self.head.len() {
let start = from - self.head.len();
out.extend(&self.tail[start..start + len]);
return;
}
let in_head = (self.head.len() - from).min(len);
out.extend(&self.head[from..from + in_head]);
if len > in_head {
out.extend(&self.tail[..len - in_head]);
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct FrameHeader {
pub(crate) version: LegacyVersion,
pub(crate) size: usize,
pub(crate) window: u64,
pub(crate) content_size: Option<u64>,
pub(crate) dict_id: u32,
pub(crate) checksum: bool,
}
pub(crate) fn read_header(src: &[u8]) -> Result<FrameHeader, Error> {
match LegacyVersion::of(src).ok_or(corrupt("not a v0.5-v0.7 frame"))? {
LegacyVersion::V05 => v05::read_header(src),
LegacyVersion::V06 => v06::read_header(src),
LegacyVersion::V07 => v07::read_header(src),
}
}
pub(crate) fn read_header_whole(src: &[u8]) -> Result<FrameHeader, Error> {
match read_header(src) {
Err(Error::UnsupportedParameter)
if LegacyVersion::of(src).is_some_and(|version| version != LegacyVersion::V05) =>
{
Err(corrupt("frame header"))
}
result => result,
}
}
pub(crate) fn declared_content_size(src: &[u8]) -> Option<u64> {
read_header(src).ok()?.content_size
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Block {
Compressed(usize),
Raw(usize),
Rle(usize),
End,
}
impl Block {
fn read(src: &[u8]) -> Result<Block, Error> {
let header = src.get(..3).ok_or(Error::Truncated)?;
let size =
usize::from(header[2]) | usize::from(header[1]) << 8 | usize::from(header[0] & 7) << 16;
Ok(match header[0] >> 6 {
0 => Block::Compressed(size),
1 => Block::Raw(size),
2 => Block::Rle(size),
_ => Block::End,
})
}
fn body_len(self) -> usize {
match self {
Block::Compressed(size) | Block::Raw(size) => size,
Block::Rle(_) => 1,
Block::End => 0,
}
}
fn check(self, version: LegacyVersion, limits: BlockLimits) -> Result<(), Error> {
let size = match self {
Block::Rle(_) if version != LegacyVersion::V07 => {
return Err(corrupt("reserved block type"));
}
Block::Compressed(size) | Block::Raw(size) | Block::Rle(size) => size,
Block::End => 0,
};
if size > limits.of_block(self) {
return Err(corrupt("block larger than the block maximum"));
}
Ok(())
}
fn decoded_max(self) -> usize {
match self {
Block::Compressed(_) => BLOCK_SIZE_MAX,
Block::Raw(size) | Block::Rle(size) => size,
Block::End => 0,
}
}
}
fn ends_frame(version: LegacyVersion, block: Block) -> bool {
match version {
LegacyVersion::V07 => block == Block::End,
LegacyVersion::V05 | LegacyVersion::V06 => block.body_len() == 0,
}
}
pub(crate) fn header_len(version: LegacyVersion, src: &[u8]) -> Result<usize, Error> {
let descriptor = *src.get(4).ok_or(Error::Truncated)?;
let fields = HeaderFields::of(version, descriptor);
Ok(5 + fields.window + fields.dict_id + fields.content_size)
}
impl FrameHeader {
#[cfg(feature = "lsm")]
pub(crate) fn window_descriptor(&self, src: &[u8]) -> Option<u8> {
let fields = HeaderFields::of(self.version, src[4]);
(fields.window == 1).then(|| src[5])
}
#[cfg(feature = "lsm")]
pub(crate) fn dictionary_id(&self) -> Option<u32> {
(self.dict_id != 0).then_some(self.dict_id)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct HeaderFields {
pub(crate) window: usize,
pub(crate) dict_id: usize,
pub(crate) content_size: usize,
}
impl HeaderFields {
pub(crate) fn of(version: LegacyVersion, descriptor: u8) -> HeaderFields {
match version {
LegacyVersion::V05 => HeaderFields {
window: 0,
dict_id: 0,
content_size: 0,
},
LegacyVersion::V06 => HeaderFields {
window: 0,
dict_id: 0,
content_size: [0, 1, 2, 8][usize::from(descriptor >> 6)],
},
LegacyVersion::V07 => {
let single_segment = descriptor & 0x20 != 0;
let fcs = [0, 2, 4, 8][usize::from(descriptor >> 6)];
HeaderFields {
window: usize::from(!single_segment),
dict_id: [0, 1, 2, 4][usize::from(descriptor & 3)],
content_size: fcs + usize::from(single_segment && fcs == 0),
}
}
}
}
}
pub(crate) fn frame_size(
version: LegacyVersion,
src: &[u8],
) -> Result<(usize, u64), FrameSizeError> {
let mut at = header_len(version, src).map_err(|_| FrameSizeError::Truncated)?;
let mut bound = 0u64;
loop {
let rest = src.get(at..).ok_or(FrameSizeError::Truncated)?;
let block = Block::read(rest).map_err(|_| FrameSizeError::Truncated)?;
if block
.check(version, BlockLimits::of(BLOCK_SIZE_MAX))
.is_err()
{
return Err(match block {
Block::Rle(_) => FrameSizeError::ReservedBlock,
_ => FrameSizeError::OversizedBlock,
});
}
at += 3;
let body = block.body_len();
if body > src.len() - at {
return Err(FrameSizeError::Truncated);
}
if ends_frame(version, block) {
break;
}
at += body;
bound += block.decoded_max() as u64;
}
Ok((at, bound))
}
pub(crate) trait Output {
fn produced(&self) -> usize;
fn held(&self) -> usize;
fn reserve(&mut self, len: usize) -> Result<(), Error>;
fn extend(&mut self, bytes: &[u8]);
fn repeat(&mut self, byte: u8, len: usize);
fn copy_back(&mut self, offset: usize, len: usize);
#[cfg(feature = "hash")]
fn since(&self, from: usize) -> &[u8];
fn copy_match(&mut self, offset: usize, len: usize, dict: &History<'_>) -> Result<(), Error>
where
Self: Sized,
{
copy_match(self, offset, len, dict)
}
}
pub(crate) struct VecOutput<'a> {
pub(crate) buf: &'a mut Vec<u8>,
pub(crate) start: usize,
}
impl Output for VecOutput<'_> {
fn produced(&self) -> usize {
self.held()
}
fn held(&self) -> usize {
self.buf.len() - self.start
}
fn reserve(&mut self, len: usize) -> Result<(), Error> {
self.buf.reserve(len);
Ok(())
}
fn extend(&mut self, bytes: &[u8]) {
self.buf.extend_from_slice(bytes);
}
fn repeat(&mut self, byte: u8, len: usize) {
self.buf.resize(self.buf.len() + len, byte);
}
fn copy_back(&mut self, offset: usize, len: usize) {
let from = self.buf.len() - offset;
let mut left = len;
while left > 0 {
let piece = left.min(self.buf.len() - from);
self.buf.extend_from_within(from..from + piece);
left -= piece;
}
}
#[cfg(feature = "hash")]
fn since(&self, from: usize) -> &[u8] {
&self.buf[self.start + from..]
}
}
fn copy_repeating(buf: &mut [u8], from: usize, to: usize, len: usize) {
debug_assert!(from < to);
let mut done = 0;
while done < len {
let piece = (to + done - from).min(len - done);
buf.copy_within(from..from + piece, to + done);
done += piece;
}
}
pub(crate) struct SliceOutput<'a> {
pub(crate) buf: &'a mut [u8],
pub(crate) pos: usize,
}
impl Output for SliceOutput<'_> {
fn produced(&self) -> usize {
self.pos
}
fn held(&self) -> usize {
self.pos
}
fn reserve(&mut self, len: usize) -> Result<(), Error> {
if len > self.buf.len() - self.pos {
return Err(Error::OutputTooSmall);
}
Ok(())
}
fn extend(&mut self, bytes: &[u8]) {
self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
self.pos += bytes.len();
}
fn repeat(&mut self, byte: u8, len: usize) {
self.buf[self.pos..self.pos + len].fill(byte);
self.pos += len;
}
fn copy_back(&mut self, offset: usize, len: usize) {
copy_repeating(self.buf, self.pos - offset, self.pos, len);
self.pos += len;
}
#[cfg(feature = "hash")]
fn since(&self, from: usize) -> &[u8] {
&self.buf[from..self.pos]
}
}
fn copy_match<O: Output>(
out: &mut O,
offset: usize,
len: usize,
dict: &History<'_>,
) -> Result<(), Error> {
debug_assert_eq!(out.held(), out.produced());
debug_assert!(offset >= 1);
let held = out.held();
if offset <= held {
out.copy_back(offset, len);
return Ok(());
}
let back = offset - held;
if back > dict.len() {
return Err(corrupt("match reaches before the dictionary"));
}
let first = len.min(back);
dict.copy_out(dict.len() - back, first, out);
let rest = len - first;
if rest > 0 {
let distance = out.held();
out.copy_back(distance, rest);
}
Ok(())
}
fn execute<O: Output>(
out: &mut O,
literals: &[u8],
lit_pos: &mut usize,
lit_len: usize,
match_len: usize,
offset: usize,
dict: &History<'_>,
) -> Result<(), Error> {
out.reserve(
lit_len
.checked_add(match_len)
.ok_or(Error::OutputTooSmall)?,
)?;
let lits = literals
.get(*lit_pos..)
.and_then(|rest| rest.get(..lit_len))
.ok_or(corrupt("sequence takes more literals than the block has"))?;
out.extend(lits);
*lit_pos += lit_len;
out.copy_match(offset, match_len, dict)
}
enum Literals {
Huffman {
header: usize,
size: usize,
compressed: usize,
single_stream: bool,
},
Repeat {
size: usize,
compressed: usize,
},
Raw {
header: usize,
size: usize,
},
Rle {
header: usize,
size: usize,
},
}
impl Literals {
fn read(src: &[u8]) -> Result<Literals, Error> {
if src.len() < 3 {
return Err(corrupt("compressed block too short"));
}
let b0 = usize::from(src[0]);
let b1 = usize::from(src[1]);
let b2 = usize::from(src[2]);
let size_format = (b0 >> 4) & 3;
Ok(match b0 >> 6 {
0 => {
if src.len() < 5 {
return Err(corrupt("compressed block too short"));
}
let b3 = usize::from(src[3]);
let b4 = usize::from(src[4]);
let (header, size, compressed, single_stream) = match size_format {
0 | 1 => (
3,
((b0 & 15) << 6) + (b1 >> 2),
((b1 & 3) << 8) + b2,
b0 & 16 != 0,
),
2 => (
4,
((b0 & 15) << 10) + (b1 << 2) + (b2 >> 6),
((b2 & 63) << 8) + b3,
false,
),
_ => (
5,
((b0 & 15) << 14) + (b1 << 6) + (b2 >> 2),
((b2 & 3) << 16) + (b3 << 8) + b4,
false,
),
};
if size > BLOCK_SIZE_MAX {
return Err(corrupt("literal section too large"));
}
if compressed + header > src.len() {
return Err(corrupt("literals run past the block"));
}
Literals::Huffman {
header,
size,
compressed,
single_stream,
}
}
1 => {
if size_format != 1 {
return Err(corrupt("repeated-table literals in an unknown form"));
}
let size = ((b0 & 15) << 6) + (b1 >> 2);
let compressed = ((b1 & 3) << 8) + b2;
if compressed + 3 > src.len() {
return Err(corrupt("literals run past the block"));
}
Literals::Repeat { size, compressed }
}
kind => {
let (header, size) = match size_format {
0 | 1 => (1, b0 & 31),
2 => (2, ((b0 & 15) << 8) + b1),
_ => (3, ((b0 & 15) << 16) + (b1 << 8) + b2),
};
if kind == 2 {
if size + header > src.len() {
return Err(corrupt("literals run past the block"));
}
Literals::Raw { header, size }
} else {
if header == 3 && src.len() < 4 {
return Err(corrupt("compressed block too short"));
}
if size > BLOCK_SIZE_MAX {
return Err(corrupt("literal section too large"));
}
Literals::Rle { header, size }
}
}
})
}
}
enum LiteralSource {
Block(core::ops::Range<usize>),
Buffer,
}
enum VersionDecoder {
V05(v05::Decoder),
V06(v06::Decoder),
V07(v07::Decoder),
}
impl VersionDecoder {
fn version(&self) -> LegacyVersion {
match self {
VersionDecoder::V05(_) => LegacyVersion::V05,
VersionDecoder::V06(_) => LegacyVersion::V06,
VersionDecoder::V07(_) => LegacyVersion::V07,
}
}
fn heap_bytes(&self) -> usize {
match self {
VersionDecoder::V05(decoder) => decoder.heap_bytes(),
VersionDecoder::V06(decoder) => decoder.heap_bytes(),
VersionDecoder::V07(decoder) => decoder.heap_bytes(),
}
}
}
#[derive(Default)]
struct Workspace {
decoder: Option<VersionDecoder>,
#[cfg(all(feature = "lsm", feature = "hash"))]
block_digests: Vec<u32>,
}
impl Workspace {
fn heap_bytes(&self) -> usize {
let bytes = self.decoder.as_ref().map_or(0, VersionDecoder::heap_bytes);
#[cfg(all(feature = "lsm", feature = "hash"))]
let bytes = bytes + self.block_digests.capacity() * core::mem::size_of::<u32>();
bytes
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct FrameOptions {
#[cfg(feature = "hash")]
pub(crate) checksum: ContentChecksum,
pub(crate) block_max: usize,
#[cfg(all(feature = "lsm", feature = "hash"))]
pub(crate) block_digests: bool,
}
impl Default for FrameOptions {
fn default() -> FrameOptions {
FrameOptions {
#[cfg(feature = "hash")]
checksum: ContentChecksum::EmitOnly,
block_max: BLOCK_SIZE_MAX,
#[cfg(all(feature = "lsm", feature = "hash"))]
block_digests: false,
}
}
}
struct Budget<'o, O: Output> {
out: &'o mut O,
end: usize,
}
impl<O: Output> Output for Budget<'_, O> {
fn produced(&self) -> usize {
self.out.produced()
}
fn held(&self) -> usize {
self.out.held()
}
fn reserve(&mut self, len: usize) -> Result<(), Error> {
if len > self.end - self.out.produced() {
return Err(corrupt("block decodes past the largest block size"));
}
self.out.reserve(len)
}
fn extend(&mut self, bytes: &[u8]) {
self.out.extend(bytes);
}
fn repeat(&mut self, byte: u8, len: usize) {
self.out.repeat(byte, len);
}
fn copy_back(&mut self, offset: usize, len: usize) {
self.out.copy_back(offset, len);
}
#[cfg(feature = "hash")]
fn since(&self, from: usize) -> &[u8] {
self.out.since(from)
}
fn copy_match(&mut self, offset: usize, len: usize, dict: &History<'_>) -> Result<(), Error> {
self.out.copy_match(offset, len, dict)
}
}
struct LegacyFrame {
version: LegacyVersion,
dict_id: u32,
history: HistoryShape,
limits: BlockLimits,
#[cfg(feature = "hash")]
checksum_mode: ContentChecksum,
content_size: u64,
blocks: usize,
has_checksum: bool,
stored_checksum: Option<u32>,
#[cfg(feature = "hash")]
calculated_checksum: Option<u32>,
#[cfg(feature = "hash")]
hash: Option<twox_hash::XxHash64>,
#[cfg(all(feature = "lsm", feature = "hash"))]
block_digests: bool,
}
impl LegacyFrame {
fn new(
header: FrameHeader,
dict: LegacyDict<'_>,
options: FrameOptions,
workspace: &mut Workspace,
) -> Result<LegacyFrame, Error> {
#[cfg(all(feature = "lsm", feature = "hash"))]
workspace.block_digests.clear();
let decoder = &mut workspace.decoder;
if decoder.as_ref().map(VersionDecoder::version) != Some(header.version) {
*decoder = Some(match header.version {
LegacyVersion::V05 => VersionDecoder::V05(v05::Decoder::empty()),
LegacyVersion::V06 => VersionDecoder::V06(v06::Decoder::empty()),
LegacyVersion::V07 => VersionDecoder::V07(v07::Decoder::empty()),
});
}
let history = prime(header.dict_id, dict, workspace)?;
#[cfg(feature = "hash")]
let hash = (header.checksum && options.checksum != ContentChecksum::None)
.then(|| twox_hash::XxHash64::with_seed(0));
Ok(LegacyFrame {
version: header.version,
dict_id: header.dict_id,
history,
limits: BlockLimits::of(options.block_max),
#[cfg(feature = "hash")]
checksum_mode: options.checksum,
content_size: header.content_size.unwrap_or(0),
blocks: 0,
has_checksum: header.checksum,
stored_checksum: None,
#[cfg(feature = "hash")]
calculated_checksum: None,
#[cfg(feature = "hash")]
hash,
#[cfg(all(feature = "lsm", feature = "hash"))]
block_digests: options.block_digests,
})
}
fn set_dict(&mut self, dict: LegacyDict<'_>, workspace: &mut Workspace) -> Result<(), Error> {
self.history = prime(self.dict_id, dict, workspace)?;
Ok(())
}
#[cfg(feature = "hash")]
fn set_checksum_mode(&mut self, mode: ContentChecksum) {
self.checksum_mode = mode;
if mode == ContentChecksum::None {
self.hash = None;
} else if self.hash.is_none() && self.has_checksum && self.blocks == 0 {
self.hash = Some(twox_hash::XxHash64::with_seed(0));
}
}
fn block<O: Output>(
&mut self,
workspace: &mut Workspace,
block: Block,
body: &[u8],
out: &mut O,
dict: LegacyDict<'_>,
) -> Result<(), Error> {
let decoder = workspace.decoder.as_mut().expect("readied with the frame");
let before = out.produced();
let mut out = Budget {
end: before + self.limits.of_block(block),
out,
};
let dict = self.history.of(dict);
match block {
Block::Compressed(_) => match decoder {
VersionDecoder::V05(decoder) => decoder.block(body, &mut out, dict)?,
VersionDecoder::V06(decoder) => decoder.block(body, &mut out, dict)?,
VersionDecoder::V07(decoder) => decoder.block(body, &mut out, dict)?,
},
Block::Raw(len) => {
out.reserve(len)?;
out.extend(body);
}
Block::Rle(len) => {
out.reserve(len)?;
out.repeat(body[0], len);
}
Block::End => {}
}
#[cfg(feature = "hash")]
if let Some(hash) = &mut self.hash {
use core::hash::Hasher;
hash.write(out.since(before));
}
#[cfg(all(feature = "lsm", feature = "hash"))]
if self.block_digests {
let digest = twox_hash::XxHash64::oneshot(0, out.since(before));
workspace.block_digests.push(digest as u32);
}
self.blocks += 1;
Ok(())
}
fn finish(&mut self, end: [u8; 3]) -> Result<(), Error> {
if !self.has_checksum {
return Ok(());
}
let expected = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16;
self.stored_checksum = Some(expected);
#[cfg(feature = "hash")]
if let Some(hash) = &self.hash {
use core::hash::Hasher;
let calculated = ((hash.finish() >> 11) & 0x3F_FFFF) as u32;
self.calculated_checksum = Some(calculated);
if self.checksum_mode == ContentChecksum::Verify && expected != calculated {
return Err(Error::ChecksumMismatch {
expected,
calculated,
});
}
}
Ok(())
}
}
fn prime(
dict_id: u32,
dict: LegacyDict<'_>,
workspace: &mut Workspace,
) -> Result<HistoryShape, Error> {
let (header, content) = dict.pieces();
Ok(
match workspace.decoder.as_mut().expect("readied for the frame") {
VersionDecoder::V07(decoder) => {
let content = decoder.reset(dict)?;
if dict_id != 0 && dict_id != decoder.dict_id {
return Err(Error::DictionaryMismatch {
expected: dict_id,
provided: decoder.dict_id,
});
}
HistoryShape::Content(content)
}
VersionDecoder::V06(decoder) if !header.is_empty() => {
decoder.reset(&[])?;
HistoryShape::Whole
}
VersionDecoder::V05(decoder) if !header.is_empty() => {
decoder.reset(&[])?;
HistoryShape::Whole
}
VersionDecoder::V06(decoder) => HistoryShape::Content(decoder.reset(content)?),
VersionDecoder::V05(decoder) => HistoryShape::Content(decoder.reset(content)?),
},
)
}
fn decode_frame<O: Output>(
src: &[u8],
dict: LegacyDict<'_>,
options: FrameOptions,
workspace: &mut Workspace,
out: &mut O,
) -> Result<(usize, LegacyFrame), Error> {
let header = read_header_whole(src)?;
let mut frame = LegacyFrame::new(header, dict, options, workspace)?;
let mut at = header.size;
loop {
let block_header = src.get(at..at + 3).ok_or(Error::Truncated)?;
let block = Block::read(block_header)?;
block.check(header.version, frame.limits)?;
let end = [block_header[0], block_header[1], block_header[2]];
at += 3;
let body = src.get(at..at + block.body_len()).ok_or(Error::Truncated)?;
if ends_frame(header.version, block) {
frame.finish(end)?;
return Ok((at, frame));
}
frame.block(workspace, block, body, out, dict)?;
at += body.len();
}
}
pub(crate) struct LegacyStream {
frame: Option<LegacyFrame>,
workspace: Workspace,
dict: Option<crate::decoding::DictionaryHandle>,
window: u64,
window_refused_at: Option<u64>,
buf: Vec<u8>,
end: usize,
earlier: usize,
previous_end: Option<usize>,
delivered: usize,
capacity: usize,
finished: bool,
consumed: u64,
block: Vec<u8>,
}
impl LegacyStream {
pub(crate) const fn new() -> LegacyStream {
LegacyStream {
frame: None,
workspace: Workspace {
decoder: None,
#[cfg(all(feature = "lsm", feature = "hash"))]
block_digests: Vec::new(),
},
dict: None,
window: 0,
window_refused_at: None,
buf: Vec::new(),
end: 0,
earlier: 0,
previous_end: None,
delivered: 0,
capacity: 0,
finished: false,
consumed: 0,
block: Vec::new(),
}
}
pub(crate) fn start(
&mut self,
header: FrameHeader,
dict: Option<&crate::decoding::DictionaryHandle>,
options: FrameOptions,
) -> Result<(), Error> {
self.frame = None;
let legacy_dict = dict.map_or(LegacyDict::None, LegacyDict::of);
self.frame = Some(LegacyFrame::new(
header,
legacy_dict,
options,
&mut self.workspace,
)?);
self.hold_dict(dict);
self.window = header.window;
self.window_refused_at = None;
self.buf.clear();
self.end = 0;
self.earlier = 0;
self.previous_end = None;
self.delivered = 0;
debug_assert!(header.window <= 1 << 27);
self.capacity = header.window as usize + BLOCK_SIZE_MAX;
self.finished = false;
self.consumed = header.size as u64;
Ok(())
}
fn hold_dict(&mut self, dict: Option<&crate::decoding::DictionaryHandle>) {
match dict {
None => self.dict = None,
Some(dict) => {
if self
.dict
.as_ref()
.is_none_or(|held| !core::ptr::eq(held.as_dict(), dict.as_dict()))
{
self.dict = Some(dict.clone());
}
}
}
}
pub(crate) fn end(&mut self) {
self.frame = None;
self.window_refused_at = None;
self.buf.clear();
self.end = 0;
self.delivered = 0;
self.finished = false;
}
pub(crate) fn decode_whole<O: Output>(
&mut self,
src: &[u8],
dict: Option<&crate::decoding::DictionaryHandle>,
options: FrameOptions,
out: &mut O,
) -> Result<usize, Error> {
self.end();
let before = out.produced();
let legacy_dict = dict.map_or(LegacyDict::None, LegacyDict::of);
let (consumed, frame) = decode_frame(src, legacy_dict, options, &mut self.workspace, out)?;
self.hold_dict(dict);
self.frame = Some(frame);
self.earlier = out.produced() - before;
self.previous_end = None;
self.finished = true;
self.consumed = consumed as u64;
Ok(consumed)
}
pub(crate) fn content_size(&self) -> u64 {
self.frame.as_ref().map_or(0, |frame| frame.content_size)
}
pub(crate) fn blocks_decoded(&self) -> usize {
self.frame.as_ref().map_or(0, |frame| frame.blocks)
}
pub(crate) fn stored_checksum(&self) -> Option<u32> {
self.frame.as_ref()?.stored_checksum
}
#[cfg(feature = "hash")]
pub(crate) fn calculated_checksum(&self) -> Option<u32> {
self.frame.as_ref()?.calculated_checksum
}
#[cfg(feature = "hash")]
pub(crate) fn set_checksum_mode(&mut self, mode: ContentChecksum) {
if self.finished {
return;
}
if let Some(frame) = self.frame.as_mut() {
frame.set_checksum_mode(mode);
}
}
pub(crate) fn force_dict(
&mut self,
dict: &crate::decoding::DictionaryHandle,
) -> Result<(), Error> {
let frame = self.frame.as_mut().ok_or(corrupt("no frame in progress"))?;
frame.set_dict(LegacyDict::of(dict), &mut self.workspace)?;
self.hold_dict(Some(dict));
Ok(())
}
pub(crate) fn dictionary(&self) -> Option<&crate::decoding::DictionaryHandle> {
self.dict.as_ref()
}
#[cfg(all(feature = "lsm", feature = "hash"))]
pub(crate) fn block_digests(&self) -> &[u32] {
&self.workspace.block_digests
}
pub(crate) fn set_window_ceiling(&mut self, limit: u64) {
if self.frame.is_some() && !self.finished {
self.window_refused_at = (self.window > limit).then_some(limit);
}
}
pub(crate) fn window_refusal(&self) -> Option<(u64, u64)> {
Some((self.window, self.window_refused_at?))
}
pub(crate) fn active(&self) -> bool {
self.frame.is_some()
}
pub(crate) fn is_finished(&self) -> bool {
self.finished
}
pub(crate) fn undelivered(&self) -> usize {
self.end - self.delivered
}
pub(crate) fn produced(&self) -> u64 {
(self.earlier + self.end) as u64
}
pub(crate) fn consumed(&self) -> u64 {
self.consumed
}
pub(crate) fn heap_bytes(&self) -> usize {
self.buf.capacity() + self.block.capacity() + self.workspace.heap_bytes()
}
pub(crate) fn input_needed(&self, pending: &[u8]) -> usize {
if self.finished {
return 0;
}
let Ok(block) = Block::read(pending) else {
return 3;
};
match &self.frame {
Some(frame) if block.check(frame.version, frame.limits).is_err() => 3,
_ => 3 + block.body_len(),
}
}
pub(crate) fn decode(
&mut self,
src: &[u8],
target: &mut [u8],
) -> Result<(usize, usize), Error> {
let mut read = 0;
let mut written = 0;
loop {
written += self.hand_out(&mut target[written..]);
if self.undelivered() > 0 || self.finished {
break;
}
let rest = &src[read..];
let need = self.input_needed(rest);
if need > rest.len() {
break;
}
self.step(&rest[..need])?;
read += need;
}
Ok((read, written))
}
pub(crate) fn hand_out(&mut self, target: &mut [u8]) -> usize {
let n = self.undelivered().min(target.len());
target[..n].copy_from_slice(&self.buf[self.delivered..self.delivered + n]);
self.delivered += n;
n
}
pub(crate) fn undelivered_bytes(&self) -> &[u8] {
&self.buf[self.delivered..self.end]
}
pub(crate) fn mark_delivered(&mut self, n: usize) {
debug_assert!(n <= self.undelivered());
self.delivered += n;
}
pub(crate) fn read_step<R: crate::io::Read>(
&mut self,
source: &mut R,
) -> Result<(), crate::decoding::errors::FrameDecoderError> {
use crate::blocks::block::BlockType;
use crate::decoding::errors::{
BlockHeaderReadError, DecodeBlockContentError, FrameDecoderError,
};
let mut block = core::mem::take(&mut self.block);
block.clear();
block.resize(3, 0);
let result = source
.read_exact(&mut block)
.map_err(|source| {
FrameDecoderError::FailedToReadBlockHeader(BlockHeaderReadError::ReadError(source))
})
.and_then(|()| {
let need = self.input_needed(&block);
block.resize(need, 0);
let step = match Block::read(&block) {
Ok(Block::Raw(_)) => BlockType::Raw,
Ok(Block::Rle(_)) => BlockType::RLE,
_ => BlockType::Compressed,
};
source.read_exact(&mut block[3..]).map_err(|source| {
FrameDecoderError::FailedToReadBlockBody(DecodeBlockContentError::ReadError {
step,
source,
})
})
})
.and_then(|()| Ok(self.step(&block)?));
self.block = block;
result
}
fn step(&mut self, step: &[u8]) -> Result<(), Error> {
let LegacyStream {
frame,
workspace,
dict,
window,
window_refused_at: _,
buf,
end,
earlier,
previous_end,
delivered,
capacity,
finished,
consumed,
block: _,
} = self;
let frame = frame.as_mut().ok_or(corrupt("no frame in progress"))?;
let block = Block::read(step)?;
block.check(frame.version, frame.limits)?;
*consumed += step.len() as u64;
if ends_frame(frame.version, block) {
frame.finish([step[0], step[1], step[2]])?;
*finished = true;
return Ok(());
}
let block_max = block.decoded_max();
let handed_out = *delivered == *end;
if *end + block_max > *capacity && handed_out && *end > 0 && *end as u64 >= *window {
*earlier += *end;
*previous_end = Some(*end);
*end = 0;
*delivered = 0;
}
let limit = if handed_out {
(*capacity).max(*end + block_max)
} else {
*capacity
};
let dict = dict.as_ref().map_or(LegacyDict::None, LegacyDict::of);
let mut out = SegmentOutput {
buf,
end: *end,
earlier: *earlier,
previous_end: *previous_end,
capacity: limit,
};
let decoded = frame.block(workspace, block, &step[3..], &mut out, dict);
*end = out.end;
decoded
}
}
struct SegmentOutput<'b> {
buf: &'b mut Vec<u8>,
end: usize,
earlier: usize,
previous_end: Option<usize>,
capacity: usize,
}
impl SegmentOutput<'_> {
fn room(&mut self, len: usize) -> core::ops::Range<usize> {
let to = self.end + len;
if to > self.buf.capacity() {
let doubled = 2 * self.buf.len();
let target = if to <= self.capacity {
doubled.clamp(to, self.capacity)
} else {
doubled.max(to)
};
self.buf.reserve_exact(target - self.buf.len());
}
if to > self.buf.len() {
self.buf.resize(to, 0);
}
self.end..to
}
}
impl Output for SegmentOutput<'_> {
fn produced(&self) -> usize {
self.earlier + self.end
}
fn held(&self) -> usize {
self.end
}
fn reserve(&mut self, _len: usize) -> Result<(), Error> {
Ok(())
}
fn extend(&mut self, bytes: &[u8]) {
let at = self.room(bytes.len());
self.buf[at].copy_from_slice(bytes);
self.end += bytes.len();
}
fn repeat(&mut self, byte: u8, len: usize) {
let at = self.room(len);
self.buf[at].fill(byte);
self.end += len;
}
fn copy_back(&mut self, offset: usize, len: usize) {
let from = self.end - offset;
let at = self.room(len);
copy_repeating(self.buf, from, at.start, len);
self.end += len;
}
#[cfg(feature = "hash")]
fn since(&self, from: usize) -> &[u8] {
&self.buf[self.end - (self.produced() - from)..self.end]
}
fn copy_match(&mut self, offset: usize, len: usize, dict: &History<'_>) -> Result<(), Error> {
let Some(previous_end) = self.previous_end else {
return copy_match(self, offset, len, dict);
};
debug_assert!(offset >= 1);
if offset <= self.end {
self.copy_back(offset, len);
return Ok(());
}
let back = offset - self.end;
let from = previous_end
.checked_sub(back)
.filter(|&from| from >= self.end)
.ok_or(corrupt("match reaches before the window"))?;
let first = len.min(back);
let at = self.room(first);
self.buf.copy_within(from..from + first, at.start);
self.end += first;
let rest = len - first;
if rest > 0 {
let distance = self.end;
self.copy_back(distance, rest);
}
Ok(())
}
}
#[cfg(test)]
mod tests;