# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)

import pytest
import six
import types
import uuid

from collections import (
    OrderedDict,
)

from datetime import (
    datetime,
)

from smbprotocol import (
    Commands,
    Dialects,
)

from smbprotocol.connection import (
    Capabilities,
)

from smbprotocol.structure import (
    _bytes_to_hex,
    BoolField,
    BytesField,
    DateTimeField,
    EnumField,
    FlagField,
    IntField,
    InvalidFieldDefinition,
    ListField,
    Structure,
    StructureField,
    TextField,
    UuidField,
)


def test_bytes_to_hex_pretty_newline():
    bytes_str = b"\x00\x01abc123new"
    expected = "00 01 61 62 63 31 32 33\n6E 65 77"
    actual = _bytes_to_hex(bytes_str, pretty=True)
    assert actual == expected


def test_bytes_to_hex_pretty_newline_override():
    bytes_str = b"\x00\x01abc123new"
    expected = "00 01 61 62\n63 31 32 33\n6E 65 77"
    actual = _bytes_to_hex(bytes_str, pretty=True, hex_per_line=4)
    assert actual == expected


def test_bytes_to_hex_pretty_nonewline():
    bytes_str = b"\x00\x01abc123new"
    expected = "00 01 61 62 63 31 32 33 6E 65 77"
    actual = _bytes_to_hex(bytes_str, pretty=True, hex_per_line=0)
    assert actual == expected


def test_bytes_to_hex_not_pretty():
    bytes_str = b"\x00\x01abc123new"
    expected = "00016162633132336e6577"
    actual = _bytes_to_hex(bytes_str, pretty=False)
    assert actual == expected


class Structure2(Structure):
    def __init__(self):
        self.fields = OrderedDict([
            ('field', IntField(
                size=4,
                default=125,
            )),
            ('bytes', BytesField(
                size=4,
                default=b"\x10\x11\x12\x13",
            )),
        ])
        super(Structure2, self).__init__()


class Structure1(Structure):
    def __init__(self):
        self.fields = OrderedDict([
            ('int_field', IntField(size=4)),
            ('bytes_field', BytesField(size=2)),
            ('var_field', BytesField(
                size=lambda s: s['int_field'].get_value(),
            )),
            ('default_field', IntField(
                size=2,
                default=b"\x01a",
            )),
            ('list_field', ListField(
                list_count=lambda s: s['int_field'].get_value(),
                list_type=BytesField(size=8),
                size=lambda s: s['int_field'].get_value() * 8,
            )),
            ('structure_length', IntField(
                size=2,
                little_endian=False,
                default=lambda s: len(s['structure_field']),
            )),
            ('structure_field', StructureField(
                size=lambda s: s['structure_length'].get_value(),
                structure_type=Structure2,
            )),
        ])

        super(Structure1, self).__init__()


