#[cfg(feature = "zstd")]
mod zstd_backend;
use crate::coding::{Decode, Encode};
use crate::io::{Read, ReadBytesExt, Write, WriteBytesExt};
#[cfg(zstd_any)]
use alloc::sync::Arc;
#[cfg(feature = "zstd")]
use once_cell::race::OnceBox;
#[cfg(zstd_any)]
pub trait CompressionProvider {
fn compress(data: &[u8], level: i32) -> crate::Result<Vec<u8>>;
fn compress_with_layout(data: &[u8], level: i32) -> crate::Result<(Vec<u8>, Vec<u32>)>;
fn decompress(data: &[u8], capacity: usize) -> crate::Result<Vec<u8>>;
fn compress_with_dict(data: &[u8], level: i32, dict_raw: &[u8]) -> crate::Result<Vec<u8>>;
fn decompress_with_dict(
data: &[u8],
dict: &ZstdDictionary,
capacity: usize,
) -> crate::Result<Vec<u8>>;
}
#[cfg(feature = "zstd")]
pub type ZstdBackend = zstd_backend::ZstdProvider;
#[cfg(zstd_any)]
pub struct ZstdDictionary {
id: u64,
raw: Arc<[u8]>,
#[cfg(feature = "zstd")]
prepared: Arc<OnceBox<structured_zstd::decoding::DictionaryHandle>>,
}
#[cfg(zstd_any)]
impl Clone for ZstdDictionary {
fn clone(&self) -> Self {
Self {
id: self.id,
raw: Arc::clone(&self.raw),
#[cfg(feature = "zstd")]
prepared: Arc::clone(&self.prepared),
}
}
}
#[cfg(zstd_any)]
impl PartialEq for ZstdDictionary {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[cfg(zstd_any)]
impl Eq for ZstdDictionary {}
#[cfg(zstd_any)]
impl ZstdDictionary {
#[must_use]
pub fn new(raw: &[u8]) -> Self {
Self {
id: compute_dict_id(raw),
raw: Arc::from(raw),
#[cfg(feature = "zstd")]
prepared: Arc::new(OnceBox::new()),
}
}
#[cfg(feature = "zstd")]
pub(crate) fn prepared_handle(
&self,
) -> crate::Result<structured_zstd::decoding::DictionaryHandle> {
use structured_zstd::decoding::{Dictionary, DictionaryHandle};
const DICT_MAGIC: [u8; 4] = [0x37, 0xA4, 0x30, 0xEC];
self.prepared
.get_or_try_init(|| -> crate::Result<Box<DictionaryHandle>> {
let handle = if self.raw.starts_with(&DICT_MAGIC) {
DictionaryHandle::decode_dict(&self.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 (matches compressor)"
)]
let raw_content_id = (self.id as u32).max(1);
let dict = Dictionary::from_raw_content(raw_content_id, self.raw.to_vec())
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
DictionaryHandle::from_dictionary(dict)
};
Ok(Box::new(handle))
})
.cloned()
}
#[must_use]
#[expect(
clippy::cast_possible_truncation,
reason = "intentional: public API returns 32-bit fingerprint"
)]
pub fn id(&self) -> u32 {
self.id as u32
}
#[cfg(feature = "zstd")]
#[must_use]
pub(crate) fn id64(&self) -> u64 {
self.id
}
#[must_use]
pub fn raw(&self) -> &[u8] {
&self.raw
}
}
#[cfg(zstd_any)]
impl core::fmt::Debug for ZstdDictionary {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ZstdDictionary")
.field("id", &format_args!("{:#018x}", self.id))
.field("size", &self.raw.len())
.finish_non_exhaustive() }
}
#[cfg(zstd_any)]
fn compute_dict_id(raw: &[u8]) -> u64 {
xxhash_rust::xxh3::xxh3_64(raw)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum CompressionType {
None,
#[cfg(feature = "lz4")]
Lz4,
#[cfg(zstd_any)]
Zstd(i32),
#[cfg(zstd_any)]
ZstdDict {
level: i32,
dict_id: u32,
},
}
impl CompressionType {
#[must_use]
pub fn dict_id(&self) -> u32 {
#[cfg(zstd_any)]
if let Self::ZstdDict { dict_id, .. } = self {
return *dict_id;
}
0
}
#[cfg(zstd_any)]
fn validate_zstd_level(level: i32) -> crate::Result<()> {
if !(i32::from(i8::MIN)..=22).contains(&level) {
return Err(crate::Error::Io(crate::io::Error::other(format!(
"invalid zstd compression level {level}, expected -128..=22"
))));
}
Ok(())
}
#[cfg(zstd_any)]
pub fn zstd(level: i32) -> crate::Result<Self> {
Self::validate_zstd_level(level)?;
Ok(Self::Zstd(level))
}
#[cfg(zstd_any)]
pub fn zstd_dict(level: i32, dict_id: u32) -> crate::Result<Self> {
Self::validate_zstd_level(level)?;
Ok(Self::ZstdDict { level, dict_id })
}
}
impl core::fmt::Display for CompressionType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "none",
#[cfg(feature = "lz4")]
Self::Lz4 => "lz4",
#[cfg(zstd_any)]
Self::Zstd(_) => "zstd",
#[cfg(zstd_any)]
Self::ZstdDict { .. } => "zstd+dict",
}
)
}
}
impl Encode for CompressionType {
fn encode_into<W: Write>(&self, writer: &mut W) -> Result<(), crate::Error> {
match self {
Self::None => {
writer.write_u8(0)?;
}
#[cfg(feature = "lz4")]
Self::Lz4 => {
writer.write_u8(1)?;
}
#[cfg(zstd_any)]
Self::Zstd(level) => {
writer.write_u8(3)?;
debug_assert!(
(i32::from(i8::MIN)..=22).contains(level),
"zstd level {level} outside valid range -128..=22"
);
#[expect(
clippy::cast_possible_truncation,
reason = "level range -128..=22 maps exactly to i8"
)]
writer.write_i8(*level as i8)?;
}
#[cfg(zstd_any)]
Self::ZstdDict { level, dict_id } => {
writer.write_u8(4)?;
debug_assert!(
(i32::from(i8::MIN)..=22).contains(level),
"zstd level {level} outside valid range -128..=22"
);
#[expect(
clippy::cast_possible_truncation,
reason = "level range -128..=22 maps exactly to i8"
)]
writer.write_i8(*level as i8)?;
crate::io::WriteBytesExt::write_u32::<crate::io::LittleEndian>(writer, *dict_id)?;
}
}
Ok(())
}
}
impl Decode for CompressionType {
fn decode_from<R: Read>(reader: &mut R) -> Result<Self, crate::Error> {
let tag = reader.read_u8()?;
match tag {
0 => Ok(Self::None),
#[cfg(feature = "lz4")]
1 => Ok(Self::Lz4),
#[cfg(zstd_any)]
3 => {
let level = i32::from(reader.read_i8()?);
Self::validate_zstd_level(level)?;
Ok(Self::Zstd(level))
}
#[cfg(zstd_any)]
4 => {
let level = i32::from(reader.read_i8()?);
Self::validate_zstd_level(level)?;
let dict_id = crate::io::ReadBytesExt::read_u32::<crate::io::LittleEndian>(reader)?;
Ok(Self::ZstdDict { level, dict_id })
}
tag => Err(crate::Error::InvalidTag(("CompressionType", tag))),
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::useless_vec,
clippy::expect_used,
reason = "test code"
)]
mod tests;