use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use super::{NodeDef, Registry};
use crate::dict::{BundleId, Dict, DictBundle, DictError, DictId};
use crate::shared::sha256;
const ZSTD_CODEC: u32 = 22;
#[derive(Debug)]
pub(crate) enum Prepared {
#[cfg(feature = "zstd")]
Zstd(mzstd::dictionary::PreparedDictionary),
}
#[derive(Debug)]
pub(crate) struct DictState {
pub(crate) id: BundleId,
pub(crate) ids: Vec<DictId>,
pub(crate) prepared: Vec<Arc<Prepared>>,
}
impl DictState {
pub(crate) fn prepared(&self, index: u32) -> Option<&Prepared> {
self.prepared
.get(index as usize)
.map(|prepared| &**prepared)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) enum LoadError {
AlreadyLoaded,
Dict(DictError),
NotFound(DictId),
}
impl From<DictError> for LoadError {
fn from(error: DictError) -> Self {
Self::Dict(error)
}
}
#[cfg(feature = "zstd")]
fn materialize(dict: &Dict) -> Result<Arc<Prepared>, DictError> {
use alloc::string::ToString;
let parsed = crate::shared::dictwire::parse_zstd_content(dict.content()).ok_or_else(|| {
DictError::Materialization("failed to parse trained zstd dict content".into())
})?;
let level = parsed
.level
.clamp(mzstd::Level::MIN.get(), mzstd::Level::MAX.get());
let level = mzstd::Level::try_from(level).unwrap_or_default();
let dictionary = mzstd::dictionary::DictionaryBuilder::new(parsed.raw)
.with_content(mzstd::dictionary::DictionaryContent::Auto)
.with_level_parameters(level)
.build()
.map_err(|error| DictError::Materialization(error.to_string()))?;
Ok(Arc::new(Prepared::Zstd(dictionary)))
}
#[cfg(not(feature = "zstd"))]
fn materialize(dict: &Dict) -> Result<Arc<Prepared>, DictError> {
Err(DictError::NoMaterializer {
codec: dict.codec(),
})
}
impl Registry {
pub(crate) fn load_bundle(&mut self, bundle: &DictBundle) -> Result<(), LoadError> {
if self.dicts.is_some() {
return Err(LoadError::AlreadyLoaded);
}
let mut prepared: Vec<Arc<Prepared>> = Vec::with_capacity(bundle.dicts().len());
let mut cache: BTreeMap<DictId, ([u8; 32], Arc<Prepared>)> = BTreeMap::new();
for dict in bundle.dicts() {
let hash = sha256::digest(dict.content());
if let Some((known, shared)) = cache.get(&dict.id()) {
if *known != hash {
return Err(DictError::ConflictingId(dict.id()).into());
}
prepared.push(Arc::clone(shared));
continue;
}
let referenced = !dict.codec().is_custom()
&& self.nodes.iter().any(|node: &NodeDef| {
node.dict_id == Some(dict.id()) && node.spec.codec == ZSTD_CODEC
});
if !referenced {
return Err(DictError::NoMaterializer {
codec: dict.codec(),
}
.into());
}
let materialized = materialize(dict)?;
cache.insert(dict.id(), (hash, Arc::clone(&materialized)));
prepared.push(materialized);
}
self.dicts = Some(DictState {
id: bundle.id(),
ids: bundle.dicts().iter().map(Dict::id).collect(),
prepared,
});
Ok(())
}
pub(crate) fn resolve_dict_indices(&mut self) -> Result<(), LoadError> {
let ids: &[DictId] = self.dicts.as_ref().map_or(&[], |state| &state.ids);
for node in &mut self.nodes {
let Some(id) = node.dict_id else {
node.dict_index = None;
continue;
};
let index = ids
.iter()
.position(|candidate| *candidate == id)
.ok_or(LoadError::NotFound(id))?;
node.dict_index = Some(index as u32);
}
Ok(())
}
}
#[cfg(all(test, feature = "zstd"))]
mod tests {
use alloc::vec;
use super::super::{gid, nodes};
use super::*;
use crate::dict::Dict;
fn zstd_node(registry: &mut Registry, dict: Option<&Dict>) {
registry.add_node(NodeDef {
spec: nodes::standard(62).unwrap(),
params: None,
dict_id: dict.map(Dict::id),
dict_index: None,
});
}
fn bundle(dicts: Vec<Dict>) -> DictBundle {
DictBundle::try_from(dicts).unwrap()
}
#[test]
fn dictionaries_load_once_and_resolve_to_their_index() {
let (a, b) = (
Dict::zstd(b"first", 3).unwrap(),
Dict::zstd(b"second", 3).unwrap(),
);
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&b));
zstd_node(&mut registry, None);
zstd_node(&mut registry, Some(&a));
let both = bundle(vec![a.clone(), b.clone()]);
registry.load_bundle(&both).unwrap();
assert_eq!(registry.load_bundle(&both), Err(LoadError::AlreadyLoaded));
registry.resolve_dict_indices().unwrap();
let indices: Vec<_> = registry.nodes.iter().map(|node| node.dict_index).collect();
assert_eq!(indices, [Some(1), None, Some(0)]);
let state = registry.dicts.as_ref().unwrap();
assert_eq!(state.id, both.id());
assert!(state.prepared(1).is_some());
assert!(state.prepared(2).is_none());
}
#[test]
fn a_repeated_dictionary_is_prepared_once_and_a_conflict_is_refused() {
let a = Dict::zstd(b"first", 3).unwrap();
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&a));
registry
.load_bundle(&bundle(vec![a.clone(), a.clone()]))
.unwrap();
let state = registry.dicts.as_ref().unwrap();
assert!(Arc::ptr_eq(&state.prepared[0], &state.prepared[1]));
let clash = Dict::zstd(b"other", 3).unwrap().with_id(a.id());
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&a));
assert_eq!(
registry.load_bundle(&bundle(vec![a.clone(), clash])),
Err(LoadError::Dict(DictError::ConflictingId(a.id())))
);
}
#[test]
fn contents_the_codec_rejects_fail_to_materialize() {
let mut broken = 0xEC30_A437u32.to_le_bytes().to_vec();
broken.extend_from_slice(&[1, 0, 0, 0, 0xff, 0xff]);
for dict in [
Dict::zstd(&broken, 3).unwrap(),
Dict::new(crate::dict::DictCodec::ZSTD, b"no envelope".to_vec()).unwrap(),
] {
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&dict));
assert!(matches!(
registry.load_bundle(&bundle(vec![dict])),
Err(LoadError::Dict(DictError::Materialization(_)))
));
}
for level in [i32::MIN, i32::MAX] {
let dict = Dict::zstd(b"history", level).unwrap();
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&dict));
registry.load_bundle(&bundle(vec![dict])).unwrap();
}
}
#[test]
fn unreferenced_custom_and_missing_dictionaries_are_errors() {
let a = Dict::zstd(b"first", 3).unwrap();
let mut registry = Registry::new(1, gid::STORE);
assert!(matches!(
registry.load_bundle(&bundle(vec![a.clone()])),
Err(LoadError::Dict(DictError::NoMaterializer { .. }))
));
let custom = Dict::new(crate::dict::DictCodec::from((22, true)), b"x".to_vec()).unwrap();
zstd_node(&mut registry, Some(&custom));
assert!(registry.load_bundle(&bundle(vec![custom])).is_err());
let mut registry = Registry::new(1, gid::STORE);
zstd_node(&mut registry, Some(&a));
assert_eq!(
registry.resolve_dict_indices(),
Err(LoadError::NotFound(a.id()))
);
registry.load_bundle(&bundle(vec![])).unwrap();
assert_eq!(
registry.resolve_dict_indices(),
Err(LoadError::NotFound(a.id()))
);
}
}