//
// Syd: rock-solid application kernel
// src/utils/syd-asm.rs: Disassemble raw instructions using objdump
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    io::{stdin, Read},
    os::unix::ffi::OsStrExt,
    process::ExitCode,
    str::FromStr,
};

use data_encoding::HEXLOWER_PERMISSIVE;
use libseccomp::ScmpArch;
use nix::{errno::Errno, sys::utsname::uname};
use syd::asm::disasm;

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    not(target_arch = "riscv64"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

// Set global allocator to tcmalloc if profiling is enabled.
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;

syd::main! {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    let mut opt_arch = ScmpArch::Native; // -a

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('a') => {
                let value = parser.value()?.parse::<String>()?;
                if matches!(value.to_ascii_lowercase().as_str(), "help" | "list") {
                    syd::confine::print_seccomp_architectures();
                    return Ok(ExitCode::SUCCESS);
                }
                opt_arch = match ScmpArch::from_str(&format!(
                    "SCMP_ARCH_{}",
                    value.to_ascii_uppercase()
                )) {
                    Ok(opt_arch) => opt_arch,
                    Err(_) => {
                        eprintln!("Invalid architecture `{value}', use `-a list' for a list.");
                        return Ok(ExitCode::FAILURE);
                    }
                };
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    if opt_arch == ScmpArch::Native {
        opt_arch = determine_arch()?;
    }

    let mut input = Vec::with_capacity(4096);
    stdin().read_to_end(&mut input)?;

    // Auto-detect hexadecimal input and decode as necessary.
    let hex = input
        .iter()
        .take(input.len().min(16))
        .all(|&b| b.is_ascii_whitespace() || b.is_ascii_hexdigit());
    if hex {
        let data = std::str::from_utf8(&input)?;
        let data = data.split_whitespace().collect::<String>();
        input = match HEXLOWER_PERMISSIVE.decode(data.as_bytes()) {
            Ok(input) => input,
            Err(error) => {
                eprintln!("Error decoding hex: {error}");
                return Ok(ExitCode::FAILURE);
            }
        }
    }

    let instructions = disasm(&input, opt_arch, 0, false, true)?;
    for instruction in &instructions {
        println!(
            "{}",
            serde_json::to_string(&instruction).or(Err(Errno::EINVAL))?
        );
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-asm [-h] [-a list|x86|x86_64|aarch64...]");
    println!("Disassembles raw CPU instructions from standard input.");
}

// Determine the architecture using `uname`.
fn determine_arch() -> Result<ScmpArch, Errno> {
    let uts = uname()?;
    let machine = uts.machine().as_bytes();

    match machine {
        b"x86_64" => Ok(ScmpArch::X8664),
        b"i386" | b"i686" => Ok(ScmpArch::X86),
        b"armv7l" | b"arm" => Ok(ScmpArch::Arm),
        b"aarch64" => Ok(ScmpArch::Aarch64),
        b"loongarch64" => Ok(ScmpArch::Loongarch64),
        b"m68k" => Ok(ScmpArch::M68k),
        b"mips" => Ok(ScmpArch::Mips),
        b"mips64" => Ok(ScmpArch::Mips64),
        b"riscv64" => Ok(ScmpArch::Riscv64),
        b"ppc64" => Ok(ScmpArch::Ppc64),
        b"ppc64le" => Ok(ScmpArch::Ppc64Le),
        b"s390x" => Ok(ScmpArch::S390X),
        b"sheb" => Ok(ScmpArch::Sheb),
        b"sh" => Ok(ScmpArch::Sh),
        _ => Err(Errno::EINVAL),
    }
}
