fsci-conformance 0.3.0

Differential conformance testing harness for FrankenSciPy
#![forbid(unsafe_code)]

use blake3::hash;
use serde::Serialize;
use serde_json::{Map, Value, json};
use std::collections::BTreeMap;
use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

const VECTORS_PER_FAMILY: usize = 100;
const CLASSES: [&str; 4] = ["nan_inf", "extreme_dimensions", "empty_inputs", "max_size"];

#[derive(Debug, Serialize)]
struct AdversarialVector {
    id: String,
    packet_family: String,
    attack_surface: String,
    class: String,
    payload: Value,
    blake3: String,
}

#[derive(Debug, Serialize)]
struct FamilyManifest {
    packet_family: String,
    attack_surface: String,
    vector_count: usize,
    class_counts: BTreeMap<String, usize>,
    corpus_file: String,
    corpus_blake3: String,
}

#[derive(Debug, Serialize)]
struct CorpusManifest {
    schema_version: u32,
    generated_at_epoch_seconds: u64,
    generator: String,
    families: Vec<FamilyManifest>,
}

fn now_epoch_seconds() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_or(0, |duration| duration.as_secs())
}

fn repo_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
}

fn corpus_root() -> PathBuf {
    repo_root().join("crates/fsci-conformance/fixtures/adversarial")
}

fn canonical_hash(value: &Value) -> String {
    let bytes = serde_json::to_vec(value).unwrap_or_default();
    hash(&bytes).to_hex().to_string()
}

fn attack_surface_for(packet_family: &str) -> &'static str {
    match packet_family {
        "P2C-001" => "tolerance_validation",
        "P2C-002" => "matrix_operations",
        "P2C-003" => "format_conversions",
        _ => "unknown",
    }
}

fn special_float_token(seed: usize) -> &'static str {
    match seed % 3 {
        0 => "NaN",
        1 => "Inf",
        _ => "-Inf",
    }
}

fn payload_for(packet_family: &str, class: &str, index: usize) -> Value {
    match (packet_family, class) {
        ("P2C-001", "nan_inf") => json!({
            "rtol": special_float_token(index),
            "atol": [special_float_token(index + 1), 1e-12_f64, 0.0_f64],
            "n": (index % 7) + 1
        }),
        ("P2C-001", "extreme_dimensions") => {
            let n = [0_u64, 1, 2, 1024, 65535][index % 5];
            let vector_len = [0_u64, 1, 3, 32, 4096][index % 5];
            let mode_hint = if index.is_multiple_of(2) {
                "strict"
            } else {
                "hardened"
            };
            json!({ "n": n, "vector_len": vector_len, "mode_hint": mode_hint })
        }
        ("P2C-001", "empty_inputs") => json!({
            "rtol": 1e-6_f64,
            "atol": [],
            "n": 0,
            "first_step": 0.0_f64
        }),
        ("P2C-001", "max_size") => json!({
            "n": 1_000_000_u64,
            "atol_repr": "sparse-vector",
            "max_step": "Inf",
            "metadata_budget_bytes": 8_388_608_u64
        }),
        ("P2C-002", "nan_inf") => json!({
            "matrix": [["NaN", 1.0_f64], [2.0_f64, special_float_token(index)]],
            "rhs": [1.0_f64, special_float_token(index + 1)]
        }),
        ("P2C-002", "extreme_dimensions") => {
            let rows = [0_u64, 1, 2, 512, 4096][index % 5];
            let cols = [0_u64, 1, 2, 512, 4096][(index + 2) % 5];
            let bw = [0_u64, 1, 2, 128][index % 4];
            json!({ "rows": rows, "cols": cols, "bandwidth_hint": bw })
        }
        ("P2C-002", "empty_inputs") => {
            let op = ["solve", "inv", "det", "lstsq"][index % 4];
            json!({ "matrix": [], "rhs": [], "operation_hint": op })
        }
        ("P2C-002", "max_size") => {
            let scale = [1e300_f64, 1e-300][index % 2];
            json!({ "rows": 10_000_u64, "cols": 10_000_u64, "nnz_hint": 100_000_000_u64, "value_scale": scale })
        }
        ("P2C-003", "nan_inf") => json!({
            "objective_value": special_float_token(index),
            "gradient_value": special_float_token(index + 1),
            "x0": [0.0_f64, 1.0_f64, special_float_token(index + 2)]
        }),
        ("P2C-003", "extreme_dimensions") => {
            let pc = [0_u64, 1, 2, 128, 8192][index % 5];
            let ib = [0_u64, 1, 10, 100, 1_000_000][index % 5];
            json!({ "parameter_count": pc, "iteration_budget": ib })
        }
        ("P2C-003", "empty_inputs") => {
            let method = ["BFGS", "CG", "Powell", "brentq", "bisect"][index % 5];
            json!({ "objective": "empty", "bounds": [], "constraints": [], "method": method })
        }
        ("P2C-003", "max_size") => {
            let scale = [1e308_f64, 1e-308][index % 2];
            json!({ "parameter_count": 1_000_000_u64, "value_scale": scale, "storage_hint": "chunked" })
        }
        _ => json!({"unsupported": true}),
    }
}

