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

import os
import pytest
import time
import uuid

from datetime import datetime

from smbprotocol import (
    Dialects,
)

from smbprotocol.connection import (
    Connection,
)

from smbprotocol.create_contexts import (
    CreateContextName,
    SMB2CreateAllocationSize,
    SMB2CreateContextRequest,
    SMB2CreateRequestLease,
    SMB2CreateRequestLeaseV2,
    SMB2CreateResponseLease,
    SMB2CreateResponseLeaseV2,
    SMB2CreateQueryMaximalAccessRequest,
    SMB2CreateQueryMaximalAccessResponse,
    SMB2CreateQueryOnDiskIDResponse,
    SMB2CreateTimewarpToken,
)

from smbprotocol.exceptions import (
    SMBException,
    SMBUnsupportedFeature,
)

from smbprotocol.file_info import (
    FileAttributes,
    FileEndOfFileInformation,
    FileFullEaInformation,
    FileInformationClass,
    FileNamesInformation,
    FileStandardInformation,
)

from smbprotocol.open import (
    CloseFlags,
    CreateAction,
    CreateDisposition,
    CreateOptions,
    DirectoryAccessMask,
    FileFlags,
    FilePipePrinterAccessMask,
    ImpersonationLevel,
    InfoType,
    Open,
    ReadWriteChannel,
    RequestedOplockLevel,
    ShareAccess,
    SMB2CloseRequest,
    SMB2CloseResponse,
    SMB2CreateRequest,
    SMB2CreateResponse,
    SMB2FlushRequest,
    SMB2FlushResponse,
    SMB2QueryDirectoryRequest,
    SMB2QueryDirectoryResponse,
    SMB2QueryInfoRequest,
    SMB2QueryInfoResponse,
    SMB2ReadRequest,
    SMB2ReadResponse,
    SMB2SetInfoRequest,
    SMB2SetInfoResponse,
    SMB2WriteRequest,
    SMB2WriteResponse,
)

from smbprotocol.session import (
    Session,
)

from smbprotocol.tree import (
    TreeConnect,
)


class TestSMB2CreateRequest(object):

    def test_create_message(self):
        timewarp_token = SMB2CreateTimewarpToken()
        timewarp_token['timestamp'] = datetime.utcfromtimestamp(0)
        timewarp_context = SMB2CreateContextRequest()
        timewarp_context['buffer_name'] = \
            CreateContextName.SMB2_CREATE_TIMEWARP_TOKEN
        timewarp_context['buffer_data'] = timewarp_token

        message = SMB2CreateRequest()
        message['impersonation_level'] = ImpersonationLevel.Impersonation
        message['desired_access'] = FilePipePrinterAccessMask.GENERIC_READ
        message['file_attributes'] = FileAttributes.FILE_ATTRIBUTE_NORMAL
        message['share_access'] = ShareAccess.FILE_SHARE_READ
        message['create_disposition'] = CreateDisposition.FILE_OPEN
        message['create_options'] = CreateOptions.FILE_NON_DIRECTORY_FILE
        message['buffer_path'] = r"\\server\share".encode("utf-16-le")
        message['buffer_contexts'] = [timewarp_context]
        expected = b"\x39\x00" \
                   b"\x00" \
                   b"\x00" \
                   b"\x02\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x80" \
                   b"\x80\x00\x00\x00" \
                   b"\x01\x00\x00\x00" \
                   b"\x01\x00\x00\x00" \
                   b"\x40\x00\x00\x00" \
                   b"\x78\x00" \
                   b"\x1c\x00" \
                   b"\x98\x00\x00\x00" \
                   b"\x20\x00\x00\x00" \
                   b"\x5c\x00\x5c\x00\x73\x00\x65\x00" \
                   b"\x72\x00\x76\x00\x65\x00\x72\x00" \
                   b"\x5C\x00\x73\x00\x68\x00\x61\x00" \
                   b"\x72\x00\x65\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x10\x00" \
                   b"\x04\x00" \
                   b"\x00\x00" \
                   b"\x18\x00" \
                   b"\x08\x00\x00\x00" \
                   b"\x54\x57\x72\x70" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        actual = message.pack()
        assert len(message) == 120
        assert actual == expected

    def test_create_message_no_contexts(self):
        message = SMB2CreateRequest()
        message['impersonation_level'] = ImpersonationLevel.Impersonation
        message['desired_access'] = FilePipePrinterAccessMask.GENERIC_READ
        message['file_attributes'] = FileAttributes.FILE_ATTRIBUTE_NORMAL
        message['share_access'] = ShareAccess.FILE_SHARE_READ
        message['create_disposition'] = CreateDisposition.FILE_OPEN
        message['create_options'] = CreateOptions.FILE_NON_DIRECTORY_FILE
        message['buffer_path'] = r"\\server\share".encode("utf-16-le")
        expected = b"\x39\x00" \
                   b"\x00" \
                   b"\x00" \
                   b"\x02\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x80" \
                   b"\x80\x00\x00\x00" \
                   b"\x01\x00\x00\x00" \
                   b"\x01\x00\x00\x00" \
                   b"\x40\x00\x00\x00" \
                   b"\x78\x00" \
                   b"\x1c\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x5c\x00\x5c\x00\x73\x00\x65\x00" \
                   b"\x72\x00\x76\x00\x65\x00\x72\x00" \
                   b"\x5C\x00\x73\x00\x68\x00\x61\x00" \
                   b"\x72\x00\x65\x00"
        actual = message.pack()
        assert len(message) == 84
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2CreateRequest()
        data = b"\x39\x00" \
               b"\x00" \
               b"\x00" \
               b"\x02\x00\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x80" \
               b"\x80\x00\x00\x00" \
               b"\x01\x00\x00\x00" \
               b"\x01\x00\x00\x00" \
               b"\x40\x00\x00\x00" \
               b"\x78\x00" \
               b"\x1c\x00" \
               b"\x98\x00\x00\x00" \
               b"\x20\x00\x00\x00" \
               b"\x5c\x00\x5c\x00\x73\x00\x65\x00" \
               b"\x72\x00\x76\x00\x65\x00\x72\x00" \
               b"\x5C\x00\x73\x00\x68\x00\x61\x00" \
               b"\x72\x00\x65\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x10\x00" \
               b"\x04\x00" \
               b"\x00\x00" \
               b"\x18\x00" \
               b"\x08\x00\x00\x00" \
               b"\x54\x57\x72\x70" \
               b"\x00\x00\x00\x00" \
               b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        data = actual.unpack(data)
        assert len(actual) == 120
        assert data == b""
        assert actual['structure_size'].get_value() == 57
        assert actual['security_flags'].get_value() == 0
        assert actual['requested_oplock_level'].get_value() == 0
        assert actual['impersonation_level'].get_value() == \
            ImpersonationLevel.Impersonation
        assert actual['smb_create_flags'].get_value() == 0
        assert actual['reserved'].get_value() == 0
        assert actual['desired_access'].get_value() == \
            FilePipePrinterAccessMask.GENERIC_READ
        assert actual['file_attributes'].get_value() == \
            FileAttributes.FILE_ATTRIBUTE_NORMAL
        assert actual['share_access'].get_value() == \
            ShareAccess.FILE_SHARE_READ
        assert actual['create_disposition'].get_value() == \
            CreateDisposition.FILE_OPEN
        assert actual['create_options'].get_value() == \
            CreateOptions.FILE_NON_DIRECTORY_FILE
        assert actual['name_offset'].get_value() == 120
        assert actual['name_length'].get_value() == 28
        assert actual['create_contexts_offset'].get_value() == 152
        assert actual['create_contexts_length'].get_value() == 32
        assert actual['buffer_path'].get_value() == \
            r"\\server\share".encode("utf-16-le")
        assert actual['padding'].get_value() == b"\x00\x00\x00\x00"

        contexts = actual['buffer_contexts'].get_value()
        assert isinstance(contexts, list)
        timewarp_context = contexts[0]
        assert timewarp_context['next'].get_value() == 0
        assert timewarp_context['name_offset'].get_value() == 16
        assert timewarp_context['name_length'].get_value() == 4
        assert timewarp_context['reserved'].get_value() == 0
        assert timewarp_context['data_offset'].get_value() == 24
        assert timewarp_context['data_length'].get_value() == 8
        assert timewarp_context['buffer_name'].get_value() == \
            CreateContextName.SMB2_CREATE_TIMEWARP_TOKEN
        assert timewarp_context['padding'].get_value() == b"\x00\x00\x00\x00"
        assert timewarp_context['buffer_data'].get_value() == \
            b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        assert timewarp_context['padding2'].get_value() == b""

    def test_parse_message_no_contexts(self):
        actual = SMB2CreateRequest()
        data = b"\x39\x00" \
               b"\x00" \
               b"\x00" \
               b"\x02\x00\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x80" \
               b"\x80\x00\x00\x00" \
               b"\x01\x00\x00\x00" \
               b"\x01\x00\x00\x00" \
               b"\x40\x00\x00\x00" \
               b"\x78\x00" \
               b"\x1c\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x5c\x00\x5c\x00\x73\x00\x65\x00" \
               b"\x72\x00\x76\x00\x65\x00\x72\x00" \
               b"\x5C\x00\x73\x00\x68\x00\x61\x00" \
               b"\x72\x00\x65\x00" \

        data = actual.unpack(data)
        assert len(actual) == 84
        assert data == b""
        assert actual['structure_size'].get_value() == 57
        assert actual['security_flags'].get_value() == 0
        assert actual['requested_oplock_level'].get_value() == 0
        assert actual['impersonation_level'].get_value() == \
            ImpersonationLevel.Impersonation
        assert actual['smb_create_flags'].get_value() == 0
        assert actual['reserved'].get_value() == 0
        assert actual['desired_access'].get_value() == \
            FilePipePrinterAccessMask.GENERIC_READ
        assert actual['file_attributes'].get_value() == \
            FileAttributes.FILE_ATTRIBUTE_NORMAL
        assert actual['share_access'].get_value() == \
            ShareAccess.FILE_SHARE_READ
        assert actual['create_disposition'].get_value() == \
            CreateDisposition.FILE_OPEN
        assert actual['create_options'].get_value() == \
            CreateOptions.FILE_NON_DIRECTORY_FILE
        assert actual['name_offset'].get_value() == 120
        assert actual['name_length'].get_value() == 28
        assert actual['create_contexts_offset'].get_value() == 0
        assert actual['create_contexts_length'].get_value() == 0
        assert actual['buffer_path'].get_value() == \
            r"\\server\share".encode("utf-16-le")
        assert actual['padding'].get_value() == b""
        assert actual['buffer_contexts'].get_value() == []


