//! Seed-corpus generator for the source2rosetta fuzz targets.
//!
//! The targets parse ELF64 bytes, so a good seed is a *valid* ELF that gets the fuzzer past the magic
//! and into the section/symbol/reloc/schema/RTTI parsing where the real edge cases live. We build a
//! minimal-but-structurally-valid ELF in-process (no committed Valve bytes), add a couple of malformed
//! variants to seed the error paths, and — for richer real-world coverage — copy a small system ELF if
//! one is present. libfuzzer mutates + minimises from there.
//!
//! Run: cd source2rosetta/fuzz && cargo +nightly run --bin gen_corpus

use std::fs;
use std::path::Path;

const TARGETS: &[&str] = &[
    "fuzz_elf",
    "fuzz_schema",
    "fuzz_rtti",
    "fuzz_sig_abi",
    "fuzz_xref",
];

fn w16(v: &mut [u8], o: usize, x: u16) {
    v[o..o + 2].copy_from_slice(&x.to_le_bytes());
}
fn w32(v: &mut [u8], o: usize, x: u32) {
    v[o..o + 4].copy_from_slice(&x.to_le_bytes());
}
fn w64(v: &mut [u8], o: usize, x: u64) {
    v[o..o + 8].copy_from_slice(&x.to_le_bytes());
}

/// A 64-byte ELF64 section header, matching the fields `elf.rs` reads.
fn sec_hdr(typ: u32, flags: u64, addr: u64, off: u64, size: u64, link: u32, entsize: u64) -> Vec<u8> {
    let mut h = vec![0u8; 64];
    w32(&mut h, 4, typ);
    w64(&mut h, 8, flags);
    w64(&mut h, 16, addr);
    w64(&mut h, 24, off);
    w64(&mut h, 32, size);
    w32(&mut h, 40, link);
    w64(&mut h, 56, entsize);
    h
}

/// A minimal, valid ELF64 with an executable `.text` (one `mov eax,1; ret`), a `.rodata` carrying an
/// Itanium type name + a distinctive string, a dynstr/dynsym pair, and two relocations (one pointing a
/// slot at the code, one at the type name). Enough for the fuzzer to reach every reader's happy path.
fn minimal_elf() -> Vec<u8> {
    const SHF_ALLOC: u64 = 0x2;
    const SHF_EXEC: u64 = 0x4;
    let text: &[u8] = &[0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3]; // mov eax,1 ; ret
    let rodata: &[u8] = b"11CBaseEntity\0a distinctive fuzz seed string\0";
    let dynstr: &[u8] = b"\0_ZTV11CBaseEntity\0";
    // one Elf64_Sym (24B): st_name=1, st_info=0x12, st_shndx=1(.text), st_value=0x1000, st_size=6
    let mut sym = vec![0u8; 24];
    w32(&mut sym, 0, 1);
    sym[4] = 0x12;
    w16(&mut sym, 6, 1);
    w64(&mut sym, 8, 0x1000);
    w64(&mut sym, 16, text.len() as u64);
    // two Elf64_Rela (24B each): R_X86_64_RELATIVE (type 8), addend = target vaddr
    let mut relas = vec![0u8; 48];
    w64(&mut relas, 0, 0x4010); // slot
    w64(&mut relas, 8, 8); // r_info: type RELATIVE
    w64(&mut relas, 16, 0x1000); // -> .text (a code-pointer target)
    w64(&mut relas, 24, 0x4018);
    w64(&mut relas, 32, 8);
    w64(&mut relas, 40, 0x2000); // -> .rodata type name

    // Lay content out after the 64-byte header; section headers go at the end.
    let mut body: Vec<u8> = Vec::new();
    let push = |data: &[u8], body: &mut Vec<u8>| -> u64 {
        let off = 64 + body.len() as u64;
        body.extend_from_slice(data);
        off
    };
    let text_off = push(text, &mut body);
    let rodata_off = push(rodata, &mut body);
    let dynstr_off = push(dynstr, &mut body);
    let dynsym_off = push(&sym, &mut body);
    let rela_off = push(&relas, &mut body);

    let secs = [
        sec_hdr(0, 0, 0, 0, 0, 0, 0), // [0] null
        sec_hdr(1, SHF_ALLOC | SHF_EXEC, 0x1000, text_off, text.len() as u64, 0, 0), // [1] .text
        sec_hdr(1, SHF_ALLOC, 0x2000, rodata_off, rodata.len() as u64, 0, 0), // [2] .rodata
        sec_hdr(3, SHF_ALLOC, 0x3000, dynstr_off, dynstr.len() as u64, 0, 0), // [3] .dynstr (STRTAB)
        sec_hdr(11, SHF_ALLOC, 0x4000, dynsym_off, sym.len() as u64, 3, 24),  // [4] .dynsym -> link 3
        sec_hdr(4, SHF_ALLOC, 0x5000, rela_off, relas.len() as u64, 4, 24),   // [5] .rela.dyn
    ];
    let shoff = 64 + body.len() as u64;

    let mut elf = vec![0u8; 64];
    elf[0..4].copy_from_slice(b"\x7fELF");
    elf[4] = 2; // ELF64
    elf[5] = 1; // little-endian
    w16(&mut elf, 16, 3); // e_type = ET_DYN
    w16(&mut elf, 18, 0x3e); // e_machine = x86-64
    w64(&mut elf, 40, shoff); // e_shoff
    w16(&mut elf, 58, 64); // e_shentsize
    w16(&mut elf, 60, secs.len() as u16); // e_shnum
    elf.extend_from_slice(&body);
    for s in &secs {
        elf.extend_from_slice(s);
    }
    elf
}

fn write(dir: &Path, name: &str, data: &[u8]) {
    fs::create_dir_all(dir).unwrap();
    fs::write(dir.join(name), data).unwrap();
}

fn main() {
    let minimal = minimal_elf();
    // sanity: the minimal seed must actually parse (else it's a poor seed)
    match source2rosetta::elf::CodeImage::from_bytes(minimal.clone()) {
        Ok(_) => println!("minimal_elf() parses OK ({} bytes)", minimal.len()),
        Err(e) => println!("WARNING: minimal_elf() failed to parse: {e}"),
    }

    let mut magic_only = vec![0u8; 64];
    magic_only[0..4].copy_from_slice(b"\x7fELF");
    magic_only[4] = 2;

    // a small real ELF for richer coverage (real symtab/eh_frame/relocs), if one is around
    let real = ["/usr/bin/true", "/bin/true", "/usr/bin/head"]
        .iter()
        .find_map(|p| fs::read(p).ok());

    for t in TARGETS {
        let dir = Path::new("corpus").join(t);
        write(&dir, "minimal.elf", &minimal);
        write(&dir, "magic_only.bin", &magic_only);
        if let Some(r) = &real {
            write(&dir, "real.elf", r);
        }
        println!(
            "seeded corpus/{t}/ ({} files)",
            2 + real.is_some() as usize
        );
    }
}
