# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import six

from kmip.core import enums
from kmip.core import objects
from kmip.core import utils

from kmip.core import primitives
from kmip.core.primitives import Struct
from kmip.core.primitives import Integer
from kmip.core.primitives import Enumeration
from kmip.core.primitives import Boolean
from kmip.core.primitives import TextString
from kmip.core.primitives import ByteString
from kmip.core.primitives import DateTime

class ProtocolVersion(primitives.Struct):
    """
    A struct representing a ProtocolVersion number.

    Attributes:
        major: The major protocol version number.
        minor: The minor protocol version number.
    """

    def __init__(self, major=None, minor=None):
        """
        Construct a ProtocolVersion struct.

        Args:
            major (int): The major protocol version number. Optional, defaults
                to None.
            minor (int): The minor protocol version number. Optional, defaults
                to None.
        """
        super(ProtocolVersion, self).__init__(enums.Tags.PROTOCOL_VERSION)

        self._major = None
        self._minor = None

        self.major = major
        self.minor = minor

    @property
    def major(self):
        if self._major:
            return self._major.value
        else:
            return None

    @major.setter
    def major(self, value):
        if value is None:
            self._major = None
        elif isinstance(value, six.integer_types):
            self._major = primitives.Integer(
                value=value,
                tag=enums.Tags.PROTOCOL_VERSION_MAJOR
            )
        else:
            raise TypeError(
                "Major protocol version number must be an integer."
            )

    @property
    def minor(self):
        if self._minor:
            return self._minor.value
        else:
            return None

    @minor.setter
    def minor(self, value):
        if value is None:
            self._minor = None
        elif isinstance(value, six.integer_types):
            self._minor = primitives.Integer(
                value=value,
                tag=enums.Tags.PROTOCOL_VERSION_MINOR
            )
        else:
            raise TypeError(
                "Minor protocol version number must be an integer."
            )

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the ProtocolVersion struct and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if either the major or minor protocol versions
                are missing from the encoding.
        """
        super(ProtocolVersion, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(enums.Tags.PROTOCOL_VERSION_MAJOR, local_stream):
            self._major = primitives.Integer(
                tag=enums.Tags.PROTOCOL_VERSION_MAJOR
            )
            self._major.read(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError(
                "Invalid encoding missing the major protocol version number."
            )

        if self.is_tag_next(enums.Tags.PROTOCOL_VERSION_MINOR, local_stream):
            self._minor = primitives.Integer(
                tag=enums.Tags.PROTOCOL_VERSION_MINOR
            )
            self._minor.read(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError(
                "Invalid encoding missing the minor protocol version number."
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the ProtocolVersion struct to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is not defined.
        """
        local_stream = utils.BytearrayStream()

        if self._major:
            self._major.write(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError(
                "Invalid struct missing the major protocol version number."
            )

        if self._minor:
            self._minor.write(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError(
                "Invalid struct missing the minor protocol version number."
            )

        self.length = local_stream.length()
        super(ProtocolVersion, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, ProtocolVersion):
            if self.major != other.major:
                return False
            elif self.minor != other.minor:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, ProtocolVersion):
            return not (self == other)
        else:
            return NotImplemented

    def __lt__(self, other):
        if isinstance(other, ProtocolVersion):
            if self.major < other.major:
                return True
            elif self.major > other.major:
                return False
            elif self.minor < other.minor:
                return True
            else:
                return False
        else:
            return NotImplemented

    def __gt__(self, other):
        if isinstance(other, ProtocolVersion):
            if (self == other) or (self < other):
                return False
            else:
                return True
        else:
            return NotImplemented

    def __le__(self, other):
        if isinstance(other, ProtocolVersion):
            if (self == other) or (self < other):
                return True
            else:
                return False
        else:
            return NotImplemented

    def __ge__(self, other):
        if isinstance(other, ProtocolVersion):
            if (self == other) or (self > other):
                return True
            else:
                return False
        else:
            return NotImplemented

    def __repr__(self):
        args = ", ".join([
            "major={}".format(self.major),
            "minor={}".format(self.minor)
        ])
        return "ProtocolVersion({})".format(args)

    def __str__(self):
        return "{}.{}".format(self.major, self.minor)

def protocol_version_to_kmip_version(value):
    """
    Convert a ProtocolVersion struct to its KMIPVersion enumeration equivalent.

    Args:
        value (ProtocolVersion): A ProtocolVersion struct to be converted into
            a KMIPVersion enumeration.

    Returns:
        KMIPVersion: The enumeration equivalent of the struct. If the struct
            cannot be converted to a valid enumeration, None is returned.
    """
    if not isinstance(value, ProtocolVersion):
        return None

    if value.major == 1:
        if value.minor == 0:
            return enums.KMIPVersion.KMIP_1_0
        elif value.minor == 1:
            return enums.KMIPVersion.KMIP_1_1
        elif value.minor == 2:
            return enums.KMIPVersion.KMIP_1_2
        elif value.minor == 3:
            return enums.KMIPVersion.KMIP_1_3
        elif value.minor == 4:
            return enums.KMIPVersion.KMIP_1_4
        else:
            return None
    else:
        return None

# 6.2
class Operation(Enumeration):

    def __init__(self, value=None):
        super(Operation, self).__init__(
            enums.Operation, value, enums.Tags.OPERATION)

# 6.3
class MaximumResponseSize(Integer):
    def __init__(self, value=None):
        super(MaximumResponseSize, self).\
            __init__(value, enums.Tags.MAXIMUM_RESPONSE_SIZE)

# 6.4
class UniqueBatchItemID(ByteString):
    def __init__(self, value=None):
        super(UniqueBatchItemID, self)\
            .__init__(value, enums.Tags.UNIQUE_BATCH_ITEM_ID)

# 6.5
class TimeStamp(DateTime):
    def __init__(self, value=None):
        super(TimeStamp, self).__init__(value, enums.Tags.TIME_STAMP)

class Authentication(Struct):
    """
    A struct representing an Authentication bundle.

    Attributes:
        credentials: A list of Credential structs to be used for
            authentication.
    """

    def __init__(self, credentials=None):
        """
        Construct an Authentication struct.

        Args:
            credentials (list): A list of Credential structs to be used for
                authentication. Optional, defaults to None.
        """
        super(Authentication, self).__init__(enums.Tags.AUTHENTICATION)

        self._credentials = []
        self.credentials = credentials

    @property
    def credentials(self):
        return self._credentials

    @credentials.setter
    def credentials(self, value):
        if value is None:
            self._credentials = []
        elif isinstance(value, list):
            credentials = []
            for i in range(len(value)):
                credential = value[i]
                if not isinstance(credential, objects.Credential):
                    raise TypeError(
                        "Credentials must be a list of Credential structs. "
                        "Item {} has type: {}".format(i + 1, type(credential))
                    )
                credentials.append(credential)
            self._credentials = credentials
        else:
            raise TypeError(
                "Credentials must be a list of Credential structs."
            )

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Authentication struct and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Authentication, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        credentials = []
        while self.is_tag_next(enums.Tags.CREDENTIAL, local_stream):
            credential = objects.Credential()
            credential.read(local_stream, kmip_version=kmip_version)
            credentials.append(credential)
        if len(credentials) == 0:
            raise ValueError("Authentication encoding missing credentials.")
        self._credentials = credentials

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Authentication struct to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        local_stream = utils.BytearrayStream()

        if len(self._credentials) == 0:
            raise ValueError("Authentication struct missing credentials.")
        for credential in self._credentials:
            credential.write(local_stream, kmip_version=kmip_version)

        self.length = local_stream.length()
        super(Authentication, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, Authentication):
            if self.credentials != other.credentials:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, Authentication):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = ", ".join([
            "credentials={}".format([x for x in self.credentials])
        ])
        return "Authentication({})".format(args)

    def __str__(self):
        credentials = ", ".join([str(x) for x in self.credentials])
        return "{'credentials': [" + credentials + "]}"