class TestSMB2CreateResponse(object):

    def test_create_message(self):
        message = SMB2CreateResponse()
        message['flag'] = FileFlags.SMB2_CREATE_FLAG_REPARSEPOINT
        message['create_action'] = CreateAction.FILE_CREATED
        message['creation_time'] = datetime.utcfromtimestamp(1024)
        message['last_access_time'] = datetime.utcfromtimestamp(2048)
        message['last_write_time'] = datetime.utcfromtimestamp(3072)
        message['change_time'] = datetime.utcfromtimestamp(4096)
        message['allocation_size'] = 10
        message['end_of_file'] = 20
        message['file_attributes'] = FileAttributes.FILE_ATTRIBUTE_ARCHIVE
        message['file_id'] = b"\xff" * 16

        timewarp_token = SMB2CreateTimewarpToken()
        timewarp_token['timestamp'] = datetime.utcfromtimestamp(0)
        timewarp_context = SMB2CreateContextRequest()
        timewarp_context['buffer_name'] = \
            CreateContextName.SMB2_CREATE_TIMEWARP_TOKEN
        timewarp_context['buffer_data'] = timewarp_token
        message['buffer'] = [timewarp_context]
        expected = b"\x59\x00" \
                   b"\x00" \
                   b"\x01" \
                   b"\x02\x00\x00\x00" \
                   b"\x00\x80\x98\x37\xe1\xb1\x9d\x01" \
                   b"\x00\x80\xf2\x99\xe3\xb1\x9d\x01" \
                   b"\x00\x80\x4c\xfc\xe5\xb1\x9d\x01" \
                   b"\x00\x80\xa6\x5e\xe8\xb1\x9d\x01" \
                   b"\x0a\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x14\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x20\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x98\x00\x00\x00" \
                   b"\x20\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x10\x00" \
                   b"\x04\x00" \
                   b"\x00\x00" \
                   b"\x18\x00" \
                   b"\x08\x00\x00\x00" \
                   b"\x54\x57\x72\x70" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        actual = message.pack()
        assert len(message) == 120
        assert actual == expected

    def test_create_message_no_contexts(self):
        message = SMB2CreateResponse()
        message['flag'] = FileFlags.SMB2_CREATE_FLAG_REPARSEPOINT
        message['create_action'] = CreateAction.FILE_CREATED
        message['creation_time'] = datetime.utcfromtimestamp(1024)
        message['last_access_time'] = datetime.utcfromtimestamp(2048)
        message['last_write_time'] = datetime.utcfromtimestamp(3072)
        message['change_time'] = datetime.utcfromtimestamp(4096)
        message['allocation_size'] = 10
        message['end_of_file'] = 20
        message['file_attributes'] = FileAttributes.FILE_ATTRIBUTE_ARCHIVE
        message['file_id'] = b"\xff" * 16
        expected = b"\x59\x00" \
                   b"\x00" \
                   b"\x01" \
                   b"\x02\x00\x00\x00" \
                   b"\x00\x80\x98\x37\xe1\xb1\x9d\x01" \
                   b"\x00\x80\xf2\x99\xe3\xb1\x9d\x01" \
                   b"\x00\x80\x4c\xfc\xe5\xb1\x9d\x01" \
                   b"\x00\x80\xa6\x5e\xe8\xb1\x9d\x01" \
                   b"\x0a\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x14\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x20\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00"
        actual = message.pack()
        assert len(message) == 88
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2CreateResponse()
        data = b"\x59\x00" \
               b"\x00" \
               b"\x01" \
               b"\x02\x00\x00\x00" \
               b"\x00\x80\x98\x37\xe1\xb1\x9d\x01" \
               b"\x00\x80\xf2\x99\xe3\xb1\x9d\x01" \
               b"\x00\x80\x4c\xfc\xe5\xb1\x9d\x01" \
               b"\x00\x80\xa6\x5e\xe8\xb1\x9d\x01" \
               b"\x0a\x00\x00\x00\x00\x00\x00\x00" \
               b"\x14\x00\x00\x00\x00\x00\x00\x00" \
               b"\x20\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x98\x00\x00\x00" \
               b"\x20\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x10\x00" \
               b"\x04\x00" \
               b"\x00\x00" \
               b"\x18\x00" \
               b"\x08\x00\x00\x00" \
               b"\x54\x57\x72\x70" \
               b"\x00\x00\x00\x00" \
               b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        data = actual.unpack(data)
        assert len(actual) == 120
        assert data == b""
        assert actual['structure_size'].get_value() == 89
        assert actual['oplock_level'].get_value() == 0
        assert actual['flag'].get_value() == \
            FileFlags.SMB2_CREATE_FLAG_REPARSEPOINT
        assert actual['create_action'].get_value() == CreateAction.FILE_CREATED
        assert actual['creation_time'].get_value() == \
            datetime.utcfromtimestamp(1024)
        assert actual['last_access_time'].get_value() == \
            datetime.utcfromtimestamp(2048)
        assert actual['last_write_time'].get_value() == \
            datetime.utcfromtimestamp(3072)
        assert actual['change_time'].get_value() == \
            datetime.utcfromtimestamp(4096)
        assert actual['allocation_size'].get_value() == 10
        assert actual['end_of_file'].get_value() == 20
        assert actual['file_attributes'].get_value() == \
            FileAttributes.FILE_ATTRIBUTE_ARCHIVE
        assert actual['reserved2'].get_value() == 0
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['create_contexts_offset'].get_value() == 152
        assert actual['create_contexts_length'].get_value() == 32

        contexts = actual['buffer'].get_value()
        assert isinstance(contexts, list)
        timewarp_context = contexts[0]
        assert timewarp_context['next'].get_value() == 0
        assert timewarp_context['name_offset'].get_value() == 16
        assert timewarp_context['name_length'].get_value() == 4
        assert timewarp_context['reserved'].get_value() == 0
        assert timewarp_context['data_offset'].get_value() == 24
        assert timewarp_context['data_length'].get_value() == 8
        assert timewarp_context['buffer_name'].get_value() == \
            CreateContextName.SMB2_CREATE_TIMEWARP_TOKEN
        assert timewarp_context['padding'].get_value() == b"\x00\x00\x00\x00"
        assert timewarp_context['buffer_data'].get_value() == \
            b"\x00\x80\x3e\xd5\xde\xb1\x9d\x01"
        assert timewarp_context['padding2'].get_value() == b""

    def test_parse_message_no_contexts(self):
        actual = SMB2CreateResponse()
        data = b"\x59\x00" \
               b"\x00" \
               b"\x01" \
               b"\x02\x00\x00\x00" \
               b"\x00\x80\x98\x37\xe1\xb1\x9d\x01" \
               b"\x00\x80\xf2\x99\xe3\xb1\x9d\x01" \
               b"\x00\x80\x4c\xfc\xe5\xb1\x9d\x01" \
               b"\x00\x80\xa6\x5e\xe8\xb1\x9d\x01" \
               b"\x0a\x00\x00\x00\x00\x00\x00\x00" \
               b"\x14\x00\x00\x00\x00\x00\x00\x00" \
               b"\x20\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00"
        data = actual.unpack(data)
        assert len(actual) == 88
        assert data == b""
        assert actual['structure_size'].get_value() == 89
        assert actual['oplock_level'].get_value() == 0
        assert actual['flag'].get_value() == \
            FileFlags.SMB2_CREATE_FLAG_REPARSEPOINT
        assert actual['create_action'].get_value() == CreateAction.FILE_CREATED
        assert actual['creation_time'].get_value() == \
            datetime.utcfromtimestamp(1024)
        assert actual['last_access_time'].get_value() == \
            datetime.utcfromtimestamp(2048)
        assert actual['last_write_time'].get_value() == \
            datetime.utcfromtimestamp(3072)
        assert actual['change_time'].get_value() == \
            datetime.utcfromtimestamp(4096)
        assert actual['allocation_size'].get_value() == 10
        assert actual['end_of_file'].get_value() == 20
        assert actual['file_attributes'].get_value() == \
            FileAttributes.FILE_ATTRIBUTE_ARCHIVE
        assert actual['reserved2'].get_value() == 0
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['create_contexts_offset'].get_value() == 0
        assert actual['create_contexts_length'].get_value() == 0
        assert actual['buffer'].get_value() == []


