use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Seek, Write};
use std::path::Path;
const TVR_MAGIC: &[u8; 4] = b"TVR1";
const TVRQ_MAGIC: &[u8; 4] = b"TVRQ";
const TVBM_MAGIC: &[u8; 4] = b"TVBM";
const TVSB_MAGIC: &[u8; 4] = b"TVSB";
const VERSION: u8 = 1;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum IndexKind {
Rank,
RankQuant,
Bitmap,
SignBitmap,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum IndexParams {
Rank,
RankQuant { bits: u8 },
Bitmap { n_top: usize },
SignBitmap,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IndexMetadata {
pub kind: IndexKind,
pub format_version: u8,
pub dim: usize,
pub vector_count: usize,
pub bytes_per_vec: usize,
pub params: IndexParams,
pub file_size_bytes: u64,
}
pub const MAX_DIM: usize = u16::MAX as usize;
pub const MAX_SIGN_BITMAP_DIM: usize = 1 << 24;
pub const MAX_VECTORS: usize = 64 * 1024 * 1024;
fn invalid<S: Into<String>>(msg: S) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, msg.into())
}
fn try_alloc_zeroed(n: usize) -> io::Result<Vec<u8>> {
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve_exact(n)
.map_err(|_| invalid("payload allocation too large"))?;
buf.resize(n, 0);
Ok(buf)
}
fn read_le_vec<R: Read, T, const W: usize>(
r: &mut R,
n: usize,
parse: impl Fn([u8; W]) -> T,
) -> io::Result<Vec<T>> {
let mut v: Vec<T> = Vec::new();
v.try_reserve_exact(n)
.map_err(|_| invalid("payload allocation too large"))?;
let mut buf = [0u8; W];
for _ in 0..n {
r.read_exact(&mut buf)?;
v.push(parse(buf));
}
Ok(v)
}
fn check_payload_matches_file<R: Seek>(
reader: &mut R,
label: &str,
file_len: u64,
payload_bytes: usize,
) -> io::Result<()> {
let pos = reader.stream_position()?;
let remaining = file_len.saturating_sub(pos);
let payload_bytes = payload_bytes as u64;
if payload_bytes > remaining {
return Err(invalid(format!(
"{label} payload truncated: header declares {payload_bytes} B but file has {remaining} B remaining"
)));
}
if payload_bytes < remaining {
return Err(invalid(format!(
"{label} payload has trailing bytes: header declares {payload_bytes} B but file has {remaining} B remaining"
)));
}
Ok(())
}
fn check_dim(dim: usize) -> io::Result<()> {
if !(2..=MAX_DIM).contains(&dim) {
return Err(invalid(format!("dim {dim} out of range [2, {MAX_DIM}]")));
}
Ok(())
}
fn check_sign_bitmap_dim(dim: usize) -> io::Result<()> {
if !(64..=MAX_SIGN_BITMAP_DIM).contains(&dim) {
return Err(invalid(format!(
"TVSB dim {dim} out of range [64, {MAX_SIGN_BITMAP_DIM}]"
)));
}
if !dim.is_multiple_of(64) {
return Err(invalid(format!("TVSB dim {dim} is not a multiple of 64")));
}
Ok(())
}
fn check_n_vectors(n_vectors: usize) -> io::Result<()> {
if n_vectors > MAX_VECTORS {
return Err(invalid(format!(
"n_vectors {n_vectors} exceeds MAX_VECTORS={MAX_VECTORS}"
)));
}
Ok(())
}
fn check_payload_bytes(payload_bytes: usize) -> io::Result<()> {
const MAX_PAYLOAD: u64 = 128 * 1024 * 1024 * 1024;
if payload_bytes as u64 > MAX_PAYLOAD {
return Err(invalid(format!(
"payload {payload_bytes} B exceeds MAX_PAYLOAD={MAX_PAYLOAD}"
)));
}
Ok(())
}
fn truncated_field(label: &str, field: &str) -> io::Error {
io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("{label} header truncated while reading {field}"),
)
}
fn read_exact_field<R: Read, const N: usize>(
reader: &mut R,
label: &str,
field: &str,
) -> io::Result<[u8; N]> {
let mut buf = [0u8; N];
reader
.read_exact(&mut buf)
.map_err(|err| match err.kind() {
io::ErrorKind::UnexpectedEof => truncated_field(label, field),
_ => err,
})?;
Ok(buf)
}
fn read_u8_field<R: Read>(reader: &mut R, label: &str, field: &str) -> io::Result<u8> {
Ok(read_exact_field::<_, 1>(reader, label, field)?[0])
}
fn read_u32_le<R: Read>(reader: &mut R, label: &str, field: &str) -> io::Result<u32> {
Ok(u32::from_le_bytes(read_exact_field::<_, 4>(
reader, label, field,
)?))
}
fn read_version<R: Read>(reader: &mut R, label: &str) -> io::Result<u8> {
let ver = read_u8_field(reader, label, "version")?;
if ver != VERSION {
return Err(invalid(format!("unsupported {label} version: {ver}")));
}
Ok(ver)
}
fn read_magic<R: Read>(reader: &mut R, label: &str) -> io::Result<[u8; 4]> {
read_exact_field(reader, label, "magic")
}
fn rank_payload_bytes(dim: usize, vector_count: usize) -> io::Result<usize> {
vector_count
.checked_mul(dim)
.and_then(|x| x.checked_mul(2))
.ok_or_else(|| invalid("TVR1 payload size overflows usize"))
}
fn rankquant_bytes_per_vec(dim: usize, bits: u8) -> io::Result<usize> {
dim.checked_mul(bits as usize)
.map(|x| x / 8)
.ok_or_else(|| invalid("TVRQ bytes_per_vec overflows usize"))
}
fn rankquant_payload_bytes(dim: usize, vector_count: usize, bits: u8) -> io::Result<usize> {
let bytes_per_vec = rankquant_bytes_per_vec(dim, bits)?;
vector_count
.checked_mul(bytes_per_vec)
.ok_or_else(|| invalid("TVRQ payload size overflows usize"))
}
fn bitmap_payload_bytes(dim: usize, vector_count: usize, label: &str) -> io::Result<usize> {
let qpv = dim / 64;
vector_count
.checked_mul(qpv)
.and_then(|x| x.checked_mul(8))
.ok_or_else(|| invalid(format!("{label} payload size overflows usize")))
}
pub fn probe_index_metadata(path: impl AsRef<Path>) -> io::Result<IndexMetadata> {
let file = File::open(path)?;
let file_size_bytes = file.metadata()?.len();
let mut f = BufReader::new(file);
let magic = read_magic(&mut f, "ordvec index")?;
match &magic {
TVR_MAGIC => probe_rank_metadata(&mut f, file_size_bytes),
TVRQ_MAGIC => probe_rankquant_metadata(&mut f, file_size_bytes),
TVBM_MAGIC => probe_bitmap_metadata(&mut f, file_size_bytes),
TVSB_MAGIC => probe_sign_bitmap_metadata(&mut f, file_size_bytes),
_ => Err(invalid("unknown ordvec index magic")),
}
}
fn probe_rank_metadata<R: Read + Seek>(
reader: &mut R,
file_size_bytes: u64,
) -> io::Result<IndexMetadata> {
let format_version = read_version(reader, "TVR1")?;
let dim = read_u32_le(reader, "TVR1", "dim")? as usize;
check_dim(dim)?;
let vector_count = read_u32_le(reader, "TVR1", "n_vectors")? as usize;
check_n_vectors(vector_count)?;
let bytes_per_vec = rank_payload_bytes(dim, 1)?;
let payload_bytes = rank_payload_bytes(dim, vector_count)?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(reader, "TVR1", file_size_bytes, payload_bytes)?;
Ok(IndexMetadata {
kind: IndexKind::Rank,
format_version,
dim,
vector_count,
bytes_per_vec,
params: IndexParams::Rank,
file_size_bytes,
})
}
fn probe_rankquant_metadata<R: Read + Seek>(
reader: &mut R,
file_size_bytes: u64,
) -> io::Result<IndexMetadata> {
let format_version = read_version(reader, "TVRQ")?;
let bits = read_u8_field(reader, "TVRQ", "bits")?;
if !matches!(bits, 1 | 2 | 4) {
return Err(invalid(format!(
"unsupported TVRQ bits: {bits} (expected 1, 2, or 4)"
)));
}
let dim = read_u32_le(reader, "TVRQ", "dim")? as usize;
check_dim(dim)?;
let n_buckets = 1usize << bits;
if !dim.is_multiple_of(n_buckets) {
return Err(invalid(format!(
"TVRQ dim {dim} is not a multiple of 2^bits = {n_buckets}; \
constant-composition invariant violated"
)));
}
let codes_per_byte = (8 / bits) as usize;
if !dim.is_multiple_of(codes_per_byte) {
return Err(invalid(format!(
"TVRQ dim {dim} is not a multiple of codes_per_byte = {codes_per_byte}"
)));
}
let vector_count = read_u32_le(reader, "TVRQ", "n_vectors")? as usize;
check_n_vectors(vector_count)?;
let payload_bytes = rankquant_payload_bytes(dim, vector_count, bits)?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(reader, "TVRQ", file_size_bytes, payload_bytes)?;
let bytes_per_vec = rankquant_bytes_per_vec(dim, bits)?;
Ok(IndexMetadata {
kind: IndexKind::RankQuant,
format_version,
dim,
vector_count,
bytes_per_vec,
params: IndexParams::RankQuant { bits },
file_size_bytes,
})
}
fn probe_bitmap_metadata<R: Read + Seek>(
reader: &mut R,
file_size_bytes: u64,
) -> io::Result<IndexMetadata> {
let format_version = read_version(reader, "TVBM")?;
let dim = read_u32_le(reader, "TVBM", "dim")? as usize;
check_dim(dim)?;
if !dim.is_multiple_of(64) {
return Err(invalid(format!("TVBM dim {dim} is not a multiple of 64")));
}
let n_top = read_u32_le(reader, "TVBM", "n_top")? as usize;
if n_top == 0 || n_top >= dim {
return Err(invalid(format!(
"TVBM n_top {n_top} must satisfy 0 < n_top < dim ({dim})"
)));
}
let vector_count = read_u32_le(reader, "TVBM", "n_vectors")? as usize;
check_n_vectors(vector_count)?;
let payload_bytes = bitmap_payload_bytes(dim, vector_count, "TVBM")?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(reader, "TVBM", file_size_bytes, payload_bytes)?;
Ok(IndexMetadata {
kind: IndexKind::Bitmap,
format_version,
dim,
vector_count,
bytes_per_vec: dim / 8,
params: IndexParams::Bitmap { n_top },
file_size_bytes,
})
}
fn probe_sign_bitmap_metadata<R: Read + Seek>(
reader: &mut R,
file_size_bytes: u64,
) -> io::Result<IndexMetadata> {
let format_version = read_version(reader, "TVSB")?;
let dim = read_u32_le(reader, "TVSB", "dim")? as usize;
check_sign_bitmap_dim(dim)?;
let vector_count = read_u32_le(reader, "TVSB", "n_vectors")? as usize;
check_n_vectors(vector_count)?;
let payload_bytes = bitmap_payload_bytes(dim, vector_count, "TVSB")?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(reader, "TVSB", file_size_bytes, payload_bytes)?;
Ok(IndexMetadata {
kind: IndexKind::SignBitmap,
format_version,
dim,
vector_count,
bytes_per_vec: dim / 8,
params: IndexParams::SignBitmap,
file_size_bytes,
})
}
pub(crate) fn write_rank(
path: impl AsRef<Path>,
dim: usize,
n_vectors: usize,
ranks: &[u16],
) -> io::Result<()> {
let payload_bytes = rank_payload_bytes(dim, n_vectors)?;
check_payload_bytes(payload_bytes)?;
assert_eq!(ranks.len(), payload_bytes / 2);
let mut f = BufWriter::new(File::create(path)?);
f.write_all(TVR_MAGIC)?;
f.write_all(&[VERSION])?;
f.write_all(&(dim as u32).to_le_bytes())?;
f.write_all(&(n_vectors as u32).to_le_bytes())?;
for &r in ranks {
f.write_all(&r.to_le_bytes())?;
}
f.flush()?;
Ok(())
}
pub(crate) fn load_rank(path: impl AsRef<Path>) -> io::Result<(usize, usize, Vec<u16>)> {
let file = File::open(path)?;
let file_len = file.metadata()?.len();
let mut f = BufReader::new(file);
let magic = read_magic(&mut f, "TVR1")?;
if &magic != TVR_MAGIC {
return Err(invalid("not a TVR1 file: wrong magic"));
}
read_version(&mut f, "TVR1")?;
let dim = read_u32_le(&mut f, "TVR1", "dim")? as usize;
check_dim(dim)?;
let n_vectors = read_u32_le(&mut f, "TVR1", "n_vectors")? as usize;
check_n_vectors(n_vectors)?;
let payload_bytes = rank_payload_bytes(dim, n_vectors)?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(&mut f, "TVR1", file_len, payload_bytes)?;
let ranks = read_le_vec(&mut f, payload_bytes / 2, u16::from_le_bytes)?;
let mut seen = vec![0u32; dim];
for (row_idx, row) in ranks.chunks_exact(dim).enumerate() {
let stamp = row_idx as u32 + 1;
for &r in row {
let ri = r as usize;
if ri >= dim {
return Err(invalid(format!(
"TVR1 rank value {r} >= dim ({dim}); ranks must be a permutation of [0, dim)"
)));
}
if seen[ri] == stamp {
return Err(invalid(format!(
"TVR1 row {row_idx} is not a permutation of [0, dim): value {r} repeats"
)));
}
seen[ri] = stamp;
}
}
Ok((dim, n_vectors, ranks))
}
pub(crate) fn write_rankquant(
path: impl AsRef<Path>,
bits: u8,
dim: usize,
n_vectors: usize,
packed: &[u8],
) -> io::Result<()> {
let payload_bytes = rankquant_payload_bytes(dim, n_vectors, bits)?;
check_payload_bytes(payload_bytes)?;
assert_eq!(packed.len(), payload_bytes);
let mut f = BufWriter::new(File::create(path)?);
f.write_all(TVRQ_MAGIC)?;
f.write_all(&[VERSION])?;
f.write_all(&[bits])?;
f.write_all(&(dim as u32).to_le_bytes())?;
f.write_all(&(n_vectors as u32).to_le_bytes())?;
f.write_all(packed)?;
f.flush()?;
Ok(())
}
pub(crate) fn load_rankquant(path: impl AsRef<Path>) -> io::Result<(u8, usize, usize, Vec<u8>)> {
let file = File::open(path)?;
let file_len = file.metadata()?.len();
let mut f = BufReader::new(file);
let magic = read_magic(&mut f, "TVRQ")?;
if &magic != TVRQ_MAGIC {
return Err(invalid("not a TVRQ file: wrong magic"));
}
read_version(&mut f, "TVRQ")?;
let bits = read_u8_field(&mut f, "TVRQ", "bits")?;
if !matches!(bits, 1 | 2 | 4) {
return Err(invalid(format!(
"unsupported TVRQ bits: {bits} (expected 1, 2, or 4)"
)));
}
let dim = read_u32_le(&mut f, "TVRQ", "dim")? as usize;
check_dim(dim)?;
let n_buckets = 1usize << bits;
if !dim.is_multiple_of(n_buckets) {
return Err(invalid(format!(
"TVRQ dim {dim} is not a multiple of 2^bits = {n_buckets}; \
constant-composition invariant violated"
)));
}
let codes_per_byte = (8 / bits) as usize;
if !dim.is_multiple_of(codes_per_byte) {
return Err(invalid(format!(
"TVRQ dim {dim} is not a multiple of codes_per_byte = {codes_per_byte}"
)));
}
let n_vectors = read_u32_le(&mut f, "TVRQ", "n_vectors")? as usize;
check_n_vectors(n_vectors)?;
let payload_bytes = rankquant_payload_bytes(dim, n_vectors, bits)?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(&mut f, "TVRQ", file_len, payload_bytes)?;
let mut packed = try_alloc_zeroed(payload_bytes)?;
f.read_exact(&mut packed)?;
let bytes_per_row = dim / codes_per_byte;
let expected_per_bucket = dim / n_buckets;
let mask = (1u8 << bits) - 1;
let bits_u = bits as usize;
for (row_idx, row) in packed.chunks_exact(bytes_per_row).enumerate() {
let mut hist = [0usize; 16]; for &byte in row {
for slot in 0..codes_per_byte {
let shift = (codes_per_byte - 1 - slot) * bits_u;
hist[((byte >> shift) & mask) as usize] += 1;
}
}
for (bucket, &count) in hist[..n_buckets].iter().enumerate() {
if count != expected_per_bucket {
return Err(invalid(format!(
"TVRQ row {row_idx} violates constant composition: bucket {bucket} \
has {count} codes, expected {expected_per_bucket} (= dim / 2^bits)"
)));
}
}
}
Ok((bits, dim, n_vectors, packed))
}
pub(crate) fn write_bitmap(
path: impl AsRef<Path>,
dim: usize,
n_top: usize,
n_vectors: usize,
bitmaps: &[u64],
) -> io::Result<()> {
let payload_bytes = bitmap_payload_bytes(dim, n_vectors, "TVBM")?;
check_payload_bytes(payload_bytes)?;
assert_eq!(bitmaps.len(), payload_bytes / 8);
let mut f = BufWriter::new(File::create(path)?);
f.write_all(TVBM_MAGIC)?;
f.write_all(&[VERSION])?;
f.write_all(&(dim as u32).to_le_bytes())?;
f.write_all(&(n_top as u32).to_le_bytes())?;
f.write_all(&(n_vectors as u32).to_le_bytes())?;
for &w in bitmaps {
f.write_all(&w.to_le_bytes())?;
}
f.flush()?;
Ok(())
}
pub(crate) fn load_bitmap(path: impl AsRef<Path>) -> io::Result<(usize, usize, usize, Vec<u64>)> {
let file = File::open(path)?;
let file_len = file.metadata()?.len();
let mut f = BufReader::new(file);
let magic = read_magic(&mut f, "TVBM")?;
if &magic != TVBM_MAGIC {
return Err(invalid("not a TVBM file: wrong magic"));
}
read_version(&mut f, "TVBM")?;
let dim = read_u32_le(&mut f, "TVBM", "dim")? as usize;
check_dim(dim)?;
if !dim.is_multiple_of(64) {
return Err(invalid(format!("TVBM dim {dim} is not a multiple of 64")));
}
let n_top = read_u32_le(&mut f, "TVBM", "n_top")? as usize;
if n_top == 0 || n_top >= dim {
return Err(invalid(format!(
"TVBM n_top {n_top} must satisfy 0 < n_top < dim ({dim})"
)));
}
let n_vectors = read_u32_le(&mut f, "TVBM", "n_vectors")? as usize;
check_n_vectors(n_vectors)?;
let qpv = dim / 64;
let payload_bytes = bitmap_payload_bytes(dim, n_vectors, "TVBM")?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(&mut f, "TVBM", file_len, payload_bytes)?;
let bitmaps = read_le_vec(&mut f, payload_bytes / 8, u64::from_le_bytes)?;
for (row_idx, row) in bitmaps.chunks_exact(qpv).enumerate() {
let pop: u32 = row.iter().map(|w| w.count_ones()).sum();
if pop as usize != n_top {
return Err(invalid(format!(
"TVBM row {row_idx} has {pop} bits set, expected n_top = {n_top}"
)));
}
}
Ok((dim, n_top, n_vectors, bitmaps))
}
pub(crate) fn write_sign_bitmap(
path: impl AsRef<Path>,
dim: usize,
n_vectors: usize,
bitmaps: &[u64],
) -> io::Result<()> {
let payload_bytes = bitmap_payload_bytes(dim, n_vectors, "TVSB")?;
check_payload_bytes(payload_bytes)?;
assert_eq!(bitmaps.len(), payload_bytes / 8);
let mut f = BufWriter::new(File::create(path)?);
f.write_all(TVSB_MAGIC)?;
f.write_all(&[VERSION])?;
f.write_all(&(dim as u32).to_le_bytes())?;
f.write_all(&(n_vectors as u32).to_le_bytes())?;
for &w in bitmaps {
f.write_all(&w.to_le_bytes())?;
}
f.flush()?;
Ok(())
}
pub(crate) fn load_sign_bitmap(path: impl AsRef<Path>) -> io::Result<(usize, usize, Vec<u64>)> {
let file = File::open(path)?;
let file_len = file.metadata()?.len();
let mut f = BufReader::new(file);
let magic = read_magic(&mut f, "TVSB")?;
if &magic != TVSB_MAGIC {
return Err(invalid("not a TVSB file: wrong magic"));
}
read_version(&mut f, "TVSB")?;
let dim = read_u32_le(&mut f, "TVSB", "dim")? as usize;
check_sign_bitmap_dim(dim)?;
let n_vectors = read_u32_le(&mut f, "TVSB", "n_vectors")? as usize;
check_n_vectors(n_vectors)?;
let payload_bytes = bitmap_payload_bytes(dim, n_vectors, "TVSB")?;
check_payload_bytes(payload_bytes)?;
check_payload_matches_file(&mut f, "TVSB", file_len, payload_bytes)?;
let bitmaps = read_le_vec(&mut f, payload_bytes / 8, u64::from_le_bytes)?;
Ok((dim, n_vectors, bitmaps))
}
#[cfg(test)]
mod tests {
use super::{
load_bitmap, load_rank, load_rankquant, probe_index_metadata, write_bitmap, write_rank,
write_rankquant, write_sign_bitmap, IndexKind, IndexParams, MAX_DIM, MAX_VECTORS, VERSION,
};
use crate::{Bitmap, Rank, RankQuant, SignBitmap};
use std::io::Write;
use std::path::PathBuf;
fn forge(suffix: &str, bytes: &[u8]) -> PathBuf {
let mut p = std::env::temp_dir();
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
p.push(format!(
"rank_io_test_{}_{}_{}",
std::process::id(),
nonce,
suffix
));
std::fs::File::create(&p).unwrap().write_all(bytes).unwrap();
p
}
fn temp_index_path(suffix: &str) -> PathBuf {
let mut p = std::env::temp_dir();
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
p.push(format!(
"rank_io_probe_{}_{}_{}",
std::process::id(),
nonce,
suffix
));
p
}
fn assert_err_contains<T>(result: std::io::Result<T>, expected: &str) {
let Err(err) = result else {
panic!("expected error containing {expected:?}, got Ok(_)");
};
let text = err.to_string();
assert!(
text.contains(expected),
"expected error containing {expected:?}, got {text:?}"
);
}
fn rank_header(dim: u32, n_vectors: u32) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(b"TVR1");
v.push(VERSION);
v.extend_from_slice(&dim.to_le_bytes());
v.extend_from_slice(&n_vectors.to_le_bytes());
v
}
fn rankquant_header(bits: u8, dim: u32, n_vectors: u32) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(VERSION);
v.push(bits);
v.extend_from_slice(&dim.to_le_bytes());
v.extend_from_slice(&n_vectors.to_le_bytes());
v
}
fn bitmap_header(dim: u32, n_top: u32, n_vectors: u32) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(b"TVBM");
v.push(VERSION);
v.extend_from_slice(&dim.to_le_bytes());
v.extend_from_slice(&n_top.to_le_bytes());
v.extend_from_slice(&n_vectors.to_le_bytes());
v
}
fn sign_bitmap_header(dim: u32, n_vectors: u32) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(b"TVSB");
v.push(VERSION);
v.extend_from_slice(&dim.to_le_bytes());
v.extend_from_slice(&n_vectors.to_le_bytes());
v
}
#[test]
fn probe_metadata_matches_full_loaders_on_generated_fixtures() {
let mut paths = Vec::new();
let rank_path = temp_index_path("rank.tvr");
let mut rank = Rank::new(8);
rank.add(&[
1.0, 3.0, 2.0, 4.0, 8.0, 7.0, 6.0, 5.0, 8.0, 6.0, 7.0, 5.0, 1.0, 2.0, 3.0, 4.0,
]);
rank.write(&rank_path).unwrap();
let meta = probe_index_metadata(&rank_path).unwrap();
let loaded = Rank::load(&rank_path).unwrap();
assert_eq!(meta.kind, IndexKind::Rank);
assert_eq!(meta.params, IndexParams::Rank);
assert_eq!(meta.format_version, VERSION);
assert_eq!(meta.dim, loaded.dim());
assert_eq!(meta.vector_count, loaded.len());
assert_eq!(meta.bytes_per_vec, loaded.bytes_per_vec());
assert_eq!(
meta.file_size_bytes,
std::fs::metadata(&rank_path).unwrap().len()
);
paths.push(rank_path);
let quant_path = temp_index_path("rankquant.tvrq");
let mut quant = RankQuant::new(16, 2);
let quant_docs: Vec<f32> = (0..32).map(|i| i as f32 - 11.0).collect();
quant.add(&quant_docs);
quant.write(&quant_path).unwrap();
let meta = probe_index_metadata(&quant_path).unwrap();
let loaded = RankQuant::load(&quant_path).unwrap();
assert_eq!(meta.kind, IndexKind::RankQuant);
assert_eq!(
meta.params,
IndexParams::RankQuant {
bits: loaded.bits()
}
);
assert_eq!(meta.format_version, VERSION);
assert_eq!(meta.dim, loaded.dim());
assert_eq!(meta.vector_count, loaded.len());
assert_eq!(meta.bytes_per_vec, loaded.bytes_per_vec());
assert_eq!(
meta.file_size_bytes,
std::fs::metadata(&quant_path).unwrap().len()
);
paths.push(quant_path);
let bitmap_path = temp_index_path("bitmap.tvbm");
let mut bitmap = Bitmap::new(64, 16);
let bitmap_docs: Vec<f32> = (0..128).map(|i| ((i * 17) % 31) as f32).collect();
bitmap.add(&bitmap_docs);
bitmap.write(&bitmap_path).unwrap();
let meta = probe_index_metadata(&bitmap_path).unwrap();
let loaded = Bitmap::load(&bitmap_path).unwrap();
assert_eq!(meta.kind, IndexKind::Bitmap);
assert_eq!(
meta.params,
IndexParams::Bitmap {
n_top: loaded.n_top()
}
);
assert_eq!(meta.format_version, VERSION);
assert_eq!(meta.dim, loaded.dim());
assert_eq!(meta.vector_count, loaded.len());
assert_eq!(meta.bytes_per_vec, loaded.bytes_per_vec());
assert_eq!(
meta.file_size_bytes,
std::fs::metadata(&bitmap_path).unwrap().len()
);
paths.push(bitmap_path);
let sign_path = temp_index_path("sign_bitmap.tvsb");
let mut sign = SignBitmap::new(64);
let sign_docs: Vec<f32> = (0usize..128)
.map(|i| if i.is_multiple_of(3) { 1.0 } else { -1.0 })
.collect();
sign.add(&sign_docs);
sign.write(&sign_path).unwrap();
let meta = probe_index_metadata(&sign_path).unwrap();
let loaded = SignBitmap::load(&sign_path).unwrap();
assert_eq!(meta.kind, IndexKind::SignBitmap);
assert_eq!(meta.params, IndexParams::SignBitmap);
assert_eq!(meta.format_version, VERSION);
assert_eq!(meta.dim, loaded.dim());
assert_eq!(meta.vector_count, loaded.len());
assert_eq!(meta.bytes_per_vec, loaded.bytes_per_vec());
assert_eq!(
meta.file_size_bytes,
std::fs::metadata(&sign_path).unwrap().len()
);
paths.push(sign_path);
for path in paths {
std::fs::remove_file(path).ok();
}
}
#[test]
fn probe_rejects_header_and_length_errors_without_payload_allocation() {
let wrong_magic = forge("wrong_magic", b"NOPE");
let err = probe_index_metadata(&wrong_magic).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
std::fs::remove_file(&wrong_magic).ok();
let bad_version = forge("bad_version", b"TVR1\x09");
let err = probe_index_metadata(&bad_version).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
std::fs::remove_file(&bad_version).ok();
let truncated = forge("truncated_header", b"TVR1\x01");
let err = probe_index_metadata(&truncated).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
assert!(
err.to_string()
.contains("TVR1 header truncated while reading dim"),
"unexpected error: {err}"
);
std::fs::remove_file(&truncated).ok();
let length_mismatch = forge("length_mismatch", &rank_header(8, 1));
assert_err_contains(
probe_index_metadata(&length_mismatch),
"TVR1 payload truncated",
);
std::fs::remove_file(&length_mismatch).ok();
let mut huge_declared = Vec::new();
huge_declared.extend_from_slice(b"TVR1");
huge_declared.push(VERSION);
huge_declared.extend_from_slice(&(MAX_DIM as u32).to_le_bytes());
huge_declared.extend_from_slice(&(MAX_VECTORS as u32).to_le_bytes());
let huge_declared = forge("huge_declared", &huge_declared);
let err = probe_index_metadata(&huge_declared).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
assert!(
err.to_string().contains("MAX_PAYLOAD"),
"unexpected error: {err}"
);
std::fs::remove_file(&huge_declared).ok();
}
#[test]
fn probe_reports_header_field_context_for_truncated_headers() {
let cases: [(&str, Vec<u8>, &str); 5] = [
(
"short_magic",
b"TV".to_vec(),
"ordvec index header truncated while reading magic",
),
(
"rank_version",
b"TVR1".to_vec(),
"TVR1 header truncated while reading version",
),
(
"rankquant_bits",
b"TVRQ\x01".to_vec(),
"TVRQ header truncated while reading bits",
),
(
"bitmap_n_top",
{
let mut v = Vec::new();
v.extend_from_slice(b"TVBM");
v.push(VERSION);
v.extend_from_slice(&64u32.to_le_bytes());
v
},
"TVBM header truncated while reading n_top",
),
(
"sign_n_vectors",
{
let mut v = Vec::new();
v.extend_from_slice(b"TVSB");
v.push(VERSION);
v.extend_from_slice(&64u32.to_le_bytes());
v
},
"TVSB header truncated while reading n_vectors",
),
];
for (suffix, bytes, expected) in cases {
let path = forge(suffix, &bytes);
assert_err_contains(probe_index_metadata(&path), expected);
std::fs::remove_file(&path).ok();
}
}
#[test]
fn probe_reports_distinct_payload_truncation_and_trailing_bytes_for_all_formats() {
let cases: [(&str, Vec<u8>, Vec<u8>, &str); 4] = [
("rank", rank_header(8, 1), rank_header(8, 0), "TVR1"),
(
"rankquant",
rankquant_header(2, 8, 1),
rankquant_header(2, 8, 0),
"TVRQ",
),
(
"bitmap",
bitmap_header(64, 16, 1),
bitmap_header(64, 16, 0),
"TVBM",
),
(
"sign_bitmap",
sign_bitmap_header(64, 1),
sign_bitmap_header(64, 0),
"TVSB",
),
];
for (suffix, truncated_header, mut trailing_bytes, label) in cases {
let truncated = forge(&format!("{suffix}_truncated"), &truncated_header);
assert_err_contains(
probe_index_metadata(&truncated),
&format!("{label} payload truncated"),
);
std::fs::remove_file(&truncated).ok();
trailing_bytes.push(0);
let trailing = forge(&format!("{suffix}_trailing"), &trailing_bytes);
assert_err_contains(
probe_index_metadata(&trailing),
&format!("{label} payload has trailing bytes"),
);
std::fs::remove_file(&trailing).ok();
}
}
#[test]
fn probe_rejects_format_specific_header_errors() {
let mut bad_bits = Vec::new();
bad_bits.extend_from_slice(b"TVRQ");
bad_bits.push(VERSION);
bad_bits.push(3);
bad_bits.extend_from_slice(&8u32.to_le_bytes());
bad_bits.extend_from_slice(&0u32.to_le_bytes());
let path = forge("probe_bad_bits.tvrq", &bad_bits);
assert_eq!(
probe_index_metadata(&path).unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
std::fs::remove_file(&path).ok();
let mut bad_rq_dim = Vec::new();
bad_rq_dim.extend_from_slice(b"TVRQ");
bad_rq_dim.push(VERSION);
bad_rq_dim.push(4);
bad_rq_dim.extend_from_slice(&8u32.to_le_bytes());
bad_rq_dim.extend_from_slice(&0u32.to_le_bytes());
let path = forge("probe_bad_rq_dim.tvrq", &bad_rq_dim);
assert_eq!(
probe_index_metadata(&path).unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
std::fs::remove_file(&path).ok();
let mut bad_bitmap_dim = Vec::new();
bad_bitmap_dim.extend_from_slice(b"TVBM");
bad_bitmap_dim.push(VERSION);
bad_bitmap_dim.extend_from_slice(&100u32.to_le_bytes());
bad_bitmap_dim.extend_from_slice(&10u32.to_le_bytes());
bad_bitmap_dim.extend_from_slice(&0u32.to_le_bytes());
let path = forge("probe_bad_bitmap_dim.tvbm", &bad_bitmap_dim);
assert_eq!(
probe_index_metadata(&path).unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
std::fs::remove_file(&path).ok();
let mut bad_n_top = Vec::new();
bad_n_top.extend_from_slice(b"TVBM");
bad_n_top.push(VERSION);
bad_n_top.extend_from_slice(&64u32.to_le_bytes());
bad_n_top.extend_from_slice(&64u32.to_le_bytes());
bad_n_top.extend_from_slice(&0u32.to_le_bytes());
let path = forge("probe_bad_n_top.tvbm", &bad_n_top);
assert_eq!(
probe_index_metadata(&path).unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
std::fs::remove_file(&path).ok();
let mut bad_sign_dim = Vec::new();
bad_sign_dim.extend_from_slice(b"TVSB");
bad_sign_dim.push(VERSION);
bad_sign_dim.extend_from_slice(&32u32.to_le_bytes());
bad_sign_dim.extend_from_slice(&0u32.to_le_bytes());
let path = forge("probe_bad_sign_dim.tvsb", &bad_sign_dim);
assert_eq!(
probe_index_metadata(&path).unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
std::fs::remove_file(&path).ok();
}
#[test]
fn probe_does_not_validate_payload_row_invariants() {
let mut forged = Vec::new();
forged.extend_from_slice(b"TVBM");
forged.push(VERSION);
forged.extend_from_slice(&64u32.to_le_bytes());
forged.extend_from_slice(&16u32.to_le_bytes());
forged.extend_from_slice(&1u32.to_le_bytes());
forged.extend_from_slice(&0u64.to_le_bytes());
let path = forge("bad_bitmap_payload.tvbm", &forged);
let meta = probe_index_metadata(&path).expect("probe reads only metadata");
assert_eq!(meta.kind, IndexKind::Bitmap);
assert_eq!(meta.dim, 64);
assert_eq!(meta.vector_count, 1);
let load_err = load_bitmap(&path).unwrap_err();
assert_eq!(load_err.kind(), std::io::ErrorKind::InvalidData);
std::fs::remove_file(&path).ok();
}
#[test]
fn tvdeser004_load_rankquant_rejects_dim_not_multiple_of_2pow_bits() {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1); v.push(2); v.extend_from_slice(&6u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); let path = forge("tvrq_dim6_bits2.tvrq", &v);
let result = std::panic::catch_unwind(|| load_rankquant(&path));
std::fs::remove_file(&path).ok();
let result = result.expect("load_rankquant panicked on bits=2 dim=6");
assert!(
result.is_err(),
"load_rankquant accepted bits=2 dim=6 (dim % 4 != 0); expected Err"
);
}
#[test]
fn tvdeser004_load_rankquant_rejects_dim_smaller_than_buckets() {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1);
v.push(4); v.extend_from_slice(&4u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes());
let path = forge("tvrq_dim4_bits4.tvrq", &v);
let result = std::panic::catch_unwind(|| load_rankquant(&path));
std::fs::remove_file(&path).ok();
let result = result.expect("load_rankquant panicked on bits=4 dim=4");
assert!(
result.is_err(),
"load_rankquant accepted bits=4 dim=4 (dim % 16 != 0); expected Err"
);
}
#[test]
fn tvdeser004_load_rankquant_accepts_valid_dim() {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1);
v.push(2);
v.extend_from_slice(&8u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes());
let path = forge("tvrq_dim8_bits2.tvrq", &v);
let result = load_rankquant(&path);
std::fs::remove_file(&path).ok();
let (bits, dim, n, packed) = result.expect("valid TVRQ should load");
assert_eq!(bits, 2);
assert_eq!(dim, 8);
assert_eq!(n, 0);
assert!(packed.is_empty());
}
#[test]
fn tvdeser005_load_rank_rejects_rank_value_ge_dim() {
let ranks: [u16; 4] = [60000, 1, 2, 3];
let mut v = Vec::new();
v.extend_from_slice(b"TVR1");
v.push(1);
v.extend_from_slice(&4u32.to_le_bytes()); v.extend_from_slice(&1u32.to_le_bytes()); for &r in &ranks {
v.extend_from_slice(&r.to_le_bytes());
}
let path = forge("tvr_rank_ge_dim.tvr", &v);
let result = std::panic::catch_unwind(|| load_rank(&path));
std::fs::remove_file(&path).ok();
let result = result.expect("load_rank panicked on rank >= dim");
assert!(
result.is_err(),
"load_rank accepted ranks=[60000,1,2,3] with dim=4 (60000 >= dim); expected Err"
);
}
#[test]
fn tvdeser005_load_rank_accepts_valid_permutation() {
let ranks: [u16; 8] = [0, 1, 2, 3, 3, 2, 1, 0];
let mut v = Vec::new();
v.extend_from_slice(b"TVR1");
v.push(1);
v.extend_from_slice(&4u32.to_le_bytes());
v.extend_from_slice(&2u32.to_le_bytes());
for &r in &ranks {
v.extend_from_slice(&r.to_le_bytes());
}
let path = forge("tvr_valid_perm.tvr", &v);
let result = load_rank(&path);
std::fs::remove_file(&path).ok();
let (d, n, loaded) = result.expect("valid TVR1 should load");
assert_eq!(d, 4);
assert_eq!(n, 2);
assert_eq!(loaded, ranks.to_vec());
}
#[test]
fn writers_reject_oversized_payload_without_truncating() {
use std::io::ErrorKind;
let tmp_dir = std::env::temp_dir();
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = |s: &str| {
tmp_dir.join(format!(
"rank_io_write_guard_{}_{}_{}.bin",
std::process::id(),
nonce,
s
))
};
let big_dim = u16::MAX as usize; let big_n = 64 * 1024 * 1024;
let pr = path("rank");
let e = write_rank(&pr, big_dim, big_n, &[]).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert!(
!pr.exists(),
"write_rank created a file despite rejecting the payload"
);
let prq = path("rankquant");
let e = write_rankquant(&prq, 4, big_dim, big_n, &[]).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert!(
!prq.exists(),
"write_rankquant created a file despite rejecting the payload"
);
let bm_dim = 32768;
let pbm = path("bitmap");
let e = write_bitmap(&pbm, bm_dim, 1, big_n, &[]).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert!(
!pbm.exists(),
"write_bitmap created a file despite rejecting the payload"
);
let psb = path("sign_bitmap");
let e = write_sign_bitmap(&psb, bm_dim, big_n, &[]).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
assert!(
!psb.exists(),
"write_sign_bitmap created a file despite rejecting the payload"
);
let keep = path("rank_existing");
{
let mut idx = Rank::new(8);
idx.add(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
idx.write(&keep).unwrap();
}
let before = std::fs::read(&keep).unwrap();
let e = write_rank(&keep, big_dim, big_n, &[]).unwrap_err();
assert_eq!(e.kind(), ErrorKind::InvalidData);
let after = std::fs::read(&keep).unwrap();
assert_eq!(
before, after,
"rejected oversized write altered an existing file"
);
let (_dim, n, _ranks) = load_rank(&keep).unwrap();
assert_eq!(
n, 1,
"existing index no longer loads after a rejected write"
);
for p in [pr, prq, pbm, psb, keep] {
let _ = std::fs::remove_file(p);
}
}
}