Nafei Zhu

66 papers B 2Journal 56Unranked 8
YearRankTypeTitle / Venue / Authors
2026 J jnl
J. Inf. Secur. Appl.
Yehong Luo, Nafei Zhu, Jingsha He, Anca Delia Jurcut, Yuzi Yi, Xiangjun Ma, Juan Fang
2025 J jnl
Softw. Pract. Exp.
Fahad Razaque Mughal, Jingsha He, Saqib Hussain, Nafei Zhu, Abdullah Raza Lakhan, Muhammad Saddam Khokhar
2025 J jnl
IEEE Trans. Consumer Electron.
Muhammad Yousaf Saeed, Jingsha He, Nafei Zhu, Muhammad Farhan, Mrim M. Alnfiai, Juan Fang
2025 J jnl
Int. J. Netw. Manag.
Muhammad Yousaf Saeed, Jingsha He, Nafei Zhu, Muhammad Farhan, Soumyabrata Dev, Thippa Reddy Gadekallu, Ahmad S. Almadhor
2025 J jnl
Complex Intell. Syst.
Muhammad Yousaf Saeed, Jingsha He, Nafei Zhu, Muhammad Farhan, Ahmad S. Almadhor, Thippa Reddy Gadekallu, Saira Iqbal
2025 J jnl
Comput. Networks
Saqib Hussain, Jingsha He, Nafei Zhu, Fahad Razaque Mughal, Sadique Ahmad, Muhammad Iftikhar Hussain, Zulfiqar Ali Zardari
2025 J jnl
Int. J. Inf. Sec.
Faheem Ullah, Jingsha He, Nafei Zhu, Ahsan Wajahat, Ahsan Nazir, Sirajuddin Qureshi, Hasan Shahzad
2025 J jnl
J. Supercomput.
Ahsan Nazir, Jingsha He, Nafei Zhu, Ahsan Wajahat, Fahim Ullah, Sirajuddin Qureshi, Muhammad Salman Pathan
2025 J jnl
Int. J. Inf. Sec.
Yehong Luo, Nafei Zhu, Ziwen Wang, Lei Sun, Jingsha He, Anca Delia Jurcut, Yuzi Yi, Xiangjun Ma
2025 J jnl
Future Gener. Comput. Syst.
Jiajia Hu, Jingsha He, Nafei Zhu, Lu Qu
2024 J jnl
Trans. Emerg. Telecommun. Technol.
Yuzi Yi, Nafei Zhu, Jingsha He, Anca Delia Jurcut, Xiangjun Ma, Yehong Luo
2024 J jnl
Clust. Comput.
Wenxiang Wei, Nafei Zhu, Jian Wang, Hongyu Song, Jingsha He
2024 J jnl
IEEE Trans. Comput. Soc. Syst.
Yuzi Yi, Nafei Zhu, Jingsha He, Anca Delia Jurcut, Xiangjun Ma, Yehong Luo
2024 J jnl
Intell. Decis. Technol.
Ahsan Wajahat, Jingsha He, Nafei Zhu, Tariq Mahmood, Ahsan Nazir, Faheem Ullah, Sirajuddin Qureshi, Musa Osman
2024 J jnl
Ad Hoc Networks
Musa Osman, Jingsha He, Nafei Zhu, Fawaz Mahiuob Mohammed Mokbal
2024 J jnl
Trans. Emerg. Telecommun. Technol.
Fahad Razaque Mughal, Jingsha He, Nafei Zhu, Mutiq Almutiq, Fayaz Ali Dharejo, Deepak Kumar Jain, Saqib Hussain, Zulfiqar Ali Zardari
2024 J jnl
J. King Saud Univ. Comput. Inf. Sci.
Ahsan Nazir, Jingsha He, Nafei Zhu, Ahsan Wajahat, Faheem Ullah, Sirajuddin Qureshi, Xiangjun Ma, Muhammad Salman Pathan
2024 J jnl
Clust. Comput.
Ahsan Nazir, Jingsha He, Nafei Zhu, Muhammad Shahid Anwar, Muhammad Salman Pathan
2024 conf
CSAIDE
Ahsan Nazir, Jingsha He, Nafei Zhu, Xiangjun Ma, Faheem Ullah, Sirajuddin Qureshi, Ahsan Wajahat
2024 J jnl
J. Supercomput.
Musa Osman, Jingsha He, Nafei Zhu, Fawaz Mahiuob Mohammed Mokbal, Asaad Ahmed
2024 J jnl
Comput. Commun.
Xiangjun Ma, Jingsha He, Tiejun Wu, Nafei Zhu, Yakang Hua
2024 J jnl
Trans. Emerg. Telecommun. Technol.
Nafei Zhu, Wenhui Li, Shijia Pan, Shuting Jin, Jingsha He
2024 J jnl
Comput. Commun.
Fahad Razaque Mughal, Jingsha He, Nafei Zhu, Saqib Hussain, Zulfiqar Ali Zardari, Ghulam Ali Mallah, Md. Jalil Piran, Fayaz Ali Dharejo
2024 conf
ISSSR
Xiaoyan Liang, Jingsha He, Nafei Zhu
2024 J jnl
J. King Saud Univ. Comput. Inf. Sci.
Sirajuddin Qureshi, Jingsha He, Saima Tunio, Nafei Zhu, Ahsan Nazir, Ahsan Wajahat, Faheem Ullah, Abdul Wadud
2024 J jnl
Clust. Comput.
Nafei Zhu, Yue Yang, Weidong Du, Yu Gan, Jingsha He
2023 J jnl
Future Internet
Jingsha He, Yue Li, Nafei Zhu
2023 J jnl
Comput. Commun.
Yuzi Yi, Nafei Zhu, Jingsha He, Anca Delia Jurcut, Xiangjun Ma, Yehong Luo
2023 J jnl
J. King Saud Univ. Comput. Inf. Sci.
Ahsan Nazir, Jingsha He, Nafei Zhu, Ahsan Wajahat, Xiangjun Ma, Faheem Ullah, Sirajuddin Qureshi, Muhammad Salman Pathan
2023 J jnl
J. Intell. Fuzzy Syst.
Ahsan Wajahat, Jingsha He, Nafei Zhu, Tariq Mahmood, Ahsan Nazir, Muhammad Salman Pathan, Sirajuddin Qureshi, Faheem Ullah
2023 J jnl
J. Comput. Sci.
Ruixin Xue, Nafei Zhu, Jingsha He, Lin He
2023 J jnl
IEEE Access
Zhiguang Chu, Jingsha He, Dongdong Peng, Xing Zhang, Nafei Zhu
2023 B conf
WISE
Yuzi Yi, Nafei Zhu, Jingsha He, Xiangjun Ma, Yehong Luo
2022 J jnl
Microprocess. Microsystems
Fahad Razaque Mughal, Jingsha He, Nafei Zhu, Ghulam Ali Mallah, Ziyue Qiao, Amir Haider, Saqib Hussain, Muhammad Iftikhar Hussain, Zulfiqar Ali Zardari
2022 J jnl
Trans. Data Priv.
Hewen Wang, Jingsha He, Nafei Zhu
2022 J jnl
Inf. Syst. Frontiers
Nafei Zhu, Baocun Chen, Siyu Wang, Da Teng, Jingsha He
2022 J jnl
Int. J. Inf. Secur. Priv.
Siyu Wang, Nafei Zhu, Jingsha He, Da Teng, Yue Yang
2022 J jnl
Secur. Priv.
Yuzi Yi, Jingsha He, Nafei Zhu, Xiangjun Ma
2022 J jnl
Comput. Networks
Yuzi Yi, Nafei Zhu, Jingsha He, Anca Delia Jurcut, Bin Zhao
2021 J jnl
IEEE Access
Sirajuddin Qureshi, Jingsha He, Saima Tunio, Nafei Zhu, Faheem Akhtar, Faheem Ullah, Ahsan Nazir, Ahsan Wajahat
2021 J jnl
Future Internet
Yanbo Chen, Jingsha He, Wei Wei, Nafei Zhu, Cong Yu
2021 J jnl
J. Intell. Fuzzy Syst.
Muhammad Iftikhar Hussain, Jingsha He, Nafei Zhu, Zulfiqar Ali Zardari, Fahad Razque, Saqib Hussain, Muhammad Salman Pathan
2021 J jnl
Int. J. Secur. Networks
Wenxin Li, Jingsha He, Nafei Zhu, Shuting Jin, Da Teng
2021 J jnl
IEEE Access
Musa Osman, Jingsha He, Fawaz Mahiuob Mohammed Mokbal, Nafei Zhu, Sirajuddin Qureshi
2021 J jnl
Inf. Process. Manag.
Hongyu Song, Nafei Zhu, Ruixin Xue, Jingsha He, Kun Zhang, Jianyu Wang
2021 J jnl
Trans. Emerg. Telecommun. Technol.
Yajing Wang, Jingsha He, Nafei Zhu, Yuzi Yi, Qingqing Zhang, Hongyu Song, Ruixin Xue
2020 J jnl
IEEE Access
Baocun Chen, Nafei Zhu, Jingsha He, Peng He, Shuting Jin, Shijia Pan
2020 J jnl
J. Web Eng.
Peng Zhai, Jingsha He, Nafei Zhu, Peng He, Yao Liang
2020 B conf
COMPSAC
Xiang Li, Jingsha He, Nafei Zhu, Ziqiang Hou
2019 J jnl
Future Internet
Zulfiqar Ali Zardari, Jingsha He, Nafei Zhu, Khalid Hussain Mohammadani, Muhammad Salman Pathan, Muhammad Iftikhar Hussain, Muhammad Qasim Memon
2019 J jnl
Int. J. Netw. Secur.
Zulfiqar Ali Zardari, Jingsha He, Nafei Zhu, Muhammad Salman Pathan, Muhammad Qasim Memon, Muhammad Iftikhar Hussain, Peng He, Chengyue Chang
2019 J jnl
Clust. Comput.
Nafei Zhu, Fangbo Cai, Jingsha He, Yixuan Zhang, Wenxin Li, Zhao Li
2019 conf
ICNSC
Shaoxun Su, Nafei Zhu, Jingsha He
2019 J jnl
Clust. Comput.
Fangbo Cai, Nafei Zhu, Jingsha He, Pengyu Mu, Wenxin Li, Yi Yu
2019 conf
NLPIR
Pengyu Mu, Jingsha He, Nafei Zhu
2018 conf
TrustCom/BigDataSE
Na Huang, Jingsha He, Nafei Zhu
2018 J jnl
Future Internet
Muhammad Salman Pathan, Nafei Zhu, Jingsha He, Zulfiqar Ali Zardari, Muhammad Qasim Memon, Muhammad Iftikhar Hussain
2018 J jnl
Digit. Investig.
Na Huang, Jingsha He, Nafei Zhu, Xinggang Xuan, Gongzheng Liu, Chengyue Chang
2018 J jnl
Future Internet
Jingsha He, Jianan Wu, Nafei Zhu, Muhammad Salman Pathan
2018 J jnl
EURASIP J. Wirel. Commun. Netw.
Jingsha He, Xinggang Xuan, Nafei Zhu, Na Huang, Peng He
2018 J jnl
Wirel. Pers. Commun.
Nafei Zhu, Siyu Wang, Jingsha He, Da Teng, Peng He, Yuqiang Zhang
2017 conf
SKG
Nafei Zhu, Min Zhang, Dengguo Feng, Jingsha He
2015 conf
SmartCity
Nafei Zhu, Min Zhang, Dengguo Feng, Jingsha He
2013 J jnl
J. Netw. Syst. Manag.
Nafei Zhu, Jingsha He, Yue Zhou, Wei Wang
2012 J jnl
J. Networks
Nafei Zhu, Jingsha He
2012 conf
ISCTCS
Nafei Zhu, Jiaping Zuo, Yue Zhou, Wei Wang
tests/unit/test_decompile_binja_extractor.py
← Index tests/unit/test_decompile_binja_extractor.py python
"""Unit tests for DecompileBinja (top-level extractor) and prepare_export_data."""
import hashlib
import json
import os
import sys
import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from datetime import datetime