class TestStructure(object):

    def test_structure_defaults(self):
        actual = Structure1()
        assert len(actual.fields) == 7
        assert actual['int_field'].get_value() == 0
        assert actual['bytes_field'].get_value() == b""
        assert actual['var_field'].get_value() == b""
        assert actual['default_field'].get_value() == 24833
        assert actual['list_field'].get_value() == []
        assert actual['structure_length'].get_value() == 0
        assert actual['structure_field'].get_value() == b""

    def test_get_field(self):
        structure = Structure1()
        actual = structure['default_field']
        assert actual.name == "default_field"
        assert actual.size == 2
        assert actual.get_value() == 24833

    def test_set_field(self):
        structure = Structure1()
        assert structure['int_field'].get_value() == 0
        structure['int_field'] = 10
        assert structure['int_field'].get_value() == 10

    def test_remove_field(self):
        structure = Structure1()
        assert len(structure.fields) == 7
        del structure['int_field']
        assert len(structure.fields) == 6
        with pytest.raises(ValueError) as exc:
            value = structure['int_field']
        assert str(exc.value) == "Structure does not contain field int_field"

    def test_pack_structure(self):
        structure = Structure1()
        sub_structure = Structure2()
        structure['int_field'] = 3
        structure['bytes_field'] = b"\x01\x02"
        structure['var_field'] = b"\x03\x04\x05"
        structure['list_field'] = [
            b"\x31\x00\x32\x00\x33\x00\x34\x00",
            b"1\x002\x003\x004\00",
            sub_structure,
        ]
        structure['structure_field'] = sub_structure

        expected = b"\x03\x00\x00\x00" \
                   b"\x01\x02" \
                   b"\x03\x04\x05" \
                   b"\x01\x61" \
                   b"\x31\x00\x32\x00\x33\x00\x34\x00" \
                   b"\x31\x00\x32\x00\x33\x00\x34\x00" \
                   b"\x7d\x00\x00\x00\x10\x11\x12\x13" \
                   b"\x00\x08" \
                   b"\x7d\x00\x00\x00\x10\x11\x12\x13"
        actual = structure.pack()
        assert actual == expected
        assert len(structure) == len(actual)

    def test_unpack_structure(self):
        packed_data = b"\x03\x00\x00\x00" \
                      b"\x01\x02" \
                      b"\x03\x04\x05" \
                      b"\x01\x61" \
                      b"\x31\x00\x32\x00\x33\x00\x34\x00" \
                      b"\x31\x00\x32\x00\x33\x00\x34\x00" \
                      b"\x7d\x00\x00\x00\x10\x11\x12\x13" \
                      b"\x00\x08" \
                      b"\x7d\x00\x00\x00\x10\x11\x12\x13"

        actual = Structure1()
        actual.unpack(packed_data)
        assert actual['int_field'].get_value() == 3
        assert actual['bytes_field'].get_value() == b"\x01\x02"
        assert actual['var_field'].get_value() == b"\x03\x04\x05"
        assert actual['default_field'].get_value() == 24833
        assert actual['list_field'].get_value() == [
            b"\x31\x00\x32\x00\x33\x00\x34\x00",
            b"\x31\x00\x32\x00\x33\x00\x34\x00",
            b"\x7d\x00\x00\x00\x10\x11\x12\x13"
        ]
        assert actual['structure_length'].get_value() == 8
        expected_struct = Structure2().pack()
        assert actual['structure_field'].get_value().pack() == expected_struct
        assert len(actual) == len(packed_data)

    def test_structure_string(self):
        structure = Structure1()
        sub_structure = Structure2()
        structure['int_field'] = 3
        structure['bytes_field'] = b"\x01\x02"
        structure['var_field'] = b"\x03\x04\x05"
        structure['list_field'] = [
            b"\x31\x00\x32\x00\x33\x00\x34\x00",
            b"1\x002\x003\x004\x00",
            sub_structure,
        ]
        structure['structure_field'] = sub_structure

        expected = """Structure1:
    int_field = 3
    bytes_field = 01 02
    var_field = 03 04 05
    default_field = 24833
    list_field = [
        31 00 32 00 33 00 34 00,
        31 00 32 00 33 00 34 00,
        Structure2:
            field = 125
            bytes = 10 11 12 13

            Raw Hex:
                7D 00 00 00 10 11 12 13
    ]
    structure_length = 8
    structure_field =
    Structure2:
        field = 125
        bytes = 10 11 12 13

        Raw Hex:
            7D 00 00 00 10 11 12 13

    Raw Hex:
        03 00 00 00 01 02 03 04
        05 01 61 31 00 32 00 33
        00 34 00 31 00 32 00 33
        00 34 00 7D 00 00 00 10
        11 12 13 00 08 7D 00 00
        00 10 11 12 13"""
        actual = str(structure)
        assert actual == expected

    def test_end_field_no_size(self):
        class Structure3(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', IntField(size=2, default=1)),
                    ('end', BytesField()),
                ])
                super(Structure3, self).__init__()

        structure = Structure3()
        structure['end'] = b"\x01\x02\x03\x04"
        expected_pack = b"\x01\x00\x01\x02\x03\x04"
        actual_pack = structure.pack()
        assert actual_pack == expected_pack
        assert len(structure['end']) == 4

        structure.unpack(b"\x02\x00\x05\x06\x07\x08\x09\x10")
        assert structure['field'].get_value() == 2
        assert structure['end'].get_value() == b"\x05\x06\x07\x08\x09\x10"
        assert len(structure['end']) == 6


