use libzstd_bitexact_rs::{DecodeOptions, Dictionary, StreamDecoder};
use std::io::{self, Read, Write};
use zstd::zstd_safe::CParameter;
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed.max(1))
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
fn below(&mut self, n: usize) -> usize {
(self.next_u64() % n as u64) as usize
}
fn bytes(&mut self, len: usize) -> Vec<u8> {
let mut v = Vec::with_capacity(len);
while v.len() < len {
v.extend_from_slice(&self.next_u64().to_le_bytes());
}
v.truncate(len);
v
}
}
struct ChunkedReader<'a> {
data: &'a [u8],
pos: usize,
chunk: usize,
}
impl Read for ChunkedReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.chunk.min(buf.len()).min(self.data.len() - self.pos);
buf[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
self.pos += n;
Ok(n)
}
}
fn stream_decode(frame: &[u8], chunk: usize) -> io::Result<Vec<u8>> {
let reader = ChunkedReader {
data: frame,
pos: 0,
chunk,
};
let mut decoder = StreamDecoder::new(reader);
let mut out = Vec::new();
decoder.read_to_end(&mut out)?;
Ok(out)
}
fn stream_decode_opts(frame: &[u8], chunk: usize, opts: DecodeOptions) -> io::Result<Vec<u8>> {
let reader = ChunkedReader {
data: frame,
pos: 0,
chunk,
};
let mut decoder = StreamDecoder::with_options(reader, opts);
let mut out = Vec::new();
decoder.read_to_end(&mut out)?;
Ok(out)
}
fn datasets() -> Vec<(&'static str, Vec<u8>)> {
let mut rng = Rng::new(0x57EA_3001);
let words: &[&[u8]] = &[
b"the",
b"quick",
b"brown",
b"fox",
b"jumps",
b"over",
b"lazy",
b"dog",
b"zstandard",
b"stream",
b"window",
b"block",
];
let mut text = Vec::with_capacity(200_000);
while text.len() < 200_000 {
text.extend_from_slice(words[rng.below(words.len())]);
text.push(b' ');
}
vec![
("empty", Vec::new()),
("tiny", b"hello".to_vec()),
("zeros-100k", vec![0u8; 100_000]),
(
"cycle-40k",
(0..40_000u32).map(|i| (i % 251) as u8).collect(),
),
("random-50k", rng.bytes(50_000)),
("text-200k", text),
]
}
const CHUNKS: [usize; 6] = [1, 3, 7, 64, 1000, 1 << 16];
#[test]
fn bulk_frames_decode_identically_at_every_chunking() {
for (name, data) in datasets() {
for level in [1, 3, 9, 19] {
let frame = zstd::bulk::compress(&data, level).unwrap();
for &chunk in &CHUNKS {
let out = stream_decode(&frame, chunk)
.unwrap_or_else(|e| panic!("{name} L{level} chunk {chunk}: {e}"));
assert_eq!(out, data, "{name} L{level} chunk {chunk}");
}
}
}
}
#[test]
fn streaming_frames_decode_identically_at_every_chunking() {
for (name, data) in datasets() {
for level in [1, 3, 19] {
let frame = zstd::stream::encode_all(&data[..], level).unwrap();
for &chunk in &CHUNKS {
let out = stream_decode(&frame, chunk)
.unwrap_or_else(|e| panic!("streaming {name} L{level} chunk {chunk}: {e}"));
assert_eq!(out, data, "streaming {name} L{level} chunk {chunk}");
}
}
}
}
#[test]
fn checksummed_frames_verify_and_corruption_is_caught() {
for (name, data) in datasets() {
let mut enc = zstd::stream::Encoder::new(Vec::new(), 3).unwrap();
enc.include_checksum(true).unwrap();
enc.write_all(&data).unwrap();
let frame = enc.finish().unwrap();
for &chunk in &[1usize, 7, 1 << 16] {
let out = stream_decode(&frame, chunk)
.unwrap_or_else(|e| panic!("checksummed {name} chunk {chunk}: {e}"));
assert_eq!(out, data, "checksummed {name}");
}
if !data.is_empty() {
let mut bad = frame.clone();
let last = bad.len() - 1;
bad[last] ^= 0xFF;
assert!(
stream_decode(&bad, 64).is_err(),
"{name}: bad checksum accepted"
);
}
}
}
#[test]
fn multi_frame_and_skippable() {
let a = b"first frame payload ".repeat(800);
let b = b"second distinct payload ".repeat(500);
let mut stream = zstd::bulk::compress(&a, 3).unwrap();
stream.extend_from_slice(&0x184D_2A50u32.to_le_bytes());
stream.extend_from_slice(&5u32.to_le_bytes());
stream.extend_from_slice(b"skip!");
stream.extend_from_slice(&zstd::stream::encode_all(&b[..], 19).unwrap());
let expected: Vec<u8> = a.iter().chain(b.iter()).copied().collect();
for &chunk in &[1usize, 3, 64, 1 << 16] {
assert_eq!(
stream_decode(&stream, chunk).unwrap(),
expected,
"chunk {chunk}"
);
}
}
#[test]
fn dictionary_streaming_round_trip() {
let words: &[&[u8]] = &[b"alpha", b"bravo", b"charlie", b"delta", b"echo"];
let mut rng = Rng::new(0xD1C7_5EED);
let mut raw = Vec::new();
while raw.len() < 4096 {
raw.extend_from_slice(words[rng.below(words.len())]);
raw.push(b' ');
}
let mut samples = Vec::new();
let mut sizes = Vec::new();
for _ in 0..3000 {
let mut s = Vec::new();
for _ in 0..(8 + rng.below(16)) {
s.extend_from_slice(words[rng.below(words.len())]);
s.push(b' ');
}
sizes.push(s.len());
samples.extend_from_slice(&s);
}
let trained = zstd::dict::from_continuous(&samples, &sizes, 16 * 1024).unwrap();
let mut data = Vec::new();
while data.len() < 20_000 {
data.extend_from_slice(words[rng.below(words.len())]);
data.push(b' ');
}
for dict_bytes in [raw, trained] {
let dict = Dictionary::new(&dict_bytes).unwrap();
for level in [3, 19] {
let mut c = zstd::bulk::Compressor::with_dictionary(level, &dict_bytes).unwrap();
let frame = c.compress(&data).unwrap();
for &chunk in &[1usize, 7, 1 << 16] {
let out = stream_decode_opts(&frame, chunk, DecodeOptions::new().dictionary(&dict))
.unwrap_or_else(|e| panic!("dict L{level} chunk {chunk}: {e}"));
assert_eq!(out, data, "dict L{level} chunk {chunk}");
}
}
}
}
fn window_log_of(frame: &[u8]) -> u32 {
assert_eq!(frame[4] & 0x20, 0, "expected a windowed frame");
10 + (frame[5] >> 3) as u32
}
fn encode_with_window_log(data: &[u8], window_log: u32) -> Vec<u8> {
let mut enc = zstd::stream::Encoder::new(Vec::new(), 3).unwrap();
enc.set_parameter(CParameter::WindowLog(window_log))
.unwrap();
enc.set_parameter(CParameter::ContentSizeFlag(false))
.unwrap();
enc.write_all(data).unwrap();
enc.finish().unwrap()
}
#[test]
fn small_window_forces_eviction_but_decodes_correctly() {
let mut rng = Rng::new(0xE71C_7000);
let mut data = Vec::with_capacity(400_000);
while data.len() < 400_000 {
data.extend_from_slice(b"window eviction stress ");
if rng.below(20) == 0 {
let n = rng.below(64);
data.extend_from_slice(&rng.bytes(n));
}
}
let frame = encode_with_window_log(&data, 17); assert!(window_log_of(&frame) <= 18, "expected a sub-content window");
for &chunk in &[7usize, 1000, 1 << 16] {
let out = stream_decode(&frame, chunk).unwrap();
assert_eq!(out, data, "eviction decode mismatch at chunk {chunk}");
}
}
#[test]
fn window_log_max_is_enforced() {
let mut rng = Rng::new(0x1A2B_3C4D);
let mut data = Vec::with_capacity(150_000);
while data.len() < 150_000 {
data.extend_from_slice(b"alpha bravo charlie ");
let _ = rng.next_u64();
}
let frame = encode_with_window_log(&data, 19);
let actual = window_log_of(&frame);
let ok = stream_decode_opts(&frame, 64, DecodeOptions::new().window_log_max(actual)).unwrap();
assert_eq!(ok, data);
assert!(
stream_decode_opts(&frame, 64, DecodeOptions::new().window_log_max(actual - 1)).is_err(),
"window log {actual} accepted under a tighter windowLogMax"
);
}
#[test]
fn output_limit_is_enforced() {
let data = vec![7u8; 1 << 20];
let frame = zstd::bulk::compress(&data, 3).unwrap();
assert!(stream_decode_opts(&frame, 1 << 16, DecodeOptions::new().limit(1000)).is_err());
assert_eq!(
stream_decode_opts(&frame, 1 << 16, DecodeOptions::new().limit(1 << 20)).unwrap(),
data
);
}
#[test]
fn truncation_errors_and_never_panics() {
let data = b"streaming truncation corpus ".repeat(2000);
let frame = zstd::stream::encode_all(&data[..], 6).unwrap();
for len in 1..frame.len() {
let r = stream_decode(&frame[..len], 13);
assert!(r.is_err(), "truncation to {len} unexpectedly succeeded");
}
}