class TestSMB2CloseRequest(object):

    def test_create_message(self):
        message = SMB2CloseRequest()
        message['flags'].set_flag(CloseFlags.SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB)
        message['file_id'] = b"\xff" * 16
        expected = b"\x18\x00" \
                   b"\x01\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff"
        actual = message.pack()
        assert len(actual) == 24
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2CloseRequest()
        data = b"\x18\x00" \
               b"\x01\x00" \
               b"\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff"
        actual.unpack(data)
        assert len(actual) == 24
        assert actual['structure_size'].get_value() == 24
        assert actual['flags'].get_value() == \
            CloseFlags.SMB2_CLOSE_FLAG_POSTQUERY_ATTRIB
        assert actual['reserved'].get_value() == 0
        assert actual['file_id'].get_value() == b"\xff" * 16


class TestSMB2CloseResponse(object):

    def test_create_message(self):
        message = SMB2CloseResponse()
        message['creation_time'] = datetime.utcfromtimestamp(0)
        message['last_access_time'] = datetime.utcfromtimestamp(0)
        message['last_write_time'] = datetime.utcfromtimestamp(0)
        message['change_time'] = datetime.utcfromtimestamp(0)
        expected = b"\x3c\x00" \
                   b"\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
                   b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
                   b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
                   b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00"
        actual = message.pack()
        assert len(actual) == 60
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2CloseResponse()
        data = b"\x3c\x00" \
               b"\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
               b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
               b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
               b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00"
        actual.unpack(data)
        assert len(actual) == 60
        assert actual['structure_size'].get_value() == 60
        assert actual['flags'].get_value() == 0
        assert actual['reserved'].get_value() == 0
        assert actual['creation_time'].get_value() == \
            datetime.utcfromtimestamp(0)
        assert actual['last_access_time'].get_value() == \
            datetime.utcfromtimestamp(0)
        assert actual['last_write_time'].get_value() == \
            datetime.utcfromtimestamp(0)
        assert actual['change_time'].get_value() == \
            datetime.utcfromtimestamp(0)
        assert actual['allocation_size'].get_value() == 0
        assert actual['end_of_file'].get_value() == 0
        assert actual['file_attributes'].get_value() == 0


class TestSMB2FlushRequest(object):

    def test_create_message(self):
        message = SMB2FlushRequest()
        message['file_id'] = b"\xff" * 16
        expected = b"\x18\x00" \
                   b"\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff"
        actual = message.pack()
        assert len(message) == 24
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2FlushRequest()
        data = b"\x18\x00" \
               b"\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff"
        actual.unpack(data)
        assert len(actual) == 24
        assert actual['structure_size'].get_value() == 24
        assert actual['reserved1'].get_value() == 0
        assert actual['reserved2'].get_value() == 0
        assert actual['file_id'].pack() == b"\xff" * 16


class TestSMB2FlushResponse(object):

    def test_create_message(self):
        message = SMB2FlushResponse()
        expected = b"\x04\x00" \
                   b"\x00\x00"
        actual = message.pack()
        assert len(message) == 4
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2FlushResponse()
        data = b"\x04\x00" \
               b"\x00\x00"
        actual.unpack(data)
        assert len(actual) == 4
        assert actual['structure_size'].get_value() == 4
        assert actual['reserved'].get_value() == 0


class TestSMB2ReadRequest(object):

    def test_create_message(self):
        message = SMB2ReadRequest()
        message['padding'] = b"\x50"
        message['length'] = 1024
        message['offset'] = 0
        message['file_id'] = b"\xff" * 16
        message['remaining_bytes'] = 0
        expected = b"\x31\x00" \
                   b"\x50" \
                   b"\x00" \
                   b"\x00\x04\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00" \
                   b"\x00\x00" \
                   b"\x00"
        actual = message.pack()
        assert len(message) == 49
        assert actual == expected

    def test_create_message_channel_info(self):
        message = SMB2ReadRequest()
        message['padding'] = b"\x50"
        message['length'] = 1024
        message['offset'] = 0
        message['file_id'] = b"\xff" * 16
        message['channel'].set_flag(ReadWriteChannel.SMB2_CHANNEL_RDMA_V1)
        message['remaining_bytes'] = 0
        message['buffer'] = b"\x00" * 16
        expected = b"\x31\x00" \
                   b"\x50" \
                   b"\x00" \
                   b"\x00\x04\x00\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x00\x00\x00\x00" \
                   b"\x01\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x70\x00" \
                   b"\x10\x00" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00"
        actual = message.pack()
        assert len(message) == 64
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2ReadRequest()
        data = b"\x31\x00" \
               b"\x50" \
               b"\x00" \
               b"\x00\x04\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00" \
               b"\x00\x00" \
               b"\x00"
        actual.unpack(data)
        assert len(actual) == 49
        assert actual['structure_size'].get_value() == 49
        assert actual['padding'].get_value() == 80
        assert actual['flags'].get_value() == 0
        assert actual['length'].get_value() == 1024
        assert actual['offset'].get_value() == 0
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['minimum_count'].get_value() == 0
        assert actual['channel'].get_value() == 0
        assert actual['remaining_bytes'].get_value() == 0
        assert actual['read_channel_info_offset'].get_value() == 0
        assert actual['read_channel_info_length'].get_value() == 0
        assert actual['buffer'].get_value() == b"\x00"

    def test_parse_message_channel_info(self):
        actual = SMB2ReadRequest()
        data = b"\x31\x00" \
               b"\x50" \
               b"\x00" \
               b"\x00\x04\x00\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x00\x00\x00\x00" \
               b"\x01\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x70\x00" \
               b"\x10\x00" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00"
        actual.unpack(data)
        assert len(actual) == 64
        assert actual['structure_size'].get_value() == 49
        assert actual['padding'].get_value() == 80
        assert actual['flags'].get_value() == 0
        assert actual['length'].get_value() == 1024
        assert actual['offset'].get_value() == 0
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['minimum_count'].get_value() == 0
        assert actual['channel'].get_value() == \
            ReadWriteChannel.SMB2_CHANNEL_RDMA_V1
        assert actual['remaining_bytes'].get_value() == 0
        assert actual['read_channel_info_offset'].get_value() == 112
        assert actual['read_channel_info_length'].get_value() == 16
        assert actual['buffer'].get_value() == b"\x00" * 16