class TestIntField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', IntField(size=4, default=1234))
            ])
            super(TestIntField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 4
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "1234"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = 1234
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\xd2\x04\x00\x00"
        actual = field.pack()
        assert actual == expected

    def test_pack_signed(self):
        class UnsignedStructure(Structure):
            def __init__(self):
                self.fields = OrderedDict([(
                    'field', IntField(size=2, unsigned=False, default=-1)
                )])
                super(UnsignedStructure, self).__init__()

        field = UnsignedStructure()['field']
        expected = b"\xff\xff"
        actual = field.pack()
        assert actual == expected

    def test_pack_with_lambda_size(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.size = lambda s: 2
        field.set_value(4)
        expected = b"\x04\x00"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\xd2\x05\x00\x00")
        expected = 1490
        actual = field.get_value()
        assert actual == expected

    def test_invalid_size_none(self):
        with pytest.raises(InvalidFieldDefinition) as exc:
            IntField(size=None)
        assert str(exc.value) == "IntField size must have a value of 1, 2, " \
                                 "4, or 8 not None"

    def test_invalid_size_bad_int(self):
        with pytest.raises(InvalidFieldDefinition) as exc:
            IntField(size=3)
        assert str(exc.value) == "IntField size must have a value of 1, 2, " \
                                 "4, or 8 not 3"

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = 0
        actual = field.get_value()
        assert isinstance(field.value, int)
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: 4567)
        expected = 4567
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 4

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x12\x34\x00\x00")
        expected = 13330
        actual = field.get_value()
        assert isinstance(field.value, int)
        assert actual == expected

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(9876)
        expected = 9876
        actual = field.get_value()
        assert isinstance(field.value, int)
        assert actual == expected

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to an int"

    def test_byte_order(self):
        class ByteOrderStructure(Structure):
            def __init__(self):
                self.fields = OrderedDict([(
                    'field', IntField(size=2, little_endian=False, default=10)
                )])
                super(ByteOrderStructure, self).__init__()

        field = ByteOrderStructure()['field']
        expected = b"\x00\x0a"
        actual = field.pack()
        assert actual == expected


class TestBytesField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', BytesField(size=4, default=b"\x10\x11\x12\x13"))
            ])
            super(TestBytesField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 4
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "10 11 12 13"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = b"\x10\x11\x12\x13"
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x10\x11\x12\x13"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x7a\x00\x79\x00")
        expected = b"\x7a\x00\x79\x00"
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = b""
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: b"\x10\x11\x12\x13")
        expected = b"\x10\x11\x12\x13"
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 4

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x78\x00\x77\x00")
        expected = b"\x78\x00\x77\x00"
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(11)
        expected = b"\x0b\x00\x00\x00"
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_structure(self):
        field = self.StructureTest()['field']
        field.size = 8
        field.set_value(Structure2())
        expected = b"\x7d\x00\x00\x00\x10\x11\x12\x13"
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected
        assert len(field) == 8

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a byte string"

    def test_pack_invalid_size(self):
        field = self.StructureTest()['field']
        field.name = "field"
        field.set_value(b"\x01\x02")
        assert len(field) == 2
        with pytest.raises(ValueError) as exc:
            field.pack()
        assert str(exc.value) == "Invalid packed data length for field " \
                                 "field of 2 does not fit field size of 4"

    def test_set_int_invalid_size(self):
        class InvalidSizeStructure(Structure):
            def __init__(self):
                self.fields = OrderedDict([(
                    'field', BytesField(size=3)
                )])
                super(InvalidSizeStructure, self).__init__()

        with pytest.raises(InvalidFieldDefinition) as exc:
            field = InvalidSizeStructure()['field']
            field.set_value(1)
        assert str(exc.value) == "Cannot struct format of size 3"

    def test_set_invalid_size(self):
        class InvalidSizeStructure(Structure):
            def __init__(self):
                self.fields = OrderedDict([(
                    'field', BytesField(size="a")
                )])
                super(InvalidSizeStructure, self).__init__()

        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidSizeStructure()
        assert str(exc.value) == "BytesField size for field must be an int " \
                                 "or None for a variable length"


