Ha Nguyen

74 papers A 9B 8C 2Misc 3Journal 30Unranked 20
YearRankTypeTitle / Venue / Authors
2026 A conf
LAK
Sirui Ren, Ha Nguyen, Kathleen Stürmer, Manuel Hopp, Richard Göllner, Tim Fütterer
2025 J jnl
Briefings Bioinform.
Dao Tran, Ha Nguyen, Van-Dung Pham, Phuong Nguyen, Hung Nguyen Luu, Liem Minh Phan, Christin Blair Destefano, Sai-Ching Jim Yeung, Tin Nguyen
2025 J jnl
Briefings Bioinform.
Dao Tran, Van-Dung Pham, Ha Nguyen, Phi Bya, Aiham Qdaisat, Liem Minh Phan, Sai-Ching Jim Yeung, Tin Nguyen
2025 A conf
LAK
Ha Nguyen, Saerok Park
2025 C conf
ASRU
Salima Mdhaffar, Haroun Elleuch, Chaimae Chellaf, Ha Nguyen, Yannick Estève
2025 J jnl
CoRR
Salima Mdhaffar, Haroun Elleuch, Chaimae Chellaf, Ha Nguyen, Yannick Estève
2025 B conf
DSAA
Muhammad Fawad Akbar Khan, Ludia Eka Feri, Ha Nguyen, Hamid Karimi
2025 Misc conf
IDC
Ravi Sinha, Idris Solola, Rida Munir, Hillary Swanson, Ha Nguyen
2025 J jnl
Br. J. Educ. Technol.
Ha Nguyen, Victoria Nguyen, Sara Ludovise, Rossella Santagata
2024 J jnl
Briefings Bioinform.
Ha Nguyen, Van-Dung Pham, Hung Nguyen, Bang Tran, Juli Petereit, Tin Nguyen
2024 B conf
L@S
Ha Nguyen, Nate Stott, Vicki Allan
2024 J jnl
Briefings Bioinform.
Quang-Huy Nguyen, Ha Nguyen, Edwin C. Oh, Tin Nguyen
2024 conf
LDT
Hillary Swanson, Ha Nguyen, Caroline Lavoie, Trilby Nelson
2024 B conf
DSAA
Muhammad Fawad Akbar Khan, Max Ramsdell, Ha Nguyen, Hamid Karimi
2024 J jnl
Comput. Speech Lang.
Titouan Parcollet, Ha Nguyen, Solène Evain, Marcely Zanon Boito, Adrien Pupier, Salima Mdhaffar, Hang Le, Sina Alisamir, Natalia A. Tomashenko, Marco Dinarelli, Shucong Zhang, Alexandre Allauzen, Maximin Coavoux, Yannick Estève, Mickael Rouvier, Jérôme Goulian, Benjamin Lecouteux, François Portet, Solange Rossato, Fabien Ringeval, Didier Schwab, Laurent Besacier
2024 J jnl
CoRR
Luis Morales-Navarro, Yasmin B. Kafai, Ha Nguyen, Kayla DesPortes, Ralph Vacca, Camillia Matuk, Megan Silander, Anna Amato, Peter J. Woods, Francisco Castro, Mia S. Shaw, Selin Akgün, Christine Greenhow, Antero Garcia
2024 J jnl
Br. J. Educ. Technol.
Fabio Campos, Ha Nguyen, June Ahn, Kara Jackson
2024 J jnl
J. Mach. Learn. Res.
Mirco Ravanelli, Titouan Parcollet, Adel Moumen, Sylvain de Langen, Cem Subakan, Peter Plantinga, Yingzhi Wang, Pooneh Mousavi, Luca Della Libera, Artem Ploujnikov, Francesco Paissan, Davide Borra, Salah Zaiem, Zeyu Zhao, Shucong Zhang, Georgios Karakasidis, Sung-Lin Yeh, Pierre Champion, Aku Rouhe, Rudolf Braun, Florian Mai, Juan Zuluaga-Gomez, Seyed Mahed Mousavi, Andreas Nautsch, Ha Nguyen, Xuechen Liu, Sangeet Sagar, Jarod Duret, Salima Mdhaffar, Gaëlle Laperrière, Mickael Rouvier, Renato De Mori, Yannick Estève
2024 J jnl
Int. J. Asian Bus. Inf. Manag.
Ha Nguyen, Phan Dinh Quyet
2024 B conf
L@S
Ha Nguyen, Victoria Nguyen, Saríah López-Fierro, Sara Ludovise, Rossella Santagata
2024 conf
LDT
Ravi Sinha, Idris Solola, Ha Nguyen, Hillary Swanson, LuEttaMae Lawrence
2024 conf
SIGCSE (1)
Ha Nguyen, Vicki Allan
2023 J jnl
Comput. Educ.
Ha Nguyen, Morgan Diederich
2023 conf
IWSLT@ACL
Sweta Agrawal, Antonios Anastasopoulos, Luisa Bentivogli, Ondrej Bojar, Claudia Borg, Marine Carpuat, Roldano Cattoni, Mauro Cettolo, Mingda Chen, William Chen, Khalid Choukri, Alexandra Chronopoulou, Anna Currey, Thierry Declerck, Qianqian Dong, Kevin Duh, Yannick Estève, Marcello Federico, Souhir Gahbiche, Barry Haddow, Benjamin Hsu, Phu Mon Htut, Hirofumi Inaguma, Dávid Javorský, John Judge, Yasumasa Kano, Tom Ko, Rishu Kumar, Pengwei Li, Xutai Ma, Prashant Mathur, Evgeny Matusov, Paul McNamee, John P. McCrae, Kenton Murray, Maria Nadejde, Satoshi Nakamura, Matteo Negri, Ha Nguyen, Jan Niehues, Xing Niu, Atul Kr. Ojha, John E. Ortega, Proyag Pal, Juan Pino, Lonneke van der Plas, Peter Polák, Elijah Rippeth, Elizabeth Salesky, Jiatong Shi, Matthias Sperber, Sebastian Stüker, Katsuhito Sudoh, Yun Tang, Brian Thompson, Kevin Tran, Marco Turchi, Alex Waibel, Mingxuan Wang, Shinji Watanabe, Rodolfo Zevallos
2023 B conf
L@S
Ha Nguyen, Morgan Diederich
2023 J jnl
CoRR
Titouan Parcollet, Ha Nguyen, Solène Evain, Marcely Zanon Boito, Adrien Pupier, Salima Mdhaffar, Hang Le, Sina Alisamir, Natalia A. Tomashenko, Marco Dinarelli, Shucong Zhang, Alexandre Allauzen, Maximin Coavoux, Yannick Estève, Mickael Rouvier, Jérôme Goulian, Benjamin Lecouteux, François Portet, Solange Rossato, Fabien Ringeval, Didier Schwab, Laurent Besacier
2023 conf
IWSLT@ACL
Antoine Laurent, Souhir Gahbiche, Ha Nguyen, Haroun Elleuch, Fethi Bougares, Antoine Thiol, Hugo Riguidel, Salima Mdhaffar, Gaëlle Laperrière, Lucas Maison, Sameer Khurana, Yannick Estève
2023 J jnl
Comput. Educ.
Ha Nguyen
2023 A conf
INTERSPEECH
Gaëlle Laperrière, Ha Nguyen, Sahar Ghannay, Bassam Jabaian, Yannick Estève
2023 J jnl
CoRR
Gaëlle Laperrière, Ha Nguyen, Sahar Ghannay, Bassam Jabaian, Yannick Estève
2023 conf
ICASSP Workshops
Gaëlle Laperrière, Ha Nguyen, Sahar Ghannay, Bassam Jabaian, Yannick Estève
2023 A conf
LAK
Ha Nguyen
2022 J jnl
Briefings Bioinform.
Zeynab Maghsoudi, Ha Nguyen, Alireza Tavakkoli, Tin Nguyen
2022 J jnl
Mach. Learn.
Ha Nguyen, Hoang Pham, Son Nguyen, Ngo Van Linh, Khoat Than
2022 conf
KSE
Duc Tran, Ha Nguyen, Hung Nguyen, Tin Nguyen
2022
Ha Nguyen
2022 Misc conf
IDC
Ha Nguyen
2022 J jnl
ACM Trans. Comput. Educ.
Sharin Rawhiya Jacob, Jonathan Montoya, Ha Nguyen, Debra J. Richardson, Mark Warschauer
2022 B conf
LREC
Vinh Van Nguyen, Ha Nguyen, Huong Thanh Le, Thai Phuong Nguyen, Tan Van Bui, Luan-Nghia Pham, Anh Tuan Phan, Cong Hoang-Minh Nguyen, Viet-Hong Tran, Anh Huu Tran
2022 A conf
LAK
Ha Nguyen, William Young
2022 J jnl
Br. J. Educ. Technol.
Ha Nguyen
2022 conf
IWSLT@ACL
Marcely Zanon Boito, John Ortega, Hugo Riguidel, Antoine Laurent, Loïc Barrault, Fethi Bougares, Firas Chaabani, Ha Nguyen, Florentin Barbier, Souhir Gahbiche, Yannick Estève
2022 J jnl
CoRR
Marcely Zanon Boito, John Ortega, Hugo Riguidel, Antoine Laurent, Loïc Barrault, Fethi Bougares, Firas Chaabani, Ha Nguyen, Florentin Barbier, Souhir Gahbiche, Yannick Estève
2021 Misc conf
ICASSP
Ha Nguyen, Yannick Estève, Laurent Besacier
2021 J jnl
CoRR
Ha Nguyen, Yannick Estève, Laurent Besacier
2021 A conf
LAK
June Ahn, Fabio Campos, Ha Nguyen, Maria Hays, Jan Morrison
2021 conf
ICQE
Ha Nguyen
2021 A conf
Interspeech
Ha Nguyen, Yannick Estève, Laurent Besacier
2021 J jnl
CoRR
Ha Nguyen, Yannick Estève, Laurent Besacier
2021 J jnl
CoRR
Solène Evain, Ha Nguyen, Hang Le, Marcely Zanon Boito, Salima Mdhaffar, Sina Alisamir, Ziyi Tong, Natalia A. Tomashenko, Marco Dinarelli, Titouan Parcollet, Alexandre Allauzen, Yannick Estève, Benjamin Lecouteux, François Portet, Solange Rossato, Fabien Ringeval, Didier Schwab, Laurent Besacier
2021 J jnl
J. Learn. Anal.
Fabio Campos, June Ahn, Daniela K. Digiacomo, Ha Nguyen, Maria Hays
2021 conf
IWSLT
Hang Le, Florentin Barbier, Ha Nguyen, Natalia A. Tomashenko, Salima Mdhaffar, Souhir Gahbiche, Benjamin Lecouteux, Didier Schwab, Yannick Estève
2021 conf
NeurIPS Datasets and Benchmarks
Solène Evain, Ha Nguyen, Hang Le, Marcely Zanon Boito, Salima Mdhaffar, Sina Alisamir, Ziyi Tong, Natalia A. Tomashenko, Marco Dinarelli, Titouan Parcollet, Alexandre Allauzen, Yannick Estève, Benjamin Lecouteux, François Portet, Solange Rossato, Fabien Ringeval, Didier Schwab, Laurent Besacier
2021 A conf
LAK
June Ahn, Ha Nguyen, Fabio Campos, William Young
2020 conf
ICLS
Ha Nguyen, Kyu Yon Lim, Lily Wu, Christian Fischer, Mark Warschauer
2020 conf
ICQE
Ha Nguyen, June Ahn, Ashlee Belgrave, Jiwon Lee, Lora Cawelti, Ha Eun Kim, Yenda Prado, Rossella Santagata, Adriana Villavicencio
2020 J jnl
ACM Trans. Comput. Educ.
Ninger Zhou, Ha Nguyen, Christian Fischer, Debra J. Richardson, Mark Warschauer
2020 A conf
INTERSPEECH
Ha Nguyen, Fethi Bougares, Natalia A. Tomashenko, Yannick Estève, Laurent Besacier
2020 conf
IWSLT
Maha Elbayad, Ha Nguyen, Fethi Bougares, Natalia A. Tomashenko, Antoine Caubrière, Benjamin Lecouteux, Yannick Estève, Laurent Besacier
2020 J jnl
CoRR
Maha Elbayad, Ha Nguyen, Fethi Bougares, Natalia A. Tomashenko, Antoine Caubrière, Benjamin Lecouteux, Yannick Estève, Laurent Besacier
2020 conf
ICLS
Ha Nguyen, Leiny Garcia, Sharin Rawhiya Jacob, Debra J. Richardson, Mark Warschauer
2020 J jnl
Remote. Sens.
Ngoc Nguyen Tran, Alfredo Huete, Ha Nguyen, Ian Grant, Tomoaki Miura, Xuanlong Ma, Alexei I. Lyapustin, Yujie Wang, Elizabeth Ebert
2020 conf
RESPECT
Ha Nguyen, Leiny Garcia, Sharin Rawhiya Jacob, Debra J. Richardson, Mark Warschauer
2020 conf
RESPECT
Sharin Rawhiya Jacob, Ha Nguyen, Leiny Garcia, Debra J. Richardson, Mark Warschauer
2020 J jnl
Comput. Electron. Agric.
Ha Nguyen, Tuan Nguyen, Ngoc Hoang, Dung Bui, Huong Vu, Tap Van
2020 B conf
L@S
Ha Nguyen, June Ahn, William Young, Fabio Campos
2019 C conf
IGARSS
Alfredo Huete, Ngoc Nguyen Tran, Ha Nguyen, Qiaoyun Xie, Constance Katelaris
2019 conf
IWSLT
Ha Nguyen
2019 J jnl
CoRR
Ha Nguyen, Natalia A. Tomashenko, Marcely Zanon Boito, Antoine Caubrière, Fethi Bougares, Mickael Rouvier, Laurent Besacier, Yannick Estève
2015 conf
ITSC
Duc-Thinh Pham, Bao An Mai Hoang, Son Nguyen Thanh, Ha Nguyen, Vu N. Duong
2014
Ha Nguyen
2013 J jnl
CoRR
Markus Gusenbauer, Ha Nguyen, Franz Reichel, Lukas Exl, Simon Bance, Johann Fischbacher, Harald Özelt, Alexander Kovacs, Martin Brandl, Thomas Schrefl
2008 conf
SLTU
Khoa Trinh, Ha Nguyen, Duc Duong, Quan Vu
2008 B conf
PRICAI
Ha Nguyen, Luis Weruaga
redb/extractors/decompiler/DecompileBinja.py
← Index redb/extractors/decompiler/DecompileBinja.py python
import hashlib
import inspect
import json
import logging
import os
import signal
import time
from datetime import datetime, timezone
from typing import Dict, Any, Optional
from pathlib import Path
import subprocess
import sys

