#!/usr/bin/env python
# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright (C) 2023 Fortra. All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
from __future__ import print_function
import unittest
from binascii import hexlify

from impacket.dcerpc.v5.ndr import (NDRSTRUCT, NDRLONG, NDRSHORT,
                                    NDRUniFixedArray,
                                    NDRUniVaryingArray,
                                    NDRUniConformantVaryingArray,
                                    NDRVaryingString,
                                    NDRConformantVaryingString,
                                    NDRPOINTERNULL)


def hexl(b):
    hexstr = str(hexlify(b).decode('ascii'))
    return ' '.join([hexstr[i:i + 8] for i in range(0, len(hexstr), 8)])


class NDRTest(object):

    def create(self, data=None, isNDR64=False):
        if data is not None:
            return self.theClass(data, isNDR64=isNDR64)
        else:
            return self.theClass(isNDR64=isNDR64)

    def do_test(self, isNDR64=False):
        a = self.create(isNDR64=isNDR64)
        self.populate(a)
        # packing...
        a_str = a.getData()
        self.check_data(a_str, isNDR64)
        # unpacking...
        b = self.create(a_str, isNDR64=isNDR64)
        b_str = b.getData()
        self.assertEqual(b_str, a_str)

    def test_false(self):
        self.do_test(False)

    def test_true(self):
        # Now the same tests but with NDR64
        self.do_test(True)

    def check_data(self, a_str, isNDR64):
        try:
            hexData = getattr(self, 'hexData64' if isNDR64 else 'hexData')
            # Regression check
            self.assertEqual(hexl(a_str), hexData)
        except AttributeError:
            # Show result, to aid adding regression check
            print(self.__class__.__name__, isNDR64, hexl(a_str))


class TestUniFixedArray(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRUniFixedArray),
        )

    def populate(self, a):
        a['Array'] = b'12345678'

    hexData = '31323334 35363738'
    hexData64 = hexData


class TestStructWithPad(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('long', NDRLONG),
            ('short', NDRSHORT),
        )

    def populate(self, a):
        a['long'] = 0xaa
        a['short'] = 0xbb

    hexData = 'aa000000 bb00'
    hexData64 = hexData


# class TestUniConformantArray(NDRTest):
#    class theClass(NDRCall):
#        structure = (
#            ('Array', PNDRUniConformantArray),
#            ('Array2', PNDRUniConformantArray),
#        )
#        def __init__(self, data = None,isNDR64 = False):
#            NDRCall.__init__(self, None, isNDR64)
#            self.fields['Array'].fields['Data'].item = RPC_UNICODE_STRING
#            self.fields['Array2'].fields['Data'].item = RPC_UNICODE_STRING
#            if data is not None:
#                self.fromString(data)
#        
#    def populate(self, a):
#        array = []
#        strstr = RPC_UNICODE_STRING()
#        strstr['Data'] = 'ThisIsMe'
#        array.append(strstr)
#        strstr = RPC_UNICODE_STRING()
#        strstr['Data'] = 'ThisIsYou'
#        array.append(strstr)
#        a['Array'] = array
#        a['Array2'] = array

class TestUniVaryingArray(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRUniVaryingArray),
        )

    def populate(self, a):
        a['Array'] = b'12345678'

    hexData = '00000000 08000000 31323334 35363738'
    hexData64 = '00000000 00000000 08000000 00000000 31323334 35363738'


class TestUniConformantVaryingArray(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRUniConformantVaryingArray),
        )

    def populate(self, a):
        a['Array'] = b'12345678'

    hexData = '08000000 00000000 08000000 31323334 35363738'
    hexData64 = '08000000 00000000 00000000 00000000 08000000 00000000 31323334 35363738'


class TestVaryingString(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRVaryingString),
        )

    def populate(self, a):
        a['Array'] = b'12345678'

    hexData = '00000000 09000000 31323334 35363738 00'
    hexData64 = '00000000 00000000 09000000 00000000 31323334 35363738 00'


class TestConformantVaryingString(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRConformantVaryingString),
        )

    def populate(self, a):
        a['Array'] = b'12345678'

    hexData = '08000000 00000000 08000000 31323334 35363738'
    hexData64 = '08000000 00000000 00000000 00000000 08000000 00000000 31323334 35363738'


class TestPointerNULL(NDRTest, unittest.TestCase):
    class theClass(NDRSTRUCT):
        structure = (
            ('Array', NDRPOINTERNULL),
        )

    def populate(self, a):
        pass

    hexData = '00000000'
    hexData64 = '00000000 00000000'


if __name__ == '__main__':
    unittest.main(verbosity=1)