class TestListField(object):
    # unpack variable length list

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', ListField(
                    size=4,
                    list_count=2,
                    list_type=BytesField(size=2),
                    default=[b"\x01\x02", b"\x03\x04"]
                ))
            ])
            super(TestListField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 4
        actual = len(field)
        assert actual == expected

    def test_get_item(self):
        field = self.StructureTest()['field']
        assert field[0] == b"\x01\x02"
        assert field[1] == b"\x03\x04"

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "[\n    01 02,\n    03 04\n]"
        actual = str(field)
        assert actual == expected

    def test_to_string_empty(self):
        field = self.StructureTest()['field']
        field.set_value([])
        expected = "[]"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = [b"\x01\x02", b"\x03\x04"]
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x01\x02\x03\x04"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        data = field.unpack(b"\x7a\x00\x79\x00")
        expected = [b"\x7a\x00", b"\x79\x00"]
        actual = field.get_value()
        assert actual == expected

    def test_unpack_func(self):
        class UnpackListStructure(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(
                        size=7,
                        unpack_func=lambda s, d: [
                            b"\x01\x02",
                            b"\x03\x04\x05\x06",
                            b"\07"
                        ]
                    ))

                ])
                super(UnpackListStructure, self).__init__()

        field = UnpackListStructure()['field']
        field.unpack(b"\x00")
        expected = [
            b"\x01\x02",
            b"\x03\x04\x05\x06",
            b"\07"
        ]
        actual = field.get_value()
        assert len(field) == 7
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = []
        actual = field.get_value()
        assert isinstance(field.value, list)
        assert actual == expected
        assert len(field) == 0
        assert len(field.get_value()) == 0

    def test_set_lambda_as_bytes(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: b"\x10\x11\x12\x13")
        expected = [b"\x10\x11", b"\x12\x13"]
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 4

    def test_set_lambda_as_list(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: [b"\x10\x11", b"\x12\x13"])
        expected = [b"\x10\x11", b"\x12\x13"]
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 4

    def test_set_bytes_fixed(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x78\x00\x77\x00")
        expected = [b"\x78\x00", b"\x77\x00"]
        actual = field.get_value()
        assert isinstance(field.value, list)
        assert actual == expected

    def test_set_list(self):
        field = self.StructureTest()['field']
        field.set_value([b"\x7d\x00", b"\x00\x00"])
        expected = [b"\x7d\x00", b"\x00\x00"]
        actual = field.get_value()
        assert isinstance(field.value, list)
        assert actual == expected
        assert len(field) == 4
        assert len(field.get_value()) == 2

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value(0)
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type int to a list"

    def test_list_count_not_int_or_lambda(self):
        class InvalidListField(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(list_count="a"))
                ])
                super(InvalidListField, self).__init__()
        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidListField()
        assert str(exc.value) == "ListField list_count must be an int, " \
                                 "lambda, or None for a variable list length"

    def test_unpack_func_not_lambda(self):
        class InvalidListField(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(unpack_func="a"))
                ])
                super(InvalidListField, self).__init__()
        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidListField()
        assert str(exc.value) == "ListField unpack_func must be a lambda " \
                                 "function or None"

    def test_list_field_not_field(self):
        class InvalidListField(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(list_type="a"))
                ])
                super(InvalidListField, self).__init__()
        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidListField()
        assert str(exc.value) == "ListField list_type must be a Field " \
                                 "definition"

    def test_list_unpack_list_type_size_not_defined(self):
        class InvalidListField(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(list_count=1))
                ])
                super(InvalidListField, self).__init__()
        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidListField()
        assert str(exc.value) == "ListField must either define unpack_func " \
                                 "as a lambda or set list_count and " \
                                 "list_size with a size"

    def test_list_unpack_list_count_not_defined(self):
        class InvalidListField(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', ListField(list_type=BytesField(size=1)))
                ])
                super(InvalidListField, self).__init__()
        with pytest.raises(InvalidFieldDefinition) as exc:
            InvalidListField()
        assert str(exc.value) == "ListField must either define unpack_func " \
                                 "as a lambda or set list_count and " \
                                 "list_size with a size"