class TestSMB2ReadResponse(object):

    def test_create_message(self):
        message = SMB2ReadResponse()
        message['data_offset'] = 80
        message['data_length'] = 4
        message['buffer'] = b"\x01\x02\x03\x04"
        expected = b"\x11\x00" \
                   b"\x50" \
                   b"\x00" \
                   b"\x04\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x01\x02\x03\x04"
        actual = message.pack()
        assert len(message) == 20
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2ReadResponse()
        data = b"\x11\x00" \
               b"\x50" \
               b"\x00" \
               b"\x04\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x01\x02\x03\x04"
        actual.unpack(data)
        assert len(actual) == 20
        assert actual['structure_size'].get_value() == 17
        assert actual['data_offset'].get_value() == 80
        assert actual['reserved'].get_value() == 0
        assert actual['data_length'].get_value() == 4
        assert actual['data_remaining'].get_value() == 0
        assert actual['reserved2'].get_value() == 0
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"


class TestSMB2WriteRequest(object):

    def test_create_message(self):
        message = SMB2WriteRequest()
        message['offset'] = 131072
        message['file_id'] = b"\xff" * 16
        message['channel'].set_flag(ReadWriteChannel.SMB2_CHANNEL_NONE)
        message['remaining_bytes'] = 0
        message['buffer'] = b"\x01\x02\x03\x04"
        expected = b"\x31\x00" \
                   b"\x70\x00" \
                   b"\x04\x00\x00\x00" \
                   b"\x00\x00\x02\x00\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00" \
                   b"\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x01\x02\x03\x04"
        actual = message.pack()
        assert len(message) == 52
        assert actual == expected

    def test_create_message_channel_info(self):
        message = SMB2WriteRequest()
        message['offset'] = 131072
        message['file_id'] = b"\xff" * 16
        message['channel'].set_flag(ReadWriteChannel.SMB2_CHANNEL_RDMA_V1)
        message['remaining_bytes'] = 0
        message['buffer'] = b"\x01\x02\x03\x04"
        message['buffer_channel_info'] = b"\x00" * 16
        expected = b"\x31\x00" \
                   b"\x70\x00" \
                   b"\x04\x00\x00\x00" \
                   b"\x00\x00\x02\x00\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x01\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x74\x00" \
                   b"\x10\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x01\x02\x03\x04" \
                   b"\x00\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00\x00\x00"
        actual = message.pack()
        assert len(message) == 68
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2WriteRequest()
        data = b"\x31\x00" \
               b"\x70\x00" \
               b"\x04\x00\x00\x00" \
               b"\x00\x00\x02\x00\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00" \
               b"\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x01\x02\x03\x04"
        actual.unpack(data)
        assert len(actual) == 52
        assert actual['structure_size'].get_value() == 49
        assert actual['data_offset'].get_value() == 112
        assert actual['length'].get_value() == 4
        assert actual['offset'].get_value() == 131072
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['channel'].get_value() == 0
        assert actual['remaining_bytes'].get_value() == 0
        assert actual['write_channel_info_offset'].get_value() == 0
        assert actual['write_channel_info_length'].get_value() == 0
        assert actual['flags'].get_value() == 0
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"
        assert actual['buffer_channel_info'].get_value() == b""

    def test_parse_message_channel_info(self):
        actual = SMB2WriteRequest()
        data = b"\x31\x00" \
               b"\x70\x00" \
               b"\x04\x00\x00\x00" \
               b"\x00\x00\x02\x00\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x74\x00" \
               b"\x10\x00" \
               b"\x00\x00\x00\x00" \
               b"\x01\x02\x03\x04" \
               b"\x00\x00\x00\x00\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00\x00\x00"
        actual.unpack(data)
        assert len(actual) == 68
        assert actual['structure_size'].get_value() == 49
        assert actual['data_offset'].get_value() == 112
        assert actual['length'].get_value() == 4
        assert actual['offset'].get_value() == 131072
        assert actual['file_id'].pack() == b"\xff" * 16
        assert actual['channel'].get_value() == 0
        assert actual['remaining_bytes'].get_value() == 0
        assert actual['write_channel_info_offset'].get_value() == 116
        assert actual['write_channel_info_length'].get_value() == 16
        assert actual['flags'].get_value() == 0
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"
        assert actual['buffer_channel_info'].get_value() == b"\x00" * 16


class TestSMB2WriteResponse(object):

    def test_create_message(self):
        message = SMB2WriteResponse()
        message['count'] = 58040
        expected = b"\x11\x00" \
                   b"\x00\x00" \
                   b"\xb8\xe2\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\x00\x00" \
                   b"\x00\x00"
        actual = message.pack()
        assert len(message) == 16
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2WriteResponse()
        data = b"\x11\x00" \
               b"\x00\x00" \
               b"\xb8\xe2\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\x00\x00" \
               b"\x00\x00"
        actual.unpack(data)
        assert len(actual) == 16
        assert actual['structure_size'].get_value() == 17
        assert actual['reserved'].get_value() == 0
        assert actual['count'].get_value() == 58040
        assert actual['remaining'].get_value() == 0
        assert actual['write_channel_info_offset'].get_value() == 0
        assert actual['write_channel_info_length'].get_value() == 0


class TestSMB2QueryDirectoryRequest(object):

    def test_create_message(self):
        message = SMB2QueryDirectoryRequest()
        message['file_information_class'] = \
            FileInformationClass.FILE_NAMES_INFORMATION
        message['file_id'] = b"\xB6\x73\xE4\x65\x00\x00\x00\x00" \
            b"\x68\xBD\xA1\xCE\x00\x00\x00\x00"
        message['output_buffer_length'] = 65536
        message['buffer'] = "*".encode('utf-16-le')
        expected = b"\x21\x00" \
                   b"\x0C" \
                   b"\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xB6\x73\xE4\x65\x00\x00\x00\x00" \
                   b"\x68\xBD\xA1\xCE\x00\x00\x00\x00" \
                   b"\x60\x00" \
                   b"\x02\x00" \
                   b"\x00\x00\x01\x00" \
                   b"\x2A\x00"
        actual = message.pack()
        assert len(message) == 34
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2QueryDirectoryRequest()
        data = b"\x21\x00" \
               b"\x0C" \
               b"\x00" \
               b"\x00\x00\x00\x00" \
               b"\xB6\x73\xE4\x65\x00\x00\x00\x00" \
               b"\x68\xBD\xA1\xCE\x00\x00\x00\x00" \
               b"\x60\x00" \
               b"\x02\x00" \
               b"\x00\x00\x01\x00" \
               b"\x2A\x00"
        data = actual.unpack(data)
        assert len(actual) == 34
        assert data == b""
        assert actual['structure_size'].get_value() == 33
        assert actual['file_information_class'].get_value() == \
            FileInformationClass.FILE_NAMES_INFORMATION
        assert actual['flags'].get_value() == 0
        assert actual['file_index'].get_value() == 0
        assert actual['file_id'].get_value() == \
            b"\xB6\x73\xE4\x65\x00\x00\x00\x00" \
            b"\x68\xBD\xA1\xCE\x00\x00\x00\x00"
        assert actual['file_name_offset'].get_value() == 96
        assert actual['file_name_length'].get_value() == 2
        assert actual['output_buffer_length'].get_value() == 65536
        assert actual['buffer'].get_value().decode('utf-16-le') == "*"


class TestSMB2QueryDirectoryResponse(object):

    def test_create_message(self):
        message = SMB2QueryDirectoryResponse()
        message['buffer'] = b"\x10\x00\x00\x00\x00\x00\x00\x00" \
            b"\x02\x00\x00\x00\x2E\x00\x00\x00"
        expected = b"\x09\x00" \
                   b"\x48\x00" \
                   b"\x10\x00\x00\x00" \
                   b"\x10\x00\x00\x00\x00\x00\x00\x00" \
                   b"\x02\x00\x00\x00\x2E\x00\x00\x00"
        actual = message.pack()
        assert len(message) == 24
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2QueryDirectoryResponse()
        data = b"\x09\x00" \
               b"\x48\x00" \
               b"\x10\x00\x00\x00" \
               b"\x10\x00\x00\x00\x00\x00\x00\x00" \
               b"\x02\x00\x00\x00\x2E\x00\x00\x00"
        data = actual.unpack(data)
        assert len(actual) == 24
        assert data == b""
        assert actual['structure_size'].get_value() == 9
        assert actual['output_buffer_offset'].get_value() == 72
        assert actual['output_buffer_length'].get_value() == 16
        assert actual['buffer'].get_value() == \
            b"\x10\x00\x00\x00\x00\x00\x00\x00" \
            b"\x02\x00\x00\x00\x2E\x00\x00\x00"


