use super::format::{align8, bytes_for, to_u64, to_usize};
use super::{HEADER_LEN, Header, NONE_ENTRY};
use crate::error::SearchError;
use crate::hnsw::NodeLinks;
pub(super) fn validate_offsets(
offsets: &[u64],
expected_last: usize,
label: &'static str,
) -> Result<(), SearchError> {
if offsets.first() != Some(&0)
|| offsets.windows(2).any(|pair| pair[0] > pair[1])
|| offsets.last().copied() != Some(to_u64(expected_last)?)
{
return Err(SearchError::CorruptSnapshot(label));
}
Ok(())
}
pub(super) fn validate_owned_graph(
nodes: &[NodeLinks],
entry_raw: u64,
max_level: usize,
count: usize,
) -> Result<(), SearchError> {
if count == 0 {
if entry_raw != NONE_ENTRY {
return Err(SearchError::CorruptSnapshot(
"empty graph has an entry node",
));
}
return Ok(());
}
let entry = to_usize(entry_raw)?;
if entry >= count || nodes[entry].layers.len() <= max_level {
return Err(SearchError::CorruptSnapshot(
"graph entry or max level is invalid",
));
}
for node in nodes {
if node.layers.is_empty() {
return Err(SearchError::CorruptSnapshot("graph node has no layer zero"));
}
for (level, layer) in node.layers.iter().enumerate() {
for neighbor in layer {
let neighbor = *neighbor as usize;
if neighbor >= count || nodes[neighbor].layers.len() <= level {
return Err(SearchError::CorruptSnapshot(
"graph neighbor or layer reference is invalid",
));
}
}
}
}
Ok(())
}
pub(super) fn validate_canonical_offsets(header: &Header) -> Result<(), SearchError> {
let count = header.count;
let vector_count = count
.checked_mul(header.config.dimensions)
.ok_or(SearchError::CapacityOverflow)?;
let expected_vectors = align8(
HEADER_LEN
.checked_add(bytes_for::<u64>(count)?)
.ok_or(SearchError::CapacityOverflow)?,
)?;
let expected_codes = align8(
expected_vectors
.checked_add(bytes_for::<f32>(vector_count)?)
.ok_or(SearchError::CapacityOverflow)?,
)?;
let expected_routing_positions = align8(
expected_codes
.checked_add(bytes_for::<u16>(count)?)
.ok_or(SearchError::CapacityOverflow)?,
)?;
let expected_graphs = align8(
expected_routing_positions
.checked_add(bytes_for::<u32>(count)?)
.ok_or(SearchError::CapacityOverflow)?,
)?;
if (
header.keys_offset,
header.vectors_offset,
header.routing_codes_offset,
header.routing_nodes_offset,
header.graphs_offset,
) != (
HEADER_LEN,
expected_vectors,
expected_codes,
expected_routing_positions,
expected_graphs,
) {
return Err(SearchError::CorruptSnapshot(
"snapshot section offsets do not match canonical layout",
));
}
Ok(())
}