from redb.extractors.enum import Tag
from redb.extractors.extractor import Extractor
import magic
import pefile
from elftools.elf.elffile import ELFFile

# Import our BinjaDecompiler (conditional)
from redb.extractors.decompiler.bninja.decompiler import BinaryNinjaDecompiler

class DecompileBinja(Extractor):
    def __init__(
        self,
        filepath,
        log,
        exporters=None,
        index_prefix=None,
        elastic_index=None,
        known_benign=False,
        known_malicious=False,
        filetype=None,
        decompile_modules=None,
    ):
        super().__init__(
            filepath,
            log,
            exporters,
            index_prefix,
            elastic_index,
            known_benign,
            known_malicious,
        )
        self.log.debug(inspect.currentframe().f_code.co_name)

        # Check if Binary Ninja is available
       # if not BINARYNINJA_AVAILABLE:
        #     self.log.error("Binary Ninja is not available in this container")
        #    raise ImportError(
        #        "Binary Ninja module not found - not available in feature extraction container"
        #    )
        self.analysis_results = None
        self.binja_decompiler = None
        self.filetype = filetype
        self.decompile_modules = decompile_modules or {"all"}
        self.goresym_data = None
        self.goresym_output_path = None

        # Convert TIMEOUT to integer with a default of 1200 seconds (20 minutes)
        try:
            self.BINJA_TIMEOUT = int(os.getenv("BINJA_TIMEOUT", "1200"))
        except ValueError:
            self.log.warning(
                "Invalid BINJA_TIMEOUT value, using default of 1200 seconds"
            )
            self.BINJA_TIMEOUT = 1200

        # Convert TIMEOUT to integer with a default of 1200 seconds (20 minutes)
        try:
            self.DECOMPILE_EXTRACTOR_TIMEOUT = int(
                os.getenv("DECOMPILE_EXTRACTOR_TIMEOUT", "2580")
            )
        except ValueError:
            self.log.warning(
                "Invalid DECOMPILE_EXTRACTOR_TIMEOUT value, using default of 2580 seconds"
            )
            self.DECOMPILE_EXTRACTOR_TIMEOUT = 2580

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.cleanup_run()

    def calculate_md5(self, input_str):
        """Calculate MD5 hash of a string."""
        return hashlib.md5(input_str.encode("utf-8")).hexdigest()

    def is_dotnet(self):
        """Check if the binary is a .NET assembly.

        Returns:
            bool: True if the file is a .NET assembly, False otherwise
        """
        try:
            if self.filetype == "pebin":
                file_type = magic.from_buffer(self.binary)
                if ".Net" in file_type:
                    return True
                pe = pefile.PE(self.filepath)
                for entry in pe.OPTIONAL_HEADER.DATA_DIRECTORY:
                    # IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR is typically 14
                    if (
                        entry.name == "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR"
                        and entry.Size > 0
                    ):
                        return True
                return False
            return False
        except AttributeError as e:
            self.log.error(
                f"AttributeError error dotnet file {self.hash.sha256} Full error : {e}"
            )
            return False

    def is_golang(self):
        """Check if the binary is a Go-compiled binary (heuristic).

        Supports PE and ELF binaries.
        """
        try:
            if self.filetype == "pebin":
                pe = pefile.PE(self.filepath)
                signatures = [b"Go build ID:", b"runtime.main", b"main.main"]

                for section in pe.sections:
                    data = section.get_data()
                    if any(sig in data for sig in signatures):
                        return True

                return False

            elif self.filetype == "elf":
                with open(self.filepath, "rb") as f:
                    elf = ELFFile(f)

                    # 1. Section-based checks
                    section_names = [sec.name for sec in elf.iter_sections()]
                    if any(
                        s in section_names
                        for s in (".note.go.buildid", ".gopclntab")
                    ):
                        return True

                    # 2. String scan in loadable sections
                    signatures = [
                        b"Go build ID:",
                        b"runtime.main",
                        b"runtime.goexit",
                        b"runtime.morestack",
                        b"main.main",
                    ]

                    for sec in elf.iter_sections():
                        if sec["sh_flags"] & 0x2:  # SHF_ALLOC
                            data = sec.data()
                            if any(sig in data for sig in signatures):
                                return True

                return False

            return False

        except Exception as e:
            self.log.error(
                f"Golang detection error {self.hash.sha256}: {e}"
            )
            return False

    def cleanup_run(self):
        """Clean up after analysis."""
        try:
            # BinaryNinjaDecompiler uses context manager pattern (__enter__/__exit__)
            # Cleanup happens automatically when exiting the 'with' block
            self.binja_decompiler = None

            # Clean up goresym temp file if it exists (keep in debug mode)
            if self.goresym_output_path and os.path.exists(self.goresym_output_path):
                if self.log.isEnabledFor(logging.DEBUG):
                    self.log.debug(f"Debug mode: keeping goresym output at {self.goresym_output_path}")
                else:
                    os.remove(self.goresym_output_path)
                    self.goresym_output_path = None

            # Force garbage collection
            import gc

            gc.collect()

        except Exception as e:
            self.log.error(f"Error in cleanup: {e}")

    def run_goresym(self, binary_path, output_json_path):
        """
            Run goresym on a Go binary and export its JSON output to a file.
            binary_path: path to the Go binary to analyze
            output_json_path: path where the JSON output will be saved
            """
        binary_path = str(Path(binary_path).resolve())
        output_json_path = str(Path(output_json_path).resolve())
        goresym_path = os.getenv("GORESYM_PATH", "GoReSym")

        try:
            # Example: goresym -t json /path/to/binary
            self.log.info("DEBUG: starting GoReSym")
            result = subprocess.run(
                [goresym_path, binary_path],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
            )
        except FileNotFoundError:
            self.log.error("Error: 'goresym' not found in PATH. Make sure it is installed.")
            raise
        except subprocess.CalledProcessError as e:
            self.log.error("Error during goresym execution:")
            self.log.error(e.stderr)


        # Assuming goresym emits valid JSON to stdout.
        try:
            parsed = json.loads(result.stdout)
            # Store parsed JSON for later export to ClickHouse
            self.goresym_data = parsed
        except json.JSONDecodeError:
            # If it's not valid JSON, save the raw output instead.
            self.log.error("Warning: goresym output is not valid JSON; saving raw.")
            with open(output_json_path, "w", encoding="utf-8") as f:
                f.write(result.stdout)
            return

        # Save pretty-printed JSON for readability and debugging (used by BinaryNinja)
        with open(output_json_path, "w", encoding="utf-8") as f:
            json.dump(parsed, f, ensure_ascii=False, indent=2)
        self.goresym_output_path = output_json_path

        self.log.debug(f"goresym output saved to: {output_json_path}")

    def analyze_binary(self) -> Optional[Dict[str, Any]]:
        """Run Binary Ninja analysis and return results."""
        self.log.debug("Starting binary analysis")
        # if the binary is dotnet (only PE)
        if self.is_dotnet():
            self.log.debug("Skipping .NET binary - decompilation not supported")
            return None

        output_json_path = None
        ## if golang: run goresym
        try:
            if self.is_golang():
                output_json_path = "./goResym.json"
                self.run_goresym(self.filepath, output_json_path)
        except Exception as e:
            self.log.error(f"Error on GoReSym extraction: {e}")

        try:
            # Use BinaryNinjaDecompiler as a context manager to ensure proper setup/cleanup
            with BinaryNinjaDecompiler(
                filepath=self.filepath,
                timeout=self.BINJA_TIMEOUT,
                log=self.log,
                exporters=self.exporters,
                index_prefix=self.index_prefix,
                filetype=self.filetype,
                goresym=output_json_path,
                decompile_modules=self.decompile_modules,
            ) as decompiler:
                self.binja_decompiler = decompiler

                if decompiler.extract():
                    # Store results before context manager exits
                    results = decompiler.analysis_results
                    return results
                else:
                    self.log.error("BinaryNinjaDecompiler extraction failed")
                    return None

        except Exception as e:
            self.log.error(f"Error in Binary Ninja analysis: {e}")
            import traceback
            self.log.error(f"Traceback: {traceback.format_exc()}")
            return None

        finally:
            self.cleanup_run()

    def extract(self):
        """Extract and process all analysis results."""
        self.log.debug(inspect.currentframe().f_code.co_name)

        # Create a flag to track if extraction completed
        extraction_completed = False
        extraction_result = False
        extraction_error = None

        # Define the extraction process as a separate function
        def do_extraction():
            nonlocal extraction_completed, extraction_result, extraction_error
            try:
                results = self.analyze_binary()
                if not results:
                    extraction_result = False
                else:
                    self.analysis_results = results
                    # Add file hashes from parent Extractor class to analysis results
                    self.analysis_results["sha256"] = self.sha256
                    self.analysis_results["sha1"] = self.sha1
                    self.analysis_results["md5"] = self.md5
                    extraction_result = True
            except Exception as e:
                extraction_error = e
                extraction_result = False
            finally:
                extraction_completed = True

        # Run extraction directly with signal-based timeout (no thread overhead).
        # SIGALRM is delivered by the OS, so there's no GIL contention or polling.
        old_handler = signal.getsignal(signal.SIGALRM)
        def _timeout_handler(signum, frame):
            raise TimeoutError("Extraction timed out")

        signal.signal(signal.SIGALRM, _timeout_handler)
        signal.alarm(self.DECOMPILE_EXTRACTOR_TIMEOUT)
        try:
            do_extraction()
        except TimeoutError:
            self.log.error(
                f"Extraction timed out after {self.DECOMPILE_EXTRACTOR_TIMEOUT} seconds"
            )
            self.cleanup_run()
            return None
        finally:
            signal.alarm(0)
            signal.signal(signal.SIGALRM, old_handler)

        if extraction_error:
            self.log.error(f"Error in extraction: {extraction_error}")
            return None

        # Return the actual analysis results, not just a boolean
        return self.analysis_results if extraction_result else None

    def prepare_export_data(self, exporter_type: str) -> Any:
        """Prepare data for database export."""
        self.log.debug(inspect.currentframe().f_code.co_name)
        if not self.analysis_results:
            return None

        # # Delegate to the BinjaDecompiler for consistent export formatting
        # if self.binja_decompiler:
        #     return self.binja_decompiler.prepare_export_data(exporter_type)
        # else:
        #     self.log.error("BinjaDecompiler not available for export preparation")
        #     return None

        if exporter_type == "ClickHouseExporter":
            now = datetime.now(timezone.utc)

            def prepare_array_field(value, array_type):
                """Helper to prepare array fields with proper null handling"""
                if value is None:
                    return []
                return value

            # Add a helper function to handle empty strings
            def ensure_not_empty(value, default="UNKNOWN"):
                """Ensure a string value is not empty"""
                if value is None or value == "":
                    return default
                return value

            def prepare_register_usage_map(register_dict):
                """Convert register usage dict to Map format with tuples
                Input: {"rbx": {"reads": 3, "writes": 1}, ...}
                Output: {"rbx": (3, 1), ...}
                """
                if not register_dict:
                    return {}
                return {
                    reg: (info.get("reads", 0), info.get("writes", 0))
                    for reg, info in register_dict.items()
                }


            decompile_modules = getattr(self, "decompile_modules", {"all"})
            run_all = "all" in decompile_modules
            run_decompilation = run_all or "decompilation" in decompile_modules
            run_disassembly = run_all or "disassembly" in decompile_modules
            run_llil = run_all or "llil" in decompile_modules
            run_cfg = run_all or "cfg" in decompile_modules
            run_strings = run_all or "strings" in decompile_modules

            export = {"multi_table": True}

            # Decompilation tables
            if run_decompilation:
                export["decompiled_content"] = {
                    "table": "code_binja_decompiled_functions_content",
                    "data": [
                        [
                            f["decompiled_function_hash"],
                            f["decompiled_function"],
                            f["function_type"],
                            f.get("flattened_score"),
                            f.get("mba_score"),
                            now,
                        ]
                        for f in self.analysis_results["decompiled"]
                    ],
                    "column_names": [
                        "decompiled_function_hash",
                        "decompiled_function",
                        "function_type",
                        "flattened_score",
                        "mba_score",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'THUNK'=3, 'EXTERNAL'=4, 'UNKNOWN'=5)",
                        "Nullable(Float64)",
                        "Nullable(Float64)",
                        "DateTime64(3, 'UTC')",
                    ],
                }
                export["decompiled_refs"] = {
                    "table": "code_binja_decompiled_functions_references",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            self.analysis_results["sha1"],
                            self.analysis_results["md5"],
                            f["decompiled_function_hash"],
                            f.get("disassembled_function_hash"),
                            f["decompiled_function_name"],
                            f["decompiled_function_prototype"],
                            f["decompiled_function_address"],
                            prepare_array_field(f.get("functions_caller"), "Array(String)"),
                            prepare_array_field(f.get("functions_call"), "Array(String)"),
                            now,
                        ]
                        for f in self.analysis_results["decompiled"]
                    ],
                    "column_names": [
                        "sha256",
                        "sha1",
                        "md5",
                        "decompiled_function_hash",
                        "disassembled_function_hash",
                        "decompiled_function_name",
                        "decompiled_function_prototype",
                        "decompiled_function_address",
                        "functions_caller",
                        "functions_call",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(40)",
                        "FixedString(32)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",
                        "LowCardinality(String)",
                        "LowCardinality(String)",
                        "UInt64",
                        "Array(String)",
                        "Array(String)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Disassembly tables
            if run_disassembly:
                export["disassembled_content"] = {
                    "table": "code_binja_disassembled_functions_content",
                    "data": [
                        [
                            f["disassembled_function_hash"],
                            f.get("disassembled_function", ""),
                            f.get("disassembled_function_no_addresses", ""),
                            f.get("function_type", "UNKNOWN"),
                            f.get("instructions_count", 0),
                            prepare_array_field(
                                f.get("instructions_types"), "LowCardinality(String)"
                            ),
                            f.get("control_flow_count", 0),
                            prepare_array_field(
                                f.get("memory_access_pattern"), "LowCardinality(String)"
                            ),
                            prepare_array_field(
                                f.get("register_usage"), "LowCardinality(String)"
                            ),
                            f.get("data_references_count", 0),
                            f.get("max_block_size"),
                            f.get("num_calls"),
                            f.get("stack_size"),
                            now,
                        ]
                        for f in self.analysis_results["disassembled"]
                    ],
                    "column_names": [
                        "disassembled_function_hash",
                        "disassembled_function",
                        "disassembled_function_no_addresses",
                        "function_type",
                        "instructions_count",
                        "instructions_types",
                        "control_flow_count",
                        "memory_access_pattern",
                        "register_usage",
                        "data_references_count",
                        "max_block_size",
                        "num_calls",
                        "stack_size",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "String",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'THUNK'=3, 'EXTERNAL'=4, 'UNKNOWN'=5)",
                        "UInt32",
                        "Array(LowCardinality(String))",
                        "UInt32",
                        "Array(LowCardinality(String))",
                        "Array(LowCardinality(String))",
                        "UInt32",
                        "Nullable(UInt32)",
                        "Nullable(UInt32)",
                        "Nullable(Int32)",
                        "DateTime64(3, 'UTC')",
                    ],
                }
                export["disassembled_refs"] = {
                    "table": "code_binja_disassembled_functions_references",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            self.analysis_results["sha1"],
                            self.analysis_results["md5"],
                            f["disassembled_function_hash"],
                            f.get("decompiled_function_hash"),
                            f["disassembled_function_name"],
                            f["disassembled_function_address"],
                            f.get("tlsh_disassembly"),
                            f.get("tlsh_llil"),
                            now,
                        ]
                        for f in self.analysis_results["disassembled"]
                    ],
                    "column_names": [
                        "sha256",
                        "sha1",
                        "md5",
                        "disassembled_function_hash",
                        "decompiled_function_hash",
                        "disassembled_function_name",
                        "disassembled_function_address",
                        "tlsh_disassembly",
                        "tlsh_llil",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(40)",
                        "FixedString(32)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",
                        "LowCardinality(String)",
                        "UInt64",
                        "Nullable(FixedString(72))",
                        "Nullable(FixedString(72))",
                        "DateTime64(3, 'UTC')",
                    ],
                }
                # Function similarity metrics table (derived from disassembly data)
                # Lookup maps to join LLIL/MLIL features by disassembled_function_hash
                llil_by_hash = {
                    l.get("disassembled_function_hash"): l
                    for l in self.analysis_results.get("llil", [])
                    if l and l.get("disassembled_function_hash")
                }
                mlil_by_hash = {
                    m.get("disassembled_function_hash"): m
                    for m in self.analysis_results.get("mlil", [])
                    if m and m.get("disassembled_function_hash")
                }

                export["function_similarity_metrics"] = {
                    "table": "code_binja_function_similarity_metrics",
                    "data": [
                        [
                            f["disassembled_function_hash"],
                            f.get("cyclomatic_complexity"),
                            f.get("tlsh_disassembly"),
                            f.get("tlsh_llil"),
                            prepare_array_field(f.get("minhash"), "Array(UInt8)"),
                            (llil_by_hash.get(f["disassembled_function_hash"]) or {}).get("tlsh_llil"),
                            (llil_by_hash.get(f["disassembled_function_hash"]) or {}).get(
                                "tlsh_instruction_typed_llil"),
                            prepare_array_field(
                                (llil_by_hash.get(f["disassembled_function_hash"]) or {}).get("minhash_llil_skeleton"),
                                "Array(UInt8)",
                            ),
                            prepare_array_field(
                                (llil_by_hash.get(f["disassembled_function_hash"]) or {}).get("minhash_llil_typed"),
                                "Array(UInt8)",
                            ),
                            (mlil_by_hash.get(f["disassembled_function_hash"]) or {}).get("tlsh_mlil_skeleton"),
                            (mlil_by_hash.get(f["disassembled_function_hash"]) or {}).get("tlsh_mlil_typed"),
                            prepare_array_field(
                                (mlil_by_hash.get(f["disassembled_function_hash"]) or {}).get("minhash_mlil_skeleton"),
                                "Array(UInt8)",
                            ),
                            prepare_array_field(
                                (mlil_by_hash.get(f["disassembled_function_hash"]) or {}).get("minhash_mlil_typed"),
                                "Array(UInt8)",
                            ),
                            now,
                        ]
                        for f in self.analysis_results.get("disassembled", [])
                    ],
                    "column_names": [
                        "disassembled_function_hash",
                        "cyclomatic_complexity",
                        "tlsh_disassembly",
                        "tlsh_llil",
                        "minhash",
                        "tlsh_llil_new",
                        "tlsh_instruction_typed_llil",
                        "minhash_llil_skeleton",
                        "minhash_llil_typed",
                        "tlsh_mlil_skeleton",
                        "tlsh_mlil_typed",
                        "minhash_mlil_skeleton",
                        "minhash_mlil_typed",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "Nullable(UInt16)",
                        "Nullable(FixedString(72))",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        # new
                        "Nullable(FixedString(72))",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        "Array(UInt8)",
                        "Nullable(FixedString(72))",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        "Array(UInt8)",
                        "DateTime64(3, 'UTC')",
                    ],
                }


            # LLIL tables
            if run_llil:
                export["llil_content"] = {
                    "table": "code_binja_llil_functions_content",
                    "data": [
                        [
                            f["sha256_llil"],
                            f["function_type"],
                            prepare_array_field(
                                f.get("instructions_types_llil"), "LowCardinality(String)"
                            ),
                            f.get("control_flow_count_llil", 0),
                            prepare_array_field(
                                f.get("memory_access_pattern_llil"), "LowCardinality(String)"
                            ),
                            prepare_register_usage_map(f.get("register_usage", {})),
                            f.get("total_reg_reads", 0),
                            f.get("total_reg_written", 0),
                            f.get("data_references_count", 0),
                            f.get("max_block_size"),
                            f.get("num_calls"),
                            f.get("stack_size"),
                            prepare_array_field(f.get("body_llil_vector"), "Array(Tuple(UInt32, Array(UInt16)))"),
                            now,
                        ]
                        for f in self.analysis_results.get("llil", [])
                    ],
                    "column_names": [
                        "llil_function_hash",
                        "function_type",
                        "instructions_types_llil",
                        "control_flow_count_llil",
                        "memory_access_pattern_llil",
                        "register_usage_llil",
                        "total_reg_reads",
                        "total_reg_written",
                        "data_references_count",
                        "max_block_size",
                        "num_calls",
                        "stack_size",
                        "body_llil_vector",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'THUNK'=3, 'EXTERNAL'=4, 'UNKNOWN'=5)",
                        "Array(LowCardinality(String))",
                        "UInt32",
                        "Array(LowCardinality(String))",
                        "Map(LowCardinality(String), Tuple(UInt32, UInt32))",
                        "UInt32",
                        "UInt32",
                        "UInt32",
                        "Nullable(UInt32)",
                        "Nullable(UInt32)",
                        "Nullable(Int32)",
                        "Array(Tuple(UInt32, Array(UInt16)))",
                        "DateTime64(3, 'UTC')",
                    ],
                }
                export["llil_refs"] = {
                    "table": "code_binja_llil_functions_references",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            self.analysis_results["sha1"],
                            self.analysis_results["md5"],
                            f.get("sha256_llil"),
                            f.get("disassembled_function_hash"),
                            f.get("function_address"),
                            f.get("tlsh_disassembly"),
                            f.get("tlsh_llil"),
                            now,
                        ]
                        for f in self.analysis_results.get("llil", [])
                    ],
                    "column_names": [
                        "sha256",
                        "sha1",
                        "md5",
                        "llil_function_hash",
                        "disassembled_function_hash",
                        "function_address",
                        "tlsh_disassembly",
                        "tlsh_llil",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(40)",
                        "FixedString(32)",
                        "Nullable(FixedString(64))",
                        "FixedString(64)",
                        "UInt64",
                        "Nullable(FixedString(72))",
                        "Nullable(FixedString(72))",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Errors table (always include if per-function loop ran)
            if run_decompilation or run_disassembly or run_llil or run_cfg:
                export["function_analysis_errors"] = {
                    "table": "new_function_analysis_errors_binja",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            f["function_name"],
                            f["function_address"],
                            f.get("error_location", "unknown"),
                            f.get("error_message", ""),
                            f.get("error_details", ""),
                            f.get("error_type", "unknown"),
                            self.calculate_md5(
                                f"{f.get('error_message', '')}{f['function_name']}{f['function_address']}{f.get('error_location', 'unknown')}"
                            ),
                            "new",
                            now,
                        ]
                        for f in self.analysis_results.get("errors", [])
                    ],
                    "column_names": [
                        "sha256",
                        "function_name",
                        "function_address",
                        "error_location",
                        "error_message",
                        "error_details",
                        "error_type",
                        "error_hash",
                        "status",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "Nullable(String)",
                        "UInt64",
                        "LowCardinality(String)",
                        "Nullable(String)",
                        "Nullable(String)",
                        "Nullable(String)",
                        "FixedString(32)",
                        "Enum8('new'=1, 'investigating'=2, 'fixed'=3, 'wontfix'=4)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # Strings table
            if run_strings:
                export["strings_raw"] = {
                    "table": "code_binja_strings_raw",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            s["string"],
                            s["string_raw"],
                            s["string_encoding"],
                            s["string_offset"],
                            s["string_length"],
                            s["string_raw_length"],
                            s["string_entropy"],
                        ]
                        for s in self.analysis_results.get("strings", [])
                    ],
                    "column_names": [
                        "sha256",
                        "string",
                        "string_raw",
                        "string_encoding",
                        "string_offset",
                        "string_length",
                        "string_raw_length",
                        "string_entropy",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "String",
                        "LowCardinality(String)",
                        "UInt64",
                        "UInt32",
                        "UInt32",
                        "Float32",
                    ],
                }

            # CFG function-level features table
            if run_cfg:
                export["cfg_functions"] = {
                    "table": "code_binja_cfg_functions",
                    "data": [
                        [
                            cfg.get("disassembled_function_hash"),
                            cfg["cfg_topology_hash"],
                            cfg["block_count"],
                            cfg["edge_count"],
                            cfg.get("llil_total_operations", 0),
                            cfg.get("call_count", 0),
                            cfg["cyclomatic_complexity"],
                            cfg.get("loop_count", 0),
                            cfg.get("max_depth", 0),
                            cfg.get("max_fan_out", 0),
                            cfg.get("md_index_topdown", 0),
                            cfg.get("md_index_bottomup", 0),
                            cfg.get("prime_product_llil", 0),
                            cfg.get("cfg_feature_tlsh"),
                            cfg.get("wl_minhash", []),
                            cfg.get("bb_features", []),
                            cfg.get("cfg_adjacency", []),
                            now,
                        ]
                        for cfg in self.analysis_results.get("cfg", [])
                        if cfg is not None
                    ],
                    "column_names": [
                        "disassembled_function_hash",
                        "cfg_topology_hash",
                        "block_count",
                        "edge_count",
                        "llil_total_operations",
                        "call_count",
                        "cyclomatic_complexity",
                        "loop_count",
                        "max_depth",
                        "max_fan_out",
                        "md_index_topdown",
                        "md_index_bottomup",
                        "prime_product_llil",
                        "cfg_feature_tlsh",
                        "wl_minhash",
                        "bb_features",
                        "cfg_adjacency",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(16)",
                        "UInt16",
                        "UInt16",
                        "UInt32",
                        "UInt16",
                        "UInt16",
                        "UInt8",
                        "UInt16",
                        "UInt8",
                        "UInt64",
                        "UInt64",
                        "UInt64",
                        "Nullable(FixedString(72))",
                        "Array(UInt8)",
                        "Array(Array(UInt16))",
                        "Array(UInt32)",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            # GoReSym metadata table (only if goresym data exists)
            if self.goresym_data:
                export["golang_metadata"] = {
                    "table": "redb_golang_metadata",
                    "data": [
                        [
                            self.analysis_results["sha256"],
                            json.dumps(self.goresym_data),
                            now,
                        ]
                    ],
                    "column_names": [
                        "sha256",
                        "goresym",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "JSON",
                        "DateTime64(3, 'UTC')",
                    ],
                }

            return export

    def tag(self) -> str:
        """Return the tag for this extractor."""
        return Tag.DECOMPILED.value

    def get_clickhouse_table(self) -> str:
        """Not used directly as we're handling multiple tables."""
        pass


if __name__ == "__main__":
    # Setup basic logging
    import logging
    import time

    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger("DecompileBinja")

    # Parse command line arguments
    import argparse

    parser = argparse.ArgumentParser(description="Binary Ninja Decompiler Wrapper")
    parser.add_argument("filepath", help="Path to the binary file to analyze")
    parser.add_argument(
        "--output", "-o", help="Output JSON file path (default: stdout)"
    )
    parser.add_argument(
        "--timeout",
        "-t",
        type=int,
        default=1200,
        help="Analysis timeout in seconds (default: 1200)",
    )
    args = parser.parse_args()
    start = time.perf_counter()
    # Create and run the extractor
    with DecompileBinja(args.filepath, logger) as extractor:
        success = extractor.extract()
        end = time.perf_counter()
        if not success:
            logger.error("Analysis failed")
            exit(1)

        # Output results
        if args.output:
            with open(args.output, "w") as f:
                json.dump(extractor.analysis_results, f)
            logger.info(f"Results written to {args.output}")
        else:
            print((extractor.analysis_results))
            with open("diff", "w") as f:
                f.write(str(f"{end - start:.3f} seconds"))