use super::*;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "std")]
mod fixtures {
use super::*;
use alloc::format;
use alloc::string::{String, ToString};
use std::path::{Path, PathBuf};
pub(super) struct Fixture {
pub(super) name: String,
pub(super) frame: Vec<u8>,
pub(super) content: Vec<u8>,
pub(super) dict: Option<Vec<u8>>,
pub(super) parsed: Option<crate::decoding::Dictionary>,
pub(super) version: LegacyVersion,
}
impl Fixture {
pub(super) fn legacy_dict(&self) -> LegacyDict<'_> {
self.parsed
.as_ref()
.map_or(LegacyDict::None, |dict| LegacyDict::Parsed {
dict,
header: &[],
})
}
}
fn root() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR"))
}
pub(super) fn all() -> Vec<Fixture> {
let dir = root().join("legacy_frames");
let mut names: Vec<String> = std::fs::read_dir(&dir)
.expect("legacy_frames directory")
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
.filter(|name| name.ends_with(".zst"))
.collect();
names.sort();
assert!(names.len() >= 50, "fixtures missing from {}", dir.display());
names
.into_iter()
.map(|name| {
let (stem, rest) = ["v05", "v06", "v07"]
.iter()
.find_map(|tag| {
name.split_once(&format!(".{tag}."))
.map(|split| (split, *tag))
})
.map(|((stem, _), tag)| (stem.to_string(), tag))
.expect("fixture name carries its version");
let version = match rest {
"v05" => LegacyVersion::V05,
"v06" => LegacyVersion::V06,
_ => LegacyVersion::V07,
};
let is_dict = name.contains(".dict");
let (file, times) = stem
.rsplit_once('x')
.and_then(|(file, n)| Some((file, n.parse::<usize>().ok()?)))
.unwrap_or((stem.as_str(), 1));
let source: PathBuf = if is_dict {
root().join("dict_tests/files").join(file)
} else {
root().join("decodecorpus_files").join(file)
};
let dict =
is_dict.then(|| std::fs::read(dir.join(format!("dictionary.{rest}"))).unwrap());
Fixture {
frame: std::fs::read(dir.join(&name)).unwrap(),
content: std::fs::read(&source).unwrap().repeat(times),
parsed: dict.as_deref().map(|raw| {
crate::decoding::Dictionary::from_serialized_or_raw_content(raw).unwrap()
}),
dict,
version,
name,
}
})
.collect()
}
}
#[cfg(feature = "std")]
use fixtures::all as fixtures;
fn decode_to_vec(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec<u8>, usize), Error> {
decode_to_vec_in(
frame,
dict,
FrameOptions::default(),
&mut Workspace::default(),
)
}
fn decode_verified(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec<u8>, usize), Error> {
let options = FrameOptions {
#[cfg(feature = "hash")]
checksum: crate::decoding::ContentChecksum::Verify,
..FrameOptions::default()
};
decode_to_vec_in(frame, dict, options, &mut Workspace::default())
}
fn decode_to_vec_in(
frame: &[u8],
dict: LegacyDict<'_>,
options: FrameOptions,
workspace: &mut Workspace,
) -> Result<(Vec<u8>, usize), Error> {
let mut buf = Vec::new();
let mut out = VecOutput {
buf: &mut buf,
start: 0,
};
let (consumed, _) = decode_frame(frame, dict, options, workspace, &mut out)?;
Ok((buf, consumed))
}
#[cfg(feature = "std")]
#[test]
fn fixtures_decode_to_their_content() {
let mut reused = Workspace::default();
for fixture in fixtures() {
assert_eq!(
LegacyVersion::of(&fixture.frame),
Some(fixture.version),
"{}",
fixture.name
);
for workspace in [&mut Workspace::default(), &mut reused] {
let (decoded, consumed) = decode_to_vec_in(
&fixture.frame,
fixture.legacy_dict(),
FrameOptions::default(),
workspace,
)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name);
assert!(
decoded == fixture.content,
"{} decodes to other bytes",
fixture.name
);
}
}
}
#[cfg(feature = "std")]
#[test]
fn fixtures_decode_into_a_slice() {
for fixture in fixtures() {
let dict = fixture.legacy_dict();
let mut buf = vec![0u8; fixture.content.len()];
let mut out = SliceOutput {
buf: &mut buf,
pos: 0,
};
decode_frame(
&fixture.frame,
dict,
FrameOptions::default(),
&mut Workspace::default(),
&mut out,
)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert_eq!(out.pos, fixture.content.len(), "{}", fixture.name);
assert!(buf == fixture.content, "{}", fixture.name);
if let Some(short) = fixture.content.len().checked_sub(1) {
let mut buf = vec![0u8; short];
let mut out = SliceOutput {
buf: &mut buf,
pos: 0,
};
assert!(
matches!(
decode_frame(
&fixture.frame,
dict,
FrameOptions::default(),
&mut Workspace::default(),
&mut out
),
Err(Error::OutputTooSmall)
),
"{}",
fixture.name
);
}
}
}
#[cfg(feature = "std")]
#[test]
fn frame_size_matches_the_decoded_frame() {
for fixture in fixtures() {
let (size, bound) = frame_size(fixture.version, &fixture.frame).unwrap();
assert_eq!(size, fixture.frame.len(), "{}", fixture.name);
assert!(bound >= fixture.content.len() as u64, "{}", fixture.name);
}
}
#[cfg(feature = "std")]
#[test]
fn a_v07_frame_naming_a_dictionary_needs_it() {
for fixture in fixtures()
.into_iter()
.filter(|f| f.name.ends_with(".v07.dict.zst"))
{
assert_eq!(
decode_to_vec(&fixture.frame, LegacyDict::None).unwrap_err(),
Error::DictionaryMismatch {
expected: 7,
provided: 0
},
"{}",
fixture.name
);
let mut other = fixture.dict.clone().unwrap();
other[4] ^= 1;
let other = crate::decoding::Dictionary::decode_dict(&other).unwrap();
assert_eq!(
decode_to_vec(
&fixture.frame,
LegacyDict::Parsed {
dict: &other,
header: &[]
}
)
.unwrap_err(),
Error::DictionaryMismatch {
expected: 7,
provided: 6
},
"{}",
fixture.name
);
}
}
#[cfg(feature = "std")]
#[test]
fn a_parsed_dictionary_decodes_like_its_bytes() {
for fixture in fixtures().into_iter().filter(|f| f.dict.is_some()) {
let raw = fixture.dict.as_deref().unwrap();
let parsed = crate::decoding::Dictionary::from_serialized_or_raw_content(raw).unwrap();
let dict = LegacyDict::Parsed {
dict: &parsed,
header: &[],
};
let (decoded, _) = decode_to_vec(&fixture.frame, dict)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert!(decoded == fixture.content, "{}", fixture.name);
}
let fixture = fixtures()
.into_iter()
.find(|f| f.name.ends_with(".v07.dict.zst"))
.unwrap();
let raw = crate::decoding::Dictionary::from_raw_content(6, vec![0; 64]).unwrap();
assert_eq!(
decode_to_vec(
&fixture.frame,
LegacyDict::Parsed {
dict: &raw,
header: &[]
}
)
.unwrap_err(),
Error::DictionaryMismatch {
expected: 7,
provided: 6
}
);
}
#[cfg(feature = "std")]
#[test]
fn decode_all_decodes_the_fixtures() {
use crate::decoding::{DictionaryHandle, FrameDecoder, errors::FrameDecoderError};
for fixture in fixtures() {
let mut out = vec![0u8; fixture.content.len()];
let mut decoder = FrameDecoder::new();
let written = match &fixture.dict {
None => decoder.decode_all(&fixture.frame, &mut out),
Some(raw) => {
let dict = DictionaryHandle::from_serialized_or_raw_content(raw).unwrap();
decoder.decode_all_with_dict_handle(&fixture.frame, &mut out, &dict)
}
}
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert_eq!(written, fixture.content.len(), "{}", fixture.name);
assert!(out == fixture.content, "{}", fixture.name);
if let Some(short) = fixture.content.len().checked_sub(1)
&& fixture.dict.is_none()
{
assert!(matches!(
FrameDecoder::new().decode_all(&fixture.frame, &mut out[..short]),
Err(FrameDecoderError::TargetTooSmall)
));
}
}
let fixture = fixtures()
.into_iter()
.find(|f| f.name.ends_with(".v07.dict.zst"))
.unwrap();
let mut out = vec![0u8; fixture.content.len()];
assert!(matches!(
FrameDecoder::new().decode_all(&fixture.frame, &mut out),
Err(FrameDecoderError::DictNotProvided { dict_id: 7 })
));
let mut decoder = FrameDecoder::new();
decoder
.add_dict_from_bytes(fixture.dict.as_deref().unwrap())
.unwrap();
decoder.decode_all(&fixture.frame, &mut out).unwrap();
assert!(out == fixture.content);
let fixture = fixtures()
.into_iter()
.find(|f| f.name == "abc.txt.v06.l1.zst")
.unwrap();
let rfc = crate::encoding::compress_to_vec(
&b"between"[..],
crate::encoding::CompressionLevel::Fastest,
);
let mut input = rfc.clone();
input.extend_from_slice(&fixture.frame);
input.extend_from_slice(&rfc);
let mut expected = b"between".to_vec();
expected.extend_from_slice(&fixture.content);
expected.extend_from_slice(b"between");
let mut out = vec![0u8; expected.len()];
assert_eq!(
FrameDecoder::new().decode_all(&input, &mut out).unwrap(),
expected.len()
);
assert!(out == expected);
}
#[cfg(feature = "std")]
struct Trickle<'a>(&'a [u8]);
#[cfg(feature = "std")]
impl std::io::Read for Trickle<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let Some((&first, rest)) = self.0.split_first() else {
return Ok(0);
};
if buf.is_empty() {
return Ok(0);
}
buf[0] = first;
self.0 = rest;
Ok(1)
}
}
#[cfg(feature = "std")]
#[test]
fn streaming_decoder_decodes_the_fixtures() {
use crate::decoding::{DictionaryHandle, StreamingDecoder};
use std::io::Read;
for fixture in fixtures() {
let dict = fixture
.dict
.as_deref()
.map(|raw| DictionaryHandle::from_serialized_or_raw_content(raw).unwrap());
let open = |source| match &dict {
None => StreamingDecoder::new(source),
Some(dict) => StreamingDecoder::new_with_dictionary_handle(source, dict).unwrap(),
};
let mut whole = Vec::new();
open(&fixture.frame[..])
.read_to_end(&mut whole)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert!(whole == fixture.content, "{}", fixture.name);
if fixture.frame.len() < 64 * 1024 {
let mut decoder = match &dict {
None => StreamingDecoder::new(Trickle(&fixture.frame)),
Some(dict) => {
StreamingDecoder::new_with_dictionary_handle(Trickle(&fixture.frame), dict)
.unwrap()
}
};
let mut pieces = Vec::new();
let mut buf = [0u8; 1000];
loop {
let n = decoder
.read(&mut buf)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
if n == 0 {
break;
}
pieces.extend_from_slice(&buf[..n]);
}
assert!(pieces == fixture.content, "{} in pieces", fixture.name);
}
}
let fixture = fixtures()
.into_iter()
.find(|f| f.name == "z000001.v07.l19.zst")
.unwrap();
let rfc = crate::encoding::compress_to_vec(
&b"between"[..],
crate::encoding::CompressionLevel::Fastest,
);
let mut input = rfc.clone();
input.extend_from_slice(&fixture.frame);
input.extend_from_slice(&rfc);
input.extend_from_slice(&fixture.frame);
let mut expected = b"between".to_vec();
expected.extend_from_slice(&fixture.content);
expected.extend_from_slice(b"between");
expected.extend_from_slice(&fixture.content);
let mut out = Vec::new();
StreamingDecoder::new(&input[..])
.read_to_end(&mut out)
.unwrap();
assert!(out == expected);
let mut out = Vec::new();
let mut decoder = StreamingDecoder::new(&input[..]);
let mut buf = [0u8; 4096];
loop {
let n = decoder.read(&mut buf).unwrap();
if n == 0 {
break;
}
out.extend_from_slice(&buf[..n]);
}
assert!(out == expected);
}
#[cfg(feature = "std")]
#[test]
fn frame_decoder_steps_through_the_fixtures() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
for fixture in fixtures().into_iter().filter(|f| f.dict.is_none()) {
let mut source = &fixture.frame[..];
let mut decoder = FrameDecoder::new();
decoder.reset(&mut source).unwrap();
let mut out = Vec::new();
while !decoder
.decode_blocks(&mut source, BlockDecodingStrategy::UptoBlocks(1))
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name))
{
out.extend(decoder.collect().unwrap());
}
out.extend(decoder.collect().unwrap());
assert!(source.is_empty(), "{}", fixture.name);
assert!(out == fixture.content, "{}", fixture.name);
assert_eq!(
decoder.bytes_read_from_source(),
fixture.frame.len() as u64,
"{}",
fixture.name
);
let mut decoder = FrameDecoder::new();
let mut source = &fixture.frame[..];
decoder.reset(&mut source).unwrap();
let mut out = Vec::new();
let mut target = [0u8; 777];
while !(decoder.is_finished() && decoder.can_collect() == 0) {
let need = decoder.input_needed(source).min(source.len());
let (read, written) = decoder
.decode_from_to(&source[..need], &mut target)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
source = &source[read..];
out.extend_from_slice(&target[..written]);
assert!(read > 0 || written > 0, "{} stalled", fixture.name);
}
assert!(out == fixture.content, "{}", fixture.name);
}
}
#[cfg(feature = "std")]
#[test]
fn frame_queries_answer_for_the_fixtures() {
use crate::decoding::{
FrameContentSize, FrameSizeError, find_decompressed_size, find_frame_compressed_size,
frame_decompressed_bound, is_frame, read_frame_content_size,
};
for fixture in fixtures() {
let frame = &fixture.frame;
assert!(is_frame(frame), "{}", fixture.name);
assert_eq!(
find_frame_compressed_size(frame).unwrap(),
frame.len(),
"{}",
fixture.name
);
let bound = frame_decompressed_bound(frame).unwrap();
assert!(bound >= fixture.content.len() as u64, "{}", fixture.name);
let declared = declared_content_size(frame);
let expected = declared.map_or(FrameContentSize::Unknown, FrameContentSize::Known);
assert_eq!(
read_frame_content_size(frame).unwrap(),
expected,
"{}",
fixture.name
);
assert_eq!(
find_decompressed_size(frame).unwrap(),
expected,
"{}",
fixture.name
);
assert!(matches!(
find_frame_compressed_size(&frame[..frame.len() - 1]),
Err(FrameSizeError::Truncated)
));
}
let v05 = fixtures()
.into_iter()
.find(|f| f.name == "abc.txt.v05.l1.zst")
.unwrap();
assert_eq!(
read_frame_content_size(&v05.frame).unwrap(),
FrameContentSize::Unknown
);
}
#[cfg(feature = "std")]
#[test]
fn truncated_frames_are_refused() {
for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) {
let dict = fixture.legacy_dict();
for len in 0..fixture.frame.len() {
assert!(
decode_to_vec(&fixture.frame[..len], dict).is_err(),
"{} cut to {len} bytes decoded",
fixture.name
);
}
}
}
#[cfg(feature = "std")]
#[test]
fn damaged_frames_never_panic() {
for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) {
let dict = fixture.legacy_dict();
let mut frame = fixture.frame.clone();
for byte in 0..frame.len() {
for bit in 0..8 {
frame[byte] ^= 1 << bit;
let decoded = decode_verified(&frame, dict);
#[cfg(feature = "hash")]
if let Ok((content, end)) = &decoded
&& *content != fixture.content
&& read_header(&frame).is_ok_and(|header| header.checksum)
{
let carried = u64::from(frame[end - 1])
| u64::from(frame[end - 2]) << 8
| u64::from(frame[end - 3] & 0x3F) << 16;
let digest = twox_hash::XxHash64::oneshot(0, content) >> 11 & 0x3F_FFFF;
assert_eq!(
digest, carried,
"{}: bit {bit} of byte {byte} changed the content unchecked",
fixture.name
);
}
drop(decoded);
frame[byte] ^= 1 << bit;
}
}
}
}
#[cfg(all(feature = "std", feature = "hash"))]
#[test]
fn a_wrong_v07_checksum_is_refused() {
let fixture = fixture("abc.txt.v07.l1.zst");
let mut frame = fixture.frame.clone();
let last = frame.len() - 1;
frame[last] ^= 1;
assert!(matches!(
decode_verified(&frame, LegacyDict::None),
Err(Error::ChecksumMismatch { .. })
));
assert!(decode_to_vec(&frame, LegacyDict::None).unwrap().0 == fixture.content);
}
#[test]
fn headers_read_their_fields() {
let v05 = read_header(&[0x25, 0xB5, 0x2F, 0xFD, 0x01]).unwrap();
assert_eq!(
(v05.version, v05.size, v05.window, v05.content_size),
(LegacyVersion::V05, 5, 1 << 12, None)
);
assert_eq!(
read_header(&[0x25, 0xB5, 0x2F, 0xFD, 0x10]).unwrap_err(),
Error::UnsupportedParameter
);
let v06 = read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x43, 200]).unwrap();
assert_eq!(
(v06.version, v06.size, v06.window, v06.content_size),
(LegacyVersion::V06, 6, 1 << 15, Some(200))
);
let v06 = read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x80, 1, 0]).unwrap();
assert_eq!(v06.content_size, Some(257));
assert_eq!(
read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x20]).unwrap_err(),
Error::UnsupportedParameter
);
let v07 = read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x25, 9, 100]).unwrap();
assert_eq!(
(
v07.size,
v07.window,
v07.content_size,
v07.dict_id,
v07.checksum
),
(7, 100, Some(100), 9, true)
);
assert_eq!(
read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x08, 0]).unwrap_err(),
Error::UnsupportedParameter
);
assert_eq!(
read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x25, 9]).unwrap_err(),
Error::Truncated
);
assert_eq!(
read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x00, 0xF8]).unwrap_err(),
Error::UnsupportedParameter
);
}
#[test]
fn other_magic_numbers_are_not_legacy() {
assert_eq!(LegacyVersion::of(&[0x28, 0xB5, 0x2F, 0xFD]), None);
assert_eq!(LegacyVersion::of(&[0x24, 0xB5, 0x2F, 0xFD]), None);
assert_eq!(LegacyVersion::of(&[0x25, 0xB5, 0x2F]), None);
assert_eq!(declared_content_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x43]), None);
}
#[test]
fn an_empty_block_ends_only_older_frames() {
let empty_raw = [0x40, 0, 0];
let end = [0xC0, 0, 0];
let mut v06 = vec![0x26, 0xB5, 0x2F, 0xFD, 0x00];
v06.extend_from_slice(&empty_raw);
assert_eq!(frame_size(LegacyVersion::V06, &v06).unwrap(), (8, 0));
let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
v07.extend_from_slice(&empty_raw);
assert!(matches!(
frame_size(LegacyVersion::V07, &v07),
Err(FrameSizeError::Truncated)
));
v07.extend_from_slice(&end);
assert_eq!(frame_size(LegacyVersion::V07, &v07).unwrap(), (12, 0));
assert_eq!(
decode_to_vec(&v07, LegacyDict::None).unwrap(),
(Vec::new(), 12)
);
}
#[test]
fn frame_size_reads_only_the_size_fields() {
let end = [0xC0, 0, 0];
let mut v06 = vec![0x26, 0xB5, 0x2F, 0xFD, 0x60, 9];
v06.extend_from_slice(&end);
assert_eq!(frame_size(LegacyVersion::V06, &v06).unwrap(), (9, 0));
let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x0A, 0, 1, 2];
v07.extend_from_slice(&end);
assert_eq!(frame_size(LegacyVersion::V07, &v07).unwrap(), (11, 0));
let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x20, 7];
v07.extend_from_slice(&end);
assert_eq!(frame_size(LegacyVersion::V07, &v07).unwrap(), (9, 0));
let mut v05 = vec![0x25, 0xB5, 0x2F, 0xFD, 0xF0];
v05.extend_from_slice(&end);
assert_eq!(frame_size(LegacyVersion::V05, &v05).unwrap(), (8, 0));
assert!(matches!(
frame_size(LegacyVersion::V05, &v05[..4]),
Err(FrameSizeError::Truncated)
));
}
#[test]
fn a_stream_holds_its_window_not_the_frame() {
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
let mut expected = Vec::new();
for n in 0..8u8 {
let size = 128 * 1024 - 1;
frame.extend_from_slice(&[0x40 | (size >> 16) as u8, (size >> 8) as u8, size as u8]);
frame.extend(core::iter::repeat_n(n, size));
expected.extend(core::iter::repeat_n(n, size));
}
frame.extend_from_slice(&[0xC0, 0, 0]);
let header = read_header(&frame).unwrap();
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
let mut src = &frame[header.size..];
let mut out = Vec::new();
let mut target = vec![0u8; 50_000];
let mut largest = 0;
while !(stream.is_finished() && stream.undelivered() == 0) {
let (read, written) = stream.decode(src, &mut target).unwrap();
src = &src[read..];
out.extend_from_slice(&target[..written]);
largest = largest.max(stream.heap_bytes());
assert!(read > 0 || written > 0);
}
assert!(src.is_empty());
assert!(out == expected);
assert_eq!(stream.produced(), expected.len() as u64);
assert_eq!(stream.consumed(), frame.len() as u64);
assert!(largest <= 4 * BLOCK_SIZE_MAX, "held {largest} bytes");
}
#[cfg(feature = "std")]
#[test]
fn a_stream_reads_matches_from_the_segment_before() {
for fixture in fixtures()
.into_iter()
.filter(|f| f.name.starts_with("z000000x8."))
{
let header = read_header(&fixture.frame).unwrap();
let capacity = header.window as usize + BLOCK_SIZE_MAX;
assert!(
fixture.content.len() > 2 * capacity,
"{}: the frame must outrun the buffer",
fixture.name
);
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
let mut src = &fixture.frame[header.size..];
let mut out = Vec::new();
let mut target = vec![0u8; 4096];
while !(stream.is_finished() && stream.undelivered() == 0) {
let (read, written) = stream
.decode(src, &mut target)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert!(read > 0 || written > 0, "{} stalled", fixture.name);
src = &src[read..];
out.extend_from_slice(&target[..written]);
}
assert!(out == fixture.content, "{}", fixture.name);
assert!(
stream.buf.capacity() <= capacity,
"{}: held {} bytes for a window of {}",
fixture.name,
stream.buf.capacity(),
header.window
);
}
}
#[cfg(feature = "std")]
fn fixture(name: &str) -> fixtures::Fixture {
fixtures()
.into_iter()
.find(|f| f.name == name)
.unwrap_or_else(|| panic!("no fixture {name}"))
}
#[cfg(feature = "std")]
#[test]
fn decode_from_to_starts_these_frames_on_a_fresh_decoder() {
use crate::decoding::FrameDecoder;
for name in [
"abc.txt.v05.l1.zst",
"z000001.v06.l19.zst",
"z000001.v07.l19.zst",
] {
let fixture = fixture(name);
let mut decoder = FrameDecoder::new();
let mut src = &fixture.frame[..];
let mut out = Vec::new();
let mut target = [0u8; 500];
loop {
let (read, written) = decoder
.decode_from_to(src, &mut target)
.unwrap_or_else(|err| panic!("{name}: {err}"));
src = &src[read..];
out.extend_from_slice(&target[..written]);
if decoder.is_finished() && decoder.can_collect() == 0 {
break;
}
assert!(read > 0 || written > 0, "{name} stalled");
}
assert!(src.is_empty(), "{name}");
assert!(out == fixture.content, "{name}");
}
}
#[cfg(all(feature = "std", feature = "hash"))]
#[test]
fn the_checksum_mode_applies_to_v07_frames() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, ContentChecksum, FrameDecoder};
let fixture = fixture("abc.txt.v07.l1.zst");
let mut frame = fixture.frame.clone();
let last = frame.len() - 1;
frame[last] ^= 1;
let end = &frame[frame.len() - 3..];
let stored = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16;
let run = |mode| {
let mut decoder = FrameDecoder::new();
decoder.set_content_checksum(mode);
let mut src = &frame[..];
decoder.reset(&mut src)?;
decoder.decode_blocks(&mut src, BlockDecodingStrategy::All)?;
let content = decoder.collect().unwrap();
Ok::<_, FrameDecoderError>((
content,
decoder.get_checksum_from_data(),
decoder.get_calculated_checksum(),
))
};
assert!(matches!(
run(ContentChecksum::Verify),
Err(FrameDecoderError::LegacyFrame(
Error::ChecksumMismatch { .. }
))
));
let (content, from_data, calculated) = run(ContentChecksum::EmitOnly).unwrap();
assert!(content == fixture.content);
assert_eq!(from_data, Some(stored));
assert!(calculated.is_some_and(|calculated| calculated != stored));
let (content, _, calculated) = run(ContentChecksum::None).unwrap();
assert!(content == fixture.content);
assert_eq!(calculated, None);
let mut out = vec![0u8; fixture.content.len()];
let mut decoder = FrameDecoder::new();
decoder.set_content_checksum(ContentChecksum::Verify);
assert!(decoder.decode_all(&frame, &mut out).is_err());
for mode in [ContentChecksum::EmitOnly, ContentChecksum::None] {
decoder.set_content_checksum(mode);
assert_eq!(
decoder.decode_all(&frame, &mut out).unwrap(),
fixture.content.len()
);
}
}
#[cfg(all(feature = "std", feature = "hash"))]
#[test]
fn a_checksum_mode_set_after_reset_applies() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, ContentChecksum, FrameDecoder};
let fixture = fixture("abc.txt.v07.l1.zst");
let mut frame = fixture.frame.clone();
let last = frame.len() - 1;
frame[last] ^= 1;
let run = |before, after| {
let mut decoder = FrameDecoder::new();
decoder.set_content_checksum(before);
let mut src = &frame[..];
decoder.reset(&mut src)?;
decoder.set_content_checksum(after);
decoder.decode_blocks(&mut src, BlockDecodingStrategy::All)?;
let content = decoder.collect().unwrap();
Ok::<_, FrameDecoderError>((content, decoder.get_calculated_checksum()))
};
assert!(matches!(
run(ContentChecksum::None, ContentChecksum::Verify),
Err(FrameDecoderError::LegacyFrame(
Error::ChecksumMismatch { .. }
))
));
let (content, calculated) = run(ContentChecksum::Verify, ContentChecksum::None).unwrap();
assert!(content == fixture.content);
assert_eq!(calculated, None);
}
#[cfg(feature = "std")]
#[test]
fn block_calls_refuse_these_frames_on_a_reused_decoder() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
use crate::encoding::{CompressionLevel, compress_to_vec};
let fixture = fixture("z000001.v07.l19.zst");
let rfc = compress_to_vec(&[7u8; 300_000][..], CompressionLevel::Fastest);
let mut decoder = FrameDecoder::new();
let mut rfc_src = rfc.as_slice();
decoder.reset(&mut rfc_src).unwrap();
let mut src = &fixture.frame[..];
decoder.reset(&mut src).unwrap();
let header = *rfc_src.first_chunk::<3>().unwrap();
let mut out = [0u8; 64];
assert!(matches!(
decoder.block_content_len(header),
Err(FrameDecoderError::NotYetInitialized)
));
assert!(matches!(
decoder.decode_block(header, &rfc_src[3..], &mut out),
Err(FrameDecoderError::NotYetInitialized)
));
assert!(matches!(
decoder.insert_block(b"history"),
Err(FrameDecoderError::NotYetInitialized)
));
#[cfg(feature = "lsm")]
assert!(matches!(
decoder.decode_blocks_partial(&mut &src[..], 0, 1, None, false),
Err(FrameDecoderError::NotYetInitialized)
));
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert!(decoder.collect().unwrap() == fixture.content);
}
#[cfg(feature = "std")]
#[test]
fn a_header_that_fails_on_a_slice_ends_these_frames() {
use crate::decoding::{DictionaryHandle, FrameDecoder};
let fixture = fixture("z000001.v07.l19.zst");
let skippable = [0x50, 0x2A, 0x4D, 0x18, 0, 0, 0, 0];
let damaged = [0x28, 0xB5, 0x2F, 0xFD, 0x08];
let dict = DictionaryHandle::from_serialized_or_raw_content(b"raw dictionary content").unwrap();
for (input, with_dict) in [
(&skippable[..], false),
(&damaged[..], false),
(&skippable[..], true),
(&damaged[..], true),
] {
let mut decoder = FrameDecoder::new();
decoder.reset(&fixture.frame[..]).unwrap();
assert_eq!(decoder.input_needed(&[]), 3);
let mut out = [0u8; 16];
let _ = if with_dict {
decoder.decode_all_with_dict_handle(input, &mut out, &dict)
} else {
decoder.decode_all(input, &mut out)
};
assert_eq!(decoder.input_needed(&[]), 0, "{input:02x?} {with_dict}");
assert!(decoder.is_finished(), "{input:02x?} {with_dict}");
}
}
#[cfg(feature = "std")]
#[test]
fn the_block_limit_applies_to_these_frames() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
for name in [
"z000000.v05.l1.zst",
"z000000.v06.l1.zst",
"z000000.v07.l1.zst",
] {
let fixture = fixture(name);
let mut decoder = FrameDecoder::new();
decoder.set_max_block_size(Some(1024));
let mut out = vec![0u8; fixture.content.len()];
assert!(
matches!(
decoder.decode_all(&fixture.frame, &mut out),
Err(FrameDecoderError::LegacyFrame(_))
),
"{name}"
);
let mut src = &fixture.frame[..];
decoder.reset(&mut src).unwrap();
assert!(
matches!(
decoder.decode_blocks(&mut src, BlockDecodingStrategy::All),
Err(FrameDecoderError::LegacyFrame(_))
),
"{name}"
);
decoder.set_max_block_size(None);
assert_eq!(
decoder.decode_all(&fixture.frame, &mut out).unwrap(),
out.len(),
"{name}"
);
}
}
#[cfg(feature = "std")]
#[test]
fn stored_blocks_run_past_the_compressed_block_maximum() {
use crate::decoding::{
BlockDecodingStrategy, FrameDecoder, StreamingDecoder, find_frame_compressed_size,
frame_decompressed_bound,
};
use std::io::Read;
let raw: Vec<u8> = (0..131_073u32).map(|n| (n % 251) as u8).collect();
let rle = 200_000usize;
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&[0x40 | 0x02, 0x00, 0x01]);
frame.extend_from_slice(&raw);
frame.extend_from_slice(&[0x80 | 0x03, 0x0D, 0x40, 0x7E]);
frame.extend_from_slice(&[0xC0, 0x00, 0x00]);
let mut content = raw.clone();
content.resize(raw.len() + rle, 0x7E);
assert_eq!(find_frame_compressed_size(&frame).unwrap(), frame.len());
assert!(frame_decompressed_bound(&frame).unwrap() >= content.len() as u64);
let mut out = vec![0u8; content.len()];
let mut decoder = FrameDecoder::new();
assert_eq!(decoder.decode_all(&frame, &mut out).unwrap(), content.len());
assert!(out == content);
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert!(decoder.collect().unwrap() == content);
let mut streamed = Vec::new();
StreamingDecoder::new(&frame[..])
.read_to_end(&mut streamed)
.unwrap();
assert!(streamed == content);
decoder.set_max_block_size(Some(64 * 1024));
assert!(decoder.decode_all(&frame, &mut out).is_err());
}
#[cfg(feature = "std")]
#[test]
fn a_lowered_window_ceiling_binds_a_started_frame() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let fixture = fixture("z000000.v07.l1.zst");
let window = read_header(&fixture.frame).unwrap().window;
let mut decoder = FrameDecoder::new();
let mut src = &fixture.frame[..];
decoder.reset(&mut src).unwrap();
decoder.set_max_window_size(window / 2).unwrap();
assert!(matches!(
decoder.decode_blocks(&mut src, BlockDecodingStrategy::All),
Err(FrameDecoderError::WindowSizeTooBig { requested, limit })
if requested == window && limit == window / 2
));
let mut target = [0u8; 64];
assert!(matches!(
decoder.decode_from_to(src, &mut target),
Err(FrameDecoderError::WindowSizeTooBig { .. })
));
decoder.set_max_window_size(window).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert!(decoder.collect().unwrap() == fixture.content);
decoder.set_max_window_size(window / 2).unwrap();
assert!(matches!(
decoder.reset(&fixture.frame[..]),
Err(FrameDecoderError::WindowSizeTooBig { requested, limit })
if requested == window && limit == window / 2
));
}
#[cfg(feature = "std")]
#[test]
fn the_accessors_describe_these_frames() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let rfc = crate::encoding::compress_slice_to_vec(
&[7u8; 500],
crate::encoding::CompressionLevel::Fastest,
);
let mut decoder = FrameDecoder::new();
let mut src = &rfc[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert_eq!(decoder.content_size(), 500);
let v06 = fixture("z000000.v06.l1.zst");
let mut src = &v06.frame[..];
decoder.reset(&mut src).unwrap();
assert_eq!(decoder.content_size(), 128 * 1024);
assert_eq!(decoder.blocks_decoded(), 0);
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
let (_, bound) = frame_size(LegacyVersion::V06, &v06.frame).unwrap();
assert_eq!(
decoder.blocks_decoded() as u64,
bound / BLOCK_SIZE_MAX as u64
);
assert!(decoder.workspace_size() > BLOCK_SIZE_MAX);
let v05 = fixture("abc.txt.v05.l1.zst");
let mut src = &v05.frame[..];
decoder.reset(&mut src).unwrap();
assert_eq!(decoder.content_size(), 0);
}
#[cfg(feature = "std")]
#[test]
fn errors_report_libzstd_codes() {
use crate::decoding::errors::{ErrorCode, FrameDecoderError};
use std::error::Error as _;
use std::string::ToString;
let cases = [
(Error::Truncated, ErrorCode::SrcSizeWrong, "ends inside"),
(
Error::UnsupportedParameter,
ErrorCode::FrameParameterUnsupported,
"unsupported setting",
),
(
Error::Corrupted {
reason: "bad table",
},
ErrorCode::CorruptionDetected,
"bad table",
),
(
Error::OutputTooSmall,
ErrorCode::DstSizeTooSmall,
"too small",
),
(
Error::DictionaryCorrupted,
ErrorCode::DictionaryCorrupted,
"dictionary could not be read",
),
(
Error::DictionaryMismatch {
expected: 0xAB,
provided: 0,
},
ErrorCode::DictionaryWrong,
"dictionary 0xAB",
),
(
Error::ChecksumMismatch {
expected: 1,
calculated: 2,
},
ErrorCode::ChecksumWrong,
"0x000001",
),
];
assert_eq!(ErrorCode::SrcSizeWrong.message(), "Src size is incorrect");
for (err, code, text) in cases {
assert_eq!(err.code(), code, "{err:?}");
let message = err.to_string();
assert!(message.contains(text), "{message}");
let wrapped = FrameDecoderError::from(err);
assert_eq!(wrapped.code(), code);
assert_eq!(wrapped.to_string(), message);
assert!(
wrapped
.source()
.is_some_and(|source| source.to_string() == message)
);
}
}
#[cfg(feature = "std")]
#[test]
fn the_accessors_describe_a_frame_decoded_whole() {
use crate::decoding::FrameDecoder;
let rfc = crate::encoding::compress_slice_to_vec(
&[7u8; 500],
crate::encoding::CompressionLevel::Fastest,
);
let v06 = fixture("z000000.v06.l1.zst");
let mut decoder = FrameDecoder::new();
let mut out = vec![0u8; v06.content.len()];
decoder.decode_all(&rfc, &mut out).unwrap();
assert_eq!(decoder.content_size(), 500);
assert_eq!(
decoder.decode_all(&v06.frame, &mut out).unwrap(),
v06.content.len()
);
let (_, bound) = frame_size(LegacyVersion::V06, &v06.frame).unwrap();
assert_eq!(decoder.content_size(), 128 * 1024);
assert_eq!(
decoder.blocks_decoded() as u64,
bound / BLOCK_SIZE_MAX as u64
);
assert_eq!(decoder.bytes_read_from_source(), v06.frame.len() as u64);
assert!(decoder.is_finished());
assert_eq!(decoder.can_collect(), 0);
#[cfg(feature = "hash")]
{
let v07 = fixture("abc.txt.v07.l1.zst");
let end = &v07.frame[v07.frame.len() - 3..];
let stored = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16;
decoder.decode_all(&v07.frame, &mut out).unwrap();
assert_eq!(decoder.get_checksum_from_data(), Some(stored));
assert_eq!(decoder.get_calculated_checksum(), Some(stored));
}
}
#[cfg(all(feature = "std", feature = "lsm", feature = "hash"))]
#[test]
fn per_block_checksums_cover_these_frames() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let one = fixture("abc.txt.v07.l1.zst");
let digest = twox_hash::XxHash64::oneshot(0, &one.content) as u32;
let many = fixture("z000000.v07.l1.zst");
let (_, bound) = frame_size(LegacyVersion::V07, &many.frame).unwrap();
let blocks = (bound / BLOCK_SIZE_MAX as u64) as usize;
let mut decoder = FrameDecoder::new();
decoder.enable_per_block_checksums();
let mut out = vec![0u8; many.content.len()];
decoder.decode_all(&one.frame, &mut out).unwrap();
assert_eq!(decoder.computed_block_checksums(), [digest]);
decoder.decode_all(&many.frame, &mut out).unwrap();
assert_eq!(decoder.computed_block_checksums().len(), blocks);
let mut src = &one.frame[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert_eq!(decoder.computed_block_checksums(), [digest]);
}
#[cfg(all(feature = "std", feature = "lsm"))]
#[test]
fn pinned_header_fields_bind_these_frames() {
use crate::decoding::FrameDecoder;
use crate::decoding::errors::FrameDecoderError;
let v07 = fixture("z000000x8.v07.l1.zst");
assert_eq!(v07.frame[4] & 0x20, 0);
let window = v07.frame[5];
let v05 = fixture("abc.txt.v05.l1.zst");
let mut out = vec![0u8; v07.content.len()];
let mut decoder = FrameDecoder::new();
decoder.expect_dict_id(Some(5));
assert!(matches!(
decoder.decode_all(&v07.frame, &mut out),
Err(FrameDecoderError::UnexpectedDictId {
expected: Some(5),
found: None
})
));
assert!(matches!(
decoder.reset(&v07.frame[..]),
Err(FrameDecoderError::UnexpectedDictId { .. })
));
decoder.expect_dict_id(Some(0));
decoder.expect_window_descriptor(Some(window ^ 1));
assert!(matches!(
decoder.decode_all(&v07.frame, &mut out),
Err(FrameDecoderError::UnexpectedWindowDescriptor { found: Some(w), .. }) if w == window
));
assert!(matches!(
decoder.reset(&v05.frame[..]),
Err(FrameDecoderError::UnexpectedWindowDescriptor { found: None, .. })
));
decoder.expect_window_descriptor(Some(window));
assert_eq!(
decoder.decode_all(&v07.frame, &mut out).unwrap(),
v07.content.len()
);
}
#[cfg(feature = "std")]
#[test]
fn a_bare_magic_number_is_a_truncated_frame() {
use crate::decoding::find_decompressed_size;
let magic = [0x27, 0xB5, 0x2F, 0xFD];
assert!(find_decompressed_size(&magic).is_err());
let mut stream = fixture("z000000.v07.l1.zst").frame;
stream.extend_from_slice(&magic);
assert!(find_decompressed_size(&stream).is_err());
}
#[cfg(feature = "std")]
#[test]
fn reader_errors_inside_the_header_are_kept() {
use crate::decoding::FrameDecoder;
use crate::decoding::errors::{FrameDecoderError, ReadFrameHeaderError};
struct Stalls<'a>(&'a [u8]);
impl std::io::Read for Stalls<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.0.is_empty() {
return Err(std::io::ErrorKind::WouldBlock.into());
}
let n = buf.len().min(self.0.len());
buf[..n].copy_from_slice(&self.0[..n]);
self.0 = &self.0[n..];
Ok(n)
}
}
let mut decoder = FrameDecoder::new();
let err = decoder
.reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD]))
.unwrap_err();
assert!(
matches!(
&err,
FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::FrameDescriptorReadError(e))
if e.kind() == std::io::ErrorKind::WouldBlock
),
"{err:?}"
);
let err = decoder
.reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD, 0x00]))
.unwrap_err();
assert!(
matches!(
&err,
FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::WindowDescriptorReadError(e))
if e.kind() == std::io::ErrorKind::WouldBlock
),
"{err:?}"
);
let err = decoder
.reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD, 0x01, 0x00]))
.unwrap_err();
assert!(
matches!(
&err,
FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::DictionaryIdReadError(e))
if e.kind() == std::io::ErrorKind::WouldBlock
),
"{err:?}"
);
let err = decoder
.reset(Stalls(&[0x26, 0xB5, 0x2F, 0xFD, 0x80, 1]))
.unwrap_err();
assert!(
matches!(
&err,
FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::FrameContentSizeReadError(e))
if e.kind() == std::io::ErrorKind::WouldBlock
),
"{err:?}"
);
}
#[cfg(feature = "std")]
#[test]
fn the_stream_footprint_counts_its_tables() {
for name in [
"z000000.v05.l1.zst",
"z000000.v06.l1.zst",
"z000000.v07.l1.zst",
] {
let fixture = fixture(name);
let header = read_header(&fixture.frame).unwrap();
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
let mut target = vec![0u8; fixture.content.len()];
stream
.decode(&fixture.frame[header.size..], &mut target)
.unwrap();
assert!(
stream.heap_bytes() > stream.buf.capacity() + stream.block.capacity(),
"{name}: {} bytes held, {} of them the output buffer",
stream.heap_bytes(),
stream.buf.capacity()
);
}
}
#[test]
fn an_oversized_block_is_refused_from_its_header() {
let frame = [0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
let header = read_header(&frame).unwrap();
let compressed = [0x03, 0xFF, 0xFF];
let raw = [0x40 | 0x03, 0xFF, 0xFF];
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
assert_eq!(stream.input_needed(&[0x40]), 3);
assert_eq!(stream.input_needed(&compressed), 3);
assert!(stream.decode(&compressed, &mut [0u8; 16]).is_err());
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
assert_eq!(stream.input_needed(&raw), 3 + 0x3FFFF);
let lowered = FrameOptions {
block_max: 64 * 1024,
..FrameOptions::default()
};
let mut stream = LegacyStream::new();
stream.start(header, None, lowered).unwrap();
assert_eq!(stream.input_needed(&raw), 3);
assert!(stream.decode(&raw, &mut [0u8; 16]).is_err());
}
#[test]
fn one_symbol_sequence_tables_decode_in_v07() {
let block = [0x81, b'a', 0x01, 0x54, 1, 2, 0, 0x04];
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x00, 0x00];
frame.push(block.len() as u8);
frame.extend_from_slice(&block);
frame.extend_from_slice(&[0xC0, 0, 0]);
assert_eq!(decode_to_vec(&frame, LegacyDict::None).unwrap().0, b"aaaa");
let at = frame.len() - 4;
frame[at - 1] = 200;
assert!(matches!(
decode_to_vec(&frame, LegacyDict::None),
Err(Error::Corrupted { .. })
));
}
#[test]
fn sequence_counts_take_their_forms() {
assert_eq!(v07::sequence_count(&[0x7F], true).unwrap(), (0x7F, 1));
assert_eq!(
v07::sequence_count(&[0x81, 0x02], true).unwrap(),
(0x102, 2)
);
assert_eq!(
v07::sequence_count(&[0xFF, 0x01, 0x00], true).unwrap(),
(0x7F01, 3)
);
assert_eq!(
v07::sequence_count(&[0xFF, 0x01], false).unwrap(),
(0x7F01, 2)
);
assert!(v07::sequence_count(&[], true).is_err());
assert!(v07::sequence_count(&[0xFF, 0x01], true).is_err());
assert!(v07::sequence_count(&[0x80], true).is_err());
}
#[test]
fn versions_answer_their_queries() {
assert_eq!(LegacyVersion::V05.minor(), 5);
assert_eq!(LegacyVersion::V06.minor(), 6);
assert_eq!(LegacyVersion::V07.minor(), 7);
let v05 = [0x25, 0xB5, 0x2F, 0xFD, 0x01];
assert_eq!(LegacyVersion::V05.header_size(&v05[..4]), None);
assert_eq!(LegacyVersion::V05.header_size(&v05), Some(5));
assert_eq!(
LegacyVersion::V06.header_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x80]),
Some(7)
);
assert_eq!(
LegacyVersion::V07.header_size(&[0x27, 0xB5, 0x2F, 0xFD, 0xE3]),
Some(17)
);
}
#[test]
fn literal_headers_refuse_what_does_not_fit() {
for header in [
&[0x00, 0, 0, 0][..],
&[0x3F, 0xFF, 0xFC, 0, 0, 0],
&[0x00, 0x04, 0xFF, 0, 0],
&[0x40, 0, 0],
&[0x50, 0x03, 0xFF],
&[0x9F, 0, 0],
&[0xF0, 0, 0],
&[0xFF, 0xFF, 0xFF, 0],
&[0x80, 0],
] {
assert!(
matches!(Literals::read(header), Err(Error::Corrupted { .. })),
"{header:02X?}"
);
}
}
#[cfg(feature = "hash")]
#[test]
fn a_budget_passes_through_to_its_output() {
let mut buf = Vec::new();
let mut inner = VecOutput {
buf: &mut buf,
start: 0,
};
let mut out = Budget {
out: &mut inner,
end: 8,
};
out.reserve(2).unwrap();
out.extend(b"ab");
out.repeat(b'c', 2);
out.copy_back(4, 2);
assert_eq!((out.produced(), out.held()), (6, 6));
assert_eq!(out.since(2), b"ccab");
assert!(out.reserve(3).is_err());
out.copy_match(
1,
2,
&History {
head: &[],
tail: &[],
},
)
.unwrap();
assert_eq!(buf, b"abccabbb");
}
#[test]
fn matches_read_a_history_in_two_pieces() {
let history = History {
head: b"ab",
tail: b"cd",
};
let mut buf = Vec::new();
let mut out = VecOutput {
buf: &mut buf,
start: 0,
};
out.copy_match(3, 2, &history).unwrap();
out.copy_match(5, 4, &history).unwrap();
assert!(out.copy_match(11, 1, &history).is_err());
assert_eq!(buf, b"bcbcdb");
}
#[cfg(feature = "std")]
#[test]
fn frame_size_refuses_a_cut_block() {
let fixture = fixture("z000000.v07.l1.zst");
let frame = &fixture.frame[..fixture.frame.len() - 10];
assert!(matches!(
frame_size(LegacyVersion::V07, frame),
Err(FrameSizeError::Truncated)
));
}
#[cfg(feature = "std")]
#[test]
fn rle_blocks_decode_on_every_path() {
use crate::decoding::FrameDecoder;
let frame = [
0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x80, 0, 5, b'z', 0xC0, 0, 0,
];
let mut out = [0u8; 5];
assert_eq!(FrameDecoder::new().decode_all(&frame, &mut out).unwrap(), 5);
assert_eq!(&out, b"zzzzz");
let mut decoder = FrameDecoder::new();
let mut target = [0u8; 8];
let (read, written) = decoder.decode_from_to(&frame, &mut target).unwrap();
assert_eq!((read, &target[..written]), (frame.len(), &b"zzzzz"[..]));
}
#[cfg(feature = "std")]
#[test]
fn reader_errors_inside_a_block_are_kept() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let fixture = fixture("z000000.v07.l1.zst");
let header = read_header(&fixture.frame).unwrap();
for (cut, header_error) in [(header.size + 1, true), (header.size + 4, false)] {
let mut decoder = FrameDecoder::new();
let mut src = &fixture.frame[..cut];
decoder.reset(&mut src).unwrap();
let err = decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap_err();
if header_error {
assert!(
matches!(err, FrameDecoderError::FailedToReadBlockHeader(_)),
"{err:?}"
);
} else {
assert!(
matches!(err, FrameDecoderError::FailedToReadBlockBody(_)),
"{err:?}"
);
}
}
}
#[cfg(feature = "std")]
#[test]
fn read_to_end_keeps_a_forced_dictionary_for_these_frames() {
use crate::decoding::{DictionaryHandle, StreamingDecoder};
use std::io::Read;
let v07 = fixture("ModemManager.service.v07.dict.zst");
let dict =
DictionaryHandle::from_serialized_or_raw_content(v07.dict.as_deref().unwrap()).unwrap();
let leading = b"a leading frame";
let mut input =
crate::encoding::compress_slice_to_vec(leading, crate::encoding::CompressionLevel::Fastest);
let rfc_len = input.len();
input.extend_from_slice(&v07.frame);
let mut out = Vec::new();
StreamingDecoder::new_with_dictionary_handle(&input[..], &dict)
.unwrap()
.read_to_end(&mut out)
.unwrap();
let mut expected = leading.to_vec();
expected.extend_from_slice(&v07.content);
assert!(out == expected);
let cut = &input[..input.len() - 1];
assert!(cut.len() > rfc_len);
let mut out = Vec::new();
assert!(
StreamingDecoder::new_with_dictionary_handle(cut, &dict)
.unwrap()
.read_to_end(&mut out)
.is_err()
);
assert_eq!(out, leading);
}
#[cfg(all(feature = "std", target_has_atomic = "ptr"))]
#[test]
fn force_dict_and_dictionary_cover_these_frames() {
use crate::decoding::{BlockDecodingStrategy, DictionaryHandle, FrameDecoder};
let noid = fixture("ModemManager.service.v07.dict-noid.zst");
let dict =
DictionaryHandle::from_serialized_or_raw_content(noid.dict.as_deref().unwrap()).unwrap();
let id = dict.id();
let mut decoder = FrameDecoder::new();
decoder.add_dict_handle(dict).unwrap();
let mut src = &noid.frame[..];
decoder.reset(&mut src).unwrap();
assert!(decoder.dictionary().is_none());
decoder.force_dict(id).unwrap();
assert_eq!(decoder.dictionary().map(DictionaryHandle::id), Some(id));
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert!(decoder.collect().unwrap() == noid.content);
let named = fixture("ModemManager.service.v07.dict.zst");
decoder.reset(&named.frame[..]).unwrap();
assert_eq!(decoder.dictionary().map(DictionaryHandle::id), Some(id));
}
#[cfg(feature = "std")]
#[test]
fn a_frame_decoded_whole_keeps_its_dictionary() {
use crate::decoding::{DictionaryHandle, FrameDecoder};
let named = fixture("ModemManager.service.v07.dict.zst");
let dict =
DictionaryHandle::from_serialized_or_raw_content(named.dict.as_deref().unwrap()).unwrap();
let mut decoder = FrameDecoder::new();
let mut out = vec![0u8; named.content.len()];
decoder
.decode_all_with_dict_handle(&named.frame, &mut out, &dict)
.unwrap();
assert!(out == named.content);
assert_eq!(
decoder.dictionary().map(DictionaryHandle::id),
Some(dict.id())
);
}
#[cfg(feature = "std")]
#[test]
fn byte_entry_points_take_v05_and_v06_dictionaries() {
use crate::decoding::{FrameDecoder, StreamingDecoder};
use std::io::Read;
let mut seen = 0;
for fixture in fixtures() {
let Some(dict) = fixture.dict.as_deref() else {
continue;
};
if fixture.version == LegacyVersion::V07 {
continue;
}
seen += 1;
let mut out = vec![0u8; fixture.content.len()];
let written = FrameDecoder::new()
.decode_all_with_dict_bytes(&fixture.frame, &mut out, dict)
.unwrap_or_else(|err| panic!("{}: {err}", fixture.name));
assert_eq!(written, fixture.content.len(), "{}", fixture.name);
assert!(out == fixture.content, "{}", fixture.name);
let mut streamed = Vec::new();
StreamingDecoder::new_with_dictionary_bytes(&fixture.frame[..], dict)
.unwrap_or_else(|refused| panic!("{}: {}", fixture.name, refused.error()))
.read_to_end(&mut streamed)
.unwrap();
assert!(streamed == fixture.content, "{}", fixture.name);
}
assert!(seen >= 4);
}
#[cfg(feature = "std")]
#[test]
fn dictionaries_kept_across_frame_kinds_are_reported() {
use crate::decoding::{DictionaryHandle, FrameDecoder};
let named = fixture("ModemManager.service.v07.dict.zst");
let dict =
DictionaryHandle::from_serialized_or_raw_content(named.dict.as_deref().unwrap()).unwrap();
let rfc = crate::encoding::compress_slice_to_vec(
b"plain",
crate::encoding::CompressionLevel::Fastest,
);
let footprint = DictionaryHandle::exclusive_heap_size([&dict, &dict.clone()]);
assert!(footprint > 0);
let mut decoder = FrameDecoder::new();
let mut out = vec![0u8; named.content.len()];
decoder
.decode_all_with_dict_handle(&named.frame, &mut out, &dict)
.unwrap();
decoder.decode_all(&rfc, &mut out).unwrap();
assert_eq!(
DictionaryHandle::exclusive_heap_size(
core::iter::once(&dict).chain(decoder.dictionaries())
),
footprint
);
drop(decoder);
let mut compressor =
crate::encoding::FrameCompressor::new(crate::encoding::CompressionLevel::Fastest);
compressor
.set_dictionary_from_bytes(named.dict.as_deref().unwrap())
.unwrap();
compressor.set_source(&b"primed"[..]);
let mut with_dict = Vec::new();
compressor.set_drain(&mut with_dict);
compressor.compress();
drop(compressor);
let v05 = fixture("abc.txt.v05.l1.zst");
let mut decoder = FrameDecoder::new();
decoder
.decode_all_with_dict_handle(&with_dict, &mut out, &dict)
.unwrap();
decoder.decode_all(&v05.frame, &mut out).unwrap();
assert_eq!(
DictionaryHandle::exclusive_heap_size(
core::iter::once(&dict).chain(decoder.dictionaries())
),
footprint
);
}
#[cfg(feature = "std")]
#[test]
fn a_serialized_dictionary_is_whole_history_to_v05_and_v06() {
use crate::decoding::{BlockDecodingStrategy, DictionaryHandle, FrameDecoder};
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let raw = std::fs::read(root.join("dict_tests/dictionary")).unwrap();
let dict = DictionaryHandle::from_serialized_or_raw_content(&raw).unwrap();
for tag in ["v05", "v06"] {
let frame =
std::fs::read(root.join(std::format!("legacy_frames/rfc_dict/dictionary.{tag}.zst")))
.unwrap();
let mut out = vec![0u8; raw.len()];
let written = FrameDecoder::new()
.decode_all_with_dict_handle(&frame, &mut out, &dict)
.unwrap_or_else(|err| panic!("{tag}: {err}"));
assert!(out[..written] == raw[..], "{tag}");
let mut decoder = FrameDecoder::new();
let mut src = &frame[..];
decoder.reset_with_dict_handle(&mut src, &dict).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap_or_else(|err| panic!("{tag}: {err}"));
assert!(decoder.collect().unwrap() == raw, "{tag}");
let mut decoder = FrameDecoder::new();
decoder.add_dict_from_bytes(&raw).unwrap();
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
decoder.force_dict(dict.id()).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap_or_else(|err| panic!("{tag} registered: {err}"));
assert!(decoder.collect().unwrap() == raw, "{tag} registered");
}
}
#[test]
fn frame_header_size_measures_these_frames() {
use crate::decoding::frame_header_size;
assert_eq!(
frame_header_size(&[0x25, 0xB5, 0x2F, 0xFD, 0x01]).unwrap(),
5
);
assert_eq!(
frame_header_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x80]).unwrap(),
7
);
assert_eq!(
frame_header_size(&[0x27, 0xB5, 0x2F, 0xFD, 0xE3]).unwrap(),
17
);
assert!(frame_header_size(&[0x27, 0xB5, 0x2F, 0xFD]).is_err());
}
#[cfg(feature = "std")]
#[test]
fn reader_errors_name_the_block_type() {
use crate::blocks::block::BlockType;
use crate::decoding::errors::{DecodeBlockContentError, FrameDecoderError};
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let frame = [0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x40, 0, 5, b'a', b'b'];
let mut decoder = FrameDecoder::new();
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
let err = decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap_err();
assert!(
matches!(
err,
FrameDecoderError::FailedToReadBlockBody(DecodeBlockContentError::ReadError {
step: BlockType::Raw,
..
})
),
"{err:?}"
);
}
#[cfg(feature = "std")]
#[test]
fn output_held_back_grows_the_buffer() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
let fixture = fixture("z000000x8.v07.l1.zst");
let mut decoder = FrameDecoder::new();
let mut src = &fixture.frame[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
assert!(decoder.collect().unwrap() == fixture.content);
}
#[cfg(feature = "std")]
#[test]
fn overwritten_bytes_never_panic() {
use crate::decoding::FrameDecoder;
let mut state = 0x2545_F491_4F6C_DD1Du64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 16384) {
let dict = fixture.legacy_dict();
let header = read_header(&fixture.frame).unwrap().size;
for _ in 0..64 {
let mut frame = fixture.frame.clone();
for _ in 0..(next() % 4) + 1 {
let at = header + (next() as usize) % (frame.len() - header);
frame[at] = next() as u8;
}
let _whole = decode_verified(&frame, dict).is_ok();
let mut decoder = FrameDecoder::new();
let mut target = vec![0u8; 4096];
let mut src = &frame[..];
while let Ok((read, written)) = decoder.decode_from_to(src, &mut target) {
if read == 0 && written == 0 {
break;
}
src = &src[read..];
}
}
}
}
#[test]
fn rle_blocks_repeat_their_byte_in_v07_only() {
for magic in [0x25u8, 0x26, 0x27] {
let mut frame = vec![magic, 0xB5, 0x2F, 0xFD, 0x00];
if magic == 0x27 {
frame.push(0x00);
}
frame.extend_from_slice(&[0x80, 0, 5, b'z', 0xC0, 0, 0]);
let decoded = decode_to_vec(&frame, LegacyDict::None);
if magic == 0x27 {
let (decoded, consumed) = decoded.unwrap();
assert_eq!(decoded, b"zzzzz");
assert_eq!(consumed, frame.len());
} else {
assert!(
matches!(decoded, Err(Error::Corrupted { .. })),
"{magic:#x}: {decoded:?}"
);
}
}
}
#[test]
fn only_compressed_blocks_stop_at_the_largest_block_size() {
let size = BLOCK_SIZE_MAX + 1;
let header = |kind: u8| [kind | (size >> 16) as u8, (size >> 8) as u8, size as u8];
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&header(0x80));
frame.push(b'z');
frame.extend_from_slice(&[0xC0, 0, 0]);
let (out, _) = decode_to_vec(&frame, LegacyDict::None).unwrap();
assert!(out.len() == size && out.iter().all(|&b| b == b'z'));
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&header(0x00));
frame.resize(frame.len() + size, 0);
frame.extend_from_slice(&[0xC0, 0, 0]);
assert!(matches!(
decode_to_vec(&frame, LegacyDict::None),
Err(Error::Corrupted { .. })
));
}
#[cfg(feature = "std")]
#[test]
fn the_declared_content_size_is_not_enforced() {
let frame = [
0x26, 0xB5, 0x2F, 0xFD, 0x40, 4, 0x40, 0, 5, b'z', b'z', b'z', b'z', b'z', 0xC0, 0, 0,
];
assert_eq!(declared_content_size(&frame), Some(4));
assert_eq!(
decode_to_vec(&frame, LegacyDict::None).unwrap(),
(b"zzzzz".to_vec(), frame.len())
);
let fixture = fixtures()
.into_iter()
.find(|f| f.name == "z000000.v06.l1.zst")
.unwrap();
assert_eq!(declared_content_size(&fixture.frame), Some(128 * 1024));
assert_eq!(fixture.content.len(), 224_787);
}
#[cfg(feature = "std")]
#[test]
fn refused_header_fields_carry_the_code_of_their_path() {
use crate::decoding::FrameDecoder;
use crate::decoding::errors::ErrorCode;
let end = [0xC0, 0, 0];
let frame = |header: &[u8]| {
let mut frame = header.to_vec();
frame.extend_from_slice(&end);
frame
};
let cases = [
(
frame(&[0x27, 0xB5, 0x2F, 0xFD, 0x08, 0x00]),
ErrorCode::CorruptionDetected,
),
(
frame(&[0x27, 0xB5, 0x2F, 0xFD, 0x00, 0xF8]),
ErrorCode::CorruptionDetected,
),
(
frame(&[0x26, 0xB5, 0x2F, 0xFD, 0x20]),
ErrorCode::CorruptionDetected,
),
(
frame(&[0x25, 0xB5, 0x2F, 0xFD, 0x10]),
ErrorCode::FrameParameterUnsupported,
),
];
for (frame, whole) in cases {
let mut out = [0u8; 8];
let err = FrameDecoder::new()
.decode_all(&frame, &mut out)
.unwrap_err();
assert_eq!(err.code(), whole, "{frame:02x?}");
let err = FrameDecoder::new().reset(&frame[..]).unwrap_err();
assert_eq!(
err.code(),
ErrorCode::FrameParameterUnsupported,
"{frame:02x?}"
);
}
}
#[test]
fn size_queries_refuse_blocks_the_decoder_refuses() {
use crate::decoding::{FrameSizeError, find_frame_compressed_size, frame_decompressed_bound};
let oversized = one_block_frame(LegacyVersion::V07, &vec![0u8; BLOCK_SIZE_MAX + 1]);
assert!(matches!(
find_frame_compressed_size(&oversized),
Err(FrameSizeError::OversizedBlock)
));
assert!(matches!(
frame_decompressed_bound(&oversized),
Err(FrameSizeError::OversizedBlock)
));
let mut rle = vec![
0x26, 0xB5, 0x2F, 0xFD, 0x00, 0x80, 0x00, 0x04, b'z', 0xC0, 0, 0,
];
assert!(matches!(
find_frame_compressed_size(&rle),
Err(FrameSizeError::ReservedBlock)
));
rle.splice(..5, [0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]);
assert_eq!(find_frame_compressed_size(&rle).unwrap(), rle.len());
}
#[test]
fn a_large_stored_block_grows_the_stream_by_itself() {
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x50];
let mut content = Vec::new();
for n in 0..7u8 {
frame.extend_from_slice(&[0x40 | 0x02, 0x00, 0x00]);
let block = vec![n; 128 * 1024];
frame.extend_from_slice(&block);
content.extend_from_slice(&block);
}
let big: Vec<u8> = (0..500_000u32).map(|n| (n % 253) as u8).collect();
frame.extend_from_slice(&[0x40 | 0x07, 0xA1, 0x20]);
frame.extend_from_slice(&big);
content.extend_from_slice(&big);
frame.extend_from_slice(&[0xC0, 0x00, 0x00]);
let header = read_header(&frame).unwrap();
let mut stream = LegacyStream::new();
stream.start(header, None, FrameOptions::default()).unwrap();
let mut out = vec![0u8; content.len()];
let (mut read, mut written) = (header.size, 0);
while !stream.is_finished() {
let (r, w) = stream.decode(&frame[read..], &mut out[written..]).unwrap();
assert!(r > 0 || w > 0);
read += r;
written += w;
}
assert_eq!(written, content.len());
assert!(out == content);
assert!(
stream.buf.capacity() <= (1 << 20) + STORED_BLOCK_MAX,
"{}",
stream.buf.capacity()
);
}
fn one_block_frame(version: LegacyVersion, body: &[u8]) -> Vec<u8> {
let mut frame = match version {
LegacyVersion::V05 => vec![0x25, 0xB5, 0x2F, 0xFD, 0x00],
LegacyVersion::V06 => vec![0x26, 0xB5, 0x2F, 0xFD, 0x00],
LegacyVersion::V07 => vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00],
};
let n = body.len();
frame.extend_from_slice(&[(n >> 16) as u8, (n >> 8) as u8, n as u8]);
frame.extend_from_slice(body);
frame.extend_from_slice(&[0xC0, 0, 0]);
frame
}
const VERSIONS: [LegacyVersion; 3] = [LegacyVersion::V05, LegacyVersion::V06, LegacyVersion::V07];
#[test]
fn a_block_of_literals_alone_decodes() {
for version in VERSIONS {
for body in [
&[0x83, b'a', b'b', b'c', 0x00][..],
&[0xA0, 0x03, b'a', b'b', b'c', 0x00][..],
] {
let frame = one_block_frame(version, body);
assert_eq!(
decode_to_vec(&frame, LegacyDict::None).unwrap(),
(b"abc".to_vec(), frame.len()),
"{version:?} {body:02x?}"
);
}
}
}
#[test]
fn degenerate_literal_sections_decode_in_older_formats() {
let run = [0x20, 0x02, 0x80, 0x01, b'x', 0x00];
let stored = [0x20, 0x00, 0xC0, 0x03, b'a', b'b', b'c', 0x00];
for version in [LegacyVersion::V05, LegacyVersion::V06] {
let frame = one_block_frame(version, &run);
assert_eq!(
decode_to_vec(&frame, LegacyDict::None).unwrap().0,
vec![b'x'; 10],
"{version:?}"
);
}
let frame = one_block_frame(LegacyVersion::V06, &stored);
assert_eq!(
decode_to_vec(&frame, LegacyDict::None).unwrap().0,
b"abc".to_vec()
);
assert!(
decode_to_vec(
&one_block_frame(LegacyVersion::V05, &stored),
LegacyDict::None
)
.is_err()
);
for body in [&run[..], &stored[..]] {
let frame = one_block_frame(LegacyVersion::V07, body);
assert!(decode_to_vec(&frame, LegacyDict::None).is_err());
}
}
#[test]
fn single_stream_literals_need_their_stream() {
let table_only = [0x10, 0x10, 0x02, 0x81, 0x11, 0x00];
let empty_stream = [0x10, 0x10, 0x03, 0x81, 0x11, 0x01, 0x00];
for version in VERSIONS {
for body in [&table_only[..], &empty_stream[..]] {
let frame = one_block_frame(version, body);
assert!(
decode_to_vec(&frame, LegacyDict::None).is_err(),
"{version:?} {body:02x?}"
);
}
}
}
#[test]
fn bad_sequence_headers_are_refused() {
let cut = [0x80, 0x01, 0x00, 0x00];
for version in [LegacyVersion::V06, LegacyVersion::V07] {
assert!(matches!(
decode_to_vec(&one_block_frame(version, &cut), LegacyDict::None),
Err(Error::Truncated)
));
}
let repeat_v06 = [0x80, 0x01, 0x80, 0x00, 0x00, 0x00];
let repeat_v05 = [0x80, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00];
for (version, body) in [
(LegacyVersion::V05, &repeat_v05[..]),
(LegacyVersion::V06, &repeat_v06[..]),
(LegacyVersion::V07, &repeat_v06[..]),
] {
assert!(matches!(
decode_to_vec(&one_block_frame(version, body), LegacyDict::None),
Err(Error::Corrupted { .. })
));
}
let rle_cut = [0x80, 0x01, 0xD0, 0x00, 0x10, 0xF8, 0x01, 0x05];
assert!(matches!(
decode_to_vec(
&one_block_frame(LegacyVersion::V05, &rle_cut),
LegacyDict::None
),
Err(Error::Truncated)
));
}
#[test]
fn v05_run_tables_and_long_dumps_decode() {
let mut body = vec![0x88];
body.extend_from_slice(b"abcdefgh");
body.extend_from_slice(&[0x01, 0x54, 0x03, 0xFF, 0x10, 0x00]);
body.extend_from_slice(&[0x3F, 0x21, 0x00, 0x80]);
let frame = one_block_frame(LegacyVersion::V05, &body);
assert_eq!(
decode_to_vec(&frame, LegacyDict::None).unwrap().0,
b"abcdefghhhhh".to_vec()
);
let literals: Vec<u8> = (0..63u8).collect();
let mut body = vec![0xA0, 63];
body.extend_from_slice(&literals);
body.extend_from_slice(&[0x01, 0x54, 0x01, 0xFF, 0x3F, 0x21, 0x00, 0x80]);
let frame = one_block_frame(LegacyVersion::V05, &body);
let mut expected = literals.clone();
expected.extend_from_slice(&[62; 4]);
assert_eq!(decode_to_vec(&frame, LegacyDict::None).unwrap().0, expected);
}
#[test]
fn a_full_size_compressed_block_is_refused() {
let body = vec![0u8; BLOCK_SIZE_MAX];
for version in VERSIONS {
assert!(matches!(
decode_to_vec(&one_block_frame(version, &body), LegacyDict::None),
Err(Error::Corrupted { .. })
));
}
}
#[test]
fn a_dictionary_table_past_its_format_is_refused() {
let offsets = [0xF5, 0x7F, 0x00, 0x00];
for (version, magic) in [(LegacyVersion::V05, 0x35), (LegacyVersion::V06, 0x36)] {
let mut bytes = vec![magic, 0xA4, 0x30, 0xEC, 0x81, 0x11];
bytes.extend_from_slice(&offsets);
bytes.extend_from_slice(&[0; 32]);
let dict = crate::decoding::Dictionary::from_raw_content(0, bytes).unwrap();
let frame = one_block_frame(version, &[0x83, b'a', b'b', b'c', 0x00]);
assert!(matches!(
decode_to_vec(
&frame,
LegacyDict::Parsed {
dict: &dict,
header: &[]
}
),
Err(Error::DictionaryCorrupted)
));
}
}
#[cfg(feature = "std")]
#[test]
fn a_v07_frame_refuses_a_dictionary_it_cannot_use() {
use crate::decoding::errors::FrameDecoderError;
use crate::decoding::{Dictionary, DictionaryHandle, FrameDecoder};
let raw = include_bytes!("../../../dict_tests/dictionary");
let frame = one_block_frame(LegacyVersion::V07, &[0x83, b'a', b'b', b'c', 0x00]);
let mut zero_offset = Dictionary::decode_dict(raw).unwrap();
zero_offset.offset_hist[1] = 0;
let mut wide_offsets = Dictionary::decode_dict(raw).unwrap();
wide_offsets.fse.offsets.accuracy_log = 9;
for dict in [zero_offset, wide_offsets] {
let handle = DictionaryHandle::from_dictionary(dict);
let mut out = [0u8; 8];
let refused = |result| {
matches!(
result,
Err(FrameDecoderError::LegacyFrame(Error::DictionaryCorrupted))
| Err(FrameDecoderError::DictionaryDecodeError(_))
)
};
let mut decoder = FrameDecoder::new();
assert!(refused(
decoder
.decode_all_with_dict_handle(&frame, &mut out, &handle)
.map(|_| ())
));
assert!(refused(decoder.reset_with_dict_handle(&frame[..], &handle)));
}
}
#[cfg(feature = "std")]
#[test]
fn the_step_interface_covers_these_frames() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
use std::io::Read;
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&[0x80, 0x00, 0x05, b'z']);
frame.extend_from_slice(&[0x40, 0x00, 0x02, b'a', b'b']);
frame.extend_from_slice(&[0xC0, 0x00, 0x00]);
let mut decoder = FrameDecoder::new();
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
assert!(
!decoder
.decode_blocks(&mut src, BlockDecodingStrategy::UptoBytes(1))
.unwrap()
);
assert_eq!(decoder.can_collect(), 5);
assert!(
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap()
);
assert_eq!(decoder.input_needed(&[]), 0);
let mut written = Vec::new();
assert_eq!(decoder.collect_to_writer(&mut written).unwrap(), 7);
assert_eq!(written, b"zzzzzab");
let mut decoder = FrameDecoder::new();
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
let mut read = [0u8; 16];
assert_eq!(decoder.read(&mut read).unwrap(), 7);
assert_eq!(&read[..7], b"zzzzzab");
}
#[cfg(feature = "std")]
#[test]
fn a_damaged_block_after_delivered_output_waits_for_the_next_read() {
use crate::decoding::StreamingDecoder;
use std::io::Read;
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&[0x40, 0x00, 0x08]);
frame.extend_from_slice(b"abcdefgh");
frame.extend_from_slice(&[0x00, 0x00, 0x02, 0x83, b'a']);
frame.extend_from_slice(&[0xC0, 0x00, 0x00]);
let mut decoder = StreamingDecoder::new(&frame[..]);
let mut buf = [0u8; 64];
assert_eq!(decoder.read(&mut buf).unwrap(), 8);
assert_eq!(&buf[..8], b"abcdefgh");
assert!(decoder.read(&mut buf).is_err());
let mut decoder = StreamingDecoder::new(&frame[..]);
assert_eq!(decoder.read(&mut buf[..3]).unwrap(), 3);
assert_eq!(decoder.read(&mut buf).unwrap(), 5);
assert_eq!(&buf[..5], b"defgh");
assert!(decoder.read(&mut buf).is_err());
}
#[cfg(feature = "std")]
#[test]
fn collect_to_writer_keeps_what_a_failing_writer_took() {
use crate::decoding::{BlockDecodingStrategy, FrameDecoder};
use std::io::{ErrorKind, Write};
struct Partial {
took: Vec<u8>,
room: usize,
}
impl Write for Partial {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.room == 0 {
return Err(ErrorKind::WouldBlock.into());
}
let n = buf.len().min(self.room);
self.took.extend_from_slice(&buf[..n]);
self.room -= n;
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00];
frame.extend_from_slice(&[0x40, 0x00, 0x08]);
frame.extend_from_slice(b"abcdefgh");
frame.extend_from_slice(&[0xC0, 0x00, 0x00]);
let mut decoder = FrameDecoder::new();
let mut src = &frame[..];
decoder.reset(&mut src).unwrap();
decoder
.decode_blocks(&mut src, BlockDecodingStrategy::All)
.unwrap();
let mut sink = Partial {
took: Vec::new(),
room: 3,
};
assert!(decoder.collect_to_writer(&mut sink).is_err());
assert_eq!(sink.took, b"abc");
assert_eq!(decoder.can_collect(), 5);
sink.room = 16;
assert_eq!(decoder.collect_to_writer(&mut sink).unwrap(), 5);
assert_eq!(sink.took, b"abcdefgh");
}