class TestSMB2QueryInfoRequest(object):

    DATA = b"\x29\x00" \
           b"\x01" \
           b"\x00" \
           b"\x00\x00\x00\x00" \
           b"\x68\x00" \
           b"\x00\x00" \
           b"\x04\x00\x00\x00" \
           b"\x00\x00\x00\x00" \
           b"\x01\x00\x00\x00" \
           b"\xff\xff\xff\xff\xff\xff\xff\xff" \
           b"\xff\xff\xff\xff\xff\xff\xff\xff" \
           b"\x01\x02\x03\x04"

    def test_create_message(self):
        message = SMB2QueryInfoRequest()
        message['info_type'] = 1
        message['flags'] = 1
        message['file_id'] = b"\xff" * 16
        message['buffer'] = b"\x01\x02\x03\x04"

        actual = message.pack()
        assert len(message) == 44
        assert actual == self.DATA

    def test_parse_message(self):
        actual = SMB2QueryInfoRequest()
        data = actual.unpack(self.DATA)

        assert len(actual) == 44
        assert data == b""

        assert actual['structure_size'].get_value() == 41
        assert actual['info_type'].get_value() == 1
        assert actual['file_info_class'].get_value() == 0
        assert actual['output_buffer_length'].get_value() == 0
        assert actual['input_buffer_offset'].get_value() == 104
        assert actual['reserved'].get_value() == 0
        assert actual['input_buffer_length'].get_value() == 4
        assert actual['additional_information'].get_value() == 0
        assert actual['flags'].get_value() == 1
        assert actual['file_id'].get_value() == b"\xff" * 16
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"


class TestSMB2QueryInfoResponse(object):

    def test_create_message(self):
        message = SMB2QueryInfoResponse()
        message['buffer'] = b"\x01\x02\x03\x04"
        expected = b"\x09\x00" \
                   b"\x48\x00" \
                   b"\x04\x00\x00\x00" \
                   b"\x01\x02\x03\x04"

        actual = message.pack()
        assert len(message) == 12
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2QueryInfoResponse()
        data = b"\x09\x00" \
               b"\x48\x00" \
               b"\x04\x00\x00\x00" \
               b"\x01\x02\x03\x04"
        data = actual.unpack(data)
        assert len(actual) == 12
        assert data == b""

        assert actual['structure_size'].get_value() == 9
        assert actual['output_buffer_offset'].get_value() == 72
        assert actual['output_buffer_length'].get_value() == 4
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"

    def test_unpack_multiple_ea_response(self):
        data = b"\x14\x00\x00\x00" \
               b"\x00" \
               b"\x04" \
               b"\x04\x00" \
               b"\x43\x41\x46\xe9\x00" \
               b"\x63\x61\x66\xe9" \
               b"\x00\x00\x00" \
               b"\x10\x00\x00\x00" \
               b"\x00" \
               b"\x03" \
               b"\x04\x00" \
               b"\x41\x42\x43\x00" \
               b"\x64\x65\x66\x67" \
               b"\x00\x00\x00\x00" \
               b"\x00" \
               b"\x0d" \
               b"\x04\x00" \
               b"\x45\x4e\x44\x20\x41\x54\x54\x52" \
               b"\x49\x42\x55\x54\x45\x00" \
               b"\x00\x01\x02\x03"

        message = SMB2QueryInfoResponse()
        message['buffer'] = data
        actual = message.parse_buffer(FileFullEaInformation)

        assert len(actual) == 3

        assert actual[0]['next_entry_offset'].get_value() == 20
        assert actual[0]['flags'].get_value() == 0
        assert actual[0]['ea_name_length'].get_value() == 4
        assert actual[0]['ea_value_length'].get_value() == 4
        assert actual[0]['ea_name'].get_value() == b"\x43\x41\x46\xe9"
        assert actual[0]['ea_value'].get_value() == b"\x63\x61\x66\xe9"

        assert actual[1]['next_entry_offset'].get_value() == 16
        assert actual[1]['flags'].get_value() == 0
        assert actual[1]['ea_name_length'].get_value() == 3
        assert actual[1]['ea_value_length'].get_value() == 4
        assert actual[1]['ea_name'].get_value() == b"\x41\x42\x43"
        assert actual[1]['ea_value'].get_value() == b"\x64\x65\x66\x67"

        assert actual[2]['next_entry_offset'].get_value() == 0
        assert actual[2]['flags'].get_value() == 0
        assert actual[2]['ea_name_length'].get_value() == 13
        assert actual[2]['ea_value_length'].get_value() == 4
        assert actual[2]['ea_name'].get_value() == b"\x45\x4e\x44\x20\x41\x54\x54\x52" \
                                                   b"\x49\x42\x55\x54\x45"
        assert actual[2]['ea_value'].get_value() == b"\x00\x01\x02\x03"


class TestSMB2SetInfoRequest(object):

    def test_create_message(self):
        message = SMB2SetInfoRequest()
        message['info_type'] = 1
        message['file_info_class'] = 1
        message['file_id'] = b"\xff" * 16
        message['buffer'] = b"\x01\x02\x03\x04"
        expected = b"\x21\x00" \
                   b"\x01" \
                   b"\x01" \
                   b"\x04\x00\x00\x00" \
                   b"\x60\x00" \
                   b"\x00\x00" \
                   b"\x00\x00\x00\x00" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\xff\xff\xff\xff\xff\xff\xff\xff" \
                   b"\x01\x02\x03\x04"

        actual = message.pack()
        assert len(message) == 36
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2SetInfoRequest()
        data = b"\x21\x00" \
               b"\x01" \
               b"\x01" \
               b"\x04\x00\x00\x00" \
               b"\x60\x00" \
               b"\x00\x00" \
               b"\x00\x00\x00\x00" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\xff\xff\xff\xff\xff\xff\xff\xff" \
               b"\x01\x02\x03\x04"
        data = actual.unpack(data)
        assert len(actual) == 36
        assert data == b""

        assert actual['structure_size'].get_value() == 33
        assert actual['info_type'].get_value() == 1
        assert actual['file_info_class'].get_value() == 1
        assert actual['buffer_length'].get_value() == 4
        assert actual['buffer_offset'].get_value() == 96
        assert actual['reserved'].get_value() == 0
        assert actual['additional_information'].get_value() == 0
        assert actual['file_id'].get_value() == b"\xff" * 16
        assert actual['buffer'].get_value() == b"\x01\x02\x03\x04"


class TestSMB2SetInfoResponse(object):

    def test_create_message(self):
        message = SMB2SetInfoResponse()
        expected = b"\x02\x00"

        actual = message.pack()
        assert len(message) == 2
        assert actual == expected

    def test_parse_message(self):
        actual = SMB2SetInfoResponse()
        data = b"\x02\x00"
        data = actual.unpack(data)
        assert len(actual) == 2
        assert data == b""

        assert actual['structure_size'].get_value() == 2


