use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
pub const MANIFEST_MAGIC: &[u8; 9] = b"LIDX-GEN1";
pub const MANIFEST_VERSION: u16 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LayerKind {
Db,
Tfidf,
Neural,
Pdg,
Symbols,
Search,
Embedder,
Fragments,
}
pub const ALL_LAYER_KINDS: [LayerKind; 5] = [
LayerKind::Db,
LayerKind::Tfidf,
LayerKind::Neural,
LayerKind::Pdg,
LayerKind::Symbols,
];
pub const OPTIONAL_LAYER_KINDS: [LayerKind; 3] =
[LayerKind::Search, LayerKind::Embedder, LayerKind::Fragments];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ModelIdentity {
pub name: String,
pub digest: String,
pub dimensions: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Manifest {
pub version: u16,
pub generation: u64,
pub model_identity: ModelIdentity,
pub graph_fingerprint: [u8; 32],
pub search_fingerprint: [u8; 32],
pub layers: HashMap<LayerKind, [u8; 32]>,
}
pub type ManifestBody = Manifest;
impl Manifest {
pub fn to_bytes(&self) -> Result<Vec<u8>, ManifestError> {
let body = bincode::serialize(self).map_err(|e| ManifestError::Serialize(e.to_string()))?;
let mut out = Vec::with_capacity(MANIFEST_MAGIC.len() + body.len());
out.extend_from_slice(MANIFEST_MAGIC);
out.extend_from_slice(&body);
Ok(out)
}
pub fn from_bytes(data: &[u8]) -> Result<Self, ManifestError> {
if data.len() < MANIFEST_MAGIC.len() {
return Err(ManifestError::Truncated {
min: MANIFEST_MAGIC.len(),
actual: data.len(),
});
}
if &data[0..MANIFEST_MAGIC.len()] != MANIFEST_MAGIC {
let mut bad = [0u8; 9];
bad.copy_from_slice(&data[0..MANIFEST_MAGIC.len()]);
return Err(ManifestError::BadMagic(bad));
}
let manifest: Manifest = bincode::deserialize(&data[MANIFEST_MAGIC.len()..])
.map_err(|e| ManifestError::Deserialize(e.to_string()))?;
if manifest.version == 0 {
return Err(ManifestError::UnsupportedVersion {
got: 0,
expected: MANIFEST_VERSION,
});
}
if manifest.version > MANIFEST_VERSION {
return Err(ManifestError::UnsupportedVersion {
got: manifest.version,
expected: MANIFEST_VERSION,
});
}
manifest.validate_layers()?;
Ok(manifest)
}
pub fn validate_layers(&self) -> Result<(), ManifestError> {
for kind in ALL_LAYER_KINDS.iter() {
if !self.layers.contains_key(kind) {
return Err(ManifestError::MissingLayer(*kind));
}
}
Ok(())
}
pub fn validate_graph_fingerprint(&self, recomputed: &[u8; 32]) -> Result<(), ManifestError> {
if &self.graph_fingerprint != recomputed {
return Err(ManifestError::GraphFingerprintMismatch {
stored: self.graph_fingerprint,
recomputed: *recomputed,
});
}
Ok(())
}
pub fn validate_search_fingerprint(&self, recomputed: &[u8; 32]) -> Result<(), ManifestError> {
if &self.search_fingerprint != recomputed {
return Err(ManifestError::SearchFingerprintMismatch {
stored: self.search_fingerprint,
recomputed: *recomputed,
});
}
Ok(())
}
pub fn layer_hashes(&self) -> Vec<[u8; 32]> {
ALL_LAYER_KINDS
.iter()
.chain(OPTIONAL_LAYER_KINDS.iter())
.filter_map(|k| self.layers.get(k).copied())
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ManifestError {
#[error("manifest truncated: {actual} bytes, need at least {min}")]
Truncated {
min: usize,
actual: usize,
},
#[error("bad manifest magic: got {0:?}")]
BadMagic([u8; 9]),
#[error("unsupported manifest version: got {got}, expected {expected}")]
UnsupportedVersion {
got: u16,
expected: u16,
},
#[error("manifest missing required layer: {0}")]
MissingLayer(LayerKind),
#[error("manifest deserialize error: {0}")]
Deserialize(String),
#[error("manifest serialize error: {0}")]
Serialize(String),
#[error("manifest not found for generation {generation}: {path}")]
MissingManifest {
generation: u64,
path: String,
},
#[error("graph fingerprint mismatch: stored {stored:?}, recomputed {recomputed:?}")]
GraphFingerprintMismatch {
stored: [u8; 32],
recomputed: [u8; 32],
},
#[error("search fingerprint mismatch: stored {stored:?}, recomputed {recomputed:?}")]
SearchFingerprintMismatch {
stored: [u8; 32],
recomputed: [u8; 32],
},
}
impl fmt::Display for LayerKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LayerKind::Db => write!(f, "db"),
LayerKind::Tfidf => write!(f, "tfidf"),
LayerKind::Neural => write!(f, "neural"),
LayerKind::Pdg => write!(f, "pdg"),
LayerKind::Symbols => write!(f, "symbols"),
LayerKind::Search => write!(f, "search"),
LayerKind::Embedder => write!(f, "embedder"),
LayerKind::Fragments => write!(f, "fragments"),
}
}
}
#[cfg(test)]
#[path = "manifest_test.rs"]
mod tests;