Manuchehr Soleimani

61 papers A* 2Misc 2Journal 54Unranked 3
YearRankTypeTitle / Venue / Authors
2025 J jnl
IEEE Trans. Instrum. Meas.
Xinyu Bi, Manuchehr Soleimani, Lu Ma
2025 J jnl
IEEE Trans. Instrum. Meas.
Yimin Wu, Yandan Jiang, Haifeng Ji, Baoliang Wang, Manuchehr Soleimani
2025 A* conf
MobiCom
Grzegorz Klosowski, Tomasz Rymarczyk, Konrad Niderla, Marcin Kowalski, Manuchehr Soleimani
2025 J jnl
IEEE Trans. Instrum. Meas.
Rinki Goyal, Tomasz Rymarczyk, Manuchehr Soleimani
2024 J jnl
IEEE Trans. Instrum. Meas.
Huaiyin Zhu, Baoliang Wang, Manuchehr Soleimani, Haifeng Ji, Yandan Jiang
2024 conf
IEEE SENSORS
Yuxi Guo, Manuchehr Soleimani, Maomao Zhang
2024 Misc conf
SenSys
Grzegorz Klosowski, Tomasz Rymarczyk, Manuchehr Soleimani, Konrad Niderla
2024 J jnl
IEEE Trans. Instrum. Meas.
Manuchehr Soleimani, Tomasz Rymarczyk, Grzegorz Klosowski
2023 J jnl
Sensors
Manuchehr Soleimani, Tomasz Rymarczyk
2023 J jnl
Sensors
Bartlomiej Baran, Edward Kozlowski, Dariusz Majerek, Tomasz Rymarczyk, Manuchehr Soleimani, Dariusz Wójcik
2023 A* conf
MobiCom
Grzegorz Klosowski, Tomasz Rymarczyk, Manuchehr Soleimani
2023 J jnl
IEEE Trans. Instrum. Meas.
Gavin Dingley, Mackenzie Cox, Manuchehr Soleimani
2023 J jnl
Sensors
Yandan Jiang, Manuchehr Soleimani, Guanghui Liang
2023 J jnl
IEEE Trans. Instrum. Meas.
Liying Zhu, Wu Lu, Manuchehr Soleimani, Zhe Li, Maomao Zhang
2023 J jnl
Entropy
Zenglan Su, Manuchehr Soleimani, Yandan Jiang, Haifeng Ji, Baoliang Wang
2023 Misc conf
SenSys
Grzegorz Klosowski, Tomasz Rymarczyk, Manuchehr Soleimani
2023 J jnl
IEEE Trans. Ind. Informatics
Hao Liu, Chao Tan, Manuchehr Soleimani, Feng Dong
2022 J jnl
IEEE Trans. Instrum. Meas.
Panagiotis Koulountzios, Tomasz Rymarczyk, Manuchehr Soleimani
2022 J jnl
Sensors
Uwe Hampel, Laurent Babout, Robert Banasiak, Eckhard Schleicher, Manuchehr Soleimani, Thomas Wondrak, Marko Vauhkonen, Timo Lähivaara, Chao Tan, Brian Hoyle, Alexander Penn
2022 J jnl
Sensors
Manuchehr Soleimani, Myron Friedrich
2022 J jnl
Sensors
Ivan Glavinic, Imamul Muttakin, Shereen S. Abouelazayem, Artem Blishchik, Frank Stefani, Sven Eckert, Manuchehr Soleimani, Iheb Saidani, Jaroslav Hlava, Sasa Kenjeres, Thomas Wondrak
2022 J jnl
IEEE Trans. Instrum. Meas.
Yandan Jiang, Junchao Huang, Haifeng Ji, Baoliang Wang, Zhiyao Huang, Manuchehr Soleimani
2021 J jnl
IEEE Trans. Instrum. Meas.
Zhen Xu, Junchao Huang, Yandan Jiang, Baoliang Wang, Zhiyao Huang, Manuchehr Soleimani
2021 J jnl
Sensors
Panagiotis Koulountzios, Soheil Aghajanian, Tomasz Rymarczyk, Tuomas Koiranen, Manuchehr Soleimani
2021 J jnl
Sensors
Manasavee Lohvithee, Wenjuan Sun, Stéphane Chrétien, Manuchehr Soleimani
2021 J jnl
Sensors
Gavin Dingley, Manuchehr Soleimani
2021 J jnl
IEEE Trans. Instrum. Meas.
Maomao Zhang, Liying Zhu, Haokun Wang, Yi Li, Manuchehr Soleimani, Yunjie Yang
2021 J jnl
IEEE Trans. Instrum. Meas.
Imamul Muttakin, Manuchehr Soleimani
2021 J jnl
Sensors
Panagiotis Koulountzios, Tomasz Rymarczyk, Manuchehr Soleimani
2020 J jnl
Sensors
Gege Ma, Manuchehr Soleimani
2020 J jnl
IEEE Access
Xi Duan, Panagiotis Koulountzios, Manuchehr Soleimani
2020 J jnl
Sensors
Chenning Wu, Martin Hutton, Manuchehr Soleimani
2020 J jnl
Sensors
Yandan Jiang, Xuekai He, Baoliang Wang, Zhiyao Huang, Manuchehr Soleimani
2020 J jnl
IEEE Access
Gege Ma, Manuchehr Soleimani
2019 J jnl
Sensors
Panagiotis Koulountzios, Tomasz Rymarczyk, Manuchehr Soleimani
2019 J jnl
IEEE Trans. Medical Imaging
Yandan Jiang, Manuchehr Soleimani
2019 J jnl
IEEE Access
Chenning Wu, Manuchehr Soleimani
2019 J jnl
Sensors
Fang Li, Stefano Spagnul, Victor Odedo, Manuchehr Soleimani
2019 J jnl
Sensors
Manuchehr Soleimani, Thomas Wondrak, Chao Tan
2019 J jnl
Sensors
Chenning Wu, Martin Hutton, Manuchehr Soleimani
2018 J jnl
IEEE Access
Yandan Jiang, Manuchehr Soleimani
2018 J jnl
Sensors
Bo Chen, Juan F. P. J. Abascal, Manuchehr Soleimani
2018 J jnl
Sensors
Bo Chen, Juan F. P. J. Abascal, Manuchehr Soleimani
2016 conf
EIT
Ali Zarafshani, Tabassum R. Qureshi, Thomas Bach, Chris R. Chatwin, Manuchehr Soleimani
2016 J jnl
Medical Biol. Eng. Comput.
Ashkan Javaherian, Manuchehr Soleimani, Knut Moeller
2014 J jnl
Sensors
Tushar Kanti Bera, Youssoufa Mohamadou, Kyounghun Lee, Hun Wi, Tong In Oh, Eung Je Woo, Manuchehr Soleimani, Jin Keun Seo
2013 J jnl
Comput. Math. Methods Medicine
A. Yao, Chuan Li Yang, Jin Keun Seo, Manuchehr Soleimani
2013 J jnl
Proc. IEEE
H.-Y. Wei, Manuchehr Soleimani
2013 J jnl
Comput. Methods Programs Biomed.
Wei Qiu, T. Pengpan, N. D. Smith, Manuchehr Soleimani
2012 J jnl
Comput. Methods Programs Biomed.
Wei Qiu, David Titley-Péloquin, Manuchehr Soleimani
2012 J jnl
Comput. Methods Programs Biomed.
T. Pengpan, Wei Qiu, N. D. Smith, Manuchehr Soleimani
2012 J jnl
IEEE Trans. Instrum. Meas.
Hsin-Yu Wei, Manuchehr Soleimani
2010 J jnl
IEEE Trans. Instrum. Meas.
Manuchehr Soleimani, Phaneendra K. Yalavarthy, Hamid Dehghani
2010 J jnl
IEEE Trans. Instrum. Meas.
Manuchehr Soleimani
2010 conf
VISAPP (1)
W. Qiu, Manuchehr Soleimani, C. N. Mitchell, Tom Marchant, Christopher J. Moore
2010 J jnl
Comput. Methods Programs Biomed.
Wei Qiu, J. R. Tong, C. N. Mitchell, Tom Marchant, P. Spencer, Christopher J. Moore, Manuchehr Soleimani
2008 J jnl
Medical Biol. Eng. Comput.
Tao Dai, Manuchehr Soleimani, Andy Adler
2007 J jnl
IEEE Trans. Instrum. Meas.
Manuchehr Soleimani, William R. B. Lionheart, Anthony J. Peyton
2006 J jnl
IEEE Trans. Biomed. Eng.
Manuchehr Soleimani, Oliver Dorn, William R. B. Lionheart
2006 J jnl
IEEE Trans. Medical Imaging
Manuchehr Soleimani, William R. B. Lionheart
2005 J jnl
IEEE Trans. Medical Imaging
Manuchehr Soleimani, Catherine E. Powell, Nicholas Polydorides
redb/extractors/decompiler/_archive/DecompileBinja-archive.py
← Index redb/extractors/decompiler/_archive/DecompileBinja-archive.py python
import hashlib
import inspect
import json
import os
import time
import threading
from datetime import datetime, timezone
from typing import Dict, Any, Optional