# Install binaryninja stubs
from tests.unit.conftest_binja_stubs import install_binja_stubs
install_binja_stubs()

from redb.extractors.enum import Tag


# ============================================================================
# 7a. DecompileBinja — methods that don't require a live Binary Ninja session
# ============================================================================


class TestDecompileBinjaCalculateMD5:
    def test_calculate_md5(self):
        """calculate_md5("test") returns expected MD5."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = object.__new__(DecompileBinja)
            obj.log = MagicMock()
            result = obj.calculate_md5("test")
            expected = hashlib.md5(b"test").hexdigest()
            assert result == expected


class TestDecompileBinjaTimeouts:
    def _make_extractor(self, env_vars=None):
        """Create a DecompileBinja with mocked Extractor.__init__."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            if env_vars:
                with patch.dict(os.environ, env_vars, clear=False):
                    obj = DecompileBinja.__new__(DecompileBinja)
                    obj.log = MagicMock()
                    obj.analysis_results = None
                    obj.binja_decompiler = None
                    obj.filetype = None
                    obj.goresym_data = None
                    obj.goresym_output_path = None
                    # Re-run timeout logic
                    try:
                        obj.BINJA_TIMEOUT = int(os.getenv("BINJA_TIMEOUT", "1200"))
                    except ValueError:
                        obj.BINJA_TIMEOUT = 1200
                    try:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = int(
                            os.getenv("DECOMPILE_EXTRACTOR_TIMEOUT", "2580")
                        )
                    except ValueError:
                        obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                    return obj
            else:
                obj = DecompileBinja.__new__(DecompileBinja)
                obj.log = MagicMock()
                obj.analysis_results = None
                obj.binja_decompiler = None
                obj.filetype = None
                obj.goresym_data = None
                obj.goresym_output_path = None
                obj.BINJA_TIMEOUT = 1200
                obj.DECOMPILE_EXTRACTOR_TIMEOUT = 2580
                return obj

    def test_timeout_defaults(self):
        obj = self._make_extractor()
        assert obj.BINJA_TIMEOUT == 1200
        assert obj.DECOMPILE_EXTRACTOR_TIMEOUT == 2580

    def test_timeout_from_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "600"})
        assert obj.BINJA_TIMEOUT == 600

    def test_timeout_invalid_env(self):
        obj = self._make_extractor(env_vars={"BINJA_TIMEOUT": "not_a_number"})
        assert obj.BINJA_TIMEOUT == 1200


