use super::CompressionProvider;
use std::io::Read;
const DICT_MAGIC: [u8; 4] = [0x37, 0xA4, 0x30, 0xEC];
fn bounded_read(reader: &mut impl Read, capacity: usize) -> crate::Result<Vec<u8>> {
let mut output = vec![0u8; capacity];
let mut filled = 0;
loop {
let dest = output
.get_mut(filled..)
.ok_or(crate::Error::DecompressedSizeTooLarge {
declared: filled as u64,
limit: capacity as u64,
})?;
match reader.read(dest) {
Ok(0) => break,
Ok(n) => filled += n,
Err(e) => return Err(crate::Error::from(e)),
}
}
let mut probe = [0u8; 1];
match reader.read(&mut probe) {
Ok(0) => {}
Ok(_) => {
return Err(crate::Error::DecompressedSizeTooLarge {
declared: (filled + 1) as u64,
limit: capacity as u64,
});
}
Err(e) => return Err(crate::Error::from(e)),
}
output.truncate(filled);
Ok(output)
}
fn decode_raw_content_bounded(
decoder: &mut structured_zstd::decoding::FrameDecoder,
cursor: &mut std::io::Cursor<&[u8]>,
capacity: usize,
) -> crate::Result<Vec<u8>> {
use structured_zstd::decoding::BlockDecodingStrategy;
let mut output: Vec<u8> = Vec::new();
loop {
let remaining = capacity.saturating_sub(output.len());
if !decoder.is_finished() {
decoder
.decode_blocks(
&mut *cursor,
BlockDecodingStrategy::UptoBytes(remaining.max(1)),
)
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
}
let can = decoder.can_collect();
if can > 0 {
let new_len =
output
.len()
.checked_add(can)
.ok_or(crate::Error::DecompressedSizeTooLarge {
declared: u64::MAX,
limit: capacity as u64,
})?;
if new_len > capacity {
return Err(crate::Error::DecompressedSizeTooLarge {
declared: new_len as u64,
limit: capacity as u64,
});
}
let prev_len = output.len();
output.resize(new_len, 0u8);
let dest = output
.get_mut(prev_len..)
.unwrap_or_else(|| unreachable!("output resized to new_len above"));
decoder.read_exact(dest).map_err(crate::Error::from)?;
}
if decoder.is_finished() && decoder.can_collect() == 0 {
break;
}
}
Ok(output)
}
fn do_decompress_with_dict(
decoder: &mut structured_zstd::decoding::FrameDecoder,
data: &[u8],
raw_content_id: u32,
capacity: usize,
is_raw_content: bool,
) -> crate::Result<Vec<u8>> {
if is_raw_content {
let mut cursor = std::io::Cursor::new(data);
decoder.expect_dict_id(Some(raw_content_id));
decoder.init(&mut cursor).map_err(|e| {
if matches!(
e,
structured_zstd::decoding::errors::FrameDecoderError::UnexpectedDictId { .. }
) {
crate::Error::Decompress(crate::CompressionType::ZstdDict {
level: 0,
dict_id: raw_content_id,
})
} else {
crate::Error::Io(crate::io::Error::other(e.to_string()))
}
})?;
let declared_size = decoder.content_size();
if declared_size > 0 && declared_size > capacity as u64 {
return Err(crate::Error::DecompressedSizeTooLarge {
declared: declared_size,
limit: capacity as u64,
});
}
decoder
.force_dict(raw_content_id)
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
decode_raw_content_bounded(decoder, &mut cursor, capacity)
} else {
let mut output = Vec::with_capacity(capacity);
decoder.decode_all_to_vec(data, &mut output).map_err(|e| {
if matches!(
e,
structured_zstd::decoding::errors::FrameDecoderError::TargetTooSmall
) {
crate::Error::DecompressedSizeTooLarge {
declared: capacity as u64 + 1,
limit: capacity as u64,
}
} else {
crate::Error::Io(crate::io::Error::other(e.to_string()))
}
})?;
Ok(output)
}
}
fn inner_block_layout(
info: Option<&structured_zstd::encoding::frame_emit_info::FrameEmitInfo>,
) -> Vec<u32> {
let Some(info) = info else { return Vec::new() };
let n = info.blocks.len();
if n < 2 {
return Vec::new();
}
let mut ends = Vec::with_capacity(n);
for i in 0..n {
let Some(range) = info.decompressed_byte_range(i) else {
return Vec::new();
};
let Ok(end) = u32::try_from(range.end) else {
return Vec::new();
};
ends.push(end);
}
ends
}
fn with_tls_compressor<R>(
level: i32,
f: impl FnOnce(&mut structured_zstd::encoding::FrameCompressor) -> R,
) -> R {
use structured_zstd::encoding::{CompressionLevel, FrameCompressor};
thread_local! {
static TLS_COMPRESSOR: std::cell::RefCell<Option<(i32, FrameCompressor)>> =
const { std::cell::RefCell::new(None) };
}
TLS_COMPRESSOR.with(|cell| {
let mut state = cell.borrow_mut();
if !matches!(&*state, Some((l, _)) if *l == level) {
*state = Some((
level,
FrameCompressor::new(CompressionLevel::from_level(level)),
));
}
let Some((_, compressor)) = state.as_mut() else {
unreachable!("TLS_COMPRESSOR initialised above");
};
f(compressor)
})
}
pub struct ZstdProvider;
impl CompressionProvider for ZstdProvider {
fn compress(data: &[u8], level: i32) -> crate::Result<Vec<u8>> {
Ok(with_tls_compressor(level, |compressor| {
compressor.compress_independent_frame(data)
}))
}
fn compress_with_layout(data: &[u8], level: i32) -> crate::Result<(Vec<u8>, Vec<u32>)> {
Ok(with_tls_compressor(level, |compressor| {
let frame = compressor.compress_independent_frame(data);
let layout = inner_block_layout(compressor.last_frame_emit_info());
(frame, layout)
}))
}
fn decompress(data: &[u8], capacity: usize) -> crate::Result<Vec<u8>> {
let mut decoder = structured_zstd::decoding::StreamingDecoder::new(data)
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
bounded_read(&mut decoder, capacity)
}
fn compress_with_dict(data: &[u8], level: i32, dict_raw: &[u8]) -> crate::Result<Vec<u8>> {
use structured_zstd::decoding::Dictionary;
use structured_zstd::encoding::{
CompressionLevel, EncoderDictionary, FrameCompressor, MatchGeneratorDriver,
};
type CachedCompressor =
FrameCompressor<std::io::Cursor<Vec<u8>>, Vec<u8>, MatchGeneratorDriver>;
thread_local! {
static TLS_COMPRESSOR: std::cell::RefCell<Option<(u64, i32, CachedCompressor)>> =
const { std::cell::RefCell::new(None) };
}
let dict_key = xxhash_rust::xxh3::xxh3_64(dict_raw);
TLS_COMPRESSOR.with(|cell| {
let mut state = cell.borrow_mut();
if !matches!(&*state, Some((k, l, _)) if *k == dict_key && *l == level) {
let mut compressor = FrameCompressor::new(CompressionLevel::from_level(level));
if dict_raw.starts_with(&DICT_MAGIC) {
compressor
.set_dictionary_from_bytes(dict_raw)
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
} else {
#[expect(
clippy::cast_possible_truncation,
reason = "intentional: lower 32 bits of xxh3 as internal dict id"
)]
let id = {
let h = dict_key as u32;
h.max(1) };
let dictionary = Dictionary::from_raw_content(id, dict_raw.to_vec())
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
compressor
.set_encoder_dictionary(EncoderDictionary::from_dictionary(dictionary))
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
}
*state = Some((dict_key, level, compressor));
}
let Some((_, _, compressor)) = state.as_mut() else {
unreachable!("TLS_COMPRESSOR always initialised above");
};
let src_buf = compressor.take_source().map_or_else(
|| data.to_vec(),
|c| {
let mut v = c.into_inner();
v.clear();
v.extend_from_slice(data);
v
},
);
compressor.set_source_size_hint(data.len() as u64);
compressor.set_source(std::io::Cursor::new(src_buf));
compressor.set_drain(Vec::new());
compressor.compress();
let compressed = compressor
.take_drain()
.unwrap_or_else(|| unreachable!("drain is always set by set_drain() above"));
Ok(compressed)
})
}
fn decompress_with_dict(
data: &[u8],
dict: &crate::compression::ZstdDictionary,
capacity: usize,
) -> crate::Result<Vec<u8>> {
use structured_zstd::decoding::FrameDecoder;
thread_local! {
static TLS_DECODER: std::cell::RefCell<Option<(u64, FrameDecoder)>> =
const { std::cell::RefCell::new(None) };
}
let is_raw_content = !dict.raw().starts_with(&DICT_MAGIC);
TLS_DECODER.with(|cell| {
let mut state = cell.borrow_mut();
if !matches!(&*state, Some((id, _)) if *id == dict.id64()) {
let handle = dict.prepared_handle()?;
let mut decoder = FrameDecoder::new();
decoder
.add_dict_handle(handle)
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
*state = Some((dict.id64(), decoder));
}
let Some((_, decoder)) = state.as_mut() else {
unreachable!("TLS_DECODER always initialised above");
};
do_decompress_with_dict(decoder, data, dict.id().max(1), capacity, is_raw_content)
})
}
}
#[cfg(test)]
#[expect(clippy::expect_used, clippy::indexing_slicing, reason = "test code")]
mod tests {
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:?}",
);
}
}