# 6.7
class AsynchronousIndicator(Boolean):
    def __init__(self, value=None):
        super(AsynchronousIndicator, self).\
            __init__(value, enums.Tags.ASYNCHRONOUS_INDICATOR)

# 6.8
class AsynchronousCorrelationValue(ByteString):
    def __init__(self, value=None):
        super(AsynchronousCorrelationValue, self).\
            __init__(value, enums.Tags.ASYNCHRONOUS_CORRELATION_VALUE)

# 6.9
class ResultStatus(Enumeration):

    def __init__(self, value=None):
        super(ResultStatus, self).__init__(
            enums.ResultStatus, value, enums.Tags.RESULT_STATUS)

# 6.10
class ResultReason(Enumeration):

    def __init__(self, value=None):
        super(ResultReason, self).__init__(
            enums.ResultReason, value, enums.Tags.RESULT_REASON)

# 6.11
class ResultMessage(TextString):
    def __init__(self, value=None):
        super(ResultMessage, self).__init__(value, enums.Tags.RESULT_MESSAGE)

# 6.12
class BatchOrderOption(Boolean):
    def __init__(self, value=None):
        super(BatchOrderOption, self).\
            __init__(value, enums.Tags.BATCH_ORDER_OPTION)

# 6.13
class BatchErrorContinuationOption(Enumeration):

    def __init__(self, value=None):
        super(BatchErrorContinuationOption, self).__init__(
            enums.BatchErrorContinuationOption, value,
            enums.Tags.BATCH_ERROR_CONTINUATION_OPTION)

# 6.14
class BatchCount(Integer):
    def __init__(self, value=None):
        super(BatchCount, self).__init__(value, enums.Tags.BATCH_COUNT)

# 6.16
class MessageExtension(Struct):
    def __init__(self):
        super(MessageExtension, self).__init__(enums.Tags.MESSAGE_EXTENSION)

# 9.1.3.2.2
class KeyCompressionType(Enumeration):

    def __init__(self, value=None):
        super(KeyCompressionType, self).__init__(
            enums.KeyCompressionType, value, enums.Tags.KEY_COMPRESSION_TYPE)


# Copyright (c) 2017 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import six

from kmip import enums
from kmip.core import primitives
from kmip.core import utils

class PollRequestPayload(primitives.Struct):
    """
    A request payload for the Poll operation.

    Attributes:
        asynchronous_correlation_value: The unique ID, in bytes, of the
            operation to poll.
    """

    def __init__(self, asynchronous_correlation_value=None):
        """
        Construct a Poll request payload struct.

        Args:
            asynchronous_correlation_value (bytes): The ID of a pending
                operation to poll the status of, in bytes. Optional, defaults
                to None.
        """
        super(PollRequestPayload, self).__init__(
            enums.Tags.REQUEST_PAYLOAD
        )

        self._asynchronous_correlation_value = None
        self.asynchronous_correlation_value = asynchronous_correlation_value

    @property
    def asynchronous_correlation_value(self):
        if self._asynchronous_correlation_value:
            return self._asynchronous_correlation_value.value
        else:
            return None

    @asynchronous_correlation_value.setter
    def asynchronous_correlation_value(self, value):
        if value is None:
            self._asynchronous_correlation_value = None
        elif isinstance(value, six.binary_type):
            self._asynchronous_correlation_value = primitives.ByteString(
                value=value,
                tag=enums.Tags.ASYNCHRONOUS_CORRELATION_VALUE
            )
        else:
            raise TypeError("Asynchronous correlation value must be bytes.")

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Poll request payload and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is missing from the
                encoded payload.
        """
        super(PollRequestPayload, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(
                enums.Tags.ASYNCHRONOUS_CORRELATION_VALUE,
                local_stream
        ):
            self._asynchronous_correlation_value = primitives.ByteString(
                tag=enums.Tags.ASYNCHRONOUS_CORRELATION_VALUE
            )
            self._asynchronous_correlation_value.read(
                local_stream,
                kmip_version=kmip_version
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Poll request payload to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is not defined.
        """
        local_stream = utils.BytearrayStream()

        if self._asynchronous_correlation_value:
            self._asynchronous_correlation_value.write(
                local_stream,
                kmip_version=kmip_version
            )

        self.length = local_stream.length()
        super(PollRequestPayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, PollRequestPayload):
            if self.asynchronous_correlation_value != \
                    other.asynchronous_correlation_value:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, PollRequestPayload):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = "asynchronous_correlation_value={0}".format(
            self.asynchronous_correlation_value
        )
        return "PollRequestPayload({0})".format(args)

    def __str__(self):
        return str({
            'asynchronous_correlation_value':
                self.asynchronous_correlation_value
        })


# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from kmip.core.attributes import CertificateType

from kmip.core import enums
from kmip.core.enums import Tags

from kmip.core.misc import CertificateValue

from kmip.core.objects import Attribute
from kmip.core.objects import KeyBlock

from kmip.core.primitives import Struct
from kmip.core.primitives import Integer
from kmip.core.primitives import Enumeration
from kmip.core.primitives import BigInteger
from kmip.core.primitives import ByteString

from kmip.core.utils import BytearrayStream

# 2.2
# 2.2.1
class Certificate(Struct):
    """
    A structure representing a DER-encoded X.509 public key certificate.

    See Section 2.2.1 of the KMIP 1.1 specification for more information.

    Attributes:
        certificate_type: The type of the certificate.
        certificate_value: The bytes of the certificate.
    """

    def __init__(self,
                 certificate_type=None,
                 certificate_value=None):
        """
        Construct a Certificate object.

        Args:
            certificate_type (CertificateType): The type of the
                certificate. Optional, defaults to None.
            certificate_value (bytes): The bytes of the certificate. Optional,
                defaults to None.
        """
        super(Certificate, self).__init__(Tags.CERTIFICATE)

        if certificate_type is None:
            self.certificate_type = CertificateType()
        else:
            self.certificate_type = CertificateType(certificate_type)

        if certificate_value is None:
            self.certificate_value = CertificateValue()
        else:
            self.certificate_value = CertificateValue(certificate_value)

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Certificate object and decode it into its
        constituent parts.

        Args:
            istream (Stream): A data stream containing encoded object data,
                supporting a read method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Certificate, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.certificate_type = CertificateType()
        self.certificate_value = CertificateValue()

        self.certificate_type.read(tstream, kmip_version=kmip_version)
        self.certificate_value.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Certificate object to a stream.

        Args:
            ostream (Stream): A data stream in which to encode object data,
                supporting a write method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        tstream = BytearrayStream()

        self.certificate_type.write(tstream, kmip_version=kmip_version)
        self.certificate_value.write(tstream, kmip_version=kmip_version)

        self.length = tstream.length()
        super(Certificate, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def __eq__(self, other):
        if isinstance(other, Certificate):
            if self.certificate_type != other.certificate_type:
                return False
            elif self.certificate_value != other.certificate_value:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, Certificate):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        return "{0}(certificate_type={1}, certificate_value=b'{2}')".format(
            type(self).__name__,
            str(self.certificate_type),
            str(self.certificate_value))

    def __str__(self):
        return "{0}".format(str(self.certificate_value))