class TestStructureField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', StructureField(
                    size=8,
                    structure_type=Structure2,
                    default=b"\x7d\x00\x00\x00\x10\x11\x12\x13"
                ))
            ])
            super(TestStructureField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 8
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = """Structure2:
    field = 125
    bytes = 10 11 12 13

    Raw Hex:
        7D 00 00 00 10 11 12 13"""
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = Structure2()
        actual = field.get_value()
        assert actual.pack() == expected.pack()

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x7d\x00\x00\x00\x10\x11\x12\x13"
        actual = field.pack()
        assert actual == expected

    def test_pack_without_type(self):
        field = self.StructureTest()['field']
        field.structure_type = None

        test_value = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        field.set_value(test_value)
        actual = field.pack()
        assert actual == test_value

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert actual.pack() == expected
        assert isinstance(actual, Structure2)

    def test_unpack_without_type(self):
        field = self.StructureTest()['field']
        field.structure_type = None
        field.unpack(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = b""
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_empty_byte(self):
        field = self.StructureTest()['field']
        field.set_value(b"")
        expected = b""
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual.pack() == expected
        assert isinstance(actual, Structure2)
        assert len(field) == 8

    def test_set_lambda_without_type(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.structure_type = None
        field.set_value(lambda s: b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert isinstance(actual, bytes)
        assert len(field) == 8

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, Structure2)
        assert actual.pack() == expected

    def test_set_bytes_without_type(self):
        field = self.StructureTest()['field']
        field.structure_type = None
        field.set_value(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected

    def test_set_bytes_then_structure_type(self):
        field = self.StructureTest()['field']
        field.structure_type = None
        field.set_value(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, bytes)
        assert actual == expected
        field.set_structure_type(Structure2)

        actual = field.get_value()
        assert isinstance(field.value, Structure2)
        assert actual.pack() == expected

    def test_set_bytes_with_lambda_type(self):
        field = self.StructureTest()['field']
        field.structure_type = lambda s: Structure2
        field.set_value(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        expected = b"\x7d\x00\x00\x00\x14\x15\x16\x17"
        actual = field.get_value()
        assert isinstance(field.value, Structure2)
        assert actual.pack() == expected

    def test_set_structure(self):
        field = self.StructureTest()['field']
        expected = b"\x7d\x00\x00\x00\x10\x11\x12\x13"
        actual = field.get_value()
        assert isinstance(field.value, Structure)
        assert actual.pack() == expected
        assert len(field) == 8

    def test_get_structure_field(self):
        field = self.StructureTest()['field']
        expected = 125
        actual = field['field'].get_value()
        assert actual == expected

    def test_fail_get_structure_field_missing(self):
        field = self.StructureTest()['field']
        with pytest.raises(ValueError) as exc:
            field['fake']
        assert str(exc.value) == "Structure does not contain field fake"

    def test_fail_get_structure_bytes_value(self):
        field = self.StructureTest()['field']
        field.structure_type = None
        field.set_value(b"\x7d\x00\x00\x00\x14\x15\x16\x17")
        with pytest.raises(ValueError) as exc:
            field['field']
        assert str(exc.value) == "Cannot get field field when structure is " \
                                 "defined as a byte string"

    def test_set_structure_field(self):
        field = self.StructureTest()['field']
        test_value = 100
        field['field'] = test_value
        actual = field['field'].get_value()
        assert actual == test_value
        # test out the normal path (convoluted way)
        assert field.get_value()['field'].get_value() == test_value

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a structure"


class TestUuidField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', UuidField())
            ])
            super(TestUuidField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 16
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "00000000-0000-0000-0000-000000000000"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = uuid.UUID("00000000-0000-0000-0000-000000000000")
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x00" * 16
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x11" * 16)
        expected = uuid.UUID(bytes=b"\x11" * 16)
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = uuid.UUID("00000000-0000-0000-0000-000000000000")
        actual = field.get_value()
        assert isinstance(field.value, uuid.UUID)
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: uuid.UUID(bytes=b"\x11" * 16))
        expected = uuid.UUID(bytes=b"\x11" * 16)
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 16

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x22" * 16)
        expected = uuid.UUID(bytes=b"\x22" * 16)
        actual = field.get_value()
        assert isinstance(field.value, uuid.UUID)
        assert actual == expected

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(45370982256125128461783280990902428194)
        expected = uuid.UUID(int=45370982256125128461783280990902428194)
        actual = field.get_value()
        assert isinstance(field.value, uuid.UUID)
        assert actual == expected

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a uuid"

    def test_invalid_size_none(self):
        with pytest.raises(InvalidFieldDefinition) as exc:
            UuidField(size=8)
        assert str(exc.value) == "UuidField type must have a size of 16 not 8"

    def test_pack_uuid_field_big_endian(self):
        field = self.StructureTest()['field']
        field.little_endian = False
        field.set_value(uuid.UUID("00000001-0001-0001-0001-000000000001"))
        expected = b"\x01\x00\x00\x00\x01\x00\x01\x00" \
                   b"\x00\x01\x00\x00\x00\x00\x00\x01"
        actual = field.pack()
        assert actual == expected

    def test_unpack_uuid_field_big_endian(self):
        field = self.StructureTest()['field']
        field.little_endian = False
        field.unpack(b"\x01\x00\x00\x00\x01\x00\x01\x00"
                     b"\x00\x01\x00\x00\x00\x00\x00\x01")
        expected = uuid.UUID("00000001-0001-0001-0001-000000000001")
        actual = field.get_value()
        assert actual == expected


class TestDateTimeField(object):

    DATE = datetime(year=1993, month=6, day=11, hour=7, minute=52,
                    second=34, microsecond=34)

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', DateTimeField(
                    default=TestDateTimeField.DATE,
                ))
            ])
            super(TestDateTimeField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 8
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "1993-06-11 07:52:34.000034"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = self.DATE
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x54\x0e\x63\x5e\x2d\xfa\xb7\x01"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x5e\x70\x27\x4a\x6e\x23\x93\x01")
        expected = datetime(year=1960, month=8, day=1, hour=22, minute=7,
                            second=1, microsecond=186774)
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = datetime.today()
        actual = field.get_value()
        assert isinstance(field.value, datetime)
        assert actual.year == expected.year
        assert actual.month == expected.month
        assert actual.day == expected.day

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: datetime(year=1960, month=8, day=2, hour=8,
                                           minute=7, second=1,
                                           microsecond=186774))
        expected = datetime(year=1960, month=8, day=2, hour=8, minute=7,
                            second=1, microsecond=186774)
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 8

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x00\x67\x7b\x21\x3d\x5d\xd3\x01")
        expected = datetime(year=2017, month=11, day=14, hour=11, minute=38,
                            second=46)
        actual = field.get_value()
        assert isinstance(field.value, datetime)
        assert actual == expected

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(131551331260000000)
        expected = datetime(year=2017, month=11, day=14, hour=11, minute=38,
                            second=46)
        actual = field.get_value()
        assert isinstance(field.value, datetime)
        assert actual == expected

    def test_set_datetime(self):
        field = self.StructureTest()['field']
        datetime_value = datetime(year=2017, month=11, day=14, hour=21,
                                  minute=38, second=46)
        field.set_value(datetime_value)
        actual = field.get_value()
        assert isinstance(field.value, datetime)
        assert actual == datetime_value

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a datetime"

    def test_invalid_size_none(self):
        with pytest.raises(InvalidFieldDefinition) as exc:
            DateTimeField(size=4)
        assert str(exc.value) == "DateTimeField type must have a size of 8 " \
                                 "not 4"


