#![no_main]
//! Valve's in-binary name tables are read by walking every writable data section in 8-byte steps and
//! treating each position as a candidate record — chasing a name pointer, two code pointers and, for the
//! entity-IO table, an input-name pointer. Every one of those fields is whatever the file says it is, so a
//! crafted (or truncated) `.so` can aim them anywhere; the readers must answer with fewer records, never a
//! panic. Also exercises the accessors the derivation reads off each record, and the ambiguity rule that
//! decides which names are safe to ship as locators.
use libfuzzer_sys::fuzz_target;
use source2rosetta::elf::CodeImage;
use source2rosetta::valvetab;

fuzz_target!(|data: &[u8]| {
    let Ok(img) = CodeImage::from_bytes(data.to_vec()) else {
        return;
    };
    let pulse = valvetab::pulse_bindings(&img);
    for b in &pulse {
        let _ = (
            b.name.len(),
            b.display.as_ref().map(String::len),
            b.description.as_ref().map(String::len),
            b.descriptor,
            b.arg_descriptor,
            b.flags.raw,
        );
    }
    let inputs = valvetab::datadesc_inputs(&img);
    for i in &inputs {
        let _ = (i.handler.len(), i.io_name.len(), i.func);
    }
    // Array segmentation walks BACKWARD and forward from a confirmed input over addresses the file
    // controls, so every step is arithmetic on untrusted values — the same shape as the `r.base + 8`
    // overflow the Pulse reader had to be hardened against. It must yield fewer arrays, never a panic.
    let arrays = valvetab::datadesc_arrays(&img);
    for a in &arrays {
        // An array is only recorded because it holds an input, and a field descriptor's offset is read
        // as a u32 — so neither list may come back as something the caller has to re-validate.
        assert!(!a.inputs.is_empty(), "an array with no inputs was recorded");
        for (n, o) in &a.fields {
            let _ = (n.len(), *o);
        }
    }
    // Every input the array walk finds must also be one the direct reader finds: the two disagree only
    // if one of them is reading a record the other rejects, which is a contradiction worth catching.
    assert!(
        arrays.iter().map(|a| a.inputs.len()).sum::<usize>() <= inputs.len(),
        "the array walk claimed more inputs than the record reader accepts"
    );
    // The shipping gate: a name reaches gamedata only through here, so it is the part that must never
    // panic AND never over-claim — no name may survive with more than one address behind it.
    let (names, _dropped) = valvetab::names(&inputs);
    for n in &names {
        assert_eq!(
            inputs
                .iter()
                .filter(|i| i.handler == n.name)
                .map(|i| i.func)
                .collect::<std::collections::BTreeSet<_>>()
                .len(),
            1,
            "shipped an ambiguous handler name"
        );
    }
});