class TestDecompileBinjaDotnet:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.binary = b"\x00" * 100
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_dotnet_non_pe(self):
        obj = self._make_extractor(filetype="elf")
        assert obj.is_dotnet() is False

    def test_is_dotnet_pe_without_com(self):
        """PE without .NET COM descriptor -> False."""
        obj = self._make_extractor(filetype="pebin")
        # Mock magic to not have .Net
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_IMPORT"
                entry.Size = 100
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is False

    def test_is_dotnet_pe_with_com_descriptor(self):
        """PE with COM descriptor -> True."""
        obj = self._make_extractor(filetype="pebin")
        with patch("redb.extractors.decompiler.DecompileBinja.magic") as mock_magic:
            mock_magic.from_buffer.return_value = "PE32 executable"
            with patch("redb.extractors.decompiler.DecompileBinja.pefile") as mock_pefile:
                mock_pe = MagicMock()
                entry = MagicMock()
                entry.name = "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR"
                entry.Size = 72
                mock_pe.OPTIONAL_HEADER.DATA_DIRECTORY = [entry]
                mock_pefile.PE.return_value = mock_pe
                assert obj.is_dotnet() is True


class TestDecompileBinjaGolang:
    def _make_extractor(self, filetype=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = filetype
            obj.filepath = "/tmp/fake.bin"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            return obj

    def test_is_golang_non_elf_pe(self):
        obj = self._make_extractor(filetype=None)
        assert obj.is_golang() is False

    def test_is_golang_elf_with_go_sections(self):
        """ELF with .gopclntab section -> True."""
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec1 = MagicMock()
                sec1.name = ".text"
                sec1.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec1.data.return_value = b""
                sec2 = MagicMock()
                sec2.name = ".gopclntab"
                sec2.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec2.data.return_value = b""
                mock_elf.iter_sections.return_value = [sec1, sec2]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is True

    def test_is_golang_elf_without_go(self):
        obj = self._make_extractor(filetype="elf")
        with patch("builtins.open", MagicMock()):
            with patch("redb.extractors.decompiler.DecompileBinja.ELFFile") as mock_elf_cls:
                mock_elf = MagicMock()
                sec = MagicMock()
                sec.name = ".text"
                sec.__getitem__ = lambda self, k: 0x2 if k == "sh_flags" else 0
                sec.data.return_value = b"no go signatures here"
                mock_elf.iter_sections.return_value = [sec]
                mock_elf_cls.return_value = mock_elf
                assert obj.is_golang() is False


class TestDecompileBinjaTag:
    def test_tag_returns_decompiled(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            assert obj.tag() == Tag.DECOMPILED.value


class TestDecompileBinjaContextManager:
    def test_context_manager_protocol(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            assert obj.__enter__() is obj
            # __exit__ calls cleanup_run
            obj.__exit__(None, None, None)

    def test_cleanup_run_clears_state(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.binja_decompiler = MagicMock()
            obj.goresym_output_path = None
            obj.cleanup_run()
            assert obj.binja_decompiler is None


class TestDecompileBinjaExtract:
    def test_extract_timeout_handling(self):
        """Thread timeout -> returns None and calls cleanup."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.DECOMPILE_EXTRACTOR_TIMEOUT = 0  # Immediate timeout
            obj.sha256 = "a" * 64
            obj.sha1 = "b" * 40
            obj.md5 = "c" * 32
            # Mock analyze_binary to block
            import threading
            obj.analyze_binary = lambda: None
            result = obj.extract()
            assert result is None

    def test_analyze_binary_skips_dotnet(self):
        """Dotnet binary -> analyze_binary returns None."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.filetype = "pebin"
            obj.filepath = "/fake"
            obj.hash = MagicMock()
            obj.hash.sha256 = "a" * 64
            obj.binja_decompiler = None
            obj.goresym_output_path = None
            obj.goresym_data = None
            obj.BINJA_TIMEOUT = 60
            obj.exporters = []
            obj.index_prefix = None
            with patch.object(obj, "is_dotnet", return_value=True):
                result = obj.analyze_binary()
                assert result is None


# ============================================================================
# 7b. prepare_export_data (ClickHouse schema)
# ============================================================================


class TestPrepareExportData:
    def _make_extractor_with_results(self, goresym_data=None):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = goresym_data
            obj.analysis_results = {
                "sha256": "a" * 64,
                "sha1": "b" * 40,
                "md5": "c" * 32,
                "decompiled": [
                    {
                        "decompiled_function_hash": "h" * 64,
                        "decompiled_function": "void test() {}",
                        "decompiled_function_name": "test",
                        "decompiled_function_prototype": "void test()",
                        "decompiled_function_address": 0x1000,
                        "function_type": "USER",
                        "functions_caller": [],
                        "functions_call": [],
                        "disassembled_function_hash": "d" * 64,
                        "flattened_score": 0.0,
                        "mba_score": 0.0,
                    }
                ],
                "disassembled": [
                    {
                        "disassembled_function_hash": "d" * 64,
                        "disassembled_function": "0x1000: push rbp",
                        "disassembled_function_no_addresses": "push rbp",
                        "disassembled_function_name": "test",
                        "disassembled_function_address": 0x1000,
                        "function_type": "USER",
                        "instructions_count": 5,
                        "instructions_types": ["DATA_MOVEMENT"],
                        "control_flow_count": 1,
                        "memory_access_pattern": ["MEM_STACK"],
                        "register_usage": ["GPR"],
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 1,
                        "stack_size": -8,
                        "decompiled_function_hash": "h" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                        "minhash": [1, 2, 3],
                        "cyclomatic_complexity": 2,
                    }
                ],
                "cfg": [
                    {
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "cfg_topology_hash": b'\x00' * 16,
                        "block_count": 3,
                        "edge_count": 4,
                        "llil_total_operations": 20,
                        "call_count": 1,
                        "cyclomatic_complexity": 3,
                        "loop_count": 1,
                        "max_depth": 2,
                        "max_fan_out": 2,
                        "md_index_topdown": 12345,
                        "md_index_bottomup": 67890,
                        "prime_product_llil": 99999,
                        "cfg_feature_tlsh": None,
                        "wl_minhash": [0] * 128,
                        "bb_features": [[5, 1, 0, 2, 0, 1, 1, 2]] * 3,
                        "cfg_adjacency": [(0 << 16) | 1, (0 << 16) | 2, (1 << 16) | 2],
                    }
                ],
                "llil": [
                    {
                        "sha256_llil": "l" * 64,
                        "function_type": "USER",
                        "instructions_types_llil": [],
                        "control_flow_count_llil": 0,
                        "memory_access_pattern_llil": [],
                        "register_usage": {},
                        "total_reg_reads": 0,
                        "total_reg_written": 0,
                        "data_references_count": 0,
                        "max_block_size": 5,
                        "num_calls": 0,
                        "stack_size": -8,
                        "body_llil_vector": [],
                        "function_address": 0x1000,
                        "disassembled_function_hash": "d" * 64,
                        "tlsh_disassembly": None,
                        "tlsh_llil": None,
                    }
                ],
                "errors": [],
                "strings": [
                    {
                        "string": "Hello",
                        "string_raw": "Hello",
                        "string_encoding": "Utf8String",
                        "string_offset": 0,
                        "string_length": 5,
                        "string_raw_length": 5,
                        "string_entropy": 2.32,
                    }
                ],
            }
            return obj

    def test_prepare_export_returns_none_no_results(self):
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.analysis_results = None
            result = obj.prepare_export_data("ClickHouseExporter")
            assert result is None

    def test_prepare_export_multi_table_flag(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        assert result["multi_table"] is True

    def test_prepare_export_all_tables_present(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        expected_tables = [
            "decompiled_content",
            "decompiled_refs",
            "disassembled_content",
            "disassembled_refs",
            "llil_content",
            "llil_refs",
            "cfg_functions",
            "function_similarity_metrics",
            "strings_raw",
        ]
        for table in expected_tables:
            assert table in result, f"Missing table: {table}"

    def test_prepare_export_decompiled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "decompiled_function_hash" in table["column_names"]

    def test_prepare_export_decompiled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["decompiled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert "sha256" in table["column_names"]

    def test_prepare_export_disassembled_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_disassembled_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["disassembled_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_content_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_content"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_llil_refs_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["llil_refs"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_cfg_functions_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["cfg_functions"]
        assert len(table["column_names"]) == len(table["column_type_names"])
        assert table["table"] == "code_binja_cfg_functions"
        assert "cfg_topology_hash" in table["column_names"]
        assert "wl_minhash" in table["column_names"]
        assert "bb_features" in table["column_names"]

    def test_prepare_export_function_similarity_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_similarity_metrics"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_strings_raw_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["strings_raw"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_errors_schema(self):
        obj = self._make_extractor_with_results()
        result = obj.prepare_export_data("ClickHouseExporter")
        table = result["function_analysis_errors"]
        assert len(table["column_names"]) == len(table["column_type_names"])

    def test_prepare_export_golang_metadata_present(self):
        obj = self._make_extractor_with_results(goresym_data={"UserFunctions": []})
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" in result

    def test_prepare_export_golang_metadata_absent(self):
        obj = self._make_extractor_with_results(goresym_data=None)
        result = obj.prepare_export_data("ClickHouseExporter")
        assert "golang_metadata" not in result


class TestPrepareHelperFunctions:
    """Test the helper functions inside prepare_export_data."""

    def test_prepare_array_field_none(self):
        # The prepare_array_field function converts None -> []
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [], "cfg": [], "llil": [],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # Verify the export succeeds (function works even with empty lists)
            assert result is not None

    def test_prepare_array_field_list(self):
        """List values pass through."""
        # Verified through the export data containing arrays
        pass

    def test_prepare_register_usage_map(self):
        """Dict converted to {reg: (reads, writes)} tuples."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": {"rbx": {"reads": 3, "writes": 1}},
                    "total_reg_reads": 3, "total_reg_written": 1,
                    "data_references_count": 0, "max_block_size": 5,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            # The LLIL content data should have register_usage_llil as a map
            llil_data = result["llil_content"]["data"]
            assert len(llil_data) == 1
            # Column index 5 is register_usage_llil
            reg_map = llil_data[0][5]
            assert reg_map == {"rbx": (3, 1)}

    def test_prepare_register_usage_map_empty(self):
        """Empty/None register usage -> {}."""
        with patch("redb.extractors.decompiler.DecompileBinja.Extractor.__init__", return_value=None):
            from redb.extractors.decompiler.DecompileBinja import DecompileBinja
            obj = DecompileBinja.__new__(DecompileBinja)
            obj.log = MagicMock()
            obj.goresym_data = None
            obj.analysis_results = {
                "sha256": "a" * 64, "sha1": "b" * 40, "md5": "c" * 32,
                "decompiled": [], "disassembled": [],
                "cfg": [],
                "llil": [{
                    "sha256_llil": "l" * 64, "function_type": "USER",
                    "instructions_types_llil": [], "control_flow_count_llil": 0,
                    "memory_access_pattern_llil": [],
                    "register_usage": None,
                    "total_reg_reads": 0, "total_reg_written": 0,
                    "data_references_count": 0, "max_block_size": 0,
                    "num_calls": 0, "stack_size": 0,
                    "body_llil_vector": [],
                    "function_address": 0x1000,
                    "disassembled_function_hash": "d" * 64,
                    "tlsh_disassembly": None,
                    "tlsh_llil": None,
                }],
                "errors": [], "strings": [],
            }
            result = obj.prepare_export_data("ClickHouseExporter")
            llil_data = result["llil_content"]["data"]
            reg_map = llil_data[0][5]
            assert reg_map == {}