use super::*;
use crate::compression::ZstdDictionary;
use test_log::test;
const DICT: &[u8] = &[
55, 164, 48, 236, 98, 64, 12, 7, 42, 16, 120, 62, 7, 204, 192, 51, 240, 12, 60, 3, 207, 192,
51, 240, 12, 60, 3, 207, 192, 51, 24, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 48,
165, 148, 2, 227, 76, 8, 33, 132, 16, 66, 136, 136, 136, 60, 84, 160, 64, 65, 65, 65, 65, 65,
65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 193, 231, 162,
40, 138, 162, 40, 138, 162, 40, 165, 148, 82, 74, 169, 170, 234, 1, 100, 160, 170, 193, 96, 48,
24, 12, 6, 131, 193, 96, 48, 12, 195, 48, 12, 195, 48, 12, 195, 48, 198, 24, 99, 140, 153, 29,
1, 0, 0, 0, 4, 0, 0, 0, 8, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2, 2, 2,
];
const COMPRESSED: &[u8] = &[
40, 181, 47, 253, 35, 98, 64, 12, 7, 35, 149, 0, 0, 96, 104, 101, 108, 108, 111, 32, 119, 111,
114, 108, 100, 32, 1, 0, 175, 75, 18,
];
const PLAINTEXT: &[u8] = b"hello world hello world hello world";
#[test]
fn decompress_with_dict_returns_correct_plaintext() {
let dict = ZstdDictionary::new(DICT);
let result = ZstdProvider::decompress_with_dict(COMPRESSED, &dict, PLAINTEXT.len() + 1)
.expect("decompression should succeed");
assert_eq!(
result, PLAINTEXT,
"decompressed output must equal the original plaintext"
);
}
#[test]
fn decompress_with_dict_is_idempotent_across_repeated_calls() {
let dict = ZstdDictionary::new(DICT);
for _ in 0..3 {
let result = ZstdProvider::decompress_with_dict(COMPRESSED, &dict, PLAINTEXT.len() + 1)
.expect("decompression should succeed on every call");
assert_eq!(result, PLAINTEXT);
}
}
#[test]
fn decompress_with_dict_rejects_frame_exceeding_capacity() {
let dict = ZstdDictionary::new(DICT);
let too_small = PLAINTEXT.len() / 2;
let result = ZstdProvider::decompress_with_dict(COMPRESSED, &dict, too_small);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"expected DecompressedSizeTooLarge but got {result:?}",
);
}
#[test]
fn compress_with_dict_roundtrip_pure_to_pure() {
let dict = ZstdDictionary::new(DICT);
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, DICT)
.expect("compression with dict should succeed");
assert!(
!compressed.is_empty(),
"compressed output must not be empty"
);
let decompressed = ZstdProvider::decompress_with_dict(&compressed, &dict, PLAINTEXT.len() + 1)
.expect("decompression with dict should succeed");
assert_eq!(
decompressed, PLAINTEXT,
"round-tripped output must equal the original plaintext"
);
}
#[test]
fn compress_with_dict_produces_zstd_magic() {
let compressed =
ZstdProvider::compress_with_dict(PLAINTEXT, 3, DICT).expect("compression should succeed");
assert!(
compressed.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]),
"output must start with zstd magic 0xFD2FB528 (LE); got {:?}",
compressed.get(..4.min(compressed.len()))
);
}
#[test]
fn compress_with_dict_roundtrip_representative_levels() {
let dict = ZstdDictionary::new(DICT);
for level in [1, 3, 9, 19] {
let compressed =
ZstdProvider::compress_with_dict(PLAINTEXT, level, DICT).expect("compress");
let decompressed =
ZstdProvider::decompress_with_dict(&compressed, &dict, PLAINTEXT.len() + 1)
.expect("decompress");
assert_eq!(
decompressed, PLAINTEXT,
"round-trip failed at compression level={level}"
);
}
}
#[test]
fn compress_with_dict_empty_dict_returns_error() {
let result = ZstdProvider::compress_with_dict(PLAINTEXT, 3, b"");
assert!(
result.is_err(),
"expected an error for empty dictionary, got Ok"
);
}
#[test]
fn compress_with_dict_raw_content_dict_works() {
let raw_content_dict = b"this is raw content dictionary data for matching";
let dict = ZstdDictionary::new(raw_content_dict);
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_content_dict)
.expect("compression with raw content dict should succeed");
let decompressed = ZstdProvider::decompress_with_dict(&compressed, &dict, PLAINTEXT.len() + 1)
.expect("decompression with raw content dict should succeed");
assert_eq!(
decompressed, PLAINTEXT,
"round-trip with raw content dict must equal the original plaintext"
);
}
#[test]
fn raw_content_dict_substitution_rejected_by_inner_frame_gate() {
let dict_a_raw = b"raw content dictionary ALPHA for the substitution test".to_vec();
let dict_b_raw = b"raw content dictionary BRAVO for the substitution test".to_vec();
let dict_a = ZstdDictionary::new(&dict_a_raw);
let dict_b = ZstdDictionary::new(&dict_b_raw);
assert_ne!(
dict_a.id(),
dict_b.id(),
"distinct raw dicts must hash to distinct ids"
);
let payload = b"defense-in-depth payload bytes compressed under a raw dict";
let frame_b =
ZstdProvider::compress_with_dict(payload, 3, &dict_b_raw).expect("compress under B");
let ok = ZstdProvider::decompress_with_dict(&frame_b, &dict_b, payload.len() + 1)
.expect("matching dict must decode");
assert_eq!(ok, payload, "matching-dict round-trip must be exact");
let err = ZstdProvider::decompress_with_dict(&frame_b, &dict_a, payload.len() + 1)
.expect_err("dict substitution must be rejected by the inner-frame gate");
let expected_dict_id = dict_a.id().max(1);
assert!(
matches!(
err,
crate::Error::Decompress(crate::CompressionType::ZstdDict { level: 0, dict_id })
if dict_id == expected_dict_id
),
"expected Decompress(ZstdDict {{ level: 0, dict_id: {expected_dict_id} }}), got {err:?}"
);
}
#[test]
fn compress_with_dict_empty_plaintext_roundtrips() {
let dict = ZstdDictionary::new(DICT);
let compressed = ZstdProvider::compress_with_dict(&[], 3, DICT)
.expect("compression of empty payload should succeed");
let decompressed = ZstdProvider::decompress_with_dict(&compressed, &dict, 1)
.expect("decompression of empty payload should succeed");
assert!(
decompressed.is_empty(),
"decompressed output of empty payload must be empty"
);
}
#[test]
fn compress_with_dict_raw_content_empty_plaintext_roundtrips_at_capacity_one() {
let raw_dict = b"raw content dictionary for empty payload smoke test";
let dict = ZstdDictionary::new(raw_dict);
let compressed = ZstdProvider::compress_with_dict(&[], 3, raw_dict)
.expect("compression of empty payload with raw-content dict should succeed");
let decompressed = ZstdProvider::decompress_with_dict(&compressed, &dict, 1)
.expect("decompression of empty payload with raw-content dict (capacity=1) should succeed");
assert!(
decompressed.is_empty(),
"decompressed output of empty payload must be empty; got {decompressed:?}"
);
}
#[test]
fn compress_with_dict_raw_content_empty_plaintext_roundtrips_at_exact_capacity() {
let raw_dict = b"raw content dictionary for empty payload exact-capacity test";
let dict = ZstdDictionary::new(raw_dict);
let compressed = ZstdProvider::compress_with_dict(&[], 3, raw_dict)
.expect("compression of empty payload with raw-content dict should succeed");
let decompressed = ZstdProvider::decompress_with_dict(&compressed, &dict, 0)
.expect("decompression of empty payload with raw-content dict (capacity=0) should succeed");
assert!(
decompressed.is_empty(),
"decompressed output of empty payload must be empty; got {decompressed:?}"
);
}
#[test]
fn decompress_with_dict_raw_content_rejects_frame_exceeding_capacity() {
let raw_dict = b"this is raw content dictionary data for matching";
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_dict)
.expect("compression with raw content dict should succeed");
let dict = ZstdDictionary::new(raw_dict);
let too_small = PLAINTEXT.len() / 2;
let result = ZstdProvider::decompress_with_dict(&compressed, &dict, too_small);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"raw-content path must return DecompressedSizeTooLarge when capacity < plaintext; got {result:?}",
);
}
#[test]
fn decompress_with_dict_raw_content_rejects_zero_capacity_non_empty() {
let raw_dict = b"raw content dict for zero-capacity test";
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_dict)
.expect("compression should succeed");
let dict = ZstdDictionary::new(raw_dict);
let result = ZstdProvider::decompress_with_dict(&compressed, &dict, 0);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"capacity=0 with non-empty frame must return DecompressedSizeTooLarge; got {result:?}",
);
}
struct AlwaysFailReader;
impl std::io::Read for AlwaysFailReader {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("simulated read error"))
}
}
struct FailOnProbeReader {
remaining: usize,
eof_sent: bool,
}
impl std::io::Read for FailOnProbeReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.remaining > 0 {
let n = buf.len().min(self.remaining);
for b in buf.iter_mut().take(n) {
*b = 0;
}
self.remaining -= n;
Ok(n)
} else if !self.eof_sent {
self.eof_sent = true;
Ok(0) } else {
Err(std::io::Error::other("simulated probe-read error"))
}
}
}
#[test]
fn bounded_read_propagates_io_error_from_read_loop() {
let mut reader = AlwaysFailReader;
let result = bounded_read(&mut reader, 64);
assert!(
matches!(result, Err(crate::Error::Io(_))),
"expected Io error from read loop; got {result:?}",
);
}
#[test]
fn bounded_read_propagates_io_error_from_probe_read() {
let mut reader = FailOnProbeReader {
remaining: 4,
eof_sent: false,
};
let result = bounded_read(&mut reader, 64);
assert!(
matches!(result, Err(crate::Error::Io(_))),
"expected Io error from probe read; got {result:?}",
);
}
#[test]
fn decompress_with_dict_returns_error_on_corrupt_finalized_frame() {
let dict = ZstdDictionary::new(DICT);
let mut frame =
ZstdProvider::compress_with_dict(PLAINTEXT, 3, DICT).expect("compression must succeed");
frame.pop(); let result = ZstdProvider::decompress_with_dict(&frame, &dict, 1024);
assert!(
matches!(result, Err(crate::Error::Io(_))),
"corrupt frame must return Err(Io(_)) on finalized dict path; got {result:?}",
);
}
#[test]
fn decompress_with_dict_returns_error_on_corrupt_raw_content_frame() {
let raw_dict = b"some raw content dictionary bytes for testing corruption";
let dict = ZstdDictionary::new(raw_dict);
let mut frame =
ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_dict).expect("compression must succeed");
frame.pop(); let result = ZstdProvider::decompress_with_dict(&frame, &dict, 1024);
assert!(
matches!(result, Err(crate::Error::Io(_))),
"corrupt frame must return Err(Io(_)) on raw-content dict path; got {result:?}",
);
}
#[expect(
clippy::cast_possible_truncation,
reason = "intentional: lower 32 bits of xxh3"
)]
fn raw_content_id(dict_raw: &[u8]) -> u32 {
let h = xxhash_rust::xxh3::xxh3_64(dict_raw) as u32;
h.max(1)
}
fn make_raw_content_decoder<'a>(
dict_raw: &[u8],
compressed: &'a [u8],
cursor: &mut std::io::Cursor<&'a [u8]>,
) -> structured_zstd::decoding::FrameDecoder {
use structured_zstd::decoding::{Dictionary, FrameDecoder};
let id = raw_content_id(dict_raw);
let parsed = Dictionary::from_raw_content(id, dict_raw.to_vec())
.expect("Dictionary::from_raw_content should succeed");
let mut decoder = FrameDecoder::new();
decoder.add_dict(parsed).expect("add_dict should succeed");
*cursor = std::io::Cursor::new(compressed);
decoder
.init(cursor)
.expect("FrameDecoder::init should succeed");
decoder.force_dict(id).expect("force_dict should succeed");
decoder
}
#[test]
fn decode_raw_content_bounded_remaining_zero_returns_error() {
let raw_dict = b"raw content dict for remaining-zero test";
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_dict)
.expect("compression should succeed");
let mut cursor = std::io::Cursor::new(compressed.as_slice());
let mut decoder = make_raw_content_decoder(raw_dict, &compressed, &mut cursor);
let result = decode_raw_content_bounded(&mut decoder, &mut cursor, 0);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"capacity=0 must return DecompressedSizeTooLarge; got {result:?}",
);
}
fn sorted_kv_payload(target: usize) -> Vec<u8> {
let mut buf = Vec::with_capacity(target + 64);
let mut i = 0u64;
while buf.len() < target {
buf.extend_from_slice(format!("key-{i:012}").as_bytes());
buf.push(0);
buf.extend_from_slice(format!("value-{i:08}-payload").as_bytes());
buf.push(0);
i += 1;
}
buf
}
#[test]
fn compress_with_layout_single_inner_block_returns_empty_layout() {
let payload = sorted_kv_payload(4 * 1024);
let (frame, layout) =
ZstdProvider::compress_with_layout(&payload, 3).expect("compress with layout");
assert!(
layout.is_empty(),
"single-inner-block frame must yield an empty layout, got {layout:?}",
);
let back = ZstdProvider::decompress(&frame, payload.len() + 1).expect("decompress");
assert_eq!(back, payload);
}
#[test]
fn compress_with_layout_multi_inner_block_offsets_are_monotonic_and_total() {
let payload = sorted_kv_payload(256 * 1024);
let (_frame, layout) =
ZstdProvider::compress_with_layout(&payload, 19).expect("compress with layout");
assert!(
layout.len() >= 2,
"256 KiB @ L19 must split into >= 2 inner blocks, got {} block(s)",
layout.len(),
);
assert!(
layout.windows(2).all(|w| w[0] < w[1]),
"cumulative ends must be strictly increasing: {layout:?}",
);
assert_eq!(
*layout.last().expect("non-empty layout") as usize,
payload.len(),
"last cumulative end must equal the total decompressed size",
);
}
#[test]
fn compress_with_layout_subrange_partial_decode_matches_full_slice() {
use structured_zstd::decoding::FrameDecoder;
let payload = sorted_kv_payload(256 * 1024);
let (frame, layout) =
ZstdProvider::compress_with_layout(&payload, 19).expect("compress with layout");
let nblocks = layout.len();
assert!(nblocks >= 2, "need a multi-block frame");
let mut src = frame.as_slice();
let mut dec = FrameDecoder::new();
dec.reset(&mut src).expect("reset frame header");
let pd = dec
.decode_blocks_partial(&mut src, 0, 1, None, false)
.expect("partial decode of first inner block");
let want_end = layout[0] as usize;
assert_eq!(
pd.data.as_slice(),
&payload[..want_end],
"partial-decode [0,1) must equal the full-decode slice [0,{want_end})",
);
}
#[test]
fn decode_raw_content_bounded_collected_exceeds_capacity_returns_error() {
let raw_dict = b"raw content dict for can-exceeds-capacity test";
let compressed = ZstdProvider::compress_with_dict(PLAINTEXT, 3, raw_dict)
.expect("compression should succeed");
let mut cursor = std::io::Cursor::new(compressed.as_slice());
let mut decoder = make_raw_content_decoder(raw_dict, &compressed, &mut cursor);
let result = decode_raw_content_bounded(&mut decoder, &mut cursor, 5);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"capacity < plaintext must return DecompressedSizeTooLarge; got {result:?}",
);
}
#[test]
fn decompress_into_rejects_frame_larger_than_dest() {
let raw = vec![7u8; 4096];
let frame = ZstdProvider::compress(&raw, 3).expect("compress");
let mut dest = vec![0u8; 2048];
let result = ZstdProvider::decompress_into(&frame, &mut dest);
assert!(
matches!(result, Err(crate::Error::DecompressedSizeTooLarge { .. })),
"a frame that decodes past dest must be rejected; got {result:?}",
);
}
#[test]
fn decompress_into_fills_exact_buffer() {
let raw = vec![3u8; 4096];
let frame = ZstdProvider::compress(&raw, 3).expect("compress");
let mut dest = vec![0u8; raw.len()];
let written = ZstdProvider::decompress_into(&frame, &mut dest).expect("decompress_into");
assert_eq!(written, raw.len());
assert_eq!(dest, raw);
}
#[test]
fn decompress_into_propagates_decode_error() {
let mut dest = vec![0u8; 64];
let result = ZstdProvider::decompress_into(b"definitely not a valid zstd frame", &mut dest);
assert!(result.is_err(), "a corrupt frame must error");
}