use std::io::Write;
use ordvec::rank::{bucket_centre, bucket_ranks, rank_norm, rank_transform, rankquant_norm};
use ordvec::{Bitmap, Rank, RankQuant, SignBitmap};
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
mod bitmap;
mod fastscan;
mod finite;
mod loader_validation;
mod rank;
#[cfg(feature = "experimental")]
mod multi_bucket;
mod quant;
pub const D: usize = 128;
pub const N: usize = 256;
pub fn make_corpus(seed: u64) -> Vec<f32> {
let mut rng = ChaCha8Rng::seed_from_u64(seed);
let mut v = vec![0.0f32; N * D];
for x in v.iter_mut() {
*x = rng.random_range(-1.0..1.0);
}
v
}
pub fn ref_rank_cosine(a: &[f32], b: &[f32]) -> f32 {
let d = a.len();
let ra = rank_transform(a);
let rb = rank_transform(b);
let mean = (d as f32 - 1.0) / 2.0;
let mut acc = 0.0f32;
for i in 0..d {
acc += (ra[i] as f32 - mean) * (rb[i] as f32 - mean);
}
let norm = rank_norm(d);
acc / (norm * norm)
}
pub fn ref_asymmetric(q: &[f32], doc: &[f32]) -> f32 {
let d = q.len();
let q_norm: f32 = q.iter().map(|x| x * x).sum::<f32>().sqrt();
let q_unit: Vec<f32> = q.iter().map(|x| x / q_norm).collect();
let r = rank_transform(doc);
let mean = (d as f32 - 1.0) / 2.0;
let norm = rank_norm(d);
let mut acc = 0.0f32;
for i in 0..d {
acc += q_unit[i] * (r[i] as f32 - mean);
}
acc / norm
}
pub fn ref_rankquant_asymmetric(q: &[f32], doc: &[f32], bits: u8) -> f32 {
let d = q.len();
let q_norm: f32 = q.iter().map(|x| x * x).sum::<f32>().sqrt();
let q_unit: Vec<f32> = q.iter().map(|x| x / q_norm).collect();
let r = rank_transform(doc);
let b = bucket_ranks(&r, bits);
let norm = rankquant_norm(d, bits);
let mut acc = 0.0f32;
for i in 0..d {
acc += q_unit[i] * bucket_centre(b[i], bits);
}
acc / norm
}
#[test]
fn rank_io_loaders_reject_malformed_files_without_panicking() {
let tmp_dir = std::env::temp_dir();
let make_file = |suffix: &str, bytes: &[u8]| -> std::path::PathBuf {
let p = tmp_dir.join(format!(
"rank_io_fuzz_{}_{}.bin",
suffix,
std::process::id()
));
std::fs::File::create(&p).unwrap().write_all(bytes).unwrap();
p
};
let cases: Vec<(&str, Vec<u8>)> = vec![
("empty", vec![]),
("garbage_16", vec![0xAB; 16]),
("garbage_4k", vec![0xCC; 4096]),
(
"wrong_magic",
b"XXXX\x01".iter().chain([0u8; 8].iter()).copied().collect(),
),
("tvr_oversize", {
let mut v = Vec::new();
v.extend_from_slice(b"TVR1");
v.push(1);
v.extend_from_slice(&u32::MAX.to_le_bytes()); v.extend_from_slice(&u32::MAX.to_le_bytes()); v
}),
("tvrq_bad_bits", {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1);
v.push(255); v.extend_from_slice(&1024u32.to_le_bytes());
v.extend_from_slice(&10u32.to_le_bytes());
v
}),
("tvrq_bad_dim", {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1);
v.push(2); v.extend_from_slice(&13u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes());
v
}),
("tvrq_oversize", {
let mut v = Vec::new();
v.extend_from_slice(b"TVRQ");
v.push(1);
v.push(4);
v.extend_from_slice(&u32::MAX.to_le_bytes());
v.extend_from_slice(&u32::MAX.to_le_bytes());
v
}),
("tvbm_dim_not_64", {
let mut v = Vec::new();
v.extend_from_slice(b"TVBM");
v.push(1);
v.extend_from_slice(&100u32.to_le_bytes()); v.extend_from_slice(&25u32.to_le_bytes()); v.extend_from_slice(&5u32.to_le_bytes()); v
}),
("tvbm_bad_n_top", {
let mut v = Vec::new();
v.extend_from_slice(b"TVBM");
v.push(1);
v.extend_from_slice(&128u32.to_le_bytes());
v.extend_from_slice(&128u32.to_le_bytes()); v.extend_from_slice(&5u32.to_le_bytes());
v
}),
("tvbm_oversize", {
let mut v = Vec::new();
v.extend_from_slice(b"TVBM");
v.push(1);
v.extend_from_slice(&u32::MAX.to_le_bytes());
v.extend_from_slice(&1u32.to_le_bytes());
v.extend_from_slice(&u32::MAX.to_le_bytes());
v
}),
("tvr_truncated", {
let mut v = Vec::new();
v.extend_from_slice(b"TVR1");
v.push(1);
v.extend_from_slice(&64u32.to_le_bytes()); v.extend_from_slice(&100u32.to_le_bytes()); v.extend(std::iter::repeat_n(0u8, 100));
v
}),
("tvsb_dim_not_64", {
let mut v = Vec::new();
v.extend_from_slice(b"TVSB");
v.push(1);
v.extend_from_slice(&100u32.to_le_bytes()); v.extend_from_slice(&5u32.to_le_bytes()); v
}),
("tvsb_oversize", {
let mut v = Vec::new();
v.extend_from_slice(b"TVSB");
v.push(1);
v.extend_from_slice(&u32::MAX.to_le_bytes()); v.extend_from_slice(&u32::MAX.to_le_bytes()); v
}),
("tvsb_truncated", {
let mut v = Vec::new();
v.extend_from_slice(b"TVSB");
v.push(1);
v.extend_from_slice(&128u32.to_le_bytes()); v.extend_from_slice(&8u32.to_le_bytes()); v
}),
];
let mut paths = Vec::new();
for (label, bytes) in &cases {
let p = make_file(label, bytes);
let p1 = p.clone();
let r1 = std::panic::catch_unwind(|| Rank::load(&p1));
assert!(r1.is_ok(), "Rank::load panicked on {label}");
assert!(r1.unwrap().is_err(), "Rank::load accepted {label}");
let p2 = p.clone();
let r2 = std::panic::catch_unwind(|| RankQuant::load(&p2));
assert!(r2.is_ok(), "RankQuant::load panicked on {label}");
assert!(r2.unwrap().is_err(), "RankQuant::load accepted {label}");
let p3 = p.clone();
let r3 = std::panic::catch_unwind(|| Bitmap::load(&p3));
assert!(r3.is_ok(), "Bitmap::load panicked on {label}");
assert!(r3.unwrap().is_err(), "Bitmap::load accepted {label}");
let p4 = p.clone();
let r4 = std::panic::catch_unwind(|| SignBitmap::load(&p4));
assert!(r4.is_ok(), "SignBitmap::load panicked on {label}");
assert!(r4.unwrap().is_err(), "SignBitmap::load accepted {label}");
paths.push(p);
}
for p in paths {
let _ = std::fs::remove_file(p);
}
}