fn build_vectors(packet_family: &str) -> Vec<AdversarialVector> {
    let attack_surface = attack_surface_for(packet_family).to_owned();
    let mut vectors = Vec::with_capacity(VECTORS_PER_FAMILY);
    for index in 0..VECTORS_PER_FAMILY {
        let class = CLASSES[index % CLASSES.len()].to_owned();
        let payload = payload_for(packet_family, &class, index);
        vectors.push(AdversarialVector {
            id: format!("{packet_family}-{index:03}"),
            packet_family: packet_family.to_owned(),
            attack_surface: attack_surface.clone(),
            class,
            blake3: canonical_hash(&payload),
            payload,
        });
    }
    vectors
}

fn write_jsonl(path: &Path, vectors: &[AdversarialVector]) -> io::Result<()> {
    let mut file = fs::File::create(path)?;
    for vector in vectors {
        serde_json::to_writer(&mut file, vector)
            .map_err(|error| io::Error::other(error.to_string()))?;
        file.write_all(b"\n")?;
    }
    Ok(())
}

fn collect_class_counts(vectors: &[AdversarialVector]) -> BTreeMap<String, usize> {
    let mut counts = BTreeMap::new();
    for vector in vectors {
        *counts.entry(vector.class.clone()).or_insert(0) += 1;
    }
    counts
}

fn parse_args() -> PathBuf {
    let mut args = std::env::args().skip(1);
    if let Some(flag) = args.next() {
        if flag == "--output-root" {
            if let Some(path) = args.next() {
                return PathBuf::from(path);
            }
            eprintln!("missing value for --output-root");
            std::process::exit(2);
        }
        eprintln!("unrecognized argument: {flag}");
        std::process::exit(2);
    }
    corpus_root()
}

fn write_family(
    root: &Path,
    packet_family: &str,
) -> Result<FamilyManifest, Box<dyn std::error::Error>> {
    let family_dir = root.join(packet_family);
    fs::create_dir_all(&family_dir)?;

    let vectors = build_vectors(packet_family);
    let corpus_path = family_dir.join("corpus.jsonl");
    write_jsonl(&corpus_path, &vectors)?;

    let corpus_bytes = fs::read(&corpus_path)?;
    let corpus_hash = hash(&corpus_bytes).to_hex().to_string();
    let class_counts = collect_class_counts(&vectors);

    Ok(FamilyManifest {
        packet_family: packet_family.to_owned(),
        attack_surface: attack_surface_for(packet_family).to_owned(),
        vector_count: vectors.len(),
        class_counts,
        corpus_file: corpus_path
            .strip_prefix(root)
            .unwrap_or(&corpus_path)
            .to_string_lossy()
            .to_string(),
        corpus_blake3: corpus_hash,
    })
}

fn write_top_manifest(root: &Path, families: Vec<FamilyManifest>) -> Result<(), io::Error> {
    let manifest = CorpusManifest {
        schema_version: 1,
        generated_at_epoch_seconds: now_epoch_seconds(),
        generator: "adversarial_corpus.rs".to_owned(),
        families,
    };
    let path = root.join("manifest.json");
    let mut document = Map::new();
    document.insert(
        "schema_version".to_owned(),
        Value::from(manifest.schema_version),
    );
    document.insert(
        "generated_at_epoch_seconds".to_owned(),
        Value::from(manifest.generated_at_epoch_seconds),
    );
    document.insert("generator".to_owned(), Value::from(manifest.generator));
    document.insert(
        "families".to_owned(),
        serde_json::to_value(manifest.families).unwrap_or(Value::Array(Vec::new())),
    );
    let payload = serde_json::to_string_pretty(&Value::Object(document))
        .map_err(|error| io::Error::other(error.to_string()))?;
    fs::write(path, payload)?;
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let output_root = parse_args();
    fs::create_dir_all(&output_root)?;

    let mut families = Vec::new();
    for packet_family in ["P2C-001", "P2C-002", "P2C-003"] {
        families.push(write_family(&output_root, packet_family)?);
    }
    write_top_manifest(&output_root, families)?;

    println!(
        "generated adversarial corpus at {}",
        output_root.to_string_lossy()
    );
    Ok(())
}