from redb.extractors.enum import Tag
from redb.extractors.extractor import Extractor
import magic
import pefile
import ppdeep
import tlsh

# 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,
    ):
        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

        # 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
        except AttributeError as e:
            self.log.error(
                f"AttributeError error dotnet file {self.hash.sha256} Full error : {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

            # Force garbage collection
            import gc

            gc.collect()

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

    def analyze_binary(self) -> Optional[Dict[str, Any]]:
        """Run Binary Ninja analysis and return results."""
        self.log.debug("Starting binary analysis")

        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,
            ) 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
                    extraction_result = True
            except Exception as e:
                extraction_error = e
                extraction_result = False
            finally:
                extraction_completed = True

        # Start extraction in a separate thread
        extraction_thread = threading.Thread(target=do_extraction)
        extraction_thread.daemon = True
        extraction_thread.start()

        # Wait for the extraction to complete or timeout
        start_time = time.time()
        while (
            not extraction_completed
            and (time.time() - start_time) < self.DECOMPILE_EXTRACTOR_TIMEOUT
        ):
            time.sleep(1)

        if not extraction_completed:
            self.log.error(
                f"Extraction timed out after {self.DECOMPILE_EXTRACTOR_TIMEOUT} seconds"
            )
            # Force cleanup
            self.cleanup_run()
            return None

        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 ssdeep_disassembly(func):
                try:
                    if len(func) > 1:
                        return ppdeep.hash(func)
                    return ""
                except Exception as e:
                    self.log.error(f"Error in disassembly ssdeep hash calculation: {e}")
                    return ""

            def tlsh_disassembly(func):
                try:
                    if len(func) >= 50:
                        return tlsh.hash(func.encode("utf-8"))
                    return ""
                except Exception as e:
                    self.log.error(f"Error in disassembly tlsh hash calculation: {e}")
                    return ""

            return {
                "multi_table": True,
                "decompiled_content": {
                    "table": "code_binja_decompiled_functions_content",
                    "data": [
                        [
                            f["decompiled_function_hash"],
                            f["decompiled_function"],
                            f["function_type"],
                            now,
                        ]
                        for f in self.analysis_results["decompiled"]
                    ],
                    "column_names": [
                        "decompiled_function_hash",
                        "decompiled_function",
                        "function_type",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "String",
                        "Enum8('USER'=1, 'LIBRARY'=2, 'THUNK'=3, 'EXTERNAL'=4, 'UNKNOWN'=5)",
                        "DateTime64(3, 'UTC')",
                    ],
                },
                "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["disassembled_function_hash"],  # New linking field
                            f["decompiled_function_name"],
                            f["decompiled_function_prototype"],
                            f["decompiled_function_address"],
                            now,
                        ]
                        for f in self.analysis_results["decompiled"]
                    ],
                    "column_names": [
                        "sha256",
                        "sha1",
                        "md5",
                        "decompiled_function_hash",
                        "disassembled_function_hash",  # New linking field
                        "decompiled_function_name",
                        "decompiled_function_prototype",
                        "decompiled_function_address",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(40)",
                        "FixedString(32)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",  # New linking field
                        "LowCardinality(String)",
                        "LowCardinality(String)",
                        "UInt64",
                        "DateTime64(3, 'UTC')",
                    ],
                },
                "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", 0),
                            f.get("num_calls", 0),
                            f.get("stack_size", 0),
                            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')",
                    ],
                },
                "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["decompiled_function_hash"],
                            f["disassembled_function_name"],
                            f["disassembled_function_address"],
                            ssdeep_disassembly(
                                f.get("disassembled_function_no_addresses", "")
                            ),
                            tlsh_disassembly(
                                f.get("disassembled_function_no_addresses", "")
                            ),
                            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",
                        "ssdeep_disassembly",
                        "tlsh_disassembly",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",
                        "FixedString(40)",
                        "FixedString(32)",
                        "FixedString(64)",
                        "Nullable(FixedString(64))",
                        "LowCardinality(String)",
                        "UInt64",
                        "Nullable(String)",
                        "Nullable(FixedString(72))",
                        "DateTime64(3, 'UTC')",
                    ],
                },
                "cfg_blocks": {
                    "table": "code_binja_cfg_blocks",
                    "data": [
                        [
                            b["block_id"],
                            self.analysis_results["sha256"],
                            self.analysis_results["sha1"],
                            self.analysis_results["md5"],
                            b["function_address"],
                            b["block_start_address"],
                            b["block_end_address"],
                            b["block_size"],
                            b["instructions_count"],
                            b["block_instructions"],  # MD5 hash of instructions
                            b["predecessor_blocks"],
                            b["successor_blocks"],
                            b["depth"],
                            b["position"],
                            b["branch_type"],
                            b["block_type"],
                            b["flags"],
                            b["dominators"],
                            b["post_dominators"],
                            now,
                        ]
                        # Flatten: iterate through all functions, then all blocks in each function
                        for func_cfg in self.analysis_results["cfg"]
                        if func_cfg is not None  # Handle None from failed extractions
                        for b in func_cfg["blocks"]
                    ],
                    "column_names": [
                        "block_id",
                        "sha256",
                        "sha1",
                        "md5",
                        "function_address",
                        "block_start_address",
                        "block_end_address",
                        "block_size",
                        "instructions_count",
                        "block_instructions_hash",
                        "predecessor_blocks",
                        "successor_blocks",
                        "depth",
                        "position",
                        "branch_type",
                        "block_type",
                        "flags",
                        "dominators",
                        "post_dominators",
                        "analysis_date",
                    ],
                    "column_type_names": [
                        "FixedString(64)",  # block_id (SHA256)
                        "FixedString(64)",  # sha256
                        "FixedString(40)",  # sha1
                        "FixedString(32)",  # md5
                        "UInt64",  # function_address
                        "UInt64",  # block_start_address
                        "UInt64",  # block_end_address
                        "UInt32",  # block_size
                        "UInt16",  # instructions_count
                        "FixedString(32)",  # block_instructions_hash (MD5)
                        "Array(UInt64)",  # predecessor_blocks
                        "Array(UInt64)",  # successor_blocks
                        "UInt16",  # depth
                        "UInt16",  # position
                        "Enum8('DIRECT'=1, 'CONDITIONAL'=2, 'CALL'=3, 'RETURN'=4, 'FALLTHROUGH'=5, 'INDIRECT'=6, 'UNKNOWN'=7)",  # branch_type
                        "Enum8('CODE'=1, 'DATA'=2, 'THUNK'=3)",  # block_type
                        "Array(String)",  # flags (EntryBlock, ExitBlock, LoopBlock)
                        "Array(UInt16)",  # dominators
                        "Array(UInt16)",  # post_dominators
                        "DateTime64(3, 'UTC')",  # analysis_date
                    ],
                },
                "function_analysis_errors": {
                    "table": "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['error_message']}{f['function_name']}{f['function_address']}{f['error_location']}"
                            ),
                            "new",
                            now,
                        ]
                        for f in self.analysis_results["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')",
                    ],
                },
            }

    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

    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()

    # Create and run the extractor
    with DecompileBinja(args.filepath, logger) as extractor:
        success = extractor.extract()

        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(json.dumps(extractor.analysis_results))