use super::compression_info::CompressionInfo;
pub(crate) const ZSTD_MAGIC_LE: [u8; 4] = [0x28, 0xB5, 0x2F, 0xFD];
pub(crate) fn zstd_dictionary_rejection(
info: &CompressionInfo,
compressed_data: &[u8],
chunk_index: usize,
) -> Option<String> {
let chunk_offset = info.chunk_offsets.get(chunk_index).copied().unwrap_or(0);
if let Some(option) = info.zstd_dictionary_option() {
return Some(format!(
"zstd dictionary compression (CompressionInfo.db option '{option}') is unsupported \
for chunk {chunk_index} at offset 0x{chunk_offset:x}"
));
}
if let Some(dict_id) = zstd_frame_dictionary_id(compressed_data) {
return Some(format!(
"zstd dictionary compression (Dictionary_ID={dict_id}) is unsupported for chunk \
{chunk_index} at offset 0x{chunk_offset:x}"
));
}
None
}
pub(crate) fn zstd_frame_dictionary_id(frame: &[u8]) -> Option<u32> {
if frame.len() < 5 || frame[0..4] != ZSTD_MAGIC_LE {
return None;
}
let frame_header_descriptor = frame[4];
let did_size = match frame_header_descriptor & 0x03 {
0 => return None, 1 => 1usize,
2 => 2usize,
_ => 4usize, };
let single_segment = (frame_header_descriptor & 0x20) != 0;
let did_start = 5 + usize::from(!single_segment);
let did_end = did_start.saturating_add(did_size);
if frame.len() < did_end {
return None;
}
let mut id: u32 = 0;
for (i, &b) in frame[did_start..did_end].iter().enumerate() {
id |= u32::from(b) << (8 * i);
}
(id != 0).then_some(id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zstd_frame_dictionary_id_parses_authoritatively() {
const MAGIC: [u8; 4] = ZSTD_MAGIC_LE;
assert_eq!(zstd_frame_dictionary_id(&[]), None);
assert_eq!(
zstd_frame_dictionary_id(&[0x00, 0x01, 0x02, 0x03, 0x04]),
None
);
assert_eq!(
zstd_frame_dictionary_id(&[MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x20]),
None
);
let frame_1b = [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x21, 0x2A];
assert_eq!(zstd_frame_dictionary_id(&frame_1b), Some(0x2A));
let frame_4b = [
MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x23, 0x04, 0x03, 0x02, 0x01,
];
assert_eq!(zstd_frame_dictionary_id(&frame_4b), Some(0x0102_0304));
let frame_win = [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x01, 0x00, 0x2A];
assert_eq!(zstd_frame_dictionary_id(&frame_win), Some(0x2A));
let truncated = [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x23, 0x04];
assert_eq!(zstd_frame_dictionary_id(&truncated), None);
let frame_zero = [MAGIC[0], MAGIC[1], MAGIC[2], MAGIC[3], 0x21, 0x00];
assert_eq!(zstd_frame_dictionary_id(&frame_zero), None);
}
}