#!/usr/bin/env python
import unittest
import lief
import tempfile
import sys
import subprocess
import stat
import os
import logging
import random
import itertools

from subprocess import Popen

from unittest import TestCase
from utils import get_sample

class TestMachO(TestCase):

    def setUp(self):
        self.logger = logging.getLogger(__name__)

    def test_function_starts(self):
        dd = lief.parse(get_sample('MachO/MachO64_x86-64_binary_dd.bin'))

        functions = [
            0x100001581, 0x1000016cc, 0x1000017cc,
            0x1000019e3, 0x100001a03, 0x100001a1d,
            0x1000020ad, 0x1000022f6, 0x1000023ef,
            0x10000246b, 0x10000248c, 0x1000026da,
            0x100002754, 0x10000286b, 0x100002914,
            0x100002bd8, 0x100002be8, 0x100002c2b,
            0x100002c62, 0x100002d24, 0x100002d5a,
            0x100002d91, 0x100002dd5, 0x100002de6,
            0x100002dfc, 0x100002e40, 0x100002e51,
            0x100002e67, 0x100002f9e
        ]

        self.assertEqual(dd.function_starts.data_offset, 21168)
        self.assertEqual(dd.function_starts.data_size,   48)
        text_segment = list(filter(lambda e : e.name == "__TEXT", dd.segments))[0]
        functions_dd = map(text_segment.virtual_address .__add__, dd.function_starts.functions)

        self.assertEqual(functions, list(functions_dd))


    def test_version_min(self):
        sshd = lief.parse(get_sample('MachO/MachO64_x86-64_binary_sshd.bin'))
        self.assertEqual(sshd.version_min.version, [10, 11, 0])
        self.assertEqual(sshd.version_min.sdk, [10, 11, 0])

    def test_va2offset(self):
        dd = lief.parse(get_sample('MachO/MachO64_x86-64_binary_dd.bin'))
        self.assertEqual(dd.virtual_address_to_offset(0x100004054), 0x4054)


    def test_thread_cmd(self):
        micromacho = lief.parse(get_sample('MachO/MachO32_x86_binary_micromacho.bin'))
        self.assertTrue(micromacho.has_thread_command)
        self.assertEqual(micromacho.thread_command.pc, 0x68)
        self.assertEqual(micromacho.thread_command.flavor, 1)
        self.assertEqual(micromacho.thread_command.count, 16)
        self.assertEqual(micromacho.entrypoint, 0x68)

    def test_rpath_cmd(self):
        rpathmacho = lief.parse(get_sample('MachO/MachO64_x86-64_binary_rpathtest.bin'))
        self.assertEqual(rpathmacho.rpath.path, "@executable_path/../lib")

    def test_relocations(self):
        helloworld = lief.parse(get_sample('MachO/MachO64_x86-64_object_HelloWorld64.o'))

        # __text Section
        text_section = helloworld.get_section("__text")
        relocations  = text_section.relocations
        self.assertEqual(len(relocations), 2)

        # 0
        self.assertEqual(relocations[0].address, 0x21b)
        self.assertEqual(relocations[0].type,    1)
        self.assertEqual(relocations[0].size,    32)

        self.assertEqual(relocations[0].is_scattered, False)

        self.assertEqual(relocations[0].has_symbol,  False)

        self.assertEqual(relocations[0].has_section,  True)
        self.assertEqual(relocations[0].section.name, text_section.name)

        # 1
        self.assertEqual(relocations[1].address, 0x233)
        self.assertEqual(relocations[1].type,    2)
        self.assertEqual(relocations[1].size,    32)

        self.assertEqual(relocations[1].is_scattered, False)

        self.assertEqual(relocations[1].has_symbol,  True)
        self.assertEqual(relocations[1].symbol.name, "_printf")

        self.assertEqual(relocations[1].has_section,  True)
        self.assertEqual(relocations[1].section.name, text_section.name)


        # __compact_unwind__LD  Section
        cunwind_section = helloworld.get_section("__compact_unwind")
        relocations  = cunwind_section.relocations
        self.assertEqual(len(relocations), 1)

        # 0
        self.assertEqual(relocations[0].address, 0x247)
        self.assertEqual(relocations[0].type,    0)
        self.assertEqual(relocations[0].size,    32)

        self.assertEqual(relocations[0].is_scattered, False)

        self.assertEqual(relocations[0].has_symbol,  False)

        self.assertEqual(relocations[0].has_section,  True)
        self.assertEqual(relocations[0].section.name, "__cstring")

    def test_data_in_code(self):
        binary = lief.parse(get_sample('MachO/MachO32_ARM_binary_data-in-code-LLVM.bin'))

        self.assertTrue(binary.has_data_in_code)
        dcode = binary.data_in_code

        self.assertEqual(dcode.data_offset, 0x11c)
        self.assertEqual(dcode.data_size, 0x20)

        self.assertEqual(len(dcode.entries), 4)

        self.assertEqual(dcode.entries[0].type, lief.MachO.DataCodeEntry.TYPES.DATA)
        self.assertEqual(dcode.entries[0].offset, 0)
        self.assertEqual(dcode.entries[0].length, 4)

        self.assertEqual(dcode.entries[1].type, lief.MachO.DataCodeEntry.TYPES.JUMP_TABLE_32)
        self.assertEqual(dcode.entries[1].offset, 4)
        self.assertEqual(dcode.entries[1].length, 4)

        self.assertEqual(dcode.entries[2].type, lief.MachO.DataCodeEntry.TYPES.JUMP_TABLE_16)
        self.assertEqual(dcode.entries[2].offset, 8)
        self.assertEqual(dcode.entries[2].length, 2)

        self.assertEqual(dcode.entries[3].type, lief.MachO.DataCodeEntry.TYPES.JUMP_TABLE_8)
        self.assertEqual(dcode.entries[3].offset, 10)
        self.assertEqual(dcode.entries[3].length, 1)


    def test_segment_split_info(self):
        binary = lief.parse(get_sample('MachO/FAT_MachO_x86_x86-64_library_libdyld.dylib'))

        self.assertTrue(binary.has_segment_split_info)
        ssi = binary.segment_split_info
        self.assertEqual(ssi.data_offset, 32852)
        self.assertEqual(ssi.data_size, 292)

    def test_dyld_environment(self):
        binary = lief.parse(get_sample('MachO/MachO64_x86-64_binary_safaridriver.bin'))
        self.assertTrue(binary.has_dyld_environment)
        self.assertEqual(binary.dyld_environment.value, "DYLD_VERSIONED_FRAMEWORK_PATH=/System/Library/StagedFrameworks/Safari")

    def test_sub_framework(self):
        binary = lief.parse(get_sample('MachO/FAT_MachO_x86_x86-64_library_libdyld.dylib'))
        self.assertTrue(binary.has_sub_framework)
        self.assertEqual(binary.sub_framework.umbrella, "System")

    def test_unwind(self):
        binary = lief.parse(get_sample('MachO/MachO64_x86-64_binary_sshd.bin'))

        functions = sorted(binary.functions, key=lambda f: f.address)

        self.assertEqual(len(functions), 2619)

        self.assertEqual(functions[0].address, 2624)
        self.assertEqual(functions[0].size,    0)
        self.assertEqual(functions[0].name,    "")

        self.assertEqual(functions[-1].address, 0x1000a4f65)
        self.assertEqual(functions[-1].size,    0)
        self.assertEqual(functions[-1].name,    "ctor_0")


    def test_build_version(self):
        binary = lief.MachO.parse(get_sample('MachO/FAT_MachO_arm-arm64-binary-helloworld.bin'))
        target = binary[1]

        self.assertTrue(target.has_build_version)
        build_version = target.build_version

        self.assertEqual(build_version.minos, [12, 1, 0])
        self.assertEqual(build_version.sdk,   [12, 1, 0])
        self.assertEqual(build_version.platform, lief.MachO.BuildVersion.PLATFORMS.IOS)

        tools = build_version.tools
        self.assertEqual(len(tools), 1)
        self.assertEqual(tools[0].version, [409, 12, 0])
        self.assertEqual(tools[0].tool, lief.MachO.BuildToolVersion.TOOLS.LD)



if __name__ == '__main__':

    root_logger = logging.getLogger()
    root_logger.setLevel(logging.DEBUG)

    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    root_logger.addHandler(ch)

    unittest.main(verbosity=2)

