#![no_main]
//! Console-command extraction decodes every function in the image and does arithmetic on values the
//! FILE controls at every step: `lea` displacements are added to tracked register contents, member
//! offsets are added to a symbolic base, and the accessor window walks `arg3 + k*8` looking for a stored
//! pointer. A crafted (or truncated) `.so` can make any of those wrap, point outside every section, or
//! nest arbitrarily deep — and the reader must answer with fewer commands, never a panic.
//!
//! It also exercises the two indirections that resolve a callback (a static object's first virtual, and
//! a constructor-stored member) against pointers the file chose, which is the same untrusted-chase shape
//! the Valve table readers had to be hardened for.
//!
//! CONVAR extraction rides the same pass and is fuzzed here with it. It adds two things worth attacking:
//! a per-registrar delegation walk (bounded call/tail-jump decoding at a file-chosen address) and a
//! statistical argument-slot choice, both driven entirely by bytes the file controls.
use libfuzzer_sys::fuzz_target;
use source2rosetta::concmd;
use source2rosetta::elf::CodeImage;

fuzz_target!(|data: &[u8]| {
    let Ok(img) = CodeImage::from_bytes(data.to_vec()) else {
        return;
    };
    let cmds = concmd::console_commands(&img);
    for c in &cmds {
        // A recovered handler is only ever accepted because it lands in executable code, so the reader
        // must never hand back one that does not — a caller treats this as a locator.
        assert!(
            img.is_code(c.handler),
            "a non-executable handler was recorded for {:?}",
            c.name
        );
        // The name gate is what stops prose and format strings being read as commands.
        assert!(
            !c.name.is_empty() && c.name.len() <= 64,
            "an implausible command name was recorded: {:?}",
            c.name
        );
        // Flag decoding is a pure bit test and must stay within the bits it claims to know.
        let named = concmd::flag_names(c.flags);
        assert!(named.len() <= 12, "more flag names than there are flag bits");
        let _ = (c.description.len(), c.form.describe(), c.flags);
    }
    // ---- ConVars: same pass, different registrar test ----
    let cvs = concmd::convars(&img, "server");
    for c in &cvs {
        // The name gate is the only thing separating a convar registration from any other call that
        // happens to pass a string, so it must hold on every row.
        assert!(
            !c.name.is_empty() && c.name.len() <= 64,
            "an implausible convar name was recorded: {:?}",
            c.name
        );
        // Flags are optional (a registrar with no identifiable slot reports none), but when present the
        // decode is a pure bit test over a table of 9 and cannot exceed it.
        if !c.flags_raw.is_empty() {
            let raw = u64::from_str_radix(c.flags_raw.trim_start_matches("0x"), 16)
                .expect("flags_raw is written as hex by this reader");
            assert!(raw <= u64::from(u32::MAX), "a convar flags word exceeded 32 bits");
            assert_eq!(
                c.flags.len(),
                concmd::convar_flag_names(raw).len(),
                "decoded flag names disagree with the raw word for {:?}",
                c.name
            );
        } else {
            assert!(c.flags.is_empty(), "flag names without a raw word for {:?}", c.name);
        }
        let _ = (c.description.len(), c.addr.len(), c.library.len());
    }
    // ConVars are deduped on (name, object address) for the same reason commands are.
    let mut cseen: Vec<(&str, &str)> = cvs.iter().map(|c| (c.name.as_str(), c.addr.as_str())).collect();
    let cbefore = cseen.len();
    cseen.sort_unstable();
    cseen.dedup();
    assert_eq!(cbefore, cseen.len(), "a duplicate (name, object) convar survived");

    // Commands are deduped on (name, address), so no pair may survive twice.
    let mut seen: Vec<(&str, u64)> = cmds.iter().map(|c| (c.name.as_str(), c.handler)).collect();
    let before = seen.len();
    seen.sort_unstable();
    seen.dedup();
    assert_eq!(before, seen.len(), "a duplicate (name, handler) survived");
});
