use crate::block::{self, BLOCK_SIZE_MAX, FrameContext};
use crate::dictionary::Dictionary;
use crate::error::Error;
use crate::frame;
use crate::xxhash::xxh64;
pub(crate) const ZSTD_MAGIC: u32 = 0xFD2F_B528;
pub(crate) const SKIPPABLE_MAGIC_MASK: u32 = 0xFFFF_FFF0;
pub(crate) const SKIPPABLE_MAGIC: u32 = 0x184D_2A50;
const MAX_PREALLOC: u64 = 32 << 20;
pub const WINDOW_LOG_MAX: u32 = 31;
#[derive(Clone)]
pub struct DecodeOptions<'d> {
limit: usize,
window_log_max: u32,
dictionary: Option<&'d Dictionary>,
}
impl Default for DecodeOptions<'_> {
fn default() -> Self {
DecodeOptions {
limit: usize::MAX,
window_log_max: WINDOW_LOG_MAX,
dictionary: None,
}
}
}
impl<'d> DecodeOptions<'d> {
pub fn new() -> Self {
Self::default()
}
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
pub fn window_log_max(mut self, window_log_max: u32) -> Self {
self.window_log_max = window_log_max;
self
}
pub fn dictionary(mut self, dictionary: &'d Dictionary) -> Self {
self.dictionary = Some(dictionary);
self
}
pub(crate) fn limit_value(&self) -> usize {
self.limit
}
pub(crate) fn window_log_max_value(&self) -> u32 {
self.window_log_max
}
pub(crate) fn dictionary_value(&self) -> Option<&'d Dictionary> {
self.dictionary
}
pub fn decompress(&self, src: &[u8]) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
let mut input = src;
while !input.is_empty() {
if input.len() < 4 {
return Err(Error::SrcSizeWrong);
}
let magic = u32::from_le_bytes(input[..4].try_into().unwrap());
if magic == ZSTD_MAGIC {
input = self.decode_frame(&input[4..], &mut out)?;
} else if magic & SKIPPABLE_MAGIC_MASK == SKIPPABLE_MAGIC {
if input.len() < 8 {
return Err(Error::SrcSizeWrong);
}
let size = u32::from_le_bytes(input[4..8].try_into().unwrap()) as usize;
input = input.get(8 + size..).ok_or(Error::SrcSizeWrong)?;
} else {
return Err(Error::UnknownMagic(magic));
}
}
Ok(out)
}
fn decode_frame<'a>(&self, src: &'a [u8], out: &mut Vec<u8>) -> Result<&'a [u8], Error> {
let header = frame::parse(src, self.window_log_max)?;
let dict = match (self.dictionary, header.dict_id) {
(_, 0) => self.dictionary,
(Some(d), id) if d.id() == id => Some(d),
(Some(d), id) => {
return Err(Error::DictionaryWrong {
expected: id,
actual: d.id(),
});
}
(None, id) => return Err(Error::DictionaryRequired(id)),
};
let dict_content = dict.map_or(&[][..], Dictionary::content);
let frame_base = out.len();
if let Some(fcs) = header.content_size {
if fcs as u128 + frame_base as u128 > self.limit as u128 {
return Err(Error::OutputTooLarge);
}
out.reserve(fcs.min(MAX_PREALLOC) as usize);
}
let block_size_max = header.window_size.min(BLOCK_SIZE_MAX as u64) as usize;
let mut ctx = FrameContext::with_dictionary(dict);
let mut input = &src[header.header_len..];
loop {
let bh = input.get(..3).ok_or(Error::SrcSizeWrong)?;
let raw = u32::from(bh[0]) | u32::from(bh[1]) << 8 | u32::from(bh[2]) << 16;
input = &input[3..];
let last = raw & 1 != 0;
let block_type = (raw >> 1) & 3;
let size = (raw >> 3) as usize;
match block_type {
0 => {
if size > block_size_max {
return Err(Error::Corrupted("block size exceeds block size limit"));
}
let data = input.get(..size).ok_or(Error::SrcSizeWrong)?;
if out.len() + size > self.limit {
return Err(Error::OutputTooLarge);
}
out.extend_from_slice(data);
input = &input[size..];
}
1 => {
if size > block_size_max {
return Err(Error::Corrupted("block size exceeds block size limit"));
}
let byte = *input.first().ok_or(Error::SrcSizeWrong)?;
if out.len() + size > self.limit {
return Err(Error::OutputTooLarge);
}
out.resize(out.len() + size, byte);
input = &input[1..];
}
2 => {
if size > block_size_max {
return Err(Error::Corrupted("block size exceeds block size limit"));
}
let data = input.get(..size).ok_or(Error::SrcSizeWrong)?;
block::decode_compressed_block(
&mut ctx,
data,
out,
frame_base,
dict_content,
block_size_max,
self.limit,
)?;
input = &input[size..];
}
_ => return Err(Error::BlockTypeInvalid),
}
if last {
break;
}
}
if let Some(fcs) = header.content_size {
if (out.len() - frame_base) as u64 != fcs {
return Err(Error::FrameContentSizeMismatch);
}
}
if header.has_checksum {
let stored = input.get(..4).ok_or(Error::SrcSizeWrong)?;
let expected = u32::from_le_bytes(stored.try_into().unwrap());
let actual = xxh64(&out[frame_base..], 0) as u32;
if expected != actual {
return Err(Error::ChecksumMismatch { expected, actual });
}
input = &input[4..];
}
Ok(input)
}
}
pub fn decompress(src: &[u8]) -> Result<Vec<u8>, Error> {
DecodeOptions::new().decompress(src)
}
pub fn decompress_with_limit(src: &[u8], limit: usize) -> Result<Vec<u8>, Error> {
DecodeOptions::new().limit(limit).decompress(src)
}