# Source code for smb.smb_structs

    import os, sys, struct, types, logging, binascii, time
    from io import StringIO
    from .smb_constants import *
    from .strategy import DataFaultToleranceStrategy

    # Set to True if you want to enable support for extended security. Required for Windows Vista and later
    SUPPORT_EXTENDED_SECURITY = True

    # Set to True if you want to enable SMB2 protocol.
    SUPPORT_SMB2 = True

    # Supported dialects
    DIALECTS = [ ]
    for i, ( name, dialect ) in enumerate([ ( 'NT_LAN_MANAGER_DIALECT', b'NT LM 0.12' ), ]):
        DIALECTS.append(dialect)
        globals()[name] = i

    DIALECTS2 = [ ]
    for i, ( name, dialect ) in enumerate([ ( 'SMB2_DIALECT', b'SMB 2.002' ) ]):
        DIALECTS2.append(dialect)
        globals()[name] = i + len(DIALECTS)

    class UnsupportedFeature(Exception):
        """
        Raised when an supported feature is present/required in the protocol but is not
        currently supported by pysmb
        """
        pass

    class ProtocolError(Exception):

        def __init__(self, message, data_buf = None, smb_message = None):
            self.message = message
            self.data_buf = data_buf
            self.smb_message = smb_message

        def __str__(self):
            b = StringIO()
            b.write(self.message + os.linesep)
            if self.smb_message:
                b.write('=' * 20 + ' SMB Message ' + '=' * 20 + os.linesep)
                b.write(str(self.smb_message))

            if self.data_buf:
                b.write('=' * 20 + ' SMB Data Packet (hex) ' + '=' * 20 + os.linesep)
                b.write(str(binascii.hexlify(self.data_buf)))
                b.write(os.linesep)

            return b.getvalue()

    class SMB2ProtocolHeaderError(ProtocolError):

        def __init__(self):
            ProtocolError.__init__(self, "Packet header belongs to SMB2")

    class OperationFailure(Exception):

        def __init__(self, message, smb_messages):
            self.message = message
            self.smb_messages = smb_messages

        def __str__(self):
            b = StringIO()
            b.write(self.message + os.linesep)

            for idx, m in enumerate(self.smb_messages):
                b.write('=' * 20 + ' SMB Message %d ' % idx + '=' * 20 + os.linesep)
                b.write('SMB Header:' + os.linesep)
                b.write('-----------' + os.linesep)
                b.write(str(m))
                b.write('SMB Data Packet (hex):' + os.linesep)
                b.write('----------------------' + os.linesep)
                b.write(str(binascii.hexlify(m.raw_data)))
                b.write(os.linesep)

            return b.getvalue()

    class SMBError:

        def __init__(self):
            self.reset()

        def reset(self):
            self.internal_value = 0
            self.is_ntstatus = True

        def __str__(self):
            if self.is_ntstatus:
                return 'NTSTATUS=0x%08X' % self.internal_value
            else:
                return 'ErrorClass=0x%02X ErrorCode=0x%04X' % ( self.internal_value >> 24, self.internal_value & 0xFFFF )

        @property
        def hasError(self):
            return self.internal_value != 0

    class SMBMessage:

        HEADER_STRUCT_FORMAT = "<4sBIBHHQxxHHHHB"
        HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)

        log = logging.getLogger('SMB.SMBMessage')
        protocol = 1

        def __init__(self, payload = None):
            self.reset()
            if payload:
                self.payload = payload
                self.payload.initMessage(self)

        def __str__(self):
            b = StringIO()
            b.write('Command: 0x%02X (%s) %s' % ( self.command, SMB_COMMAND_NAMES.get(self.command, '<unknown>'), os.linesep ))
            b.write('Status: %s %s' % ( str(self.status), os.linesep ))
            b.write('Flags: 0x%02X %s' % ( self.flags, os.linesep ))
            b.write('Flags2: 0x%04X %s' % ( self.flags2, os.linesep ))
            b.write('PID: %d %s' % ( self.pid, os.linesep ))
            b.write('UID: %d %s' % ( self.uid, os.linesep ))
            b.write('MID: %d %s' % ( self.mid, os.linesep ))
            b.write('TID: %d %s' % ( self.tid, os.linesep ))
            b.write('Security: 0x%016X %s' % ( self.security, os.linesep ))
            b.write('Parameters: %d bytes %s%s %s' % ( len(self.parameters_data), os.linesep, str(binascii.hexlify(self.parameters_data)), os.linesep ))
            b.write('Data: %d bytes %s%s %s' % ( len(self.data), os.linesep, str(binascii.hexlify(self.data)), os.linesep ))
            return b.getvalue()

        def reset(self):
            self.raw_data = b''
            self.command = 0
            self.status = SMBError()
            self.flags = 0
            self.flags2 = 0
            self.pid = 0
            self.tid = 0
            self.uid = 0
            self.mid = 0
            self.security = 0
            self.parameters_data = b''
            self.data = b''
            self.payload = None

        @property
        def isAsync(self):
            return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND)

        @property
        def isReply(self):
            return bool(self.flags & SMB_FLAGS_REPLY)

        @property
        def hasExtendedSecurity(self):
            return bool(self.flags2 & SMB_FLAGS2_EXTENDED_SECURITY)

        def encode(self):
            """
            Encode this SMB message into a series of bytes suitable to be embedded with a NetBIOS session message.
            AssertionError will be raised if this SMB message has not been initialized with a Payload instance

            @return: a string containing the encoded SMB message
            """
            assert self.payload

            self.pid = os.getpid()
            self.payload.prepare(self)

            parameters_len = len(self.parameters_data)
            assert parameters_len % 2 == 0

            headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
                                       b'\xFFSMB', self.command, self.status.internal_value, self.flags,
                                       self.flags2, (self.pid >> 16) & 0xFFFF, self.security, self.tid,
                                       self.pid & 0xFFFF, self.uid, self.mid, int(parameters_len / 2))
            return headers_data + self.parameters_data + struct.pack('<H', len(self.data)) + self.data

        def decode(self, buf):
            """
            Decodes the SMB message in buf.
            All fields of the SMBMessage object will be reset to default values before decoding.
            On errors, do not assume that the fields will be reinstated back to what they are before
            this method is invoked.

            @param buf: data containing one complete SMB message
            @type buf: string
            @return: a positive integer indicating the number of bytes used in buf to decode this SMB message
            @raise ProtocolError: raised when decoding fails
            """
            buf_len = len(buf)
            if buf_len < self.HEADER_STRUCT_SIZE:
                # We need at least 32 bytes (header) + 1 byte (parameter count)
                raise ProtocolError('Not enough data to decode SMB header', buf)

            self.reset()

            protocol, self.command, status, self.flags, \
            self.flags2, pid_high, self.security, self.tid, \
            pid_low, self.uid, self.mid, params_count = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])

            if protocol == b'\xFESMB':
                raise SMB2ProtocolHeaderError()
            if protocol != b'\xFFSMB':
                raise ProtocolError('Invalid 4-byte protocol field', buf)

            self.pid = (pid_high << 16) | pid_low
            self.status.internal_value = status
            self.status.is_ntstatus = bool(self.flags2 & SMB_FLAGS2_NT_STATUS)

            offset = self.HEADER_STRUCT_SIZE
            if buf_len < params_count * 2 + 2:
                # Not enough data in buf to decode up to body length
                raise ProtocolError('Not enough data. Parameters list decoding failed', buf)

            datalen_offset = offset + params_count*2
            body_len = struct.unpack('<H', buf[datalen_offset:datalen_offset+2])[0]
            if body_len > 0 and buf_len < (datalen_offset + 2 + body_len):
                # Not enough data in buf to decode body
                raise ProtocolError('Not enough data. Body decoding failed', buf)

            self.parameters_data = buf[offset:datalen_offset]

            if body_len > 0:
                self.data = buf[datalen_offset+2:datalen_offset+2+body_len]

            self.raw_data = buf
            self._decodePayload()

            return self.HEADER_STRUCT_SIZE + params_count * 2 + 2 + body_len

        def _decodePayload(self):
            if self.command == SMB_COM_READ_ANDX:
                self.payload = ComReadAndxResponse()
            elif self.command == SMB_COM_WRITE_ANDX:
                self.payload = ComWriteAndxResponse()
            elif self.command == SMB_COM_TRANSACTION:
                self.payload = ComTransactionResponse()
            elif self.command == SMB_COM_TRANSACTION2:
                self.payload = ComTransaction2Response()
            elif self.command == SMB_COM_OPEN_ANDX:
                self.payload = ComOpenAndxResponse()
            elif self.command == SMB_COM_NT_CREATE_ANDX:
                self.payload = ComNTCreateAndxResponse()
            elif self.command == SMB_COM_TREE_CONNECT_ANDX:
                self.payload = ComTreeConnectAndxResponse()
            elif self.command == SMB_COM_ECHO:
                self.payload = ComEchoResponse()
            elif self.command == SMB_COM_SESSION_SETUP_ANDX:
                self.payload = ComSessionSetupAndxResponse()
            elif self.command == SMB_COM_NEGOTIATE:
                self.payload = ComNegotiateResponse()

            if self.payload:
                self.payload.decode(self)

    class Payload:

        DEFAULT_ANDX_PARAM_HEADER = b'\xFF\x00\x00\x00'
        DEFAULT_ANDX_PARAM_SIZE = 4

        def initMessage(self, message):
            # SMB_FLAGS2_UNICODE must always be enabled. Without this, almost all the Payload subclasses will need to be
            # rewritten to check for OEM/Unicode strings which will be tedious. Fortunately, almost all tested CIFS services
            # support SMB_FLAGS2_UNICODE by default.
            assert message.payload == self
            message.flags =  SMB_FLAGS_CASE_INSENSITIVE | SMB_FLAGS_CANONICALIZED_PATHS
            message.flags2 = SMB_FLAGS2_UNICODE | SMB_FLAGS2_NT_STATUS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_LONG_NAMES

            if SUPPORT_EXTENDED_SECURITY:
                message.flags2 |= SMB_FLAGS2_EXTENDED_SECURITY

        def prepare(self, message):
            raise NotImplementedError

        def decode(self, message):
            raise NotImplementedError

    class ComNegotiateRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.52.1
        - [MS-SMB]: 2.2.4.5.1
        """

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_NEGOTIATE

        def prepare(self, message):
            assert message.payload == self
            message.parameters_data = b''
            if SUPPORT_SMB2:
                message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS + DIALECTS2])
            else:
                message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS])

    class ComNegotiateResponse(Payload):
        """
        Contains information on the SMB_COM_NEGOTIATE response from server

        After calling the decode method, each instance will contain the following attributes,
        - security_mode (integer)
        - max_mpx_count (integer)
        - max_number_vcs (integer)
        - max_buffer_size (long)
        - max_raw_size (long)
        - session_key (long)
        - capabilities (long)
        - system_time (long)
        - server_time_zone (integer)
        - challenge_length (integer)

        If the underlying SMB message's flag2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
        then the instance will have the following additional attributes,
        - challenge (string)
        - domain (unicode)

        If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
        then the instance will have the following additional attributes,
        - server_guid (string)
        - security_blob (string)

        References:
        ===========
        - [MS-SMB]: 2.2.4.5.2.1
        - [MS-CIFS]: 2.2.4.52.2
        """

        PAYLOAD_STRUCT_FORMAT = '<HBHHIIIIQHB'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_NEGOTIATE

            if not message.isReply:
                raise ProtocolError('Not a SMB_COM_NEGOTIATE reply', message.raw_data, message)

            self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
            self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
            self.challenge_length = ( 0, ) * 10

            data_len = len(message.parameters_data)
            if data_len < 2:
                raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE dialect_index field', message.raw_data, message)

            self.dialect_index = struct.unpack('<H', message.parameters_data[:2])[0]
            if self.dialect_index == NT_LAN_MANAGER_DIALECT:
                if data_len != (0x11 * 2):
                    raise ProtocolError('NT LAN Manager dialect selected in SMB_COM_NEGOTIATE but parameters bytes count (%d) does not meet specs' % data_len,
                                        message.raw_data, message)
                else:
                    _, self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
                    self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
                    self.challenge_length = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
            elif self.dialect_index == 0xFFFF:
                raise ProtocolError('Server does not support any of the pysmb dialects. Please email pysmb to add in support for your OS',
                                    message.raw_data, message)
            else:
                raise ProtocolError('Unknown dialect index (0x%04X)' % self.dialect_index, message.raw_data, message)

            data_len = len(message.data)
            if not message.hasExtendedSecurity:
                self.challenge, self.domain = '', ''
                if self.challenge_length > 0:
                    if data_len >= self.challenge_length:
                        self.challenge = message.data[:self.challenge_length]

                        s = b''
                        offset = self.challenge_length
                        while offset < data_len:
                            _s = message.data[offset:offset+2]
                            if _s == b'\0\0':
                                self.domain = DataFaultToleranceStrategy.data_bytes_decode(s)#.decode('UTF-16LE')
                                break
                            else:
                                s += _s
                                offset += 2
                    else:
                        raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (without security extensions) Challenge field', message.raw_data, message)
            else:
                if data_len < 16:
                    raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (with security extensions) ServerGUID field', message.raw_data, message)

                self.server_guid = message.data[:16]
                self.security_blob = message.data[16:]

        @property
        def supportsExtendedSecurity(self):
            return bool(self.capabilities & CAP_EXTENDED_SECURITY)

    class ComSessionSetupAndxRequest__WithSecurityExtension(Payload):
        """
        References:
        ===========
        - [MS-SMB]: 2.2.4.6.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHIHII'

        def __init__(self, session_key, security_blob):
            self.session_key = session_key
            self.security_blob = security_blob

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_SESSION_SETUP_ANDX

        def prepare(self, message):
            assert message.hasExtendedSecurity

            message.flags2 |= SMB_FLAGS2_UNICODE

            cap = CAP_UNICODE | CAP_STATUS32 | CAP_EXTENDED_SECURITY | CAP_NT_SMBS

            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            16644, 10, 1, self.session_key, len(self.security_blob), 0, cap)

            message.data = self.security_blob
            if (SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data) + len(message.data)) % 2 != 0:
                message.data = message.data + b'\0'
            message.data = message.data + b'\0' * 4

    class ComSessionSetupAndxRequest__NoSecurityExtension(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.53.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHIHHII'

        def __init__(self, session_key, username, password, is_unicode, domain):
            self.username = username
            self.session_key = session_key
            self.password = password
            self.is_unicode = is_unicode
            self.domain = domain

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_SESSION_SETUP_ANDX

        def prepare(self, message):
            if self.is_unicode:
                message.flags2 |= SMB_FLAGS2_UNICODE
            else:
                message.flags2 &= (~SMB_FLAGS2_UNICODE & 0xFFFF)

            password_len = len(self.password)
            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            16644, 10, 0, self.session_key,
                            (not self.is_unicode and password_len) or 0,
                            (self.is_unicode and password_len) or 0,
                            0,
                            CAP_UNICODE | CAP_LARGE_FILES | CAP_STATUS32)

            est_offset = SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data)  # To check if data until SMB paramaters are aligned to a 16-bit boundary

            message.data = self.password
            if (est_offset + len(message.data)) % 2 != 0 and message.flags2 & SMB_FLAGS2_UNICODE:
                message.data = message.data + b'\0'

            if message.flags2 & SMB_FLAGS2_UNICODE:
                message.data = message.data + self.username.encode('UTF-16LE') + b'\0'
            else:
                message.data = message.data + str(self.username).encode('UTF-8') + b'\0'

            if (est_offset + len(message.data)) % 2 != 0 and message.flags2 & SMB_FLAGS2_UNICODE:
                message.data = message.data + b'\0'

            if message.flags2 & SMB_FLAGS2_UNICODE:
                message.data = message.data + self.domain.encode('UTF-16LE') + b'\0\0' + 'pysmb'.encode('UTF-16LE') + b'\0\0'
            else:
                message.data = message.data + self.domain.encode('UTF-8') + b'\0pysmb\0'

    class ComSessionSetupAndxResponse(Payload):
        """
        Contains information on the SMB_COM_SESSION_SETUP_ANDX response from server

        If the underlying SMB message's flags2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
        then the instance will have the following attributes,
        - action

        If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled
        and the message status is STATUS_MORE_PROCESSING_REQUIRED or equals to 0x00 (no error),
        then the instance will have the following attributes,
        - action
        - securityblob

        If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled but
        the message status is not STATUS_MORE_PROCESSING_REQUIRED

        References:
        ===========
        - [MS-SMB]: 2.2.4.6.2
        - [MS-CIFS]: 2.2.4.53.2
        """

        NOSECURE_PARAMETER_STRUCT_FORMAT = '<BBHH'
        NOSECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(NOSECURE_PARAMETER_STRUCT_FORMAT)

        SECURE_PARAMETER_STRUCT_FORMAT = '<BBHHH'
        SECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(SECURE_PARAMETER_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_SESSION_SETUP_ANDX
            if not message.hasExtendedSecurity:
                if not message.status.hasError:
                    if len(message.parameters_data) < self.NOSECURE_PARAMETER_STRUCT_SIZE:
                        raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (no security extensions) parameters', message.raw_data, message)

                    _, _, _, self.action = struct.unpack(self.NOSECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.NOSECURE_PARAMETER_STRUCT_SIZE])
            else:
                if not message.status.hasError or message.status.internal_value == 0xc0000016:   # STATUS_MORE_PROCESSING_REQUIRED
                    if len(message.parameters_data) < self.SECURE_PARAMETER_STRUCT_SIZE:
                        raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) parameters', message.raw_data, message)

                    _, _, _, self.action, blob_length = struct.unpack(self.SECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.SECURE_PARAMETER_STRUCT_SIZE])
                    if len(message.data) < blob_length:
                        raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) security blob', message.raw_data, message)

                    self.security_blob = message.data[:blob_length]

    class ComTreeConnectAndxRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.55.1
        - [MS-SMB]: 2.2.4.7.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, path, service, password = ''):
            self.path = path
            self.service = service
            self.password = password + '\0'

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_TREE_CONNECT_ANDX

        def prepare(self, message):
            password_len = len(self.password)
            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            0x08 | \
                                ((message.hasExtendedSecurity and 0x0004) or 0x00) | \
                                ((message.tid and message.tid != 0xffff and 0x0001) or 0x00),  # Disconnect tid, if message.tid must be non-zero
                            password_len)

            padding = b''
            if password_len % 2 == 0:
                padding = b'\0'

            # Note that service field is never encoded in UTF-16LE. [MS-CIFS]: 2.2.1.1
            message.data = self.password.encode('UTF-8') + padding + self.path.encode('UTF-16LE') + b'\0\0' + self.service.encode('UTF-8') + b'\0'

    class ComTreeConnectAndxResponse(Payload):
        """
        Contains information about the SMB_COM_TREE_CONNECT_ANDX response from the server.

        If the message has no errors, each instance contains the following attributes:
        - optional_support

        References:
        ===========
        - [MS-CIFS]: 2.2.4.55.2
        """

        PAYLOAD_STRUCT_FORMAT = '<BBHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_TREE_CONNECT_ANDX

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_TREE_CONNECT_ANDX parameters', message.raw_data, message)

                _, _, _, self.optional_support = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

    class ComNTCreateAndxRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.64.1
        - [MS-SMB]: 2.2.4.9.1
        """

        PAYLOAD_STRUCT_FORMAT = '<BHIIIQIIIIIB'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, filename, flags = 0, root_fid = 0, access_mask = 0, allocation_size = 0, ext_attr = 0,
                     share_access = 0, create_disp = 0, create_options = 0, impersonation = 0, security_flags = 0):
            self.filename = (filename + '\0').encode('UTF-16LE')
            self.flags = flags
            self.root_fid = root_fid
            self.access_mask = access_mask
            self.allocation_size = allocation_size
            self.ext_attr = ext_attr
            self.share_access = share_access
            self.create_disp = create_disp
            self.create_options = create_options
            self.impersonation = impersonation
            self.security_flags = security_flags

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_NT_CREATE_ANDX

        def prepare(self, message):
            filename_len = len(self.filename)

            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            0x00,                  # reserved
                            filename_len,          # NameLength
                            self.flags,            # Flags
                            self.root_fid,         # RootDirectoryFID
                            self.access_mask,      # DesiredAccess
                            self.allocation_size,  # AllocationSize
                            self.ext_attr,         # ExtFileAttributes
                            self.share_access,     # ShareAccess
                            self.create_disp,      # CreateDisposition
                            self.create_options,   # CreateOptions
                            self.impersonation,    # ImpersonationLevel
                            self.security_flags)   # SecurityFlags

            padding = b''
            if (message.HEADER_STRUCT_SIZE + len(message.parameters_data)) % 2 != 0:
                padding = b'\0'

            message.data = padding + self.filename

    class ComNTCreateAndxResponse(Payload):
        """
        Contains (partial) information about the SMB_COM_NT_CREATE_ANDX response from the server.

        Each instance contains the following attributes after decoding:
        - oplock_level
        - fid

        References:
        ===========
        - [MS-CIFS]: 2.2.4.64.2
        """
        PAYLOAD_STRUCT_FORMAT = '<BBHBH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_NT_CREATE_ANDX

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_NT_CREATE_ANDX parameters', message.raw_data, message)

                _, _, _, self.oplock_level, self.fid = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

    class ComTransactionRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.33.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHHBBHIHHHHHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, max_params_count, max_data_count, max_setup_count,
                     total_params_count = 0, total_data_count = 0,
                     params_bytes = b'', data_bytes = b'', setup_bytes = b'',
                     flags = 0, timeout = 0, name = "\\PIPE\\"):
            self.total_params_count = total_params_count or len(params_bytes)
            self.total_data_count = total_data_count or len(data_bytes)
            self.max_params_count = max_params_count
            self.max_data_count = max_data_count
            self.max_setup_count = max_setup_count
            self.flags = flags
            self.timeout = timeout
            self.params_bytes = params_bytes
            self.data_bytes = data_bytes
            self.setup_bytes = setup_bytes
            self.name = name

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_TRANSACTION

        def prepare(self, message):
            name = (self.name + '\0').encode('UTF-16LE')
            name_len = len(name)
            setup_bytes_len = len(self.setup_bytes)
            params_bytes_len = len(self.params_bytes)
            data_bytes_len = len(self.data_bytes)

            padding0 = b''
            offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
            if offset % 2 != 0:
                padding0 = b'\0'
                offset += 1

            offset += name_len  # For the name field
            padding1 = b''
            if offset % 4 != 0:
                padding1 = b'\0'*(4-offset%4)
                offset += (4-offset%4)

            if params_bytes_len > 0:
                params_bytes_offset = offset
                offset += params_bytes_len
            else:
                params_bytes_offset = 0

            padding2 = b''
            if offset % 4 != 0:
                padding2 = b'\0'*(4-offset%4)
                offset += (4-offset%4)

            if data_bytes_len > 0:
                data_bytes_offset = offset
            else:
                data_bytes_offset = 0

            message.parameters_data = \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.total_params_count,
                            self.total_data_count,
                            self.max_params_count,
                            self.max_data_count,
                            self.max_setup_count,
                            0x00,           # Reserved1. Must be 0x00
                            self.flags,
                            self.timeout,
                            0x0000,         # Reserved2. Must be 0x0000
                            params_bytes_len,
                            params_bytes_offset,
                            data_bytes_len,
                            data_bytes_offset,
                            int(setup_bytes_len / 2)) + \
                self.setup_bytes

            message.data = padding0 + name + padding1 + self.params_bytes + padding2 + self.data_bytes

    class ComTransactionResponse(Payload):
        """
        Contains information about a SMB_COM_TRANSACTION response from the server

        After decoding, each instance contains the following attributes:
        - total_params_count (integer)
        - total_data_count (integer)
        - setup_bytes (string)
        - data_bytes (string)
        - params_bytes (string)

        References:
        ===========
        - [MS-CIFS]: 2.2.4.33.2
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_TRANSACTION

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)

                self.total_params_count, self.total_data_count, _, \
                params_bytes_len, params_bytes_offset, params_bytes_displ, \
                data_bytes_len, data_bytes_offset, data_bytes_displ, \
                setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

                if setup_count > 0:
                    setup_bytes_len = setup_count * 2

                    if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE + setup_bytes_len:
                        raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)

                    self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_bytes_len]
                else:
                    self.setup_bytes = ''

                offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count * 2 + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)

                if params_bytes_len > 0:
                    self.params_bytes = message.data[params_bytes_offset-offset:params_bytes_offset-offset+params_bytes_len]
                else:
                    self.params_bytes = ''

                if data_bytes_len > 0:
                    self.data_bytes = message.data[data_bytes_offset-offset:data_bytes_offset-offset+data_bytes_len]
                else:
                    self.data_bytes = ''

    class ComTransaction2Request(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.46.1
        """

        PAYLOAD_STRUCT_FORMAT = 'HHHHBBHIHHHHHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, max_params_count, max_data_count, max_setup_count,
                     total_params_count = 0, total_data_count = 0,
                     params_bytes = b'', data_bytes = b'', setup_bytes = b'',
                     flags = 0, timeout = 0):
            self.total_params_count = total_params_count or len(params_bytes)
            self.total_data_count = total_data_count or len(data_bytes)
            self.max_params_count = max_params_count
            self.max_data_count = max_data_count
            self.max_setup_count = max_setup_count
            self.flags = flags
            self.timeout = timeout
            self.params_bytes = params_bytes
            self.data_bytes = data_bytes
            self.setup_bytes = setup_bytes

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_TRANSACTION2

        def prepare(self, message):
            setup_bytes_len = len(self.setup_bytes)
            params_bytes_len = len(self.params_bytes)
            data_bytes_len = len(self.data_bytes)
            name = b'\0\0'

            padding0 = b''
            offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
            if offset % 2 != 0:
                padding0 = b'\0'
                offset += 1

            offset += 2  # For the name field
            padding1 = b''
            if offset % 4 != 0:
                padding1 = b'\0'*(4-offset%4)

            if params_bytes_len > 0:
                params_bytes_offset = offset
                offset += params_bytes_len
            else:
                params_bytes_offset = 0

            padding2 = b''
            if offset % 4 != 0:
                padding2 = b'\0'*(4-offset%4)

            if data_bytes_len > 0:
                data_bytes_offset = offset
            else:
                data_bytes_offset = 0

            message.parameters_data = \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.total_params_count,
                            self.total_data_count,
                            self.max_params_count,
                            self.max_data_count,
                            self.max_setup_count,
                            0x00,           # Reserved1. Must be 0x00
                            self.flags,
                            self.timeout,
                            0x0000,         # Reserved2. Must be 0x0000
                            params_bytes_len,
                            params_bytes_offset,
                            data_bytes_len,
                            data_bytes_offset,
                            int(setup_bytes_len / 2)) + \
                self.setup_bytes

            message.data = padding0 + name + padding1 + self.params_bytes + padding2 + self.data_bytes

    class ComTransaction2Response(Payload):
        """
        Contains information about a SMB_COM_TRANSACTION2 response from the server

        After decoding, each instance contains the following attributes:
        - total_params_count (integer)
        - total_data_count (integer)
        - setup_bytes (string)
        - data_bytes (string)
        - params_bytes (string)

        References:
        ===========
        - [MS-CIFS]: 2.2.4.46.2
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHBB'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_TRANSACTION2

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION2 parameters', message.raw_data, message)

                self.total_params_count, self.total_data_count, _, \
                params_bytes_len, params_bytes_offset, params_bytes_displ, \
                data_bytes_len, data_bytes_offset, data_bytes_displ, \
                setup_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

                if setup_count > 0:
                    setup_bytes_len = setup_count * 2

                    if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE + setup_bytes_len:
                        raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)

                    self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_bytes_len]
                else:
                    self.setup_bytes = ''

                offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count * 2 + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)

                if params_bytes_len > 0:
                    self.params_bytes = message.data[params_bytes_offset-offset:params_bytes_offset-offset+params_bytes_len]
                else:
                    self.params_bytes = ''

                if data_bytes_len > 0:
                    self.data_bytes = message.data[data_bytes_offset-offset:data_bytes_offset-offset+data_bytes_len]
                else:
                    self.data_bytes = ''

    class ComCloseRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.5.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HI'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, fid, last_modified_time = 0xFFFFFFFF):
            self.fid = fid
            self.last_modified_time = last_modified_time

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_CLOSE

        def prepare(self, message):
            message.parameters_data = struct.pack(self.PAYLOAD_STRUCT_FORMAT, self.fid, self.last_modified_time)
            message.data = b''

    class ComOpenAndxRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.41.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HHHHIHIII'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, filename, access_mode, open_mode, flags = 0x0000, search_attributes = 0, file_attributes = 0, create_time = 0, timeout = 0):
            """
            @param create_time: Epoch time value to indicate the time of creation for this file. If zero, we will automatically assign the current time
            @type create_time: int
            @param timeout: Number of milliseconds to wait for blocked open request before failing
            @type timeout: int
            """
            self.filename = filename
            self.access_mode = access_mode
            self.open_mode = open_mode
            self.flags = flags
            self.search_attributes = search_attributes
            self.file_attributes = file_attributes
            self.create_time = create_time or int(time.time())
            self.timeout = timeout

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_OPEN_ANDX

        def prepare(self, message):
            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.flags,
                            self.access_mode,
                            self.search_attributes,
                            self.file_attributes,
                            self.create_time,
                            self.open_mode,
                            0,  # AllocationSize
                            0,  # Timeout (in milli-secs)
                            0)  # Reserved

            message.data = b'\0' + self.filename.encode('UTF-16LE') + b'\0\0'

    class ComOpenAndxResponse(Payload):
        """
        Contains information about a SMB_COM_OPEN_ANDX response from the server

        After decoding, each instance will contain the following attributes:
        - fid (integer)
        - file_attributes (integer)
        - last_write_time (long)
        - access_rights (integer)
        - resource_type (integer)
        - open_results (integer)

        References:
        ===========
        - [MS-CIFS]: 2.2.4.41.2
        - [MS-SMB]: 2.2.4.1.2
        """

        PAYLOAD_STRUCT_FORMAT = '<BBHHHIIHHHHHHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_OPEN_ANDX

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_OPEN_ANDX parameters', message.raw_data, message)

                _, _, _, self.fid, self.file_attributes, self.last_write_time, _, \
                self.access_rights, self.resource_type, _, self.open_results, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                                                      message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

    class ComWriteAndxRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.43.1
        - [MS-SMB]: 2.2.4.3.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HIIHHHHHI'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, fid, data_bytes, offset, write_mode = 0, timeout = 0):
            """
            @param timeout: Number of milliseconds to wait for blocked write request before failing. Must be zero for writing to regular file
            @type timeout: int
            """
            self.fid = fid
            self.offset = offset
            self.data_bytes = data_bytes
            self.timeout = timeout
            self.write_mode = write_mode

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_WRITE_ANDX

        def prepare(self, message):
            # constant 1 is to account for the pad byte in the message.data
            # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
            data_offset = message.HEADER_STRUCT_SIZE + self.DEFAULT_ANDX_PARAM_SIZE + self.PAYLOAD_STRUCT_SIZE + 1 + 2
            data_len = len(self.data_bytes)

            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.fid,
                            self.offset & 0xFFFFFFFF,
                            self.timeout,
                            self.write_mode,
                            data_len,   # Remaining
                            0x0000,     # Reserved
                            len(self.data_bytes),  # DataLength
                            data_offset,           # DataOffset
                            self.offset >> 32)     # OffsetHigh field defined in [MS-SMB]: 2.2.4.3.1

            message.data = b'\0' + self.data_bytes

    class ComWriteAndxResponse(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.43.2
        - [MS-SMB]: 2.2.4.3.2
        """

        PAYLOAD_STRUCT_FORMAT = '<BBHHHHH'  # We follow the SMB_COM_WRITEX_ANDX server extensions in [MS-SMB]: 2.2.4.3.2
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_WRITE_ANDX

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_WRITE_ANDX parameters', message.raw_data, message)

                _, _, _, count, self.available, high_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
                self.count = (count & 0xFFFF) | (high_count << 16)

    class ComReadAndxRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.42.1
        - [MS-SMB]: 2.2.4.2.1
        """

        PAYLOAD_STRUCT_FORMAT = '<HIHHIHI'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, fid, offset, max_return_bytes_count, min_return_bytes_count, timeout = 0, remaining = 0):
            """
            @param timeout: If reading from a regular file, this parameter must be 0.
            @type timeout: int
            """
            self.fid = fid
            self.remaining = remaining
            self.max_return_bytes_count = max_return_bytes_count
            self.min_return_bytes_count = min_return_bytes_count
            self.offset = offset
            self.timeout = timeout

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_READ_ANDX

        def prepare(self, message):
            message.parameters_data = \
                self.DEFAULT_ANDX_PARAM_HEADER + \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.fid,
                            self.offset & 0xFFFFFFFF,
                            self.max_return_bytes_count,
                            self.min_return_bytes_count,
                            self.timeout or (self.max_return_bytes_count >> 32),  # Note that in [MS-SMB]: 2.2.4.2.1, this field can also act as MaxCountHigh field
                            self.remaining, # In [MS-CIFS]: 2.2.4.42.1, this field must be set to 0x0000
                            self.offset >> 32)

            message.data = b''

    class ComReadAndxResponse(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.42.2
        - [MS-SMB]: 2.2.4.2.2
        """

        PAYLOAD_STRUCT_FORMAT = '<BBHHHHHHHHHHH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_READ_ANDX

            if not message.status.hasError:
                if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
                    raise ProtocolError('Not enough data to decode SMB_COM_READ_ANDX parameters', message.raw_data, message)

                _, _, _, _, _, _, self.data_length, data_offset, _, _, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                                               message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

                offset = data_offset - message.HEADER_STRUCT_SIZE - self.PAYLOAD_STRUCT_SIZE - 2  # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
                self.data = message.data[offset:offset+self.data_length]
                assert len(self.data) == self.data_length

    class ComDeleteRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.7.1
        """

        def __init__(self, filename_pattern, search_attributes = 0):
            self.filename_pattern = filename_pattern
            self.search_attributes = search_attributes

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_DELETE

        def prepare(self, message):
            message.parameters_data = struct.pack('<H', self.search_attributes)
            message.data = b'\x04' + self.filename_pattern.encode('UTF-16LE') + b'\0\0'

    class ComCreateDirectoryRequest(Payload):
        """
        Although this command has been marked deprecated in [MS-CIFS], we continue to use it for its simplicity
        as compared to its replacement TRANS2_CREATE_DIRECTORY sub-command [MS-CIFS]: 2.2.6.14

        References:
        ===========
        - [MS-CIFS]: 2.2.4.1.1
        """

        def __init__(self, path):
            self.path = path

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_CREATE_DIRECTORY

        def prepare(self, message):
            message.parameters_data = b''
            message.data = b'\x04' + self.path.encode('UTF-16LE') + b'\0\0'

    class ComDeleteDirectoryRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.2.1
        """

        def __init__(self, path):
            self.path = path

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_DELETE_DIRECTORY

        def prepare(self, message):
            message.parameters_data = b''
            message.data = b'\x04' + self.path.encode('UTF-16LE') + b'\0\0'

    class ComRenameRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.8.1
        """

        def __init__(self, old_path, new_path, search_attributes = 0):
            self.old_path = old_path
            self.new_path = new_path
            self.search_attributes = search_attributes

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_RENAME

        def prepare(self, message):
            message.parameters_data = struct.pack('<H', self.search_attributes)
            message.data = b'\x04' + self.old_path.encode('UTF-16LE') + b'\x00\x00\x04\x00' + self.new_path.encode('UTF-16LE') + b'\x00\x00'

    class ComEchoRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.39.1
        """

        def __init__(self, echo_data = b'', echo_count = 1):
            self.echo_count = echo_count
            self.echo_data = echo_data

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_ECHO
            message.tid = 0xFFFF

        def prepare(self, message):
            message.parameters_data = struct.pack('<H', self.echo_count)
            message.data = self.echo_data

    class ComEchoResponse(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.39.2
        """

        def decode(self, message):
            self.sequence_number = struct.unpack('<H', message.parameters_data[:2])[0]
            self.data = message.data

    class ComNTTransactRequest(Payload):
        """
        References:
        ===========
        - [MS-CIFS]: 2.2.4.62.1
        """
        PAYLOAD_STRUCT_FORMAT = '<BHIIIIIIIIBH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def __init__(self, function, max_params_count, max_data_count, max_setup_count,
                     total_params_count = 0, total_data_count = 0,
                     params_bytes = b'', setup_bytes = b'', data_bytes = b''):
            self.function = function
            self.total_params_count = total_params_count or len(params_bytes)
            self.total_data_count = total_data_count or len(data_bytes)
            self.max_params_count = max_params_count
            self.max_data_count = max_data_count
            self.max_setup_count = max_setup_count
            self.params_bytes = params_bytes
            self.setup_bytes = setup_bytes
            self.data_bytes = data_bytes

        def initMessage(self, message):
            Payload.initMessage(self, message)
            message.command = SMB_COM_NT_TRANSACT

        def prepare(self, message):
            setup_bytes_len = len(self.setup_bytes)
            params_bytes_len = len(self.params_bytes)
            data_bytes_len = len(self.data_bytes)

            padding0 = b''
            offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
            if offset % 4 != 0:
                padding0 = b'\0'*(4-offset%4)
                offset += (4-offset%4)

            if params_bytes_len > 0:
                params_bytes_offset = offset
            else:
                params_bytes_offset = 0

            offset += params_bytes_len
            padding1 = b''
            if offset % 4 != 0:
                padding1 = b'\0'*(4-offset%4)
                offset += (4-offset%4)

            if data_bytes_len > 0:
                data_bytes_offset = offset
            else:
                data_bytes_offset = 0

            message.parameters_data = \
                struct.pack(self.PAYLOAD_STRUCT_FORMAT,
                            self.max_setup_count,
                            0x00,           # Reserved1. Must be 0x00
                            self.total_params_count,
                            self.total_data_count,
                            self.max_params_count,
                            self.max_data_count,
                            params_bytes_len,
                            params_bytes_offset,
                            data_bytes_len,
                            data_bytes_offset,
                            int(setup_bytes_len / 2),
                            self.function) + \
                self.setup_bytes

            message.data = padding0 + self.params_bytes + padding1 + self.data_bytes

    class ComNTTransactResponse(Payload):
        """
        Contains information about a SMB_COM_NT_TRANSACT response from the server

        After decoding, each instance contains the following attributes:
        - total_params_count (integer)
        - total_data_count (integer)
        - setup_bytes (string)
        - data_bytes (string)
        - params_bytes (string)

        References:
        ===========
        - [MS-CIFS]: 2.2.4.62.2
        """
        PAYLOAD_STRUCT_FORMAT = '<3sIIIIIIIIBH'
        PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)

        def decode(self, message):
            assert message.command == SMB_COM_NT_TRANSACT

            if not message.status.hasError:
                _, self.total_params_count, self.total_data_count, \
                params_count, params_offset, params_displ, \
                data_count, data_offset, data_displ, setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
                                                                                 message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])

                self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_count*2]

                if params_count > 0:
                    params_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
                    self.params_bytes = message.data[params_offset:params_offset+params_count]
                else:
                    self.params_bytes = b''

                if data_count > 0:
                    data_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
                    self.data_bytes = message.data[data_offset:data_offset+data_count]
                else:
                    self.data_bytes = b''