Xiang Tian

72 papers A* 4B 2Journal 57Unranked 9
YearRankTypeTitle / Venue / Authors
2026 J jnl
IEEE Robotics Autom. Lett.
Li Huang, Tong Yang, Xiang Tian, Rongxin Jiang, Yaowu Chen
2025 J jnl
Mach. Learn.
Yiqun Wang, Chaoqun Wan, Xiang Tian, Xuesong Liu, Yaowu Chen
2025 J jnl
Int. J. Mach. Learn. Cybern.
Shixuan Meng, Rongxin Jiang, Xiang Tian, Fan Zhou, Yaowu Chen, Junjie Liu, Chen Shen
2025 J jnl
Vis. Comput.
Yuesong Tian, Li Shen, Xiang Tian, Dacheng Tao, Zhifeng Li, Wei Liu, Yaowu Chen
2025 J jnl
Int. J. Neural Syst.
Shixuan Meng, Rongxin Jiang, Xiang Tian, Fan Zhou, Yaowu Chen, Junjie Liu, Chen Shen
2025 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Dongdong Zhao, Tiancheng Cai, Peng Chen, Weibo Mao, Yingtian Hu, Yilong Zhang, Yuanjie Dang, Ronghua Liang, Xiang Tian
2025 B conf
IEEE Big Data
Yiqun Wang, Chaoqun Wan, Xiang Tian, Xuesong Liu, Yaowu Chen
2025 conf
ACL (1)
Yiqun Wang, Chaoqun Wan, Sile Hu, Yonggang Zhang, Xiang Tian, Yaowu Chen, Xu Shen, Jieping Ye
2024 J jnl
Neural Process. Lett.
Xin Ye, Xiang Tian, Bolun Zheng, Fan Zhou, Yaowu Chen
2024 J jnl
IEEE Robotics Autom. Lett.
Li Huang, Tong Yang, Rongxin Jiang, Xiang Tian, Fan Zhou, Yaowu Chen
2024 J jnl
J. Electronic Imaging
Yuesong Tian, Li Shen, Xiang Tian, Zhifeng Li, Yaowu Chen
2024 J jnl
IEEE Signal Process. Lett.
Xin Ye, Rongxin Jiang, Xiang Tian, Rui Zhang, Yaowu Chen
2024 J jnl
Int. J. Comput. Vis.
Yao Zhu, Yuefeng Chen, Xiaodan Li, Rong Zhang, Hui Xue, Xiang Tian, Rongxin Jiang, Bolun Zheng, Yaowu Chen
2024 A* conf
KDD
Ge Teng, Ting Mao, Chen Shen, Xiang Tian, Xuesong Liu, Yaowu Chen, Jieping Ye
2024 J jnl
CoRR
Ge Teng, Ting Mao, Chen Shen, Xiang Tian, Xuesong Liu, Yaowu Chen, Jieping Ye
2023 J jnl
IEEE Trans. Inf. Forensics Secur.
Yao Zhu, Yuefeng Chen, Xiaodan Li, Rong Zhang, Xiang Tian, Bolun Zheng, Yaowu Chen
2023 J jnl
CoRR
Yao Zhu, Yuefeng Chen, Xiaodan Li, Rong Zhang, Xiang Tian, Bolun Zheng, Yaowu Chen
2022 J jnl
J. Comput. Sci.
Xiang Gao, Fan Zhou, Kedi Xu, Xiang Tian, Yaowu Chen
2022 J jnl
Biomed. Signal Process. Control.
Xiang Gao, Yue Zhu, Yufang Yang, Fang Zhang, Fan Zhou, Xiang Tian, Kedi Xu, Yaowu Chen
2022 A* conf
NeurIPS
Yao Zhu, Yuefeng Chen, Chuanlong Xie, Xiaodan Li, Rong Zhang, Hui Xue, Xiang Tian, Bolun Zheng, Yaowu Chen
2022 J jnl
CoRR
Yao Zhu, Yuefeng Chen, Chuanlong Xie, Xiaodan Li, Rong Zhang, Hui Xue, Xiang Tian, Bolun Zheng, Yaowu Chen
2022 J jnl
IEEE Trans. Instrum. Meas.
Fei Wang, Xiang Tian, Xuesong Liu, Boxuan Gu, Fan Zhou, Yaowu Chen
2022 conf
ROBIO
Li Huang, Tong Yang, Yu Zhang, Rongxin Jiang, Xiang Tian, Yaowu Chen
2022 J jnl
CoRR
Ze Chen, Zhihang Fu, Jianqiang Huang, Mingyuan Tao, Shengyu Li, Rongxin Jiang, Xiang Tian, Yaowu Chen, Xian-Sheng Hua
2022 J jnl
Neurocomputing
Ze Chen, Zhihang Fu, Jianqiang Huang, Mingyuan Tao, Shengyu Li, Rongxin Jiang, Xiang Tian, Yaowu Chen, Xian-Sheng Hua
2022 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Bolun Zheng, Shanxin Yuan, Chenggang Yan, Xiang Tian, Jiyong Zhang, Yaoqi Sun, Lin Liu, Ales Leonardis, Gregory G. Slabaugh
2022 A* conf
CVPR
Junjie Liu, Junlong Liu, Shaotian Yan, Rongxin Jiang, Xiang Tian, Boxuan Gu, Yaowu Chen, Chen Shen, Jianqiang Huang
2022 J jnl
CoRR
Yao Zhu, Yuefeng Chen, Xiaodan Li, Rong Zhang, Hui Xue, Xiang Tian, Rongxin Jiang, Bolun Zheng, Yaowu Chen
2022 J jnl
CoRR
Ze Chen, Zhihang Fu, Jianqiang Huang, Mingyuan Tao, Rongxin Jiang, Xiang Tian, Yaowu Chen, Xian-Sheng Hua
2022 J jnl
IEEE Trans. Image Process.
Yao Zhu, Yuefeng Chen, Xiaodan Li, Kejiang Chen, Yuan He, Xiang Tian, Bolun Zheng, Yaowu Chen, Qingming Huang
2022 J jnl
CoRR
Yao Zhu, Yuefeng Chen, Xiaodan Li, Kejiang Chen, Yuan He, Xiang Tian, Bolun Zheng, Yaowu Chen, Qingming Huang
2021 J jnl
J. Electronic Imaging
Caiyang Xie, Xiang Tian, Rongxin Jiang, Yaowu Chen
2021 J jnl
J. Electronic Imaging
Xiang Tian, Bolun Zheng, Shengyu Li, Chenggang Yan, Jiyong Zhang, Yaoqi Sun, Tao Shen, Mang Xiao
2021 J jnl
Comput. Math. Methods Medicine
Shangjie Yao, Yaowu Chen, Xiang Tian, Rongxin Jiang
2021 J jnl
J. Electronic Imaging
Zhuojun Cai, Xiang Tian, Ze Chen, Yaowu Chen
2021 J jnl
Image Vis. Comput.
Ze Chen, Zhihang Fu, Jianqiang Huang, Mingyuan Tao, Rongxin Jiang, Xiang Tian, Yaowu Chen, Xian-Sheng Hua
2020 J jnl
J. Electronic Imaging
Bolun Zheng, Xiang Tian, Yaowu Chen, Rongxin Jiang, Xuesong Liu
2020 J jnl
IEEE Access
Shangjie Yao, Yaowu Chen, Xiang Tian, Rongxin Jiang
2020 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Bolun Zheng, Yaowu Chen, Xiang Tian, Fan Zhou, Xuesong Liu
2020 J jnl
J. Electronic Imaging
Zhuojun Cai, Yaowu Chen, Xiang Tian, Rongxin Jiang
2019 J jnl
IEEE Access
Haonan Chen, Yaowu Chen, Xiang Tian, Rongxin Jiang
2019 conf
ICCV Workshops
Andrey Ignatov, Praveen Kandula, Maitreya Suin, A. N. Rajagopalan, Zhiwei Xiong, Jie Huang, Guanting Dong, Mingde Yao, Dong Liu, Wenjin Yang, Ming Hong, Wenying Lin, Jagruti Patel, Yanyun Qu, Jae-Seok Choi, Woonsung Park, Munchurl Kim, Rui Liu, Xiangyu Mao, Chengxi Yang, Qiong Yan, Wenxiu Sun, Junkai Fang, Radu Timofte, Meimei Shang, Fei Gao, Sujoy Ghosh, Prasen Kumar Sharma, Arijit Sur, Bolun Zheng, Xin Ye, Li Huang, Xiang Tian, Saikat Dutta, Kuldeep Purohit
2019 conf
ICCV Workshops
Shanxin Yuan, Zhenyong Fu, Jian Yang, Ming Hong, Wenying Lin, Wenjin Yang, Yanyun Qu, Hong-Kyu Shin, Joon-Yeon Kim, Sung-Jea Ko, Hang Dong, Radu Timofte, Yu Guo, Jie Wang, Xuan Ding, Zongyan Han, Sourya Dipta Das, Kuldeep Purohit, Praveen Kandula, Maitreya Suin, A. N. Rajagopalan, Gregory G. Slabaugh, Ales Leonardis, Bolun Zheng, Xin Ye, Xiang Tian, Yaowu Chen, Xi Cheng
2019 J jnl
CoRR
Shanxin Yuan, Radu Timofte, Gregory G. Slabaugh, Ales Leonardis, Bolun Zheng, Xin Ye, Xiang Tian, Yaowu Chen, Xi Cheng, Zhenyong Fu, Jian Yang, Ming Hong, Wenying Lin, Wenjin Yang, Yanyun Qu, Hong-Kyu Shin, Joon-Yeon Kim, Sung-Jea Ko, Hang Dong, Yu Guo, Jie Wang, Xuan Ding, Zongyan Han, Sourya Dipta Das, Kuldeep Purohit, Praveen Kandula, Maitreya Suin, A. N. Rajagopalan
2019 J jnl
J. Comput. Neurosci.
Zhengdong Xiao, Sile Hu, Qiaosheng Zhang, Xiang Tian, Yaowu Chen, Jing Wang, Zhe Chen
2019 B conf
ICIP
Lunan Zhou, Yaowu Chen, Xiang Tian, Rongxin Jiang
2019 J jnl
IEEE Access
Shengyu Li, Yaowu Chen, Rongxin Jiang, Xiang Tian
2019 J jnl
IEEE Access
Lin Chen, Yaowu Chen, Haibin Ling, Xiang Tian, Yuesong Tian
2019 J jnl
J. Electronic Imaging
Lunan Zhou, Yaowu Chen, Xiang Tian, Rongxin Jiang
2019 J jnl
J. Electronic Imaging
Lin Chen, Haibin Ling, Yu Shen, Fan Zhou, Ping Wang, Xiang Tian, Yaowu Chen
2018 conf
BigMM
Zhihang Fu, Chang Zhou, Hongwei Yong, Rongxin Jiang, Xiang Tian, Yaowu Chen, Xian-Sheng Hua
2018 J jnl
CoRR
Bolun Zheng, Yaowu Chen, Xiang Tian, Fan Zhou, Xuesong Liu
2018 J jnl
J. Electronic Imaging
Yuge Huang, Xiang Tian, Yaowu Chen, Rongxin Jiang
2018 J jnl
J. Electronic Imaging
Lunan Zhou, Rui Sun, Xiang Tian, Yaowu Chen
2018 J jnl
CoRR
Bolun Zheng, Rui Sun, Xiang Tian, Yaowu Chen
2018 J jnl
J. Electronic Imaging
Bolun Zheng, Rui Sun, Xiang Tian, Yaowu Chen
2017 conf
EMBC
Zelan Yang, Kedi Xu, Xiang Tian, Shaomin Zhang, Xiaoxiang Zheng
2017 J jnl
Int. J. Syst. Sci.
Weijun Xu, Rongxin Jiang, Li Xie, Xiang Tian, Yaowu Chen
2017 A* conf
ICRA
Lin Chen, Fan Zhou, Yu Shen, Xiang Tian, Haibin Ling, Yaowu Chen
2016 J jnl
J. Electronic Imaging
Lilin Guo, Lunan Zhou, Xiang Tian, Yaowu Chen
2015 J jnl
Digit. Signal Process.
Guangming Zhu, Fan Zhou, Rongxin Jiang, Xiang Tian, Yaowu Chen
2015 J jnl
J. Electronic Imaging
Lilin Guo, Lunan Zhou, Xiang Tian, Yaowu Chen
2014 J jnl
Multim. Tools Appl.
Wei Jiang, Yaowu Chen, Xiang Tian
2013 J jnl
IEEE Trans. Consumer Electron.
Lei Luo, Rongxin Jiang, Xiang Tian, Yaowu Chen
2011 J jnl
Multim. Tools Appl.
Hua Zhang, Xiang Tian, Yaowu Chen
2010 J jnl
IEEE Trans. Consumer Electron.
Wei Zhu, Xiang Tian, Fan Zhou, Yaowu Chen
2010 J jnl
IEEE Trans. Consumer Electron.
Wei Zhu, Xiang Tian, Fan Zhou, Yaowu Chen
2010 J jnl
IEEE Trans. Instrum. Meas.
Peng Chen, Xiang Tian, Yaowu Chen
2008 conf
ICNSC
Yayu Zheng, Xiang Tian, Yaowu Chen
2008 conf
PACIIA (1)
Peng Chen, Xiang Tian, Yaowu Chen
2008 J jnl
IEEE Trans. Consumer Electron.
Yayu Zheng, Fan Zhou, Xiang Tian, Yaowu Chen
2008 conf
ICNC (5)
Lin Qu, Xiang Tian, Yaowu Chen
tests/unit/test_apk_strings_ioc.py
← Index tests/unit/test_apk_strings_ioc.py python
"""Tests for APK string extraction and IOC extraction integration.

Covers:
- APKCodeAnalyzer._extract_strings() — string extraction from DEX objects
- APKCodeAnalyzer._string_entropy() — Shannon entropy computation
- DecompileAPK strings export table schema
- IOCExtractorFromResults with APK-format analysis_results
- Worker IOC wiring for APK
"""
import os
import tempfile
from dataclasses import asdict
from unittest.mock import MagicMock, patch