class TestEnumField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', EnumField(
                    size=1,
                    enum_type=Commands,
                    default=Commands.SMB2_IOCTL,
                )),
            ])
            super(TestEnumField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 1
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "(11) SMB2_IOCTL"
        actual = str(field)
        assert actual == expected

    def test_to_string_default_as_zero(self):
        class StructureTestDefaultZero(Structure):
            def __init__(self):
                self.fields = OrderedDict([
                    ('field', EnumField(
                        size=2,
                        enum_type=Dialects,
                    ))
                ])
                super(StructureTestDefaultZero, self).__init__()
        field = StructureTestDefaultZero()['field']
        expected = "(0) UNKNOWN_ENUM"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = 11
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x0b"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x0b")
        expected = 11
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = 0
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "(0) SMB2_NEGOTIATE"

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x08")
        expected = 8
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "(8) SMB2_READ"

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(8)
        expected = 8
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "(8) SMB2_READ"

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to an int"

    def test_set_invalid_value(self):
        field = self.StructureTest()['field']
        with pytest.raises(ValueError) as exc:
            field.set_value(0x13)
        assert str(exc.value) == "Enum value 19 does not exist in enum type " \
                                 "<class 'smbprotocol.Commands'>"


class TestFlagField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', FlagField(
                    size=4,
                    flag_type=Capabilities,
                    default=Capabilities.SMB2_GLOBAL_CAP_LEASING |
                    Capabilities.SMB2_GLOBAL_CAP_ENCRYPTION
                )),
            ])
            super(TestFlagField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 4
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "(66) SMB2_GLOBAL_CAP_ENCRYPTION, SMB2_GLOBAL_CAP_LEASING"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = 66
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x42\x00\x00\x00"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x4a\x00\x00\x00")
        expected = 74
        actual = field.get_value()
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = 0
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "0"

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x08\x00\x00\x00")
        expected = 8
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "(8) SMB2_GLOBAL_CAP_MULTI_CHANNEL"

    def test_set_int(self):
        field = self.StructureTest()['field']
        field.set_value(8)
        expected = 8
        actual = field.get_value()
        assert actual == expected
        assert isinstance(field.value, int)
        assert str(field) == "(8) SMB2_GLOBAL_CAP_MULTI_CHANNEL"

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to an int"

    def test_check_flag_set(self):
        field = self.StructureTest()['field']
        assert field.has_flag(Capabilities.SMB2_GLOBAL_CAP_ENCRYPTION)
        assert not field.has_flag(Capabilities.SMB2_GLOBAL_CAP_MULTI_CHANNEL)

    def test_set_flag(self):
        field = self.StructureTest()['field']
        assert not field.has_flag(Capabilities.SMB2_GLOBAL_CAP_MULTI_CHANNEL)
        field.set_flag(Capabilities.SMB2_GLOBAL_CAP_MULTI_CHANNEL)
        assert field.has_flag(Capabilities.SMB2_GLOBAL_CAP_MULTI_CHANNEL)

    def test_set_invalid_flag(self):
        field = self.StructureTest()['field']
        with pytest.raises(ValueError) as ex:
            field.set_flag(10)
        assert str(ex.value) == "Flag value does not exist in flag type " \
                                "<class 'smbprotocol.connection.Capabilities'>"

    def test_set_invalid_value(self):
        field = self.StructureTest()['field']
        with pytest.raises(ValueError) as exc:
            field.set_value(0x00000082)
        assert str(exc.value) == "Invalid flag for field field value set 128"


