#![cfg_attr(
feature = "compression",
doc = "[`GraphBuilder::load_dictionaries`]: crate::graph::GraphBuilder::load_dictionaries\n\n[`NodeParams::with_dictionary`]: crate::graph::NodeParams::with_dictionary"
)]
#![cfg_attr(
not(feature = "compression"),
doc = "[`GraphBuilder::load_dictionaries`]: crate#select-codecs\n\n[`NodeParams::with_dictionary`]: crate#select-codecs"
)]
#![cfg_attr(
feature = "decompression",
doc = "[`DecompressorBuilder::with_dictionaries`]: crate::DecompressorBuilder::with_dictionaries"
)]
#![cfg_attr(
not(feature = "decompression"),
doc = "[`BundleStore`]: crate#select-codecs\n\n[`DecompressorBuilder::with_dictionaries`]: crate#select-codecs"
)]
pub(crate) mod core;
use ::core::fmt;
use alloc::string::String;
use alloc::vec::Vec;
use thiserror::Error;
use crate::shared::dictwire::{self, ID_SIZE};
use crate::shared::sha256;
macro_rules! unique_id {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct $name([u8; ID_SIZE]);
impl From<[u8; ID_SIZE]> for $name {
fn from(bytes: [u8; ID_SIZE]) -> Self {
Self(bytes)
}
}
impl From<$name> for [u8; ID_SIZE] {
fn from(id: $name) -> Self {
id.0
}
}
impl AsRef<[u8]> for $name {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.iter().try_for_each(|byte| write!(f, "{byte:02x}"))
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, concat!(stringify!($name), "({})"), self)
}
}
};
}
unique_id! {
DictId
}
unique_id! {
BundleId
}
#[derive(Error, Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum DictError {
#[error("malformed dictionary data: {0}")]
Malformed(String),
#[error("{count} dictionaries exceed the limit of 65535 per bundle")]
TooManyDicts {
count: usize,
},
#[error("a dictionary content is limited to 4 GiB - 1")]
ContentTooLarge,
#[error("two different dictionary contents share the ID {0}")]
ConflictingId(DictId),
#[error("no materializer for dictionaries of codec {}{}", .codec.id(), if .codec.is_custom() { " (custom)" } else { "" })]
NoMaterializer {
codec: DictCodec,
},
#[error("dictionary materialization failed: {0}")]
Materialization(String),
#[cfg_attr(
not(feature = "decompression"),
doc = "[`BundleStore`]: crate#select-codecs"
)]
#[error("the bundle store is full")]
StoreFull,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct DictCodec {
id: u32,
custom: bool,
}
impl DictCodec {
pub const ZSTD: Self = Self {
id: 22,
custom: false,
};
#[must_use]
pub const fn id(self) -> u32 {
self.id
}
#[must_use]
pub const fn is_custom(self) -> bool {
self.custom
}
}
impl From<(u32, bool)> for DictCodec {
fn from((id, custom): (u32, bool)) -> Self {
Self { id, custom }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Dict {
id: DictId,
codec: DictCodec,
content: Vec<u8>,
}
impl Dict {
pub fn new(codec: DictCodec, content: impl Into<Vec<u8>>) -> Result<Self, DictError> {
let content = content.into();
u32::try_from(content.len()).map_err(|_| DictError::ContentTooLarge)?;
Ok(Self {
id: DictId(sha256::digest(&content)),
codec,
content,
})
}
pub fn zstd(raw: &[u8], level: i32) -> Result<Self, DictError> {
Self::new(DictCodec::ZSTD, dictwire::pack_zstd_content(level, raw))
}
#[must_use]
pub fn with_id(mut self, id: DictId) -> Self {
self.id = if id.0 == [0; ID_SIZE] {
DictId(sha256::digest(&self.content))
} else {
id
};
self
}
#[must_use]
pub const fn id(&self) -> DictId {
self.id
}
#[must_use]
pub const fn codec(&self) -> DictCodec {
self.codec
}
#[must_use]
pub fn content(&self) -> &[u8] {
&self.content
}
}
impl From<&Dict> for Vec<u8> {
fn from(dict: &Dict) -> Self {
let mut out = Vec::new();
let _ = dictwire::pack_dict(
&mut out,
&dict.id.0,
dict.codec.id,
dict.codec.custom,
&dict.content,
);
out
}
}
impl TryFrom<&[u8]> for Dict {
type Error = DictError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let parsed = dictwire::parse_dict(bytes)?;
Ok(Self {
id: DictId(parsed.id),
codec: DictCodec::from((parsed.codec, parsed.custom)),
content: parsed.content.to_vec(),
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DictBundle {
id: BundleId,
dicts: Vec<Dict>,
}
impl DictBundle {
#[must_use]
pub const fn id(&self) -> BundleId {
self.id
}
#[must_use]
pub fn dicts(&self) -> &[Dict] {
&self.dicts
}
}
impl TryFrom<Vec<Dict>> for DictBundle {
type Error = DictError;
fn try_from(dicts: Vec<Dict>) -> Result<Self, Self::Error> {
if dicts.len() > dictwire::MAX_DICTS_PER_BUNDLE {
return Err(DictError::TooManyDicts { count: dicts.len() });
}
let id = BundleId(dictwire::bundle_id(dicts.iter().map(|dict| &dict.id.0)));
Ok(Self { id, dicts })
}
}
impl TryFrom<&[u8]> for DictBundle {
type Error = DictError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let parsed = dictwire::parse_fat_bundle(bytes)?;
let dicts = parsed
.dicts
.iter()
.map(|dict| Dict {
id: DictId(dict.id),
codec: DictCodec::from((dict.codec, dict.custom)),
content: dict.content.to_vec(),
})
.collect();
Ok(Self {
id: BundleId(parsed.id),
dicts,
})
}
}
impl From<&DictBundle> for Vec<u8> {
fn from(bundle: &DictBundle) -> Self {
let packed: Vec<Vec<u8>> = bundle.dicts.iter().map(Vec::from).collect();
let parsed: Vec<_> = packed
.iter()
.filter_map(|bytes| dictwire::parse_dict(bytes).ok())
.collect();
dictwire::pack_fat_bundle(&parsed).unwrap_or_default()
}
}
#[cfg(feature = "decompression")]
mod store;
#[cfg(feature = "decompression")]
pub use store::BundleStore;
#[cfg(feature = "decompression")]
pub(crate) use store::StoredBundle;
#[cfg(test)]
mod tests;