import pytest

from redb.extractors.ioc_extractor.ioc_extractor import IOCExtractorFromResults
from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
    IOCType,
    SourceType,
)

pytestmark = [pytest.mark.unit, pytest.mark.apk]


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_analyzer(min_instructions=1):
    """Create an APKCodeAnalyzer with mocked dependencies."""
    with patch(
        "redb.extractors.decompiler.apk.analyzer.JADXDecompiler"
    ), patch(
        "redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler"
    ):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk")
        analyzer.min_instructions = min_instructions
        return analyzer


def _make_mock_dex(strings):
    """Create a mock DEX object that returns the given strings."""
    dex = MagicMock()
    dex.get_strings.return_value = strings
    return dex


def _create_fake_apk_file():
    """Create a temporary file to act as a fake APK."""
    fd, path = tempfile.mkstemp(suffix=".apk")
    os.write(fd, b"PK\x03\x04fake apk content for hashing")
    os.close(fd)
    return path


# ---------------------------------------------------------------------------
# Tests: _string_entropy
# ---------------------------------------------------------------------------

class TestStringEntropy:
    """Tests for Shannon entropy computation."""

    def test_empty_string(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("") == 0.0

    def test_single_char(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("a") == 0.0

    def test_repeated_char(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("aaaa") == 0.0

    def test_two_equal_chars(self):
        analyzer = _make_analyzer()
        # "ab" -> entropy = 1.0
        assert abs(analyzer._string_entropy("ab") - 1.0) < 0.01

    def test_higher_entropy(self):
        analyzer = _make_analyzer()
        # More variety = higher entropy
        low = analyzer._string_entropy("aabb")
        high = analyzer._string_entropy("abcd")
        assert high > low

    def test_url_string_entropy(self):
        analyzer = _make_analyzer()
        entropy = analyzer._string_entropy("https://evil.com/payload")
        assert entropy > 2.0  # URLs have moderate entropy


# ---------------------------------------------------------------------------
# Tests: _extract_strings
# ---------------------------------------------------------------------------

class TestExtractStrings:
    """Tests for DEX string extraction."""

    def test_basic_extraction(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["hello", "world"])
        result = analyzer._extract_strings([dex])

        assert len(result) == 2
        assert result[0]["string"] == "hello"
        assert result[1]["string"] == "world"

    def test_string_fields_present(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["test_string"])
        result = analyzer._extract_strings([dex])

        entry = result[0]
        assert "string" in entry
        assert "string_encoding" in entry
        assert "string_offset" in entry
        assert "string_length" in entry
        assert "string_entropy" in entry
        assert entry["string_encoding"] == "UTF8"
        assert entry["string_length"] == len("test_string")

    def test_deduplication_across_dex(self):
        """Same string in multiple DEX files is only extracted once."""
        analyzer = _make_analyzer()
        dex1 = _make_mock_dex(["shared", "unique1"])
        dex2 = _make_mock_dex(["shared", "unique2"])
        result = analyzer._extract_strings([dex1, dex2])

        string_values = [s["string"] for s in result]
        assert string_values.count("shared") == 1
        assert "unique1" in string_values
        assert "unique2" in string_values
        assert len(result) == 3

    def test_empty_strings_filtered(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["", "valid", None, "also_valid"])
        result = analyzer._extract_strings([dex])

        string_values = [s["string"] for s in result]
        assert "" not in string_values
        assert None not in string_values
        assert "valid" in string_values
        assert "also_valid" in string_values

    def test_no_dex_files(self):
        analyzer = _make_analyzer()
        result = analyzer._extract_strings([])
        assert result == []

    def test_dex_get_strings_returns_none(self):
        analyzer = _make_analyzer()
        dex = MagicMock()
        dex.get_strings.return_value = None
        result = analyzer._extract_strings([dex])
        assert result == []

    def test_dex_get_strings_exception(self):
        analyzer = _make_analyzer()
        dex = MagicMock()
        dex.get_strings.side_effect = RuntimeError("corrupt DEX")
        result = analyzer._extract_strings([dex])
        assert result == []

    def test_entropy_computed(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["abcdefgh"])
        result = analyzer._extract_strings([dex])
        assert result[0]["string_entropy"] > 0.0

    def test_incremental_offsets(self):
        """String offsets are incremented sequentially."""
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["a", "b", "c"])
        result = analyzer._extract_strings([dex])

        offsets = [s["string_offset"] for s in result]
        assert offsets == [0, 1, 2]

    def test_url_strings_preserved(self):
        """URL strings are extracted without modification."""
        analyzer = _make_analyzer()
        dex = _make_mock_dex([
            "https://evil.com/payload",
            "http://c2.malware.org/gate",
        ])
        result = analyzer._extract_strings([dex])
        strings = [s["string"] for s in result]
        assert "https://evil.com/payload" in strings
        assert "http://c2.malware.org/gate" in strings


# ---------------------------------------------------------------------------
# Tests: strings in extract() integration
# ---------------------------------------------------------------------------

class TestExtractStringsIntegration:
    """Tests that strings are included in extract() results."""

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    @patch("redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler")
    @patch("redb.extractors.decompiler.apk.analyzer.JADXDecompiler")
    def test_extract_includes_strings(self, mock_jadx, mock_apktool, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        mock_dex = _make_mock_dex(["hello", "world"])
        mock_analysis = MagicMock()
        mock_analysis.get_methods.return_value = []
        mock_androguard.return_value = (MagicMock(), [mock_dex], mock_analysis)

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        assert "strings" in results
        assert len(results["strings"]) == 2
        assert results["strings"][0]["string"] == "hello"
        analyzer.cleanup()

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    @patch("redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler")
    @patch("redb.extractors.decompiler.apk.analyzer.JADXDecompiler")
    def test_extract_strings_error_is_non_fatal(self, mock_jadx, mock_apktool, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        # Return a bad dex list that will cause _extract_strings to fail
        mock_analysis = MagicMock()
        mock_analysis.get_methods.return_value = []
        # dexs is not iterable — triggers the except block
        mock_androguard.return_value = (MagicMock(), 42, mock_analysis)

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        # Should not crash, strings should be empty, error recorded
        assert results["strings"] == []
        assert any(
            e.get("error_location") == "strings"
            for e in results["analysis_errors"]
        )
        analyzer.cleanup()


# ---------------------------------------------------------------------------
# Tests: DecompileAPK strings export table
# ---------------------------------------------------------------------------

class TestDecompileAPKStringsExport:
    """Tests for strings table in DecompileAPK.prepare_export_data()."""

    def _make_extractor_with_strings(self, strings_data):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        ext = DecompileAPK(apk_path, MagicMock())
        ext.analysis_results = {
            "sha256": "a" * 64,
            "sha1": "b" * 40,
            "md5": "c" * 32,
            "decompiled_content": [],
            "decompiled_refs": [],
            "smali_content": [],
            "smali_refs": [],
            "similarity_metrics": [],
            "strings": strings_data,
            "analysis_errors": [],
        }
        return ext, apk_path

    def test_strings_table_present(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "hello", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 5, "string_entropy": 2.32}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert "strings_raw" in export
        finally:
            os.unlink(path)

    def test_strings_table_schema(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "hello", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 5, "string_entropy": 2.32}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            sr = export["strings_raw"]
            assert sr["table"] == "code_binja_strings_raw"
            assert len(sr["column_names"]) == 8
            assert len(sr["column_type_names"]) == 8
            assert "sha256" in sr["column_names"]
            assert "string" in sr["column_names"]
            assert "string_raw" in sr["column_names"]
            assert "string_encoding" in sr["column_names"]
            assert "string_offset" in sr["column_names"]
            assert "string_length" in sr["column_names"]
            assert "string_raw_length" in sr["column_names"]
            assert "string_entropy" in sr["column_names"]
            assert len(sr["data"]) == 1
            assert len(sr["data"][0]) == 8
        finally:
            os.unlink(path)

    def test_strings_sha256_propagated(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "test", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 4, "string_entropy": 2.0}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            row = export["strings_raw"]["data"][0]
            assert row[0] == "a" * 64  # sha256 is first column
        finally:
            os.unlink(path)

    def test_empty_strings_not_exported(self):
        ext, path = self._make_extractor_with_strings([])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert "strings_raw" not in export
        finally:
            os.unlink(path)


# ---------------------------------------------------------------------------
# Tests: IOCExtractorFromResults with APK format
# ---------------------------------------------------------------------------

class TestIOCExtractorAPKFormat:
    """Tests that IOCExtractorFromResults handles APK analysis_results."""

    def setup_method(self):
        self.log = MagicMock()

    def test_extract_iocs_from_apk_strings(self):
        """IOCs extracted from APK strings (same format as Binja)."""
        results = {
            "strings": [
                {"string": "https://evil.com/payload", "string_offset": 0},
                {"string": "Contact [email protected]", "string_offset": 1},
            ],
            "decompiled": [],
            "decompiled_content": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1
        assert any("evil.com" in i.ioc_value for i in url_iocs)

    def test_extract_iocs_from_apk_decompiled_content(self):
        """IOCs extracted from APK's decompiled_content key."""
        results = {
            "strings": [],
            "decompiled": [],  # Binja format — empty
            "decompiled_content": [  # APK format
                {
                    "decompiled_method": "connect('https://c2.malware.org/gate');",
                    "decompiled_method_hash": "hash123",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1

    def test_apk_library_methods_skipped(self):
        """LIBRARY methods in decompiled_content are skipped."""
        results = {
            "strings": [],
            "decompiled": [],
            "decompiled_content": [
                {
                    "decompiled_method": "https://should-skip.com",
                    "decompiled_method_hash": "lib_hash",
                    "method_type": "LIBRARY",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert len(iocs) == 0

    def test_combined_binja_and_apk_sources(self):
        """Both Binja and APK format decompiled entries are processed."""
        results = {
            "strings": [],
            "decompiled": [
                {
                    "decompiled_function": "call('https://binja.example.com');",
                    "decompiled_function_hash": "binja_hash",
                    "function_type": "USER",
                }
            ],
            "decompiled_content": [
                {
                    "decompiled_method": "send('https://apk.example.com');",
                    "decompiled_method_hash": "apk_hash",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        urls = [i.ioc_value for i in url_iocs]
        assert any("binja.example.com" in u for u in urls)
        assert any("apk.example.com" in u for u in urls)

    def test_apk_results_only(self):
        """Works when only APK keys are present (no 'decompiled' key)."""
        results = {
            "strings": [
                {"string": "https://evil.com", "string_offset": 0},
            ],
            "decompiled_content": [
                {
                    "decompiled_method": "x = 1",
                    "decompiled_method_hash": "h",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        # Should not crash, and should find URL from strings
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1

    def test_source_type_is_string_for_strings(self):
        results = {
            "strings": [
                {"string": "https://evil.com/test", "string_offset": 42},
            ],
            "decompiled_content": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert url_iocs[0].source_type == SourceType.STRING
        assert url_iocs[0].source_identifier == "42"

    def test_source_type_is_decompiled_for_methods(self):
        results = {
            "strings": [],
            "decompiled_content": [
                {
                    "decompiled_method": "https://evil.com/method",
                    "decompiled_method_hash": "myhash",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert url_iocs[0].source_type == SourceType.DECOMPILED_FUNCTION
        assert url_iocs[0].source_identifier == "myhash"


# ---------------------------------------------------------------------------
# Tests: Worker IOC wiring for APK
# ---------------------------------------------------------------------------

class TestWorkerAPKIOCWiring:
    """Tests that workers.py wires IOC extraction for APK."""

    def test_worker_source_contains_ioc_for_apk(self):
        """Verify workers.py source has IOC extraction in APK branch."""
        import inspect
        from redb import workers
        source = inspect.getsource(workers)
        # APK branch should import IOCExtractorFromResults
        assert "IOCExtractorFromResults" in source
        # Should be in the APK section (near DecompileAPK)
        apk_section = source[source.index("DecompileAPK("):]
        ioc_pos = apk_section.find("IOCExtractorFromResults")
        assert ioc_pos != -1, "IOCExtractorFromResults not found after DecompileAPK"