class TestBoolField(object):

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', BoolField(size=1))
            ])
            super(TestBoolField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 1
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "False"
        actual = str(field)
        assert actual == expected

    def test_to_string_true(self):
        field = self.StructureTest()['field']
        field.set_value(True)
        expected = "True"
        actual = str(field)
        assert actual == expected

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = False
        actual = field.get_value()
        assert actual == expected

    def test_get_value_true(self):
        field = self.StructureTest()['field']
        field.set_value(True)
        expected = True
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x00"
        actual = field.pack()
        assert actual == expected

    def test_pack_true(self):
        field = self.StructureTest()['field']
        field.set_value(True)
        expected = b"\x01"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x00")
        expected = False
        actual = field.get_value()
        assert actual == expected

    def test_unpack_true(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x01")
        expected = True
        actual = field.get_value()
        assert actual == expected

    def test_invalid_size_bad_int(self):
        with pytest.raises(InvalidFieldDefinition) as exc:
            BoolField(size=2)
        assert str(exc.value) == "BoolField size must have a value of 1, not 2"

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = False
        actual = field.get_value()
        assert isinstance(field.value, bool)
        assert actual == expected

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(b"\x01")
        expected = True
        actual = field.get_value()
        assert isinstance(field.value, bool)
        assert actual == expected

    def test_set_bool(self):
        field = self.StructureTest()['field']
        field.set_value(True)
        expected = True
        actual = field.get_value()
        assert isinstance(field.value, bool)
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: True)
        expected = True
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 1

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a bool"


class TestTextField(object):

    STRING_VALUE = u"Hello World - café"

    class StructureTest(Structure):
        def __init__(self):
            self.fields = OrderedDict([
                ('field', TextField(encoding='utf-8', default=TestTextField.STRING_VALUE))
            ])
            super(TestTextField.StructureTest, self).__init__()

    def test_get_size(self):
        field = self.StructureTest()['field']
        expected = 19
        actual = len(field)
        assert actual == expected

    def test_to_string(self):
        field = self.StructureTest()['field']
        expected = "Hello World - café"  # Need to rely on native string for Python 2 support
        actual = str(field)
        assert actual == expected
        assert field.get_value() == self.STRING_VALUE  # Make's sure the value is a unicode string

    def test_get_value(self):
        field = self.StructureTest()['field']
        expected = self.STRING_VALUE
        actual = field.get_value()
        assert actual == expected

    def test_pack(self):
        field = self.StructureTest()['field']
        expected = b"\x48\x65\x6c\x6c\x6f\x20\x57\x6f" \
                   b"\x72\x6c\x64\x20\x2d\x20\x63\x61" \
                   b"\x66\xc3\xa9"
        actual = field.pack()
        assert actual == expected

    def test_unpack(self):
        field = self.StructureTest()['field']
        field.unpack(b"\x48\x65\x6c\x6c\x6f\x20\x57\x6f"
                     b"\x72\x6c\x64\x20\x2d\x20\x63\x61"
                     b"\x66\xc3\xa9")
        expected = self.STRING_VALUE
        actual = field.get_value()
        assert actual == expected

    def test_set_lambda(self):
        structure = self.StructureTest()
        field = structure['field']
        field.name = "field"
        field.structure = self.StructureTest
        field.set_value(lambda s: self.STRING_VALUE)
        expected = self.STRING_VALUE
        actual = field.get_value()
        assert isinstance(field.value, types.LambdaType)
        assert actual == expected
        assert len(field) == 19

    def test_set_bytes(self):
        field = self.StructureTest()['field']
        field.set_value(self.STRING_VALUE.encode('utf-8'))
        expected = self.STRING_VALUE
        actual = field.get_value()
        assert isinstance(field.value, six.text_type)
        assert actual == expected

    def test_set_none(self):
        field = self.StructureTest()['field']
        field.set_value(None)
        expected = u""
        actual = field.get_value()
        assert isinstance(field.value, six.text_type)
        assert actual == expected

    def test_set_invalid(self):
        field = self.StructureTest()['field']
        field.name = "field"
        with pytest.raises(TypeError) as exc:
            field.set_value([])
        assert str(exc.value) == "Cannot parse value for field field of " \
                                 "type list to a text string"

    def test_set_with_different_encoding(self):
        structure = self.StructureTest()
        field = structure['field']
        field.encoding = 'utf-16-le'
        field.set_value(self.STRING_VALUE)

        assert len(field) == 36
        actual = field.get_value()
        assert actual == self.STRING_VALUE
        actual_pack = field.pack()
        assert actual_pack == self.STRING_VALUE.encode('utf-16-le')

        field.set_value("")
        field.unpack(actual_pack)
        assert field.get_value() == self.STRING_VALUE