use core::fmt;
use core::num::NonZeroU32;
#[cfg(feature = "decompression")]
pub(crate) mod decode;
#[cfg(feature = "decompression")]
pub use decode::{DecodeDictionary, DictionaryRef};
#[cfg(feature = "compression")]
pub use crate::compressor::dictionary::{DictionaryBuilder, PreparedDictionary};
pub(crate) const MAGIC_DICTIONARY: u32 = 0xEC30_A437;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum DictionaryContent {
#[default]
Auto,
Raw,
Zstd,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DictionaryId(NonZeroU32);
impl DictionaryId {
pub(crate) const fn new(id: u32) -> Option<Self> {
match NonZeroU32::new(id) {
Some(id) => Some(Self(id)),
None => None,
}
}
}
impl From<NonZeroU32> for DictionaryId {
fn from(id: NonZeroU32) -> Self {
Self(id)
}
}
impl From<DictionaryId> for NonZeroU32 {
fn from(id: DictionaryId) -> Self {
id.0
}
}
impl From<DictionaryId> for u32 {
fn from(id: DictionaryId) -> Self {
id.0.get()
}
}
impl fmt::Display for DictionaryId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum DictionaryError {
#[error("dictionary does not start with the Zstandard dictionary magic number")]
NotZstdFormat,
#[error("Zstandard dictionary is corrupted")]
Corrupted,
#[cfg(feature = "compression")]
#[error("invalid configuration for the dictionary")]
Config(#[from] crate::ConfigError),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum Layout {
Raw,
Zstd {
id: u32,
},
}
pub(crate) fn zstd_header(bytes: &[u8]) -> Option<u32> {
let magic = bytes.get(..4)?;
let id = bytes.get(4..8)?;
(u32::from_le_bytes(magic.try_into().ok()?) == MAGIC_DICTIONARY)
.then(|| u32::from_le_bytes(id.try_into().unwrap_or_default()))
}
pub(crate) fn layout(bytes: &[u8], content: DictionaryContent) -> Result<Layout, DictionaryError> {
match (content, zstd_header(bytes)) {
(DictionaryContent::Raw, _) | (DictionaryContent::Auto, None) => Ok(Layout::Raw),
(_, Some(id)) => Ok(Layout::Zstd { id }),
(DictionaryContent::Zstd, None) => Err(DictionaryError::NotZstdFormat),
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn layout_follows_the_magic_number_and_the_requested_content() {
let mut zstd = MAGIC_DICTIONARY.to_le_bytes().to_vec();
zstd.extend_from_slice(&9u32.to_le_bytes());
assert_eq!(
layout(&zstd, DictionaryContent::Auto),
Ok(Layout::Zstd { id: 9 })
);
assert_eq!(
layout(&zstd, DictionaryContent::Zstd),
Ok(Layout::Zstd { id: 9 })
);
assert_eq!(layout(&zstd, DictionaryContent::Raw), Ok(Layout::Raw));
assert_eq!(
layout(b"raw bytes", DictionaryContent::Auto),
Ok(Layout::Raw)
);
assert_eq!(
layout(b"raw bytes", DictionaryContent::Zstd),
Err(DictionaryError::NotZstdFormat)
);
assert_eq!(layout(&zstd[..7], DictionaryContent::Auto), Ok(Layout::Raw));
assert_eq!(zstd_header(&zstd[..7]), None);
}
#[test]
fn ids_convert_and_display() {
assert_eq!(DictionaryId::new(0), None);
let id = DictionaryId::new(42).expect("non-zero");
assert_eq!(u32::from(id), 42);
assert_eq!(NonZeroU32::from(id).get(), 42);
assert_eq!(DictionaryId::from(NonZeroU32::from(id)), id);
assert_eq!(alloc::format!("{id}"), "42");
assert_eq!(
DictionaryError::Corrupted.to_string(),
"Zstandard dictionary is corrupted"
);
}
}