Radovan Stojanovic

83 papers B 1C 5Journal 19Unranked 58
YearRankTypeTitle / Venue / Authors
2025 conf
MECO
Jovan Durkovic, Radovan Stojanovic, Andrej Skraba, Mihailo Vukmirovic, Blagoje Babic, Vesna Miranovic
2025 conf
MECO
Dusko Petrovic, Radovan Stojanovic, Maria Mirto, Antonio Aloisio
2025 conf
MECO
Radovan Stojanovic, Jovan Durkovic, Mihailo Vukmirovic, Blagoje Babic, Vesna Miranovic
2025 C conf
DSD
Radovan Stojanovic, Jovan Durkovic
2024 conf
MECO
Radovan Stojanovic, Jovan Djurkovic, Blagoje Babic, Veselin N. Ivanovic, Budimir Lutovac, Milan Stork
2024 conf
MECO
Radovan Stojanovic, Jovan Djurkovic, Andrej Skraba
2024 conf
MECO
Maysoon Elhusseiny, Yousef Elsayed, Sarah Khafagy, Ziad Zaher, Ramez M. Daoud, Gehad I. Alkady, Hassanein H. Amer, Radovan Stojanovic
2024 conf
MECO
Miroslav Hagara, Alexander Satka, Peter Kubinec, Radovan Stojanovic
2023 J jnl
WiPiEC Journal
Danko Milic, Radovan Stojanovic
2023 J jnl
WiPiEC Journal
Jovan Djurkovic, Radovan Stojanovic, Betim Cico
2023 conf
MECO
Miroslav Hagara, Alexander Satka, Oldrich Ondrácek, Radovan Stojanovic
2023 C conf
DSD
Radovan Stojanovic
2023 conf
MECO
Radovan Stojanovic, Jovan Djurkovic, Slavisa Mijuskovic, Budimir Lutovac, Andrej Skraba
2022 conf
MECO
Miroslav Hagara, Peter Kubinec, Alexander Satka, Radovan Stojanovic
2022 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Viera Stopjaková, Radovan Stojanovic
2022 J jnl
Microprocess. Microsystems
Miroslav Hagara, Radovan Stojanovic, Alexander Satka, Peter Kubinec, Oldrich Ondrácek
2022 conf
MECO
Radovan Stojanovic, Andrej Skraba, Jovan Djurkovic, Budimir Lutovac
2021 conf
MECO
Radovan Stojanovic, Vesna Maras, Sanja Sucur Radonjic, Anita Martic, Jovan Durkovic, Katarina Pavicevic, Vasilije Mirovic, Miljan Cvetkovic
2021 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Peter Kubinec, Radovan Stojanovic
2021 conf
MECO
Radovan Stojanovic, Andrej Skraba
2021 J jnl
WiPiEC Journal
Danilo Jankovic, Radovan Stojanovic
2020 conf
MECO
Vladimir I. Kubov, Yuri Y. Dymytrov, Radovan Stojanovic, Raziya M. Kubova, Andrej Skraba
2020 conf
MECO
Radovan Stojanovic, Andrej Skraba, Budimir Lutovac
2020 conf
MECO
Alexandros Spournias, Christos P. Antonopoulos, Georgios Keramidas, Nikolaos S. Voros, Radovan Stojanovic
2020 J jnl
Microprocess. Microsystems
Miroslav Hagara, Radovan Stojanovic, Tomás Bagala, Peter Kubinec, Oldrich Ondrácek
2019 C conf
DSD
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic, Eugene Semenkin, Vladimir Stanovov
2019 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Peter Kubinec, Radovan Stojanovic
2019 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic, Eugene Semenkin, Vladimir Stanovov
2019 J jnl
Microprocess. Microsystems
Andrej Kolozvari, Radovan Stojanovic, Anton Zupan, Eugene Semenkin, Vladimir Stanovov, Davorin Kofjac, Andrej Skraba
2018 conf
MECO
Suzana Petrovic Savic, Goran Devedzic, Branko Ristic, Aleksandar Matic, Nikola Prodanovic, Radovan Stojanovic
2018 conf
MECO
Mirko Sajic, Dusanka Bundalo, Zlatko Bundalo, Radovan Stojanovic, Luka Sajic
2018 J jnl
Microprocess. Microsystems
Davorin Kofjac, Radovan Stojanovic, Andrej Kolozvari, Andrej Skraba
2018 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Bojan Vavtar, Radovan Stojanovic, Vladimir Stanovov, Eugene Semenkin
2018 J jnl
Microprocess. Microsystems
Milorad Papic, Zlatko Bundalo, Dusanka Bundalo, Radovan Stojanovic, Zivorad Kovacevic, Drazen Pasalic, Branimir Cvijic
2018 conf
MECO
Tomás Bagala, Adam Fibich, Miroslav Hagara, Peter Kubinec, Oldrich Ondrácek, Vladimir Stofanik, Radovan Stojanovic
2017 J jnl
Microprocess. Microsystems
Miroslav Hagara, Radovan Stojanovic, Peter Kubinec, Oldrich Ondrácek
2017 conf
MECO
Milorad Papic, Zlatko Bundalo, Dusanka Bundalo, Radovan Stojanovic, Zivorad Kovacevic, Drazen Pasalic, Branimir Cvijic
2017 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic, Vladimir Stanovov, Eugene Semenkin
2017 J jnl
Microprocess. Microsystems
Radovan Stojanovic, Lech Józwiak
2017 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Peter Kubinec, Radovan Stojanovic
2016 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Peter Kupec, Radovan Stojanovic
2016 conf
MECO
Lech Józwiak, Radovan Stojanovic, Budimir Lutovac
2016 J jnl
WiPiEC Journal
Zivorad Kovacevic, Radovan Stojanovic, Gojko Nikolic
2016 conf
ICINCO (2)
Andrej Skraba, Vladimir Stanovov, Eugene Semenkin, Andrej Kolozvari, Radovan Stojanovic, Davorin Kofjac
2016 J jnl
IEEE Des. Test
Radovan Stojanovic, Radenka Krsmanovic Whiffen
2016 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic, Vladimir Stanovov, Eugene Semenkin
2016 conf
MECO
Drazen Pasalic, Branimir Cvijic, Dusanka Bundalo, Zlatko Bundalo, Radovan Stojanovic
2015 conf
MECO
Radovan Stojanovic, Miroslav Hagara, Oldrich Ondrácek, Anetta Caplanova
2015 conf
MECO
Miroslav Hagara, Oldrich Ondrácek, Radovan Stojanovic
2015 conf
MECO
Ferid Softic, Radovan Stojanovic, Zeljko Blagojevic, Zlatko Bundalo, Drazen Pasalic
2015 J jnl
Microprocess. Microsystems
Andrej Skraba, Radovan Stojanovic, Anton Zupan, Andrej Kolozvari, Davorin Kofjac
2015 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic
2014 conf
MECO
Jovan Kovacevic, Radovan Stojanovic, Dejan Karadaglic, Bogdan Asanin, Zivorad Kovacevic, Zlatko Bundalo, Ferid Softic
2014 conf
MECO
Andrej Skraba, Andrej Kolozvari, Davorin Kofjac, Radovan Stojanovic
2014 conf
MECO
Nemanja Filipovic, Radovan Stojanovic, Anetta Caplanova
2013 C conf
BIBE
Sasa Knezevic, Radovan Stojanovic, Bogdan Asanin, Dejan Karadaglic
2013 conf
MECO
Sasa Knezevic, Radovan Stojanovic, Dejan Karadaglic, Bogdan Asanin
2013 conf
MECO
Dzenan Strujic, Ramo Sendelj, Radovan Stojanovic
2013 J jnl
Sensors
Radovan Stojanovic, Dejan Karadaglic
2013 conf
MECO
Goran Devedzic, Radovan Stojanovic, Zlatko Bundalo, Duncan E. T. Shepherd, Suzana Petrovic, Andelka Stankovic, Sasa Cukovic
2013 conf
MECO
Radovan Stojanovic
2013 conf
MECO
Suzana Petrovic, Goran Devedzic, Branko Ristic, Aleksandar Matic, Radovan Stojanovic
2013 conf
MECO
Lech Józwiak, Radovan Stojanovic, Budimir Lutovac
2013 conf
MECO
Nemanja Filipovic, Radovan Stojanovic, Matjaz Debevc, Goran Devedzic
2013 J jnl
Comput. Sci. Inf. Syst.
Radovan Stojanovic, Sasa Knezevic, Dejan Karadaglic, Goran Devedzic
2013 conf
SimuTools
Alberto Debiasi, Federico Prandi, Giuseppe Conti, Raffaele De Amicis, Radovan Stojanovic
2012 C conf
BIBE
Radovan Stojanovic, Sasa Knezevic, Dejan Karadaglic
2012 conf
MECO
Vladimir Popovic, Radovan Stojanovic, Marko Dragovic, Jovan Kovacevic, Andrej Skraba, Simon Berkowicz, Raffaele De Amicis, Mira Cerovic
2012 conf
MECO
Goran Devedzic, Radovan Stojanovic, Vanja Lukovic, Sasa Cukovic, Danijela Milosevic
2012 conf
MECO
Sasa Knezevic, Radovan Stojanovic, Jovan Kovacevic, Dejan Karadaglic
2011 B conf
CBMS
Radovan Stojanovic, Dejan Karadaglic
2011 conf
EUROCON
Nedjeljko Lekic, Zoran Mijanovic, Radovan Stojanovic
2009 conf
BMEI
Radovan Stojanovic, Bogdan Asanin, Dejan Karadaglic
2009 J jnl
Comput. Sci. Inf. Syst.
Andreas Miaoudakis, Dimitrios I. Stratakis, Emmanouel Antonidakis, Vassilios Zacharopoulos, Radovan Stojanovic
2008 conf
PETRA
Konstantinos Perakis, Maria Haritou, Radovan Stojanovic, Bogdan Asanin, Dimitris Koutsouris
2006 conf
EUSIPCO
Veselin N. Ivanovic, Radovan Stojanovic, Srdjan Jovanovski, Ljubisa Stankovic
2006 J jnl
EURASIP J. Adv. Signal Process.
Veselin N. Ivanovic, Radovan Stojanovic, Ljubisa Stankovic
2002 J jnl
Ann. des Télécommunications
Srdjan Stankovic, Ljubisa Stankovic, Veselin N. Ivanovic, Radovan Stojanovic
2001 conf
ICIP (1)
Radovan Stojanovic, George D. Papadopoulos, Panagiotis Mitropulos, Manos J. Georgoudakis, Robert Alcock, Igor Djurovic
2001 conf
ICIP (3)
Igor Djurovic, Srdjan Stankovic, Ljubisa Stankovic, Radovan Stojanovic
2001 J jnl
Real Time Imaging
Radovan Stojanovic, Panagiotis Mitropulos, Christos Koulamas, Yorgos A. Karayiannis, Stavros A. Koubias, George D. Papadopoulos
1999 conf
IIA/SOCO
Radovan Stojanovic, Stavros A. Koubias, George D. Papadopoulos
1999 conf
ICECS
Yorgos A. Karayiannis, Radovan Stojanovic, Panagiotis Mitropoulos, Christos Koulamas, Thanos Stouraitis, Stavros A. Koubias, George D. Papadopoulos
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))