class TestOpen(object):

    # basic file open tests for each dialect
    def test_dialect_2_0_2(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_0_2)
        session = Session(connection, smb_real[0], smb_real[1], False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is None
            assert open.create_options is None
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is None
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == 32
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "file.txt"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode is None
        finally:
            connection.disconnect(True)

    def test_dialect_2_1_0(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_1_0)
        session = Session(connection, smb_real[0], smb_real[1], False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is None
            assert open.create_options is None
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is None
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == 32
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "file.txt"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode is None
        finally:
            connection.disconnect(True)

    def test_dialect_3_0_0(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_0)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is \
                CreateDisposition.FILE_OVERWRITE_IF
            assert open.create_options is CreateOptions.FILE_NON_DIRECTORY_FILE
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is \
                FilePipePrinterAccessMask.MAXIMUM_ALLOWED
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == 32
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "file.txt"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode == 0
        finally:
            connection.disconnect(True)

    def test_dialect_3_0_2(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_2)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is \
                CreateDisposition.FILE_OVERWRITE_IF
            assert open.create_options is CreateOptions.FILE_NON_DIRECTORY_FILE
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is \
                FilePipePrinterAccessMask.MAXIMUM_ALLOWED
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == 32
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "file.txt"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode == 0
        finally:
            connection.disconnect(True)

    def test_dialect_3_1_1(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_1_1)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is \
                CreateDisposition.FILE_OVERWRITE_IF
            assert open.create_options is CreateOptions.FILE_NON_DIRECTORY_FILE
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is \
                FilePipePrinterAccessMask.MAXIMUM_ALLOWED
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == 32
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "file.txt"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode == 0
        finally:
            connection.disconnect(True)

    def test_open_root_directory(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_1_1)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[5])
        dir_open = Open(tree, "")
        try:
            session.connect()
            tree.connect()

            dir_open.create(ImpersonationLevel.Impersonation,
                            DirectoryAccessMask.FILE_LIST_DIRECTORY,
                            FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                            ShareAccess.FILE_SHARE_READ,
                            CreateDisposition.FILE_OPEN_IF,
                            CreateOptions.FILE_DIRECTORY_FILE)
            dir_open.close(get_attributes=False)
        finally:
            connection.disconnect(True)

    # test more file operations here
    def test_create_directory(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_0)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[5])
        open = Open(tree, "folder")
        try:
            session.connect()
            tree.connect()

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   DirectoryAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                                   0,
                                   CreateDisposition.FILE_OPEN_IF,
                                   CreateOptions.FILE_DIRECTORY_FILE)
            assert out_cont is None
            assert open.allocation_size == 0
            assert isinstance(open.change_time, datetime)
            assert open.create_disposition is \
                CreateDisposition.FILE_OPEN_IF
            assert open.create_options is CreateOptions.FILE_DIRECTORY_FILE
            assert isinstance(open.creation_time, datetime)
            assert open.desired_access is \
                DirectoryAccessMask.MAXIMUM_ALLOWED
            assert not open.durable
            assert open.durable_timeout is None
            assert open.end_of_file == 0
            assert open.file_attributes == \
                FileAttributes.FILE_ATTRIBUTE_DIRECTORY
            assert isinstance(open.file_id, bytes)
            assert open.file_name == "folder"
            assert open.is_persistent is None
            assert isinstance(open.last_access_time, datetime)
            assert open.last_disconnect_time == 0
            assert isinstance(open.last_write_time, datetime)
            assert open.operation_buckets == []
            assert open.oplock_level == 0
            assert not open.resilient_handle
            assert not open.resilient_timeout
            assert open.share_mode == 0
        finally:
            connection.disconnect(True)

    def test_create_file_create_contexts(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_0)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[5])
        open = Open(tree, "file-cont.txt")
        try:
            session.connect()
            tree.connect()

            alloc_size = SMB2CreateAllocationSize()
            alloc_size['allocation_size'] = 1024

            alloc_size_context = SMB2CreateContextRequest()
            alloc_size_context['buffer_name'] = \
                CreateContextName.SMB2_CREATE_ALLOCATION_SIZE
            alloc_size_context['buffer_data'] = alloc_size

            query_disk = SMB2CreateContextRequest()
            query_disk['buffer_name'] = \
                CreateContextName.SMB2_CREATE_QUERY_ON_DISK_ID

            max_req_data = SMB2CreateQueryMaximalAccessRequest()
            max_req = SMB2CreateContextRequest()
            max_req['buffer_name'] = \
                CreateContextName.SMB2_CREATE_QUERY_MAXIMAL_ACCESS_REQUEST
            max_req['buffer_data'] = max_req_data

            create_contexts = [
                alloc_size_context,
                query_disk,
                max_req
            ]
            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE,
                                   create_contexts)
            assert len(out_cont) == 2
            assert isinstance(out_cont[0],
                              SMB2CreateQueryMaximalAccessResponse) or \
                isinstance(out_cont[0], SMB2CreateQueryOnDiskIDResponse)
            assert isinstance(out_cont[1],
                              SMB2CreateQueryMaximalAccessResponse) or \
                isinstance(out_cont[1], SMB2CreateQueryOnDiskIDResponse)
        finally:
            connection.disconnect(True)

    @pytest.mark.parametrize('lease_version', ['v1', 'v2'])
    def test_create_file_with_lease(self, smb_real, lease_version):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-lease.txt")
        try:
            session.connect()
            tree.connect()

            if lease_version == 'v1':
                lease_request = SMB2CreateRequestLease()
            else:
                lease_request = SMB2CreateRequestLeaseV2()
                lease_request['parent_lease_key'] = b"\x00" * 16
                lease_request['epoch'] = os.urandom(2)

            lease_request['lease_key'] = os.urandom(16)
            lease_request['lease_state'] = 1

            out_cont = open.create(ImpersonationLevel.Impersonation,
                                   FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                                   FileAttributes.FILE_ATTRIBUTE_NORMAL,
                                   0,
                                   CreateDisposition.FILE_OVERWRITE_IF,
                                   CreateOptions.FILE_NON_DIRECTORY_FILE,
                                   create_contexts=[lease_request],
                                   oplock_level=RequestedOplockLevel.SMB2_OPLOCK_LEVEL_LEASE)
            assert len(out_cont) == 1

            if lease_version == 'v1':
                assert isinstance(out_cont[0], SMB2CreateResponseLease)
            else:
                assert isinstance(out_cont[0], SMB2CreateResponseLeaseV2)
        finally:
            connection.disconnect(True)

    def test_create_read_write_from_file(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            actual = open.write(b"\x01\x02\x03\x04")
            assert actual == 4
            actual = open.read(0, 4)
            assert actual == b"\x01\x02\x03\x04"
        finally:
            connection.disconnect(True)

    def test_flush_file(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_2)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[5])
        open = Open(tree, "file-cont.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)
            open.flush()

            # Test flush without send
            flush_req, flush_resp = open.flush(send=False)
            request = open.connection.send(flush_req, open.tree_connect.session.session_id,
                                           open.tree_connect.tree_connect_id)
            flush_resp = flush_resp(request)
            assert isinstance(flush_resp, SMB2FlushResponse)
        finally:
            connection.disconnect(True)

    def test_close_file_dont_get_attributes(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            old_last_write_time = open.last_write_time
            old_end_of_file = open.end_of_file
            open.write(b"\x01")
            open.close(False)
            assert open.last_write_time == old_last_write_time
            assert open.end_of_file == old_end_of_file
        finally:
            open.close(False)  # test close when it has already been closed
            connection.disconnect(True)

    def test_close_file_get_attributes(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            old_last_write_time = open.last_write_time
            old_end_of_file = open.end_of_file
            time.sleep(2)
            open.write(b"\x01")
            open.close(True)
            assert open.last_write_time != old_last_write_time
            assert open.end_of_file != old_end_of_file
            assert open.end_of_file == 1
        finally:
            connection.disconnect(True)

    def test_read_file_unbuffered(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_2)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            open.write(b"\x01")
            actual = open.read(0, 1, unbuffered=True)
            assert actual == b"\x01"
        finally:
            connection.disconnect(True)

    def test_read_file_unbuffered_unsupported(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_0)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            open.write(b"\x01")
            with pytest.raises(SMBUnsupportedFeature) as exc:
                open.read(0, 1, unbuffered=True)
            assert exc.value.feature_name == "SMB2_READFLAG_READ_UNBUFFERED"
            assert exc.value.negotiated_dialect == Dialects.SMB_3_0_0
            assert exc.value.required_dialect == Dialects.SMB_3_0_2
            assert exc.value.requires_newer
            assert str(exc.value) == \
                "SMB2_READFLAG_READ_UNBUFFERED is not available on the " \
                "negotiated dialect (768) SMB_3_0_0, requires dialect (770) " \
                "SMB_3_0_2 or newer"
        finally:
            connection.disconnect(True)

    @pytest.mark.skipif(os.name == "nt",
                        reason="write-through writes don't work on windows?")
    def test_write_file_write_through(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE |
                        CreateOptions.FILE_WRITE_THROUGH)

            actual = open.write(b"\x01", write_through=True)
            assert actual == 1
            actual = open.read(0, 1)
            assert actual == b"\x01"
        finally:
            connection.disconnect(True)

    def test_write_file_write_through_unsupported(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_0_2)
        session = Session(connection, smb_real[0], smb_real[1], False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE |
                        CreateOptions.FILE_WRITE_THROUGH)

            with pytest.raises(SMBUnsupportedFeature) as exc:
                open.write(b"\x01", write_through=True)
            assert exc.value.feature_name == "SMB2_WRITEFLAG_WRITE_THROUGH"
            assert exc.value.negotiated_dialect == Dialects.SMB_2_0_2
            assert exc.value.required_dialect == Dialects.SMB_2_1_0
            assert exc.value.requires_newer
            assert str(exc.value) == \
                "SMB2_WRITEFLAG_WRITE_THROUGH is not available on the " \
                "negotiated dialect (514) SMB_2_0_2, requires dialect (528) " \
                "SMB_2_1_0 or newer"
        finally:
            connection.disconnect(True)

    @pytest.mark.skipif(os.name == "nt",
                        reason="unbufferred writes don't work on windows?")
    def test_write_file_unbuffered(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE |
                        CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING)

            actual = open.write(b"\x01", unbuffered=True)
            assert actual == 1
            actual = open.read(0, 1)
            assert actual == b"\x01"
        finally:
            connection.disconnect(True)

    def test_write_file_unbuffered_unsupported(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_1_0)
        session = Session(connection, smb_real[0], smb_real[1], False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE |
                        CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING)

            with pytest.raises(SMBUnsupportedFeature) as exc:
                open.write(b"\x01", unbuffered=True)
            assert exc.value.feature_name == "SMB2_WRITEFLAG_WRITE_UNBUFFERED"
            assert exc.value.negotiated_dialect == Dialects.SMB_2_1_0
            assert exc.value.required_dialect == Dialects.SMB_3_0_2
            assert exc.value.requires_newer
            assert str(exc.value) == \
                "SMB2_WRITEFLAG_WRITE_UNBUFFERED is not available on the " \
                "negotiated dialect (528) SMB_2_1_0, requires dialect (770) " \
                "SMB_3_0_2 or newer"
        finally:
            connection.disconnect(True)

    def test_query_directory(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "directory-query")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        DirectoryAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                        ShareAccess.FILE_SHARE_READ |
                        ShareAccess.FILE_SHARE_WRITE |
                        ShareAccess.FILE_SHARE_DELETE,
                        CreateDisposition.FILE_OPEN_IF,
                        CreateOptions.FILE_DIRECTORY_FILE)

            file1 = Open(tree, r"directory-query\\file1.txt")
            file1.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)
            file1.write(b"\x01\x02\x03\x04", 0)

            file2 = Open(tree, r"directory-query\\file2.log")
            file2.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)
            file2.write(b"\x05\x06", 0)

            actual = open.query_directory("*",
                                          FileInformationClass.
                                          FILE_NAMES_INFORMATION)

            assert len(actual) == 4
            assert isinstance(actual[0], FileNamesInformation)
            assert actual[0]['file_name'].get_value().decode('utf-16-le') == \
                "."
            assert isinstance(actual[1], FileNamesInformation)
            assert actual[1]['file_name'].get_value().decode('utf-16-le') == \
                ".."

            file1_name = "file1.txt".encode('utf-16-le')
            file2_name = "file2.log".encode('utf-16-le')
            assert isinstance(actual[2], FileNamesInformation)
            assert actual[2]['file_name'].get_value() in \
                [file1_name, file2_name]
            assert isinstance(actual[3], FileNamesInformation)
            assert actual[3]['file_name'].get_value() in \
                [file1_name, file2_name]

            open.close()
        finally:
            connection.disconnect(True)

    def test_compounding_related_opens_encrypted(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-related.txt")
        try:
            session.connect()
            tree.connect()

            messages = [
                open.create(ImpersonationLevel.Impersonation,
                            FilePipePrinterAccessMask.GENERIC_READ |
                            FilePipePrinterAccessMask.GENERIC_WRITE |
                            FilePipePrinterAccessMask.DELETE,
                            FileAttributes.FILE_ATTRIBUTE_NORMAL,
                            0,
                            CreateDisposition.FILE_OVERWRITE_IF,
                            CreateOptions.FILE_NON_DIRECTORY_FILE |
                            CreateOptions.FILE_DELETE_ON_CLOSE,
                            send=False),
                open.write(b"\x01\x02\x03\x04", send=False),
                open.read(0, 4, send=False),
                open.close(False, send=False)
            ]
            requests = connection.send_compound([x[0] for x in messages],
                                                session.session_id,
                                                tree.tree_connect_id,
                                                related=True)
            responses = []
            for i, request in enumerate(requests):
                response = messages[i][1](request)
                responses.append(response)

            assert open.file_id != b"\xff" * 16
            assert len(responses) == 4
            assert responses[0] is None
            assert responses[1] == 4
            assert responses[2] == b"\x01\x02\x03\x04"
            assert isinstance(responses[3], SMB2CloseResponse)
        finally:
            connection.disconnect(True)

    def test_compounding_related_opens_signed(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_0_2)
        session = Session(connection, smb_real[0], smb_real[1],
                          require_encryption=False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-related.txt")
        try:
            session.connect()
            tree.connect()

            messages = [
                open.create(ImpersonationLevel.Impersonation,
                            FilePipePrinterAccessMask.GENERIC_READ |
                            FilePipePrinterAccessMask.GENERIC_WRITE |
                            FilePipePrinterAccessMask.DELETE,
                            FileAttributes.FILE_ATTRIBUTE_NORMAL,
                            0,
                            CreateDisposition.FILE_OVERWRITE_IF,
                            CreateOptions.FILE_NON_DIRECTORY_FILE |
                            CreateOptions.FILE_DELETE_ON_CLOSE,
                            send=False),
                open.write(b"\x01\x02\x03\x04", send=False),
                open.read(0, 4, send=False),
                open.close(False, send=False)
            ]
            requests = connection.send_compound([x[0] for x in messages],
                                                session.session_id,
                                                tree.tree_connect_id,
                                                related=True)
            responses = []
            for i, request in enumerate(requests):
                response = messages[i][1](request)
                responses.append(response)

            assert open.file_id != b"\xff" * 16
            assert len(responses) == 4
            assert responses[0] is None
            assert responses[1] == 4
            assert responses[2] == b"\x01\x02\x03\x04"
            assert isinstance(responses[3], SMB2CloseResponse)
        finally:
            connection.disconnect(True)

    def test_compounding_open_requests(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "directory-compound-open")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        DirectoryAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                        ShareAccess.FILE_SHARE_READ |
                        ShareAccess.FILE_SHARE_WRITE |
                        ShareAccess.FILE_SHARE_DELETE,
                        CreateDisposition.FILE_OPEN_IF,
                        CreateOptions.FILE_DIRECTORY_FILE)

            file1 = Open(tree, r"directory-compound-open\\file1.txt")
            file1.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)
            file2 = Open(tree, r"directory-compound-open\\file2.log")
            file2.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)

            # create messages for each operation
            messages = [
                file1.write(b"\x01\x02\x03\x04", 0, send=False),
                file2.write(b"\x05\x06", 0, send=False),
                file1.read(0, 4, send=False),
                open.query_directory("*",
                                     FileInformationClass.
                                     FILE_ID_BOTH_DIRECTORY_INFORMATION,
                                     send=False),
                file1.close(send=False),
                file2.close(send=False)
            ]

            # send each message as a compound request
            requests = connection.send_compound([x[0] for x in messages],
                                                session.session_id,
                                                tree.tree_connect_id)

            # get responses and run unpack function
            responses = []
            for i, request in enumerate(requests):
                response = messages[i][1](request)
                responses.append(response)

            # assert each response
            assert len(responses) == 6
            assert isinstance(responses[0], int)
            assert isinstance(responses[1], int)
            assert isinstance(responses[2], bytes)
            assert isinstance(responses[3], list)
            assert isinstance(responses[4], SMB2CloseResponse)
            assert isinstance(responses[5], SMB2CloseResponse)

            write1 = responses[0]
            assert write1 == 4

            write2 = responses[1]
            assert write2 == 2

            read1 = responses[2]
            assert read1 == b"\x01\x02\x03\x04"

            query1 = responses[3]
            assert query1[0]['file_name'].get_value() == \
                ".".encode('utf-16-le')
            assert query1[1]['file_name'].get_value() == \
                "..".encode('utf-16-le')
            file1_name = "file1.txt".encode('utf-16-le')
            file2_name = "file2.log".encode('utf-16-le')
            assert query1[2]['file_name'].get_value() \
                in [file1_name, file2_name]
            assert query1[3]['file_name'].get_value() \
                in [file1_name, file2_name]

            open.close()
        finally:
            connection.disconnect(True)

    def test_compounding_open_requests_unencrypted(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_2_1_0)
        session = Session(connection, smb_real[0], smb_real[1], False)
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "directory-compound-open-plaintext")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        DirectoryAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_DIRECTORY,
                        ShareAccess.FILE_SHARE_READ |
                        ShareAccess.FILE_SHARE_WRITE |
                        ShareAccess.FILE_SHARE_DELETE,
                        CreateDisposition.FILE_OPEN_IF,
                        CreateOptions.FILE_DIRECTORY_FILE)

            file1 = Open(tree, r"directory-compound-open-plaintext\\file1.txt")
            file1.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)
            file2 = Open(tree, r"directory-compound-open-plaintext\\file2.log")
            file2.create(ImpersonationLevel.Impersonation,
                         FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                         FileAttributes.FILE_ATTRIBUTE_NORMAL,
                         ShareAccess.FILE_SHARE_READ,
                         CreateDisposition.FILE_OVERWRITE_IF,
                         CreateOptions.FILE_NON_DIRECTORY_FILE)

            # create messages for each operation
            messages = [
                file1.write(b"\x01\x02\x03\x04", 0, send=False),
                file2.write(b"\x05\x06", 0, send=False),
                file1.read(0, 4, send=False),
                open.query_directory("*",
                                     FileInformationClass.
                                     FILE_ID_BOTH_DIRECTORY_INFORMATION,
                                     send=False),
                file1.close(send=False),
                file2.close(send=False)
            ]

            # send each message as a compound request
            requests = connection.send_compound([x[0] for x in messages],
                                                session.session_id,
                                                tree.tree_connect_id)

            # get responses and run unpack function
            responses = []
            for i, request in enumerate(requests):
                response = messages[i][1](request)
                responses.append(response)

            # assert each response
            assert len(responses) == 6
            assert isinstance(responses[0], int)
            assert isinstance(responses[1], int)
            assert isinstance(responses[2], bytes)
            assert isinstance(responses[3], list)
            assert isinstance(responses[4], SMB2CloseResponse)
            assert isinstance(responses[5], SMB2CloseResponse)

            write1 = responses[0]
            assert write1 == 4

            write2 = responses[1]
            assert write2 == 2

            read1 = responses[2]
            assert read1 == b"\x01\x02\x03\x04"

            query1 = responses[3]
            assert query1[0]['file_name'].get_value() == \
                ".".encode('utf-16-le')
            assert query1[1]['file_name'].get_value() == \
                "..".encode('utf-16-le')
            file1_name = "file1.txt".encode('utf-16-le')
            file2_name = "file2.log".encode('utf-16-le')
            assert query1[2]['file_name'].get_value() \
                in [file1_name, file2_name]
            assert query1[3]['file_name'].get_value() \
                in [file1_name, file2_name]

            open.close()
        finally:
            connection.disconnect(True)

    def test_close_file_already_closed(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect(Dialects.SMB_3_0_2)
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file-read-write.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)
            open.close()

            # we will just manually say it is still connected so we get the
            # proper error msg
            open._connected = True
            open.close()
        finally:
            connection.disconnect(True)

    def test_read_greater_than_max_size(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            with pytest.raises(SMBException) as exc:
                open.read(0, connection.max_read_size + 1)
            assert str(exc.value) == "The requested read length %d is " \
                                     "greater than the maximum negotiated " \
                                     "read size %d"\
                % (connection.max_read_size + 1, connection.max_read_size)
        finally:
            connection.disconnect(True)

    def test_write_greater_than_max_size(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            with pytest.raises(SMBException) as exc:
                open.write(b"\x00" * (connection.max_write_size + 1), 0)
            assert str(exc.value) == "The requested write length %d is " \
                                     "greater than the maximum negotiated " \
                                     "write size %d"\
                % (connection.max_write_size + 1, connection.max_write_size)
        finally:
            connection.disconnect(True)

    def test_read_file_multi_credits(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")
        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)
            open.write(b"\x01\x02\x03\x04", 0)
            actual = open.read(0, 65538)
            assert actual == b"\x01\x02\x03\x04"
        finally:
            connection.disconnect(True)

    def test_receive_with_timeout(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")

        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)
            read_req, unpack_func = open.write(b"\x00", 0, send=False)
            req = connection.send(read_req, sid=session.session_id,
                                  tid=tree.tree_connect_id)
            # get the response so we know the timeout will fail next as there
            # is no response to get
            connection.receive(request=req)
            req.response = None
            req.response_event.clear()

            start_time = time.time()
            with pytest.raises(SMBException) as exc:
                connection.receive(request=req, timeout=2)
            end_time = int(time.time() - start_time)
            assert end_time < 5
            assert str(exc.value) == "Connection timeout of 2 seconds exceeded while waiting for a message id %s " \
                                     "response from the server" % req.message['message_id'].get_value()
        finally:
            connection.disconnect(True)

    def test_close_file_invalid_id(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "file.txt")

        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)

            # create a request for a known failure and pass that into the
            # _close_response to ensure the exception is thrown
            read_msg = open.read(10, 0, min_length=1024,
                                 send=False)[0]
            req = connection.send(read_msg, sid=session.session_id,
                                  tid=tree.tree_connect_id)

            with pytest.raises(SMBException) as exc:
                open._close_response(req)
            assert str(exc.value) == "Received unexpected status from the " \
                                     "server: (3221225489) " \
                                     "STATUS_END_OF_FILE: 0xc0000011"
        finally:
            connection.disconnect(True)

    def test_truncate_file(self, smb_real):
        connection = Connection(uuid.uuid4(), smb_real[2], smb_real[3])
        connection.connect()
        session = Session(connection, smb_real[0], smb_real[1])
        tree = TreeConnect(session, smb_real[4])
        open = Open(tree, "truncate-file.txt")

        try:
            session.connect()
            tree.connect()

            open.create(ImpersonationLevel.Impersonation,
                        FilePipePrinterAccessMask.MAXIMUM_ALLOWED,
                        FileAttributes.FILE_ATTRIBUTE_NORMAL,
                        0,
                        CreateDisposition.FILE_OVERWRITE_IF,
                        CreateOptions.FILE_NON_DIRECTORY_FILE)
            assert open.end_of_file == 0

            def read_and_eof(open):
                read_req, read_func = open.read(0, 12, send=False)

                file_info = FileStandardInformation()
                query_req = SMB2QueryInfoRequest()
                query_req['info_type'] = InfoType.SMB2_0_INFO_FILE
                query_req['file_info_class'] = FileInformationClass.FILE_STANDARD_INFORMATION
                query_req['file_id'] = open.file_id
                query_req['output_buffer_length'] = len(file_info)

                requests = open.connection.send_compound([read_req, query_req],
                                                         open.tree_connect.session.session_id,
                                                         open.tree_connect.tree_connect_id,
                                                         related=True)
                data = read_func(requests[0])
                resp = open.connection.receive(requests[1])

                query_resp = SMB2QueryInfoResponse()
                query_resp.unpack(resp['data'].get_value())
                file_info = query_resp.parse_buffer(FileStandardInformation)
                return data, file_info['end_of_file'].get_value()

            def truncate(open, size):
                eof_info = FileEndOfFileInformation()
                eof_info['end_of_file'] = size
                req = SMB2SetInfoRequest()
                req['info_type'] = InfoType.SMB2_0_INFO_FILE
                req['file_info_class'] = FileInformationClass.FILE_END_OF_FILE_INFORMATION
                req['file_id'] = open.file_id
                req['buffer'] = eof_info
                request = open.connection.send(req, open.tree_connect.session.session_id,
                                               open.tree_connect.tree_connect_id)
                response = open.connection.receive(request)
                set_resp = SMB2SetInfoResponse()
                set_resp.unpack(response['data'].get_value())

            # Populate the file with some bytes
            open.write(b"\x01\x02\x03\x04")
            assert read_and_eof(open) == (b"\x01\x02\x03\x04", 4)

            # Make the file bigger
            truncate(open, 8)
            assert read_and_eof(open) == (b"\x01\x02\x03\x04\x00\x00\x00\x00", 8)

            # Make the file smaller
            truncate(open, 3)
            assert read_and_eof(open) == (b"\x01\x02\x03", 3)
        finally:
            connection.disconnect(True)