use alloc::boxed::Box;
use core::fmt;
use super::{DictionaryContent, DictionaryError, DictionaryId, Layout, layout};
use crate::decompressor::{DecoderEntropy, DictView};
#[derive(Clone, Debug)]
pub(crate) struct DecodeParts {
pub(crate) id: u32,
pub(crate) entropy: Option<DecoderEntropy>,
}
impl DecodeParts {
pub(crate) fn view<'a>(&'a self, bytes: &'a [u8]) -> DictView<'a> {
DictView {
content: bytes,
id: self.id,
entropy: self.entropy.as_ref(),
}
}
pub(crate) fn retained_bytes(&self) -> usize {
self.entropy
.as_ref()
.map_or(0, DecoderEntropy::retained_bytes)
}
}
pub(crate) fn parse_decode_parts(
bytes: &[u8],
content: DictionaryContent,
) -> Result<DecodeParts, DictionaryError> {
if bytes.is_empty() {
return Ok(DecodeParts {
id: 0,
entropy: None,
});
}
match layout(bytes, content)? {
Layout::Raw => Ok(DecodeParts {
id: 0,
entropy: None,
}),
Layout::Zstd { id } => {
let (entropy, _) =
DecoderEntropy::load(bytes).map_err(|_| DictionaryError::Corrupted)?;
Ok(DecodeParts {
id,
entropy: Some(entropy),
})
}
}
}
pub struct DecodeDictionary {
bytes: Box<[u8]>,
parts: DecodeParts,
}
impl fmt::Debug for DecodeDictionary {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DecodeDictionary")
.field("id", &self.id())
.field("content_len", &self.content_len())
.field("entropy", &self.parts.entropy.is_some())
.finish()
}
}
impl DecodeDictionary {
pub fn new(bytes: &[u8], content: DictionaryContent) -> Result<Self, DictionaryError> {
let parts = parse_decode_parts(bytes, content)?;
Ok(Self {
bytes: bytes.into(),
parts,
})
}
#[must_use]
pub const fn id(&self) -> Option<DictionaryId> {
DictionaryId::new(self.parts.id)
}
#[must_use]
pub fn content_len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub fn retained_bytes(&self) -> usize {
self.bytes.len() + self.parts.retained_bytes()
}
pub(crate) fn view(&self) -> DictView<'_> {
self.parts.view(&self.bytes)
}
}
impl TryFrom<&[u8]> for DecodeDictionary {
type Error = DictionaryError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::new(bytes, DictionaryContent::Auto)
}
}
impl AsRef<Self> for DecodeDictionary {
fn as_ref(&self) -> &Self {
self
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum DictionaryRef<'a> {
Decode(&'a DecodeDictionary),
#[cfg(feature = "compression")]
Prepared(&'a crate::dictionary::PreparedDictionary),
}
impl<'a> From<&'a DecodeDictionary> for DictionaryRef<'a> {
fn from(dictionary: &'a DecodeDictionary) -> Self {
Self::Decode(dictionary)
}
}
#[cfg(feature = "compression")]
impl<'a> From<&'a crate::dictionary::PreparedDictionary> for DictionaryRef<'a> {
fn from(dictionary: &'a crate::dictionary::PreparedDictionary) -> Self {
Self::Prepared(dictionary)
}
}
impl<'a> DictionaryRef<'a> {
pub(crate) fn view(self) -> DictView<'a> {
match self {
Self::Decode(dictionary) => dictionary.view(),
#[cfg(feature = "compression")]
Self::Prepared(dictionary) => dictionary.decode_view(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dictionary::MAGIC_DICTIONARY;
use alloc::vec::Vec;
fn header_only(id: u32) -> Vec<u8> {
let mut bytes = MAGIC_DICTIONARY.to_le_bytes().to_vec();
bytes.extend_from_slice(&id.to_le_bytes());
bytes
}
#[test]
fn empty_bytes_are_an_empty_raw_dictionary_for_every_content_type() {
for content in [
DictionaryContent::Auto,
DictionaryContent::Raw,
DictionaryContent::Zstd,
] {
let dictionary = DecodeDictionary::new(&[], content).expect("empty");
assert_eq!(dictionary.id(), None);
assert_eq!(dictionary.content_len(), 0);
assert_eq!(dictionary.retained_bytes(), 0);
}
}
#[test]
fn raw_content_keeps_every_byte_and_has_no_id() {
let bytes = header_only(5);
let raw = DecodeDictionary::new(&bytes, DictionaryContent::Raw).expect("raw");
let view = raw.view();
assert_eq!(view.content, &bytes[..]);
assert_eq!(view.id, 0);
assert!(view.entropy.is_none());
let short = DecodeDictionary::try_from(&bytes[..7]).expect("short");
assert_eq!(short.content_len(), 7);
assert_eq!(
DecodeDictionary::new(&bytes[..7], DictionaryContent::Zstd).map(|_| ()),
Err(DictionaryError::NotZstdFormat)
);
}
#[test]
fn zstd_dictionaries_need_valid_entropy_tables() {
let bytes = header_only(5);
assert_eq!(
DecodeDictionary::try_from(&bytes[..]).map(|_| ()),
Err(DictionaryError::Corrupted)
);
let bytes = crate::decompressor::test_dictionary([1, 2, 3], b"content");
let dictionary = DecodeDictionary::new(&bytes, DictionaryContent::Zstd).expect("valid");
assert_eq!(dictionary.id().map(u32::from), Some(7));
assert_eq!(dictionary.content_len(), bytes.len());
assert!(dictionary.retained_bytes() > bytes.len());
let view = DictionaryRef::from(&dictionary).view();
assert_eq!(view.id, 7);
assert_eq!(view.entropy.map(|entropy| entropy.reps), Some([1, 2, 3]));
let debug = alloc::format!("{dictionary:?}");
assert!(debug.contains("entropy: true"), "{debug}");
assert!(core::ptr::eq(dictionary.as_ref(), &dictionary));
}
}