# 2.2.2
class KeyBlockKey(Struct):

    def __init__(self, key_block=None, tag=Tags.DEFAULT):
        super(KeyBlockKey, self).__init__(tag)
        self.key_block = key_block
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(KeyBlockKey, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.key_block = KeyBlock()
        self.key_block.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        tstream = BytearrayStream()

        self.key_block.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the template attribute
        self.length = tstream.length()
        super(KeyBlockKey, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

class SymmetricKey(KeyBlockKey):

    def __init__(self, key_block=None):
        super(SymmetricKey, self).__init__(key_block, Tags.SYMMETRIC_KEY)
        self.validate()

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.3
class PublicKey(KeyBlockKey):

    def __init__(self, key_block=None):
        super(PublicKey, self).__init__(key_block, Tags.PUBLIC_KEY)
        self.validate()

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.4
class PrivateKey(KeyBlockKey):

    def __init__(self, key_block=None):
        super(PrivateKey, self).__init__(key_block, Tags.PRIVATE_KEY)
        self.validate()

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.5
class SplitKey(Struct):

    class SplitKeyParts(Integer):

        def __init__(self, value=None):
            super(SplitKey.SplitKeyParts, self).__init__(
                value, Tags.SPLIT_KEY_PARTS)

    class KeyPartIdentifier(Integer):

        def __init__(self, value=None):
            super(SplitKey.KeyPartIdentifier, self).__init__(
                value, Tags.KEY_PART_IDENTIFIER)

    class SplitKeyThreshold(Integer):

        def __init__(self, value=None):
            super(SplitKey.SplitKeyThreshold, self).__init__(
                value, Tags.SPLIT_KEY_THRESHOLD)

    class SplitKeyMethod(Enumeration):

        def __init__(self, value=None):
            super(SplitKey.SplitKeyMethod, self).__init__(
                enums.SplitKeyMethod, value, Tags.SPLIT_KEY_METHOD)

    class PrimeFieldSize(BigInteger):

        def __init__(self, value=None):
            super(SplitKey.PrimeFieldSize, self).__init__(
                value, Tags.PRIME_FIELD_SIZE)

    def __init__(self,
                 split_key_parts=None,
                 key_part_identifier=None,
                 split_key_threshold=None,
                 split_key_method=None,
                 prime_field_size=None,
                 key_block=None):
        super(SplitKey, self).__init__(Tags.SPLIT_KEY)
        self.split_key_parts = split_key_parts
        self.key_part_identifier = key_part_identifier
        self.split_key_threshold = split_key_threshold
        self.split_key_method = split_key_method
        self.prime_field_size = prime_field_size
        self.key_block = key_block
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(SplitKey, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.split_key_parts = SplitKey.SplitKeyParts()
        self.split_key_parts.read(tstream, kmip_version=kmip_version)

        self.key_part_identifier = SplitKey.KeyPartIdentifier()
        self.key_part_identifier.read(tstream, kmip_version=kmip_version)

        self.split_key_threshold = SplitKey.SplitKeyThreshold()
        self.split_key_threshold.read(tstream, kmip_version=kmip_version)

        if self.is_tag_next(Tags.PRIME_FIELD_SIZE, tstream):
            self.prime_field_size = SplitKey.PrimeFieldSize()
            self.prime_field_size.read(tstream, kmip_version=kmip_version)

        self.key_block = KeyBlock()
        self.key_block.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        tstream = BytearrayStream()

        self.split_key_parts.write(tstream, kmip_version=kmip_version)
        self.key_part_identifier.write(tstream, kmip_version=kmip_version)
        self.split_key_threshold.write(tstream, kmip_version=kmip_version)
        self.split_key_method.write(tstream, kmip_version=kmip_version)

        if self.prime_field_size is not None:
            self.prime_field_size.write(tstream, kmip_version=kmip_version)

        self.key_block.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the template attribute
        self.length = tstream.length()
        super(SplitKey, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.6
class Template(Struct):

    def __init__(self, attributes=None):
        super(Template, self).__init__(Tags.TEMPLATE)
        self.attributes = attributes
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(Template, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.attributes = list()

        attribute = Attribute()
        attribute.read(tstream, kmip_version=kmip_version)
        self.attributes.append(attribute)

        while self.is_tag_next(Tags.ATTRIBUTE, tstream):
            attribute = Attribute()
            attribute.read(tstream, kmip_version=kmip_version)
            self.attributes.append(attribute)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        tstream = BytearrayStream()

        for attribute in self.attributes:
            attribute.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the template attribute
        self.length = tstream.length()
        super(Template, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.7
class SecretData(Struct):

    class SecretDataType(Enumeration):

        def __init__(self, value=None):
            super(SecretData.SecretDataType, self).__init__(
                enums.SecretDataType, value, Tags.SECRET_DATA_TYPE)

    def __init__(self,
                 secret_data_type=None,
                 key_block=None):
        super(SecretData, self).__init__(Tags.SECRET_DATA)
        self.secret_data_type = secret_data_type
        self.key_block = key_block
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(SecretData, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.secret_data_type = SecretData.SecretDataType()
        self.key_block = KeyBlock()

        self.secret_data_type.read(tstream, kmip_version=kmip_version)
        self.key_block.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        tstream = BytearrayStream()

        self.secret_data_type.write(tstream, kmip_version=kmip_version)
        self.key_block.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the template attribute
        self.length = tstream.length()
        super(SecretData, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass

# 2.2.8
class OpaqueObject(Struct):

    class OpaqueDataType(Enumeration):

        def __init__(self, value=None):
            super(OpaqueObject.OpaqueDataType, self).__init__(
                enums.OpaqueDataType, value, Tags.OPAQUE_DATA_TYPE)

    class OpaqueDataValue(ByteString):

        def __init__(self, value=None):
            super(OpaqueObject.OpaqueDataValue, self).__init__(
                value, Tags.OPAQUE_DATA_VALUE)

    def __init__(self,
                 opaque_data_type=None,
                 opaque_data_value=None):
        super(OpaqueObject, self).__init__(Tags.OPAQUE_OBJECT)
        self.opaque_data_type = opaque_data_type
        self.opaque_data_value = opaque_data_value
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(OpaqueObject, self).read(istream, kmip_version=kmip_version)
        tstream = BytearrayStream(istream.read(self.length))

        self.opaque_data_type = OpaqueObject.OpaqueDataType()
        self.opaque_data_value = OpaqueObject.OpaqueDataValue()

        self.opaque_data_type.read(tstream, kmip_version=kmip_version)
        self.opaque_data_value.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        tstream = BytearrayStream()

        self.opaque_data_type.write(tstream, kmip_version=kmip_version)
        self.opaque_data_value.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the template attribute
        self.length = tstream.length()
        super(OpaqueObject, self).write(ostream, kmip_version=kmip_version)
        ostream.write(tstream.buffer)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Finish implementation.
        pass


# Copyright (c) 2018 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import requests
import six

from kmip.core import exceptions
from kmip.services.server.auth import api
from kmip.services.server.auth import utils

class SLUGSConnector(api.AuthAPI):
    """
    An authentication API connector for a SLUGS service.
    """

    def __init__(self, url=None):
        """
        Construct a SLUGSConnector.

        Args:
            url (string): The base URL for the remote SLUGS instance. Optional,
                defaults to None. Required for authentication.
        """
        self._url = None
        self.users_url = None
        self.groups_url = None

        self.url = url

    @property
    def url(self):
        return self._url

    @url.setter
    def url(self, value):
        if value is None:
            self._url = None
            self.users_url = None
            self.groups_url = None
        elif isinstance(value, six.string_types):
            self._url = value
            if not self._url.endswith("/"):
                self._url += "/"
            self.users_url = self._url + "users/{}"
            self.groups_url = self.users_url + "/groups"
        else:
            raise TypeError("URL must be a string.")

    def authenticate(self,
                     connection_certificate=None,
                     connection_info=None,
                     request_credentials=None):
        """
        Query the configured SLUGS service with the provided credentials.

        Args:
            connection_certificate (cryptography.x509.Certificate): An X.509
                certificate object obtained from the connection being
                authenticated. Required for SLUGS authentication.
            connection_info (tuple): A tuple of information pertaining to the
                connection being authenticated, including the source IP address
                and a timestamp (e.g., ('127.0.0.1', 1519759267.467451)).
                Optional, defaults to None. Ignored for SLUGS authentication.
            request_credentials (list): A list of KMIP Credential structures
                containing credential information to use for authentication.
                Optional, defaults to None. Ignored for SLUGS authentication.
        """
        if (self.users_url is None) or (self.groups_url is None):
            raise exceptions.ConfigurationError(
                "The SLUGS URL must be specified."
            )

        user_id = utils.get_client_identity_from_certificate(
            connection_certificate
        )

        try:
            response = requests.get(self.users_url.format(user_id))
        except Exception:
            raise exceptions.ConfigurationError(
                "A connection could not be established using the SLUGS URL."
            )
        if response.status_code == 404:
            raise exceptions.PermissionDenied(
                "Unrecognized user ID: {}".format(user_id)
            )

        response = requests.get(self.groups_url.format(user_id))
        if response.status_code == 404:
            raise exceptions.PermissionDenied(
                "Group information could not be retrieved for user ID: "
                "{}".format(user_id)
            )

        return user_id, response.json().get('groups')


# Copyright (c) 2017 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import six

from kmip import enums
from kmip.core import primitives
from kmip.core import utils

class ArchiveRequestPayload(primitives.Struct):
    """
    A request payload for the Archive operation.

    Attributes:
        unique_identifier: The unique ID of the object to archive.
    """

    def __init__(self, unique_identifier=None):
        """
        Construct an Archive request payload struct.

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a public key) to archive. Optional, defaults to None.
        """
        super(ArchiveRequestPayload, self).__init__(
            enums.Tags.REQUEST_PAYLOAD
        )

        self._unique_identifier = None
        self.unique_identifier = unique_identifier

    @property
    def unique_identifier(self):
        if self._unique_identifier:
            return self._unique_identifier.value
        else:
            return None

    @unique_identifier.setter
    def unique_identifier(self, value):
        if value is None:
            self._unique_identifier = None
        elif isinstance(value, six.string_types):
            self._unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Unique identifier must be a string.")

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Archive request payload and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is missing from the
                encoded payload.
        """
        super(ArchiveRequestPayload, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_stream):
            self._unique_identifier = primitives.TextString(
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
            self._unique_identifier.read(
                local_stream,
                kmip_version=kmip_version
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Archive request payload to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is not defined.
        """
        local_stream = utils.BytearrayStream()

        if self._unique_identifier:
            self._unique_identifier.write(
                local_stream,
                kmip_version=kmip_version
            )

        self.length = local_stream.length()
        super(ArchiveRequestPayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, ArchiveRequestPayload):
            if self.unique_identifier != other.unique_identifier:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, ArchiveRequestPayload):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = "unique_identifier='{0}'".format(self.unique_identifier)
        return "ArchiveRequestPayload({0})".format(args)

    def __str__(self):
        return str({
            'unique_identifier': self.unique_identifier
        })

class ArchiveResponsePayload(primitives.Struct):
    """
    A response payload for the Archive operation.

    Attributes:
        unique_identifier: The unique ID of the object that was archived.
    """

    def __init__(self, unique_identifier=None):
        """
        Construct an Archive response payload struct.

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a public key) that was archived. Optional, defaults to None.
        """
        super(ArchiveResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._unique_identifier = None
        self.unique_identifier = unique_identifier

    @property
    def unique_identifier(self):
        if self._unique_identifier:
            return self._unique_identifier.value
        else:
            return None

    @unique_identifier.setter
    def unique_identifier(self, value):
        if value is None:
            self._unique_identifier = None
        elif isinstance(value, six.string_types):
            self._unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Unique identifier must be a string.")

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Archive response payload and decode it
        into its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is missing from the
                encoded payload.
        """
        super(ArchiveResponsePayload, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_stream):
            self._unique_identifier = primitives.TextString(
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
            self._unique_identifier.read(
                local_stream,
                kmip_version=kmip_version
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Archive response payload to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the data attribute is not defined.
        """
        local_stream = utils.BytearrayStream()

        if self._unique_identifier:
            self._unique_identifier.write(
                local_stream,
                kmip_version=kmip_version
            )

        self.length = local_stream.length()
        super(ArchiveResponsePayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, ArchiveResponsePayload):
            if self.unique_identifier != other.unique_identifier:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, ArchiveResponsePayload):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = "unique_identifier='{0}'".format(self.unique_identifier)
        return "ArchiveResponsePayload({0})".format(args)

    def __str__(self):
        return str({
            'unique_identifier': self.unique_identifier,
        })


# Copyright (c) 2016 The Johns Hopkins University/Applied Physics Laboratory
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import logging
import socket
import struct
import threading
import time

from cryptography import x509

from kmip.core import enums
from kmip.core import exceptions
from kmip.core.messages import contents
from kmip.core.messages import messages
from kmip.core import utils

from kmip.services.server import auth

class KmipSession(threading.Thread):
    """
    A session thread representing a single KMIP client/server interaction.
    """

    def __init__(self,
                 engine,
                 connection,
                 address,
                 name=None,
                 enable_tls_client_auth=True,
                 auth_settings=None):
        """
        Create a KmipSession.

        Args:
            engine (KmipEngine): A reference to the central server application
                that handles message processing. Required.
            connection (socket): A client socket.socket TLS connection
                representing a new KMIP connection. Required.
            address (tuple): The address tuple produced with the session
                connection. Contains the IP address and port number of the
                remote connection endpoint. Required.
            name (str): The name of the KmipSession. Optional, defaults to
                None.
            enable_tls_client_auth (bool): A flag that enables a strict check
                for the client auth flag in the extended key usage extension
                in client certificates when establishing the client/server TLS
                connection. Optional, defaults to True.
            auth_settings (list): A list of tuples, each containing (1) the
                name of the 'auth:' settings block from the server config file,
                and (2) a dictionary of configuration settings for a specific
                authentication plugin. Optional, defaults to None.
        """
        super(KmipSession, self).__init__(
            group=None,
            target=None,
            name=name,
            args=(),
            kwargs={}
        )

        self._logger = logging.getLogger(
            'kmip.server.session.{0}'.format(self.name)
        )

        self._engine = engine
        self._connection = connection
        self._address = address

        self._enable_tls_client_auth = enable_tls_client_auth
        self._auth_settings = [] if auth_settings is None else auth_settings

        self._session_time = time.time()
        self._max_buffer_size = 4096
        self._max_request_size = 1048576
        self._max_response_size = 1048576

    def run(self):
        """
        The main thread routine executed by invoking thread.start.

        This method manages the new client connection, running a message
        handling loop. Once this method completes, the thread is finished.
        """
        self._logger.info("Starting session: {0}".format(self.name))

        try:
            self._connection.do_handshake()
        except Exception as e:
            self._logger.info("Failure running TLS handshake")
            self._logger.exception(e)
        else:
            while True:
                try:
                    self._handle_message_loop()
                except exceptions.ConnectionClosed as e:
                    break
                except Exception as e:
                    self._logger.info("Failure handling message loop")
                    self._logger.exception(e)

        self._connection.shutdown(socket.SHUT_RDWR)
        self._connection.close()
        self._logger.info("Stopping session: {0}".format(self.name))

    def _handle_message_loop(self):
        request_data = self._receive_request()
        request = messages.RequestMessage()

        max_size = self._max_response_size
        kmip_version = contents.protocol_version_to_kmip_version(
            self._engine.default_protocol_version
        )

        try:
            if hasattr(self._connection, 'shared_ciphers'):
                shared_ciphers = self._connection.shared_ciphers()
                self._logger.debug(
                    "Possible session ciphers: {0}".format(len(shared_ciphers))
                )
                for cipher in shared_ciphers:
                    self._logger.debug(cipher)
            self._logger.debug(
                "Session cipher selected: {0}".format(
                    self._connection.cipher()
                )
            )

            certificate = auth.get_certificate_from_connection(
                self._connection
            )
            if certificate is None:
                raise exceptions.PermissionDenied(
                    "The client certificate could not be loaded from the "
                    "session connection."
                )

            if self._enable_tls_client_auth:
                extension = auth.get_extended_key_usage_from_certificate(
                    certificate
                )
                if extension is None:
                    raise exceptions.PermissionDenied(
                        "The extended key usage extension is missing from "
                        "the client certificate."
                    )
                if x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH not in extension:
                    raise exceptions.PermissionDenied(
                        "The extended key usage extension is not marked for "
                        "client authentication in the client certificate."
                    )

            request.read(request_data, kmip_version=kmip_version)
        except exceptions.PermissionDenied as e:
            self._logger.warning("Failure verifying the client certificate.")
            self._logger.exception(e)
            response = self._engine.build_error_response(
                contents.ProtocolVersion(1, 0),
                enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL,
                "Error verifying the client certificate. "
                "See server logs for more information."
            )
        except Exception as e:
            self._logger.warning("Failure parsing request message.")
            self._logger.exception(e)
            response = self._engine.build_error_response(
                contents.ProtocolVersion(1, 0),
                enums.ResultReason.INVALID_MESSAGE,
                "Error parsing request message. See server logs for more "
                "information."
            )
        else:
            try:
                client_identity = self.authenticate(certificate, request)
                self._logger.info(
                    "Session client identity: {}".format(client_identity[0])
                )
            except Exception:
                self._logger.warning("Authentication failed.")
                response = self._engine.build_error_response(
                    request.request_header.protocol_version,
                    enums.ResultReason.AUTHENTICATION_NOT_SUCCESSFUL,
                    "An error occurred during client authentication. "
                    "See server logs for more information."
                )
            else:
                try:
                    results = self._engine.process_request(
                        request,
                        client_identity
                    )
                    response, max_response_size, protocol_version = results
                    kmip_version = contents.protocol_version_to_kmip_version(
                        protocol_version
                    )

                    if max_response_size:
                        max_size = max_response_size
                except exceptions.KmipError as e:
                    response = self._engine.build_error_response(
                        request.request_header.protocol_version,
                        e.reason,
                        str(e)
                    )
                except Exception as e:
                    self._logger.warning(
                        "An unexpected error occurred while processing "
                        "request."
                    )
                    self._logger.exception(e)
                    response = self._engine.build_error_response(
                        request.request_header.protocol_version,
                        enums.ResultReason.GENERAL_FAILURE,
                        "An unexpected error occurred while processing "
                        "request. See server logs for more information."
                    )

        response_data = utils.BytearrayStream()
        response.write(response_data, kmip_version=kmip_version)

        if len(response_data) > max_size:
            self._logger.warning(
                "Response message length too large: "
                "{0} bytes, max {1} bytes".format(
                    len(response_data),
                    self._max_response_size
                )
            )
            response = self._engine.build_error_response(
                request.request_header.protocol_version,
                enums.ResultReason.RESPONSE_TOO_LARGE,
                "Response message length too large. See server logs for "
                "more information."
            )
            response_data = utils.BytearrayStream()
            response.write(response_data, kmip_version=kmip_version)

        self._send_response(response_data.buffer)

    def authenticate(self, certificate, request):
        credentials = []
        if request.request_header.authentication is not None:
            credentials = request.request_header.authentication.credentials

        plugin_enabled = False

        for auth_settings in self._auth_settings:
            plugin_name, plugin_config = auth_settings

            if plugin_name.startswith("auth:slugs"):
                if plugin_config.get("enabled") == "True":
                    plugin_enabled = True
                    plugin = auth.SLUGSConnector(plugin_config.get("url"))
                    self._logger.debug(
                        "Authenticating with plugin: {}".format(plugin_name)
                    )
                    try:
                        client_identity = plugin.authenticate(
                            certificate,
                            (self._address, self._session_time),
                            credentials
                        )
                    except Exception as e:
                        self._logger.warning(
                            "Authentication failed."
                        )
                        self._logger.error(e)
                        self._logger.exception(e)
                    else:
                        self._logger.debug(
                            "Authentication succeeded for client identity: "
                            "{}".format(client_identity[0])
                        )
                        return client_identity
            else:
                self._logger.warning(
                    "Authentication plugin '{}' is not "
                    "supported.".format(plugin_name)
                )

        if not plugin_enabled:
            self._logger.debug(
                "No authentication plugins are enabled. The client identity "
                "will be extracted from the client certificate."
            )
            try:
                client_identity = auth.get_client_identity_from_certificate(
                    certificate
                )
            except Exception as e:
                self._logger.warning("Client identity extraction failed.")
                self._logger.exception(e)
            else:
                self._logger.debug(
                    "Extraction succeeded for client identity: {}".format(
                        client_identity
                    )
                )
                return tuple([client_identity, None])

        raise exceptions.PermissionDenied("Authentication failed.")

    def _receive_request(self):
        header = self._receive_bytes(8)
        message_size = struct.unpack('!I', header[4:])[0]

        payload = self._receive_bytes(message_size)
        data = utils.BytearrayStream(header + payload)

        return data

    def _receive_bytes(self, message_size):
        bytes_received = 0
        message = b''

        while bytes_received < message_size:
            partial_message = self._connection.recv(
                min(message_size - bytes_received, self._max_buffer_size)
            )

            if partial_message is None:
                break
            elif len(partial_message) == 0:
                raise exceptions.ConnectionClosed()
            else:
                bytes_received += len(partial_message)
                message += partial_message

        if bytes_received != message_size:
            raise ValueError(
                "Invalid KMIP message received. Actual message length "
                "does not match the advertised header length."
            )
        else:
            return message

    def _send_response(self, data):
        if len(data) > 0:
            self._connection.sendall(bytes(data))


# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import six

from kmip.core import enums
from kmip.core import objects
from kmip.core import primitives
from kmip.core import utils

class RekeyRequestPayload(primitives.Struct):
    """
    A request payload for the Rekey operation.

    Attributes:
        unique_identifier: The unique ID of the symmetric key to rekey.
        offset: The interval between the initialization and activation dates
            of the replacement key.
        template_attribute: A collection of attributes that should be set on
            the replacement key.
    """
    def __init__(self,
                 unique_identifier=None,
                 offset=None,
                 template_attribute=None):
        """
        Construct a Rekey request payload struct.

        Args:
            unique_identifier (string): The ID of the symmetric key to rekey.
                Optional, defaults to None.
            offset (int): The number of seconds between the initialization and
                activation dates of the replacement key. Optional, defaults to
                None.
            template_attribute (TemplateAttribute): A structure containing a
                set of attributes (e.g., cryptographic algorithm,
                cryptographic length) that should be set on the replacement
                key. Optional, defaults to None.
        """
        super(RekeyRequestPayload, self).__init__(
            enums.Tags.REQUEST_PAYLOAD
        )

        self._unique_identifier = None
        self._offset = None
        self._template_attribute = None

        self.unique_identifier = unique_identifier
        self.offset = offset
        self.template_attribute = template_attribute

    @property
    def unique_identifier(self):
        if self._unique_identifier:
            return self._unique_identifier.value
        else:
            return None

    @unique_identifier.setter
    def unique_identifier(self, value):
        if value is None:
            self._unique_identifier = None
        elif isinstance(value, six.string_types):
            self._unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Unique identifier must be a string.")

    @property
    def offset(self):
        if self._offset:
            return self._offset.value
        else:
            return None

    @offset.setter
    def offset(self, value):
        if value is None:
            self._offset = None
        elif isinstance(value, six.integer_types):
            self._offset = primitives.Interval(
                value=value,
                tag=enums.Tags.OFFSET
            )
        else:
            raise TypeError("Offset must be an integer.")

    @property
    def template_attribute(self):
        if self._template_attribute:
            return self._template_attribute
        else:
            return None

    @template_attribute.setter
    def template_attribute(self, value):
        if value is None:
            self._template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            self._template_attribute = value
        else:
            raise TypeError(
                "Template attribute must be a TemplateAttribute struct."
            )

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Rekey request payload and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(RekeyRequestPayload, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_stream):
            self._unique_identifier = primitives.TextString(
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
            self._unique_identifier.read(
                local_stream,
                kmip_version=kmip_version
            )

        if self.is_tag_next(enums.Tags.OFFSET, local_stream):
            self._offset = primitives.Interval(
                tag=enums.Tags.OFFSET
            )
            self._offset.read(local_stream, kmip_version=kmip_version)

        if self.is_tag_next(enums.Tags.TEMPLATE_ATTRIBUTE, local_stream):
            self._template_attribute = objects.TemplateAttribute()
            self._template_attribute.read(
                local_stream,
                kmip_version=kmip_version
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Rekey request payload to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        local_stream = utils.BytearrayStream()

        if self._unique_identifier is not None:
            self._unique_identifier.write(
                local_stream,
                kmip_version=kmip_version
            )
        if self._offset is not None:
            self._offset.write(local_stream, kmip_version=kmip_version)
        if self._template_attribute is not None:
            self._template_attribute.write(
                local_stream,
                kmip_version=kmip_version
            )

        self.length = local_stream.length()
        super(RekeyRequestPayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, RekeyRequestPayload):
            if self.unique_identifier != other.unique_identifier:
                return False
            elif self.offset != other.offset:
                return False
            elif self.template_attribute != other.template_attribute:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, RekeyRequestPayload):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = ", ".join([
            "unique_identifier='{0}'".format(self.unique_identifier),
            "offset={0}".format(self.offset),
            "template_attribute={0}".format(repr(self.template_attribute))
        ])
        return "RekeyRequestPayload({0})".format(args)

    def __str__(self):
        return str({
            'unique_identifier': self.unique_identifier,
            'offset': self.offset,
            'template_attribute': str(self.template_attribute)
        })

class RekeyResponsePayload(primitives.Struct):
    """
    A response payload for the Rekey operation.

    Attributes:
        unique_identifier: The unique ID of the replacement key.
        template_attribute: A collection of server attributes that were set on
            the replacement key.
    """
    def __init__(self,
                 unique_identifier=None,
                 template_attribute=None):
        """
        Construct a Rekey response payload struct.

        Args:
            unique_identifier (string): The ID of the replacement key.
                Optional, defaults to None. Required for read/write.
            template_attribute (TemplateAttribute): A structure containing a
                set of attributes (e.g., cryptographic algorithm,
                cryptographic length) that were set by the server on the
                replacement key. Optional, defaults to None.
        """
        super(RekeyResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._unique_identifier = None
        self._template_attribute = None

        self.unique_identifier = unique_identifier
        self.template_attribute = template_attribute

    @property
    def unique_identifier(self):
        if self._unique_identifier:
            return self._unique_identifier.value
        else:
            return None

    @unique_identifier.setter
    def unique_identifier(self, value):
        if value is None:
            self._unique_identifier = None
        elif isinstance(value, six.string_types):
            self._unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Unique identifier must be a string.")

    @property
    def template_attribute(self):
        if self._template_attribute:
            return self._template_attribute
        else:
            return None

    @template_attribute.setter
    def template_attribute(self, value):
        if value is None:
            self._template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            self._template_attribute = value
        else:
            raise TypeError(
                "Template attribute must be a TemplateAttribute struct."
            )

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Rekey response payload and decode it into
        its constituent parts.

        Args:
            input_stream (stream): A data stream containing encoded object
                data, supporting a read method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the unique identifier attribute is missing
                from the encoded payload.
        """
        super(RekeyResponsePayload, self).read(
            input_stream,
            kmip_version=kmip_version
        )
        local_stream = utils.BytearrayStream(input_stream.read(self.length))

        if self.is_tag_next(enums.Tags.UNIQUE_IDENTIFIER, local_stream):
            self._unique_identifier = primitives.TextString(
                tag=enums.Tags.UNIQUE_IDENTIFIER
            )
            self._unique_identifier.read(
                local_stream,
                kmip_version=kmip_version
            )
        else:
            raise ValueError(
                "The Rekey response payload encoding is missing the unique "
                "identifier."
            )

        if self.is_tag_next(enums.Tags.TEMPLATE_ATTRIBUTE, local_stream):
            self._template_attribute = objects.TemplateAttribute()
            self._template_attribute.read(
                local_stream,
                kmip_version=kmip_version
            )

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Rekey request payload to a stream.

        Args:
            output_stream (stream): A data stream in which to encode object
                data, supporting a write method; usually a BytearrayStream
                object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the payload is missing the unique identifier.
        """
        local_stream = utils.BytearrayStream()

        if self._unique_identifier is not None:
            self._unique_identifier.write(
                local_stream,
                kmip_version=kmip_version
            )
        else:
            raise ValueError(
                "The Rekey response payload is missing the unique identifier."
            )
        if self._template_attribute is not None:
            self._template_attribute.write(
                local_stream,
                kmip_version=kmip_version
            )

        self.length = local_stream.length()
        super(RekeyResponsePayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, RekeyResponsePayload):
            if self.unique_identifier != other.unique_identifier:
                return False
            elif self.template_attribute != other.template_attribute:
                return False
            else:
                return True
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, RekeyResponsePayload):
            return not (self == other)
        else:
            return NotImplemented

    def __repr__(self):
        args = ", ".join([
            "unique_identifier='{0}'".format(self.unique_identifier),
            "template_attribute={0}".format(repr(self.template_attribute))
        ])
        return "RekeyResponsePayload({0})".format(args)

    def __str__(self):
        return str({
            'unique_identifier': self.unique_identifier,
            'template_attribute': str(self.template_attribute)
        })


# Copyright (c) 2015 Hewlett Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from kmip.core import attributes
from kmip.core import enums

from kmip.core.primitives import Struct

from kmip.core.utils import BytearrayStream

class ActivateRequestPayload(Struct):
    """
    A request payload for the Activate operation.

    The payload contains a UUID of a cryptographic object that that server
    should activate. See Section 4.19 of the KMIP 1.1 specification for more
    information.

    Attributes:
        unique_identifier: The UUID of a managed cryptographic object
    """
    def __init__(self,
                 unique_identifier=None):
        """
        Construct a ActivateRequestPayload object.
        Args:
            unique_identifier (UniqueIdentifier): The UUID of a managed
                cryptographic object.
        """
        super(ActivateRequestPayload, self).__init__(
            tag=enums.Tags.REQUEST_PAYLOAD)
        self.unique_identifier = unique_identifier
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the ActivateRequestPayload object and decode it
        into its constituent parts.
        Args:
            istream (Stream): A data stream containing encoded object data,
                supporting a read method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(ActivateRequestPayload, self).read(
            istream,
            kmip_version=kmip_version
        )
        tstream = BytearrayStream(istream.read(self.length))

        self.unique_identifier = attributes.UniqueIdentifier()
        self.unique_identifier.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the ActivateRequestPayload object to a stream.
        Args:
            ostream (Stream): A data stream in which to encode object data,
                supporting a write method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        tstream = BytearrayStream()

        # Write the contents of the request payload
        if self.unique_identifier is not None:
            self.unique_identifier.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the request payload
        self.length = tstream.length()
        super(ActivateRequestPayload, self).write(
            ostream,
            kmip_version=kmip_version
        )
        ostream.write(tstream.buffer)

    def validate(self):
        """
        Error check the attributes of the ActivateRequestPayload object.
        """
        if self.unique_identifier is not None:
            if not isinstance(self.unique_identifier,
                              attributes.UniqueIdentifier):
                msg = "invalid unique identifier"
                raise TypeError(msg)

class ActivateResponsePayload(Struct):
    """
    A response payload for the Activate operation.

    The payload contains the server response to the initial Activate request.
    See Section 4.19 of the KMIP 1.1 specification for more information.

    Attributes:
        unique_identifier: The UUID of a managed cryptographic object.
    """
    def __init__(self,
                 unique_identifier=None):
        """
        Construct a ActivateResponsePayload object.

        Args:
            unique_identifier (UniqueIdentifier): The UUID of a managed
                cryptographic object.
        """
        super(ActivateResponsePayload, self).__init__(
            tag=enums.Tags.RESPONSE_PAYLOAD)
        if unique_identifier is None:
            self.unique_identifier = attributes.UniqueIdentifier()
        else:
            self.unique_identifier = unique_identifier
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the ActivateResponsePayload object and decode it
        into its constituent parts.

        Args:
            istream (Stream): A data stream containing encoded object data,
                supporting a read method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(ActivateResponsePayload, self).read(
            istream,
            kmip_version=kmip_version
        )
        tstream = BytearrayStream(istream.read(self.length))

        self.unique_identifier = attributes.UniqueIdentifier()
        self.unique_identifier.read(tstream, kmip_version=kmip_version)

        self.is_oversized(tstream)
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the ActivateResponsePayload object to a stream.

        Args:
            ostream (Stream): A data stream in which to encode object data,
                supporting a write method; usually a BytearrayStream object.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        tstream = BytearrayStream()

        # Write the contents of the response payload
        self.unique_identifier.write(tstream, kmip_version=kmip_version)

        # Write the length and value of the request payload
        self.length = tstream.length()
        super(ActivateResponsePayload, self).write(
            ostream,
            kmip_version=kmip_version
        )
        ostream.write(tstream.buffer)

    def validate(self):
        """
        Error check the attributes of the ActivateRequestPayload object.
        """
        if not isinstance(self.unique_identifier, attributes.UniqueIdentifier):
            msg = "invalid unique identifier"
            raise TypeError(msg)