use std::io::Read as _;
use ruzstd::decoding::errors::FrameDecoderError;
use ruzstd::decoding::{BlockDecodingStrategy, FrameDecoder};
use super::{u64_from_index, DecodeContext, View};
use crate::CodecError;
const BLOCK_BYTES: u64 = 128 * 1024;
const MAX_DECODER_WINDOW: u64 = 1_u64 << if usize::BITS == 32 { 30 } else { 31 };
const OUTPUT_STEP_BYTES: usize = 16 * 1024;
const FRAME_MAGIC: u32 = 0xfd2f_b528;
pub struct ZstdDecoder<'ctx, 'input> {
frame: Option<ActiveFrame<'input>>,
_ctx: std::marker::PhantomData<&'ctx DecodeContext<'input>>,
input: View<'input>,
finished: bool,
}
struct ActiveFrame<'input> {
decoder: FrameDecoder,
source: &'input [u8],
window: u64,
block_maximum: u64,
content_size: Option<u64>,
produced: u64,
buffered: Option<u64>,
}
struct Inspected {
size: usize,
data: Option<FrameFacts>,
}
#[derive(Clone, Copy)]
struct FrameFacts {
window: u64,
content_size: Option<u64>,
}
impl<'input> DecodeContext<'input> {
pub fn open_zstd<'ctx>(
&'ctx self,
source: View<'input>,
) -> Result<ZstdDecoder<'ctx, 'input>, CodecError> {
let mut offset = 0;
let allowance = self.per_expand_allowance();
while offset < source.window().len() {
let frame = source
.child(source.start() + offset, source.end())
.ok_or_else(|| {
CodecError::Malformed("Zstandard frame offset escapes input".into())
})?;
if let Some(facts) = frame_header(frame)?.1 {
let buffered = facts
.content_size
.map_or(facts.window, |size| size.min(facts.window));
if buffered > allowance {
return Err(CodecError::NotImplemented(format!(
"Zstandard window of {buffered} bytes exceeds the \
{allowance}-byte per-expansion allowance"
)));
}
if facts.window > MAX_DECODER_WINDOW {
return Err(window_unsupported(facts.window));
}
}
let Inspected { size, .. } = inspect_frame(frame)?;
if size == 0 || size > frame.window().len() {
return Err(CodecError::Malformed("invalid Zstandard frame size".into()));
}
offset += size;
}
if offset == 0 {
return Err(CodecError::Malformed("empty Zstandard stream".into()));
}
Ok(ZstdDecoder {
frame: None,
_ctx: std::marker::PhantomData,
input: source
.child(source.start(), source.end())
.ok_or_else(|| CodecError::Malformed("Zstandard input escapes its view".into()))?,
finished: false,
})
}
}
impl ZstdDecoder<'_, '_> {
pub fn read_chunk(&mut self, bytes: &mut [u8]) -> Result<usize, CodecError> {
if self.finished || bytes.is_empty() {
return Ok(0);
}
let capacity = bytes.len().min(OUTPUT_STEP_BYTES);
loop {
if self.frame.is_none() {
if self.input.is_empty() {
self.finished = true;
return Ok(0);
}
self.open_next_frame()?;
}
let Some(frame) = self.frame.as_mut() else {
continue;
};
if frame.decoder.can_collect() == 0 && !frame.decoder.is_finished() {
frame.decode_block()?;
}
let produced = frame
.decoder
.read(&mut bytes[..capacity])
.map_err(|error| malformed(&error))?;
frame.produced += u64_from_index(produced);
if frame
.content_size
.is_some_and(|declared| frame.produced > declared)
{
return Err(CodecError::Malformed(
"Zstandard frame regenerates more than its declared content size".into(),
));
}
if frame.decoder.is_finished() && frame.decoder.can_collect() == 0 {
self.close_frame()?;
if self.input.is_empty() {
self.finished = true;
}
}
if produced != 0 || self.finished {
return Ok(produced);
}
}
}
fn open_next_frame(&mut self) -> Result<(), CodecError> {
let inspected = inspect_frame(self.input)?;
let source = self
.input
.take(inspected.size)
.ok_or_else(|| CodecError::Malformed("truncated Zstandard stream".into()))?;
let Some(facts) = inspected.data else {
return Ok(());
};
let mut decoder = FrameDecoder::new();
decoder.set_max_window_size(facts.window);
let mut frame = ActiveFrame {
decoder,
source,
window: facts.window,
block_maximum: facts.window.min(BLOCK_BYTES),
content_size: facts.content_size,
produced: 0,
buffered: Some(0),
};
frame
.decoder
.init(&mut frame.source)
.map_err(|error| zstd_error(&error, facts.window))?;
self.frame = Some(frame);
Ok(())
}
fn close_frame(&mut self) -> Result<(), CodecError> {
let Some(frame) = self.frame.take() else {
return Ok(());
};
if let (Some(declared), Some(computed)) = (
frame.decoder.get_checksum_from_data(),
frame.decoder.get_calculated_checksum(),
) {
if declared != computed {
return Err(CodecError::Malformed(
"Zstandard content checksum mismatch".into(),
));
}
}
if frame
.content_size
.is_some_and(|declared| declared != frame.produced)
{
return Err(CodecError::Malformed(
"Zstandard frame does not regenerate its declared content size".into(),
));
}
if !frame.source.is_empty() {
return Err(CodecError::Malformed(
"Zstandard decoder stopped before the end of its frame".into(),
));
}
Ok(())
}
}
impl ActiveFrame<'_> {
fn decode_block(&mut self) -> Result<(), CodecError> {
let header = View::u24_le_at(self.source, 0)
.ok_or_else(|| CodecError::Malformed("truncated Zstandard stream".into()))?;
let kind = (header >> 1) & 3;
let declared = u64::from(header >> 3);
if declared > self.block_maximum {
return Err(CodecError::Malformed(
"Zstandard block size exceeds its frame's maximum".into(),
));
}
self.decoder
.decode_blocks(&mut self.source, BlockDecodingStrategy::UptoBlocks(1))
.map_err(|error| zstd_error(&error, self.window))?;
let collectable = u64_from_index(self.decoder.can_collect());
let length = if self.decoder.is_finished() {
Some(collectable)
} else if collectable != 0 {
self.window.checked_add(collectable)
} else if kind == 2 {
None
} else {
self.buffered
.and_then(|buffered| buffered.checked_add(declared))
};
if let Some(length) = length {
let before = self.buffered.unwrap_or(self.window);
if length
.checked_sub(before)
.is_some_and(|grown| grown > self.block_maximum)
{
return Err(CodecError::Malformed(
"Zstandard block regenerates more than its frame's maximum".into(),
));
}
}
self.buffered = length.map(|length| length.min(self.window));
Ok(())
}
}
fn window_unsupported(window: u64) -> CodecError {
CodecError::NotImplemented(format!(
"Zstandard window of {window} bytes exceeds the {MAX_DECODER_WINDOW}-byte decoder window"
))
}
fn malformed(error: &dyn std::fmt::Display) -> CodecError {
CodecError::malformed(format_args!("invalid Zstandard stream: {error}"))
}
fn zstd_error(error: &FrameDecoderError, window: u64) -> CodecError {
match error {
FrameDecoderError::WindowSizeTooBig { .. } => window_unsupported(window),
_ => malformed(error),
}
}
fn frame_header(mut frame: View<'_>) -> Result<(View<'_>, Option<FrameFacts>), CodecError> {
let magic = frame.req_u32_le()?;
if (0x184d_2a50..=0x184d_2a5f).contains(&magic) {
return Ok((frame, None));
}
if magic != FRAME_MAGIC {
return Err(CodecError::Malformed("invalid Zstandard magic".into()));
}
let flags = frame.req_u8()?;
if flags & 8 != 0 {
return Err(CodecError::Malformed(
"reserved Zstandard frame flag".into(),
));
}
let single_segment = flags & 32 != 0;
let window = if single_segment {
0
} else {
let descriptor = frame.req_u8()?;
let base = 1_u64 << (10 + u32::from(descriptor >> 3));
base + (base / 8) * u64::from(descriptor & 7)
};
let dictionary_bytes = match flags & 3 {
0 => 0,
1 => 1,
2 => 2,
_ => 4,
};
frame.req_take(dictionary_bytes)?;
let content_size = match flags >> 6 {
0 if single_segment => Some(u64::from(frame.req_u8()?)),
0 => None,
1 => Some(u64::from(frame.req_u16_le()?) + 256),
2 => Some(u64::from(frame.req_u32_le()?)),
_ => Some(frame.req_u64_le()?),
};
Ok((
frame,
Some(FrameFacts {
window: if single_segment {
content_size.unwrap_or(0)
} else {
window
},
content_size,
}),
))
}
fn inspect_frame(frame: View<'_>) -> Result<Inspected, CodecError> {
let truncated = || CodecError::Malformed("truncated Zstandard frame".into());
let start = frame.read_len();
let checksum = frame.unread().get(4).is_some_and(|flags| flags & 4 != 0);
let (mut body, facts) = frame_header(frame)?;
let Some(facts) = facts else {
let length = usize::try_from(body.req_u32_le()?).map_err(|_| truncated())?;
body.skip(length).ok_or_else(truncated)?;
return Ok(Inspected {
size: body.read_len() - start,
data: None,
});
};
loop {
let header = body.take(3).ok_or_else(truncated)?;
let header = u32::from(header[0]) | u32::from(header[1]) << 8 | u32::from(header[2]) << 16;
let declared = header >> 3;
let kind = (header >> 1) & 3;
let stored = match kind {
1 => 1,
3 => {
return Err(CodecError::Malformed(
"reserved Zstandard block type".into(),
))
}
_ => usize::try_from(declared).map_err(|_| truncated())?,
};
body.skip(stored).ok_or_else(truncated)?;
if header & 1 != 0 {
break;
}
}
if checksum {
body.skip(4).ok_or_else(truncated)?;
}
Ok(Inspected {
size: body.read_len() - start,
data: Some(facts),
})
}
#[cfg(test)]
mod tests {
use super::{BLOCK_BYTES, MAX_DECODER_WINDOW};
use crate::decode::{DecodeArena, DecodeContext, DecodePolicy, ExpandSpec};
use crate::CodecError;
const MAGIC: [u8; 4] = [0x28, 0xb5, 0x2f, 0xfd];
fn block(last: bool, kind: u32, size: usize, stored: &[u8]) -> Vec<u8> {
let header = u32::try_from(size).expect("block size") << 3 | kind << 1 | u32::from(last);
let mut out = header.to_le_bytes()[..3].to_vec();
out.extend_from_slice(stored);
out
}
fn raw(last: bool, payload: &[u8]) -> Vec<u8> {
block(last, 0, payload.len(), payload)
}
fn single_segment(payload: &[u8]) -> Vec<u8> {
let mut out = MAGIC.to_vec();
out.extend_from_slice(&[0x20, u8::try_from(payload.len()).expect("one-byte size")]);
out.extend(raw(true, payload));
out
}
fn windowed(window_log: u8, blocks: &[Vec<u8>]) -> Vec<u8> {
let mut out = MAGIC.to_vec();
out.extend_from_slice(&[0x00, (window_log - 10) << 3]);
for block in blocks {
out.extend_from_slice(block);
}
out
}
fn decode_with(bytes: &[u8], policy: &DecodePolicy) -> Result<Vec<u8>, CodecError> {
let arena = DecodeArena::new();
let (ctx, root) = DecodeContext::from_root_bytes(bytes, &arena, policy).expect("root");
let mut decoder = ctx.open_zstd(root)?;
let mut output = Vec::new();
let mut chunk = [0_u8; 16 * 1024];
loop {
let read = decoder.read_chunk(&mut chunk)?;
if read == 0 {
return Ok(output);
}
output.extend_from_slice(&chunk[..read]);
}
}
fn decode(bytes: &[u8]) -> Result<Vec<u8>, CodecError> {
decode_with(bytes, &DecodePolicy::service())
}
fn assert_malformed(bytes: &[u8], what: &str) {
let result = decode(bytes);
assert!(
matches!(result, Err(CodecError::Malformed(_))),
"{what}: {result:?}"
);
}
fn assert_cut_short(bytes: &[u8], what: &str) {
let result = decode(bytes);
assert!(
matches!(
result,
Err(CodecError::Malformed(_) | CodecError::Truncated { .. })
),
"{what}: {result:?}"
);
}
fn frame_with_window(window: u64) -> Vec<u8> {
let exponent = u8::try_from(window.trailing_zeros() - 10).expect("window exponent");
vec![0x28, 0xb5, 0x2f, 0xfd, 0, exponent << 3, 1, 0, 0]
}
#[test]
fn zstd_window_past_the_per_expand_allowance_is_unsupported_and_leaves_the_session_open() {
let window = 32 << 20;
let bytes = frame_with_window(window);
let policy = DecodePolicy::service();
let arena = DecodeArena::new();
let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
let allowance = ctx.per_expand_allowance();
assert!(allowance < window);
assert!(window < policy.limits.max_decompressed_bytes_per_expand);
let Err(CodecError::NotImplemented(message)) = ctx.open_zstd(root) else {
panic!("a window above the allowance is unsupported");
};
assert_eq!(
message,
format!(
"Zstandard window of {window} bytes exceeds the \
{allowance}-byte per-expansion allowance"
)
);
assert_eq!(ctx.resource_refusal(), None);
let mut writer = ctx
.begin_expand(ExpandSpec::Exact(1))
.expect("later expansions are admitted");
writer.write(b"x").expect("write");
writer.finalize().expect("finalize");
assert!(ctx.finish_session().is_ok());
}
#[test]
fn zstd_window_is_admitted_by_a_smaller_declared_content_size() {
let window = 32 << 20;
let content = [7_u8; 256];
let mut bytes = vec![
0x28,
0xb5,
0x2f,
0xfd,
0x40,
15 << 3,
0,
0,
0x01,
0x08,
0x00,
];
bytes.extend_from_slice(&content);
let policy = DecodePolicy::service();
let arena = DecodeArena::new();
let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
assert!(ctx.per_expand_allowance() < window);
let mut decoder = ctx.open_zstd(root).expect("the content size fits");
let mut output = Vec::new();
let mut chunk = [0; 64];
loop {
let read = decoder.read_chunk(&mut chunk).expect("read");
if read == 0 {
break;
}
output.extend_from_slice(&chunk[..read]);
}
assert_eq!(output, content);
assert_eq!(ctx.resource_refusal(), None);
}
#[test]
fn zstd_window_above_the_decoder_is_unsupported_and_leaves_the_session_open() {
let window = MAX_DECODER_WINDOW * 2;
let mut bytes = frame_with_window(window);
bytes.resize(usize::try_from(window / 256).expect("padding length"), 0);
let mut policy = DecodePolicy::service();
policy.limits.max_decompressed_bytes_per_expand = window;
let arena = DecodeArena::new();
let (ctx, root) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).expect("root");
assert!(ctx.per_expand_allowance() >= window);
assert!(matches!(
ctx.open_zstd(root),
Err(CodecError::NotImplemented(message)) if message.contains("decoder window")
));
assert_eq!(ctx.resource_refusal(), None);
assert!(ctx.finish_session().is_ok());
}
#[test]
fn zstd_checks_each_window_before_decoder_allocation() {
let mut bytes = single_segment(b"prefix");
bytes.extend_from_slice(&[0x28, 0xb5, 0x2f, 0xfd, 0, 0xa0, 1, 0, 0]);
let arena = DecodeArena::new();
let (ctx, root) =
DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service()).expect("root");
assert!(
matches!(ctx.open_zstd(root), Err(CodecError::NotImplemented(message)) if message.starts_with(&format!("Zstandard window of {} bytes", 1_u64 << 30)) && message.ends_with("per-expansion allowance"))
);
assert_eq!(ctx.resource_refusal(), None);
}
#[test]
fn zstd_decodes_a_frame() {
let bytes = single_segment(b"payload");
let arena = DecodeArena::new();
let (ctx, root) =
DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service()).expect("root");
let mut decoder = ctx.open_zstd(root).expect("decoder");
let mut output = [0; 7];
assert_eq!(decoder.read_chunk(&mut output).expect("read"), 7);
assert_eq!(&output, b"payload");
}
#[test]
fn zstd_decodes_a_frame_that_fills_its_window() {
let block_bytes = usize::try_from(BLOCK_BYTES).expect("block size");
let mut blocks = vec![block(false, 1, block_bytes, b"x"); 19];
blocks.push(block(true, 1, block_bytes, b"x"));
let bytes = windowed(20, &blocks);
assert_eq!(decode(&bytes).expect("fits"), vec![b'x'; 20 * block_bytes]);
}
#[test]
fn zstd_short_frame_under_a_large_window_needs_its_content_size() {
let bytes = windowed(28, &[raw(true, b"abcd")]);
assert!(matches!(
decode(&bytes),
Err(CodecError::NotImplemented(message)) if message.ends_with("per-expansion allowance")
));
let mut sized = MAGIC.to_vec();
sized.extend_from_slice(&[0x80, 18 << 3, 4, 0, 0, 0]); sized.extend(raw(true, b"abcd"));
assert_eq!(decode(&sized).expect("frame"), b"abcd");
}
#[test]
fn zstd_decodes_every_frame_and_skips_skippable_ones() {
let first = single_segment(b"first ");
let second = windowed(10, &[raw(false, b"sec"), raw(true, b"ond")]);
let skippable = [&[0x53, 0x2a, 0x4d, 0x18, 3, 0, 0, 0][..], b"zzz"].concat();
assert_eq!(
decode(&[first.clone(), second.clone()].concat()).expect("two frames"),
b"first second"
);
assert_eq!(
decode(&[skippable.clone(), first.clone(), skippable.clone(), second].concat())
.expect("skippable frames between data frames"),
b"first second"
);
assert_eq!(
decode(&skippable).expect("only a skippable frame"),
b"",
"a stream of skippable frames is an empty payload"
);
assert_malformed(b"", "an empty stream");
for tail in [&b"junk"[..], &b"\0"[..], &skippable[..9]] {
let bytes = [&first[..], tail].concat();
let arena = DecodeArena::new();
let (ctx, root) =
DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service())
.expect("root");
assert!(
matches!(
ctx.open_zstd(root),
Err(CodecError::Malformed(_) | CodecError::Truncated { .. })
),
"a {}-byte tail was admitted",
tail.len()
);
}
}
#[test]
fn zstd_rle_blocks_and_block_boundaries_decode() {
let frame = windowed(
10,
&[
raw(false, b"ab"),
block(false, 1, 1000, b"x"),
block(false, 0, 0, b""),
block(true, 1, 600, b"y"),
],
);
let mut expected = b"ab".to_vec();
expected.extend(std::iter::repeat_n(b'x', 1000));
expected.extend(std::iter::repeat_n(b'y', 600));
assert_eq!(decode(&frame).expect("frame"), expected);
for keep in 0..frame.len() {
assert_cut_short(&frame[..keep], "a truncated frame");
}
}
#[test]
fn zstd_content_checksum_is_compared() {
let mut frame = MAGIC.to_vec();
frame.extend_from_slice(&[0x04, 0x00]);
frame.extend(raw(true, b"abcd"));
frame.extend_from_slice(&0xd25d_92cc_u32.to_le_bytes());
assert_eq!(decode(&frame).expect("matching checksum"), b"abcd");
for index in frame.len() - 4..frame.len() {
let mut corrupt = frame.clone();
corrupt[index] ^= 1;
assert_malformed(&corrupt, "a wrong content checksum");
}
let payload = frame.len() - 5;
frame[payload] ^= 1;
assert_malformed(&frame, "a payload its checksum does not cover");
}
#[test]
fn zstd_declared_content_size_is_compared() {
let mut frame = single_segment(b"abcd");
assert_eq!(decode(&frame).expect("exact"), b"abcd");
frame[5] = 8;
assert_malformed(&frame, "a frame shorter than its declared size");
let mut frame = MAGIC.to_vec();
frame.extend_from_slice(&[0x40, 0x00, 0x00, 0x00]); frame.extend(raw(true, &[7; 300]));
assert_malformed(&frame, "a frame longer than its declared size");
frame[6] = 44; assert_eq!(decode(&frame).expect("exact"), [7; 300]);
}
#[test]
fn zstd_stops_at_the_declared_content_size() {
let block_bytes = usize::try_from(BLOCK_BYTES).expect("block size");
let mut frame = MAGIC.to_vec();
frame.extend_from_slice(&[0x40, 0x50, 0x00, 0x00]); for index in 0..10 {
frame.extend(block(index == 9, 1, block_bytes, b"x"));
}
let arena = DecodeArena::new();
let (ctx, root) =
DecodeContext::from_root_bytes(&frame, &arena, &DecodePolicy::service()).expect("root");
let mut decoder = ctx.open_zstd(root).expect("decoder");
let mut chunk = [0_u8; 16 * 1024];
let mut delivered = 0;
let error = loop {
match decoder.read_chunk(&mut chunk) {
Ok(0) => panic!("the frame was accepted"),
Ok(read) => delivered += read,
Err(error) => break error,
}
};
assert!(matches!(error, CodecError::Malformed(_)), "{error:?}");
assert!(
delivered <= 256,
"{delivered} bytes were delivered past a declared size of 256"
);
}
#[test]
fn zstd_blocks_are_held_to_their_frames_maximum() {
assert_eq!(
decode(&windowed(10, &[raw(true, &[1; 1024])])).expect("at the maximum"),
[1; 1024]
);
assert_malformed(
&windowed(10, &[raw(true, &[1; 1025])]),
"a raw block over its window",
);
assert_malformed(
&windowed(10, &[block(true, 1, 1025, b"x")]),
"an RLE block over its window",
);
let compressed = rle_literals;
assert_eq!(
decode(&windowed(10, &[compressed(true, 1000)])).expect("under the maximum"),
[b'A'; 1000]
);
assert_malformed(
&windowed(10, &[compressed(true, 2000)]),
"a compressed block regenerating more than its window, last",
);
assert_malformed(
&windowed(10, &[compressed(false, 2000), raw(true, b"z")]),
"a compressed block regenerating more than its window, not last",
);
assert_malformed(
&windowed(10, &[raw(false, &[1; 600]), compressed(true, 1025)]),
"a compressed block regenerating more than its window after a raw one",
);
}
#[test]
fn zstd_block_regenerating_past_128_kib_is_refused() {
const SEQUENCES: u8 = 100;
let mut content = vec![0x00, SEQUENCES, 0x54, 0, 0, 52];
content.extend(std::iter::repeat_n(0xff, 2 * usize::from(SEQUENCES)));
content.push(0x01);
let frame = windowed(
21,
&[
raw(false, b"abcdefgh"),
block(true, 2, content.len(), &content),
],
);
assert_malformed(&frame, "a block regenerating 13 MiB");
}
const REFERENCE_BLOCK: [u8; 77] = [
0x42, 0x43, 0x0c, 0x11, 0xa0, 0x6f, 0x20, 0xe9, 0x9f, 0x94, 0x37, 0x01, 0x80, 0xf3, 0xc9,
0x9f, 0x90, 0xaa, 0xdf, 0x42, 0x1e, 0xc7, 0x14, 0x32, 0x84, 0xb7, 0x27, 0x74, 0x4c, 0x21,
0x43, 0x38, 0xf8, 0x69, 0xeb, 0x75, 0x8c, 0x9f, 0xaa, 0x15, 0x28, 0x75, 0x90, 0xbf, 0x55,
0x66, 0x92, 0xbb, 0x9b, 0x3f, 0xba, 0x17, 0x12, 0xa8, 0x10, 0xe8, 0xeb, 0xff, 0x19, 0xf0,
0x35, 0x03, 0x10, 0x66, 0x84, 0x1d, 0xf1, 0x7f, 0xe3, 0x03, 0xac, 0x7c, 0x2b, 0xad, 0x02,
0x92, 0x0c,
];
fn reference_block_plaintext() -> Vec<u8> {
let mut out = Vec::new();
for line in 0..20 {
out.extend_from_slice(format!("line {line:03} of the miss-case fixture\n").as_bytes());
}
out.truncate(600);
out
}
fn rle_literals(last: bool, size: u16) -> Vec<u8> {
let header = 1 | 1 << 2 | size << 4;
block(
last,
2,
4,
&[&header.to_le_bytes()[..], b"A", &[0]].concat(),
)
}
#[test]
fn zstd_decodes_a_reference_encoded_block_with_sequences() {
let plain = reference_block_plaintext();
let reference = |last| block(last, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
assert_eq!(
decode(&windowed(10, &[reference(true)])).expect("block"),
plain
);
let expected = [&plain[..], &[b'A'; 1000], b"end"].concat();
assert_eq!(
decode(&windowed(
10,
&[
reference(false),
rle_literals(false, 1000),
raw(true, b"end")
]
))
.expect("three blocks"),
expected
);
}
#[test]
fn zstd_block_header_maximum_holds_when_the_buffer_length_is_unknown() {
let reference = block(false, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
let raw_literals = |size: u16| {
let header = 1_u16 << 2 | size << 4;
let stored = [
&header.to_le_bytes()[..],
&vec![b'A'; usize::from(size)],
&[0],
]
.concat();
block(true, 2, stored.len(), &stored)
};
assert_eq!(
decode(&windowed(10, &[reference.clone(), raw_literals(400)]))
.expect("control: a compressed block of raw literals under the maximum")
.len(),
reference_block_plaintext().len() + 400
);
assert_malformed(
&windowed(10, &[reference.clone(), raw_literals(1025)]),
"a compressed block storing more than its window after a compressed one",
);
assert_malformed(
&windowed(10, &[reference.clone(), raw(true, &[1; 1025])]),
"a raw block over its window after a compressed one",
);
assert_malformed(
&windowed(10, &[reference, block(true, 1, 1025, b"x")]),
"an RLE block over its window after a compressed one",
);
}
#[test]
fn zstd_known_gap_a_compressed_block_over_its_maximum_under_an_unfilled_window() {
let reference = block(false, 2, REFERENCE_BLOCK.len(), &REFERENCE_BLOCK);
let expected = [&reference_block_plaintext()[..], &[b'A'; 1100]].concat();
assert_eq!(
decode(&windowed(10, &[reference, rle_literals(true, 1100)]))
.expect("accepted, where libzstd refuses"),
expected
);
assert_malformed(
&windowed(
10,
&[
raw(false, &reference_block_plaintext()),
rle_literals(true, 1100),
],
),
"the same block after a raw one",
);
}
#[test]
fn zstd_refuses_what_the_frame_format_reserves() {
let mut frame = single_segment(b"abcd");
frame[4] |= 8;
assert_malformed(&frame, "the reserved frame-descriptor bit");
assert_malformed(
&windowed(10, &[block(true, 3, 0, b"")]),
"the reserved block type",
);
let mut frame = MAGIC.to_vec();
frame.extend_from_slice(&[0x01, 0x00, 0x05]);
frame.extend(raw(true, b"abcd"));
assert_malformed(&frame, "a frame naming a dictionary");
frame[6] = 0;
assert_eq!(decode(&frame).expect("dictionary ID 0"), b"abcd");
}
#[test]
fn zstd_last_bytes_carry_the_frames_verdict() {
let mut frame = MAGIC.to_vec();
frame.extend_from_slice(&[0x04, 0x00]);
frame.extend(raw(true, b"abcd"));
frame.extend_from_slice(&0xd25d_92cd_u32.to_le_bytes());
let arena = DecodeArena::new();
let (ctx, root) =
DecodeContext::from_root_bytes(&frame, &arena, &DecodePolicy::service()).expect("root");
let mut decoder = ctx.open_zstd(root).expect("decoder");
let mut output = [0; 4];
assert!(matches!(
decoder.read_chunk(&mut output),
Err(CodecError::Malformed(_))
));
}
}