# 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 exceptions
from kmip.core import objects
from kmip.core import primitives
from kmip.core import utils

class CreateKeyPairRequestPayload(primitives.Struct):
    """
    A request payload for the CreateKeyPair operation.

    Attributes:
        common_template_attribute: A group of attributes to set on the new
            public and private keys.
        private_key_template_attribute: A group of attributes to set on the new
            private key.
        public_key_template_attribute: A group of attributes to set on the new
            public key.
    """

    def __init__(self,
                 common_template_attribute=None,
                 private_key_template_attribute=None,
                 public_key_template_attribute=None):
        """
        Construct a CreateKeyPair request payload structure.

        Args:
            common_template_attribute (TemplateAttribute): A TemplateAttribute
                structure with the CommonTemplateAttribute tag containing a set
                of attributes to set on the new public and private keys.
                Optional, defaults to None.
            private_key_template_attribute (TemplateAttribute): A
                TemplateAttribute structure with the
                PrivateKeyTemplateAttribute tag containing a set of attributes
                to set on the new private key. Optional, defaults to None.
            public_key_template_attribute (TemplateAttribute): A
                TemplateAttribute structure with the PublicKeyTemplateAttribute
                tag containing a set of attributes to set on the new public
                key. Optional, defaults to None.
        """
        super(CreateKeyPairRequestPayload, self).__init__(
            enums.Tags.REQUEST_PAYLOAD
        )

        self._common_template_attribute = None
        self._private_key_template_attribute = None
        self._public_key_template_attribute = None

        self.common_template_attribute = common_template_attribute
        self.private_key_template_attribute = private_key_template_attribute
        self.public_key_template_attribute = public_key_template_attribute

    @property
    def common_template_attribute(self):
        return self._common_template_attribute

    @common_template_attribute.setter
    def common_template_attribute(self, value):
        if value is None:
            self._common_template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            if value.tag == enums.Tags.COMMON_TEMPLATE_ATTRIBUTE:
                self._common_template_attribute = value
            else:
                raise TypeError(
                    "Common template attribute must be a TemplateAttribute "
                    "structure with a CommonTemplateAttribute tag."
                )
        else:
            raise TypeError(
                "Common template attribute must be a TemplateAttribute "
                "structure."
            )

    @property
    def private_key_template_attribute(self):
        return self._private_key_template_attribute

    @private_key_template_attribute.setter
    def private_key_template_attribute(self, value):
        if value is None:
            self._private_key_template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            if value.tag == enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE:
                self._private_key_template_attribute = value
            else:
                raise TypeError(
                    "Private key template attribute must be a "
                    "TemplateAttribute structure with a "
                    "PrivateKeyTemplateAttribute tag."
                )
        else:
            raise TypeError(
                "Private key template attribute must be a TemplateAttribute "
                "structure."
            )

    @property
    def public_key_template_attribute(self):
        return self._public_key_template_attribute

    @public_key_template_attribute.setter
    def public_key_template_attribute(self, value):
        if value is None:
            self._public_key_template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            if value.tag == enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE:
                self._public_key_template_attribute = value
            else:
                raise TypeError(
                    "Public key template attribute must be a "
                    "TemplateAttribute structure with a "
                    "PublicKeyTemplateAttribute tag."
                )
        else:
            raise TypeError(
                "Public key template attribute must be a TemplateAttribute "
                "structure."
            )

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

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

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(
                    enums.Tags.COMMON_TEMPLATE_ATTRIBUTE,
                    local_buffer
            ):
                self._common_template_attribute = objects.TemplateAttribute(
                    tag=enums.Tags.COMMON_TEMPLATE_ATTRIBUTE
                )
                self._common_template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self.is_tag_next(enums.Tags.COMMON_ATTRIBUTES, local_buffer):
                attributes = objects.Attributes(
                    tag=enums.Tags.COMMON_ATTRIBUTES
                )
                attributes.read(local_buffer, kmip_version=kmip_version)
                self._common_template_attribute = \
                    objects.convert_attributes_to_template_attribute(
                        attributes
                    )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(
                    enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE,
                    local_buffer
            ):
                self._private_key_template_attribute = \
                    objects.TemplateAttribute(
                        tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE
                    )
                self._private_key_template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self.is_tag_next(
                    enums.Tags.PRIVATE_KEY_ATTRIBUTES,
                    local_buffer
            ):
                attributes = objects.Attributes(
                    tag=enums.Tags.PRIVATE_KEY_ATTRIBUTES
                )
                attributes.read(local_buffer, kmip_version=kmip_version)
                self._private_key_template_attribute = \
                    objects.convert_attributes_to_template_attribute(
                        attributes
                    )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(
                    enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE,
                    local_buffer
            ):
                self._public_key_template_attribute = \
                    objects.TemplateAttribute(
                        tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE
                    )
                self._public_key_template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self.is_tag_next(
                    enums.Tags.PUBLIC_KEY_ATTRIBUTES,
                    local_buffer
            ):
                attributes = objects.Attributes(
                    tag=enums.Tags.PUBLIC_KEY_ATTRIBUTES
                )
                attributes.read(local_buffer, kmip_version=kmip_version)
                self._public_key_template_attribute = \
                    objects.convert_attributes_to_template_attribute(
                        attributes
                    )

        self.is_oversized(local_buffer)

    def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the CreateKeyPair request payload to a buffer.

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

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self._common_template_attribute is not None:
                self._common_template_attribute.write(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self._common_template_attribute is not None:
                attributes = objects.convert_template_attribute_to_attributes(
                    self._common_template_attribute
                )
                attributes.write(local_buffer, kmip_version=kmip_version)

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self._private_key_template_attribute is not None:
                self._private_key_template_attribute.write(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self._private_key_template_attribute is not None:
                attributes = objects.convert_template_attribute_to_attributes(
                    self._private_key_template_attribute
                )
                attributes.write(local_buffer, kmip_version=kmip_version)

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self._public_key_template_attribute is not None:
                self._public_key_template_attribute.write(
                    local_buffer,
                    kmip_version=kmip_version
                )
        else:
            if self._public_key_template_attribute is not None:
                attributes = objects.convert_template_attribute_to_attributes(
                    self._public_key_template_attribute
                )
                attributes.write(local_buffer, kmip_version=kmip_version)

        self.length = local_buffer.length()
        super(CreateKeyPairRequestPayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

    def __eq__(self, other):
        if isinstance(other, CreateKeyPairRequestPayload):
            if self.common_template_attribute != \
                    other.common_template_attribute:
                return False
            elif self.private_key_template_attribute != \
                    other.private_key_template_attribute:
                return False
            elif self.public_key_template_attribute != \
                    other.public_key_template_attribute:
                return False
            else:
                return True
        else:
            return NotImplemented

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

    def __repr__(self):
        args = ", ".join([
            "common_template_attribute={}".format(
                self.common_template_attribute
            ),
            "private_key_template_attribute={}".format(
                self.private_key_template_attribute
            ),
            "public_key_template_attribute={}".format(
                self.public_key_template_attribute
            )
        ])
        return "CreateKeyPairRequestPayload({})".format(args)

    def __str__(self):
        value = ", ".join(
            [
                '"common_template_attribute": {}'.format(
                    self.common_template_attribute
                ),
                '"private_key_template_attribute": {}'.format(
                    self.private_key_template_attribute
                ),
                '"public_key_template_attribute": {}'.format(
                    self.public_key_template_attribute
                )
            ]
        )
        return '{' + value + '}'

class CreateKeyPairResponsePayload(primitives.Struct):
    """
    A response payload for the CreateKeyPair operation.

    Attributes:
        private_key_unique_identifier: The ID of the new private key.
        public_key_unique_identifier: The ID of the new public key.
        private_key_template_attribute: A group of attributes to set on the new
            private key.
        public_key_template_attribute: A group of attributes to set on the new
            public key.
    """

    def __init__(self,
                 private_key_unique_identifier=None,
                 public_key_unique_identifier=None,
                 private_key_template_attribute=None,
                 public_key_template_attribute=None):
        """
        Construct a CreateKeyPair response payload structure.

        Args:
            private_key_unique_identifier (string): A string specifying the
                ID of the new private key. Optional, defaults to None. Required
                for read/write.
            public_key_unique_identifier (string): A string specifying the
                ID of the new public key. Optional, defaults to None. Required
                for read/write.
            private_key_template_attribute (TemplateAttribute): A
                TemplateAttribute structure with the
                PrivateKeyTemplateAttribute tag containing the set of
                attributes that were set on the new private key. Optional,
                defaults to None.
            public_key_template_attribute (TemplateAttribute): A
                TemplateAttribute structure with the PublicKeyTemplateAttribute
                tag containing the set of attributes that were set on the new
                public key. Optional, defaults to None.
        """
        super(CreateKeyPairResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._private_key_unique_identifier = None
        self._public_key_unique_identifier = None
        self._private_key_template_attribute = None
        self._public_key_template_attribute = None

        self.private_key_unique_identifier = private_key_unique_identifier
        self.public_key_unique_identifier = public_key_unique_identifier
        self.private_key_template_attribute = private_key_template_attribute
        self.public_key_template_attribute = public_key_template_attribute

    @property
    def private_key_unique_identifier(self):
        if self._private_key_unique_identifier:
            return self._private_key_unique_identifier.value
        else:
            return None

    @private_key_unique_identifier.setter
    def private_key_unique_identifier(self, value):
        if value is None:
            self._private_key_unique_identifier = None
        elif isinstance(value, six.string_types):
            self._private_key_unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.PRIVATE_KEY_UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Private key unique identifier must be a string.")

    @property
    def public_key_unique_identifier(self):
        if self._public_key_unique_identifier:
            return self._public_key_unique_identifier.value
        else:
            return None

    @public_key_unique_identifier.setter
    def public_key_unique_identifier(self, value):
        if value is None:
            self._public_key_unique_identifier = None
        elif isinstance(value, six.string_types):
            self._public_key_unique_identifier = primitives.TextString(
                value=value,
                tag=enums.Tags.PUBLIC_KEY_UNIQUE_IDENTIFIER
            )
        else:
            raise TypeError("Public key unique identifier must be a string.")

    @property
    def private_key_template_attribute(self):
        return self._private_key_template_attribute

    @private_key_template_attribute.setter
    def private_key_template_attribute(self, value):
        if value is None:
            self._private_key_template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            if value.tag == enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE:
                self._private_key_template_attribute = value
            else:
                raise TypeError(
                    "Private key template attribute must be a "
                    "TemplateAttribute structure with a "
                    "PrivateKeyTemplateAttribute tag."
                )
        else:
            raise TypeError(
                "Private key template attribute must be a TemplateAttribute "
                "structure."
            )

    @property
    def public_key_template_attribute(self):
        return self._public_key_template_attribute

    @public_key_template_attribute.setter
    def public_key_template_attribute(self, value):
        if value is None:
            self._public_key_template_attribute = None
        elif isinstance(value, objects.TemplateAttribute):
            if value.tag == enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE:
                self._public_key_template_attribute = value
            else:
                raise TypeError(
                    "Public key template attribute must be a "
                    "TemplateAttribute structure with a "
                    "PublicKeyTemplateAttribute tag."
                )
        else:
            raise TypeError(
                "Public key template attribute must be a TemplateAttribute "
                "structure."
            )

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

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

        Raises:
            InvalidKmipEncoding: Raised if the private key unique identifier or
                the public key unique identifier is missing from the encoded
                payload.
        """
        super(CreateKeyPairResponsePayload, self).read(
            input_buffer,
            kmip_version=kmip_version
        )
        local_buffer = utils.BytearrayStream(input_buffer.read(self.length))

        if self.is_tag_next(
                enums.Tags.PRIVATE_KEY_UNIQUE_IDENTIFIER,
                local_buffer
        ):
            self._private_key_unique_identifier = primitives.TextString(
                tag=enums.Tags.PRIVATE_KEY_UNIQUE_IDENTIFIER
            )
            self._private_key_unique_identifier.read(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidKmipEncoding(
                "The CreateKeyPair response payload encoding is missing the "
                "private key unique identifier."
            )

        if self.is_tag_next(
                enums.Tags.PUBLIC_KEY_UNIQUE_IDENTIFIER,
                local_buffer
        ):
            self._public_key_unique_identifier = primitives.TextString(
                tag=enums.Tags.PUBLIC_KEY_UNIQUE_IDENTIFIER
            )
            self._public_key_unique_identifier.read(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidKmipEncoding(
                "The CreateKeyPair response payload encoding is missing the "
                "public key unique identifier."
            )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(
                    enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE,
                    local_buffer
            ):
                self._private_key_template_attribute = \
                    objects.TemplateAttribute(
                        tag=enums.Tags.PRIVATE_KEY_TEMPLATE_ATTRIBUTE
                    )
                self._private_key_template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )

            if self.is_tag_next(
                    enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE,
                    local_buffer
            ):
                self._public_key_template_attribute = \
                    objects.TemplateAttribute(
                        tag=enums.Tags.PUBLIC_KEY_TEMPLATE_ATTRIBUTE
                    )
                self._public_key_template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )

        self.is_oversized(local_buffer)

    def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the CreateKeyPair response payload to a buffer.

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

        Raises:
            InvalidField: Raised if the private key unique identifier or the
                public key unique identifier is not defined.
        """
        local_buffer = utils.BytearrayStream()

        if self._private_key_unique_identifier:
            self._private_key_unique_identifier.write(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidField(
                "The CreateKeyPair response payload is missing the private "
                "key unique identifier field."
            )

        if self._public_key_unique_identifier:
            self._public_key_unique_identifier.write(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidField(
                "The CreateKeyPair response payload is missing the public "
                "key unique identifier field."
            )

        if self._private_key_template_attribute:
            self._private_key_template_attribute.write(
                local_buffer,
                kmip_version=kmip_version
            )

        if self._public_key_template_attribute:
            self._public_key_template_attribute.write(
                local_buffer,
                kmip_version=kmip_version
            )

        self.length = local_buffer.length()
        super(CreateKeyPairResponsePayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

    def __eq__(self, other):
        if isinstance(other, CreateKeyPairResponsePayload):
            if self.private_key_unique_identifier != \
                    other.private_key_unique_identifier:
                return False
            elif self.public_key_unique_identifier != \
                    other.public_key_unique_identifier:
                return False
            elif self.private_key_template_attribute != \
                    other.private_key_template_attribute:
                return False
            elif self.public_key_template_attribute != \
                    other.public_key_template_attribute:
                return False
            else:
                return True
        else:
            return NotImplemented

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

    def __repr__(self):
        args = ", ".join([
            "private_key_unique_identifier='{}'".format(
                self.private_key_unique_identifier
            ),
            "public_key_unique_identifier='{}'".format(
                self.public_key_unique_identifier
            ),
            "private_key_template_attribute={}".format(
                self.private_key_template_attribute
            ),
            "public_key_template_attribute={}".format(
                self.public_key_template_attribute
            )
        ])
        return "CreateKeyPairResponsePayload({})".format(args)

    def __str__(self):
        value = ", ".join(
            [
                '"private_key_unique_identifier": "{}"'.format(
                    self.private_key_unique_identifier
                ),
                '"public_key_unique_identifier": "{}"'.format(
                    self.public_key_unique_identifier
                ),
                '"private_key_template_attribute": {}'.format(
                    self.private_key_template_attribute
                ),
                '"public_key_template_attribute": {}'.format(
                    self.public_key_template_attribute
                )
            ]
        )
        return '{' + value + '}'


# Copyright (c) 2015 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 exceptions
from kmip.core import objects
from kmip.core import primitives
from kmip.core import utils

class GetAttributeListRequestPayload(primitives.Struct):
    """
    A request payload for the GetAttributeList operation.

    The payload can contain the ID of the managed object the attributes should
    belong too. If omitted, the server will use the ID placeholder by default.
    See Section 4.13 of the KMIP 1.1 specification for more information.

    Attributes:
        unique_identifier: The unique ID of the managed object with which the
            retrieved attributes should be associated.
    """

    def __init__(self, unique_identifier=None):
        """
        Construct a GetAttributeList request payload.

        Args:
            unique_identifier (string): The ID of the managed object with
                which the retrieved attribute names should be associated.
                Optional, defaults to None.
        """
        super(GetAttributeListRequestPayload, 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 self._unique_identifier

    @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_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the GetAttributeList request payload and decode
        it into its constituent parts.

        Args:
            input_buffer (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(GetAttributeListRequestPayload, self).read(
            input_buffer,
            kmip_version=kmip_version
        )
        local_buffer = utils.BytearrayStream(input_buffer.read(self.length))

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

        self.is_oversized(local_buffer)

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

        Args:
            output_buffer (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_buffer = utils.BytearrayStream()

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

        self.length = local_buffer.length()
        super(GetAttributeListRequestPayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

    def __repr__(self):
        uid = "unique_identifier={0}".format(self.unique_identifier)
        return "GetAttributeListRequestPayload({0})".format(uid)

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

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

    def __ne__(self, other):
        if isinstance(other, GetAttributeListRequestPayload):
            return not self.__eq__(other)
        else:
            return NotImplemented

class GetAttributeListResponsePayload(primitives.Struct):
    """
    A response payload for the GetAttributeList operation.

    The payload contains the ID of the managed object with which the
    attributes are associated, along with a list of attribute names
    identifying the types of attributes associated with the aforementioned
    managed object.

    Attributes:
        unique_identifier: The unique ID of the managed object with which the
            retrieved attributes should be associated.
        attribute_names: A list of strings identifying the names of the
            attributes associated with the managed object.
    """

    def __init__(self, unique_identifier=None, attribute_names=None):
        """
        Construct a GetAttributeList response payload.

        Args:
            unique_identifier (string): The ID of the managed object with
                which the retrieved attribute names should be associated.
                Optional, defaults to None.
            attribute_names: A list of strings identifying the names of the
                attributes associated with the managed object. Optional,
                defaults to None.
        """

        super(GetAttributeListResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._unique_identifier = None
        self._attribute_names = list()

        self.unique_identifier = unique_identifier
        self.attribute_names = attribute_names

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

    @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 attribute_names(self):
        if self._attribute_names:
            names = list()
            for attribute_name in self._attribute_names:
                names.append(attribute_name.value)
            return names
        else:
            return self._attribute_names

    @attribute_names.setter
    def attribute_names(self, value):
        if value is None:
            self._attribute_names = list()
        elif isinstance(value, list):
            names = list()
            for i in range(len(value)):
                name = value[i]
                if not isinstance(name, six.string_types):
                    raise TypeError(
                        "Attribute names must be a list of strings; "
                        "item {0} has type {1}".format(i + 1, type(name))
                    )
                if name not in names:
                    names.append(name)
            self._attribute_names = list()
            for name in names:
                self._attribute_names.append(
                    primitives.TextString(
                        value=name,
                        tag=enums.Tags.ATTRIBUTE_NAME
                    )
                )
        else:
            raise TypeError("Attribute names must be a list of strings.")

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

        Args:
            input_buffer (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:
            InvalidKmipEncoding: Raised if the unique identifier or attribute
                names are missing from the encoded payload.
        """
        super(GetAttributeListResponsePayload, self).read(
            input_buffer,
            kmip_version=kmip_version
        )
        local_buffer = utils.BytearrayStream(input_buffer.read(self.length))

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

        names = list()
        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            while self.is_tag_next(enums.Tags.ATTRIBUTE_NAME, local_buffer):
                name = primitives.TextString(tag=enums.Tags.ATTRIBUTE_NAME)
                name.read(local_buffer, kmip_version=kmip_version)
                names.append(name)
            if len(names) == 0:
                raise exceptions.InvalidKmipEncoding(
                    "The GetAttributeList response payload encoding is "
                    "missing the attribute names."
                )
            self._attribute_names = names
        else:
            while self.is_tag_next(
                    enums.Tags.ATTRIBUTE_REFERENCE,
                    local_buffer
            ):
                if self.is_type_next(enums.Types.STRUCTURE, local_buffer):
                    reference = objects.AttributeReference()
                    reference.read(local_buffer, kmip_version=kmip_version)
                    names.append(
                        primitives.TextString(
                            value=reference.attribute_name,
                            tag=enums.Tags.ATTRIBUTE_NAME
                        )
                    )
                elif self.is_type_next(enums.Types.ENUMERATION, local_buffer):
                    reference = primitives.Enumeration(
                        enums.Tags,
                        tag=enums.Tags.ATTRIBUTE_REFERENCE
                    )
                    reference.read(local_buffer, kmip_version=kmip_version)
                    name = enums.convert_attribute_tag_to_name(reference.value)
                    names.append(
                        primitives.TextString(
                            value=name,
                            tag=enums.Tags.ATTRIBUTE_NAME
                        )
                    )
                else:
                    raise exceptions.InvalidKmipEncoding(
                        "The GetAttributeList response payload encoding "
                        "contains an invalid AttributeReference type."
                    )
            self._attribute_names = names

        self.is_oversized(local_buffer)

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

        Args:
            output_buffer (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:
            InvalidField: Raised if the unique identifier or attribute name
                are not defined.
        """
        local_buffer = utils.BytearrayStream()

        if self._unique_identifier:
            self._unique_identifier.write(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidField(
                "The GetAttributeList response payload is missing the unique "
                "identifier field."
            )

        if self._attribute_names:
            if kmip_version < enums.KMIPVersion.KMIP_2_0:
                for attribute_name in self._attribute_names:
                    attribute_name.write(
                        local_buffer,
                        kmip_version=kmip_version
                    )
            else:
                # NOTE (ph) This approach simplifies backwards compatible
                #           issues but limits easy support for Attribute
                #           Reference structures going forward, specifically
                #           limiting the use of VendorIdentification for
                #           custom attributes. If custom attributes need to
                #           be retrieved using the GetAttributeList operation
                #           for KMIP 2.0 applications this code will need to
                #           change.
                for attribute_name in self._attribute_names:
                    t = enums.convert_attribute_name_to_tag(
                        attribute_name.value
                    )
                    e = primitives.Enumeration(
                        enums.Tags,
                        value=t,
                        tag=enums.Tags.ATTRIBUTE_REFERENCE
                    )
                    e.write(local_buffer, kmip_version=kmip_version)

        else:
            raise exceptions.InvalidField(
                "The GetAttributeList response payload is missing the "
                "attribute names field."
            )

        self.length = local_buffer.length()
        super(GetAttributeListResponsePayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

    def __repr__(self):
        unique_identifier = "unique_identifier={0}".format(
            self.unique_identifier
        )
        attribute_names = "attribute_names={0}".format(self.attribute_names)
        return "GetAttributeListResponsePayload({0}, {1})".format(
            unique_identifier,
            attribute_names
        )

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

    def __eq__(self, other):
        if isinstance(other, GetAttributeListResponsePayload):
            if self.unique_identifier == other.unique_identifier:
                if set(self.attribute_names) == set(other.attribute_names):
                    return True
                else:
                    return False
            else:
                return False
        else:
            return NotImplemented

    def __ne__(self, other):
        if isinstance(other, GetAttributeListResponsePayload):
            return not self.__eq__(other)
        else:
            return NotImplemented


# 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 logging
import multiprocessing
import os
import signal
import time

from kmip.core import policy as operation_policy

def get_json_files(p):
    """
    Scan the provided policy directory for all JSON policy files.
    """
    f = [os.path.join(p, x) for x in os.listdir(p) if x.endswith(".json")]
    return sorted(f)

class PolicyDirectoryMonitor(multiprocessing.Process):
    """
    A file monitor that tracks modifications made within the policy directory.
    """

    def __init__(self, policy_directory, policy_store, live_monitoring=True):
        """
        Set up the file monitor with the policy directory to track.

        Args:
            policy_directory (string): The system path of the policy directory
                that should be monitored. Required.
            policy_store (DictProxy): A dictionary proxy created by the server
                multiprocessing resource manager. Used to store and share the
                policy information across server processes and threads.
                Required.
            live_monitoring (boolean): A boolean indicating whether or not
                live monitoring should continue indefinitely. Optional,
                defaults to True.
        """
        super(PolicyDirectoryMonitor, self).__init__()

        self.halt_trigger = multiprocessing.Event()
        self.policy_directory = policy_directory
        self.live_monitoring = live_monitoring

        self.file_timestamps = None
        self.policy_cache = None
        self.policy_files = None
        self.policy_map = None
        self.policy_store = policy_store

        self.reserved_policies = ['default', 'public']

        def interrupt_handler(trigger, frame):
            self.stop()
        signal.signal(signal.SIGINT, interrupt_handler)
        signal.signal(signal.SIGTERM, interrupt_handler)

        self.logger = logging.getLogger("kmip.server.monitor")
        self.initialize_tracking_structures()

    def stop(self):
        self.halt_trigger.set()

    def scan_policies(self):
        """
        Scan the policy directory for policy data.
        """
        policy_files = get_json_files(self.policy_directory)
        for f in set(policy_files) - set(self.policy_files):
            self.file_timestamps[f] = 0
        for f in set(self.policy_files) - set(policy_files):
            self.logger.info("Removing policies for file: {}".format(f))
            self.file_timestamps.pop(f, None)
            for p in self.policy_cache.keys():
                self.disassociate_policy_and_file(p, f)
            for p in [k for k, v in self.policy_map.items() if v == f]:
                self.restore_or_delete_policy(p)
        self.policy_files = policy_files

        for f in sorted(self.file_timestamps.keys()):
            t = os.path.getmtime(f)
            if t > self.file_timestamps[f]:
                self.logger.info("Loading policies for file: {}".format(f))
                self.file_timestamps[f] = t
                old_p = [k for k, v in self.policy_map.items() if v == f]
                try:
                    new_p = operation_policy.read_policy_from_file(f)
                except ValueError:
                    self.logger.error("Failure loading file: {}".format(f))
                    self.logger.debug("", exc_info=True)
                    continue
                for p in new_p.keys():
                    self.logger.info("Loading policy: {}".format(p))
                    if p in self.reserved_policies:
                        self.logger.warning(
                            "Policy '{}' overwrites a reserved policy and "
                            "will be thrown out.".format(p)
                        )
                        continue
                    if p in sorted(self.policy_store.keys()):
                        self.logger.debug(
                            "Policy '{}' overwrites an existing "
                            "policy.".format(p)
                        )
                        if f != self.policy_map.get(p):
                            self.policy_cache.get(p).append(
                                (
                                    time.time(),
                                    self.policy_map.get(p),
                                    self.policy_store.get(p)
                                )
                            )
                    else:
                        self.policy_cache[p] = []
                    self.policy_store[p] = new_p.get(p)
                    self.policy_map[p] = f
                for p in set(old_p) - set(new_p.keys()):
                    self.disassociate_policy_and_file(p, f)
                    self.restore_or_delete_policy(p)

    def run(self):
        """
        Start monitoring operation policy files.
        """
        self.initialize_tracking_structures()

        if self.live_monitoring:
            self.logger.info("Starting up the operation policy file monitor.")
            while not self.halt_trigger.is_set():
                time.sleep(1)
                self.scan_policies()
            self.logger.info("Stopping the operation policy file monitor.")
        else:
            self.scan_policies()

    def initialize_tracking_structures(self):
        self.file_timestamps = {}
        self.policy_cache = {}
        self.policy_files = []
        self.policy_map = {}

        for k in self.policy_store.keys():
            if k not in self.reserved_policies:
                self.policy_store.pop(k, None)

    def disassociate_policy_and_file(self, policy, file_name):
        c = self.policy_cache.get(policy, [])
        for i in [c.index(e) for e in c if e[1] == file_name][::-1]:
            c.pop(i)

    def restore_or_delete_policy(self, policy):
        c = self.policy_cache.get(policy, [])
        if len(c) == 0:
            self.logger.info("Removing policy: {}".format(policy))
            self.policy_store.pop(policy, None)
            self.policy_map.pop(policy, None)
            self.policy_cache.pop(policy, None)
        else:
            e = c.pop()
            self.policy_store[policy] = e[2]
            self.policy_map[policy] = e[1]


# 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.

from cryptography import x509
from cryptography.hazmat import backends

from kmip.core import exceptions

def get_certificate_from_connection(connection):
    """
    Extract an X.509 certificate from a socket connection.
    """
    certificate = connection.getpeercert(binary_form=True)
    if certificate:
        return x509.load_der_x509_certificate(
            certificate,
            backends.default_backend()
        )
    return None

def get_extended_key_usage_from_certificate(certificate):
    """
    Given an X.509 certificate, extract and return the extendedKeyUsage
    extension.
    """
    try:
        return certificate.extensions.get_extension_for_oid(
            x509.oid.ExtensionOID.EXTENDED_KEY_USAGE
        ).value
    except x509.ExtensionNotFound:
        return None

def get_common_names_from_certificate(certificate):
    """
    Given an X.509 certificate, extract and return all common names.
    """

    common_names = certificate.subject.get_attributes_for_oid(
        x509.oid.NameOID.COMMON_NAME
    )
    return [common_name.value for common_name in common_names]

def get_client_identity_from_certificate(certificate):
    """
    Given an X.509 certificate, extract and return the client identity.
    """
    client_ids = get_common_names_from_certificate(certificate)

    if len(client_ids) > 0:
        if len(client_ids) > 1:
            raise exceptions.PermissionDenied(
                "Multiple client identities found."
            )
        return client_ids[0]
    else:
        raise exceptions.PermissionDenied(
            "The certificate does not define any subject common names. "
            "Client identity unavailable."
        )


# 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 exceptions
from kmip.core import objects
from kmip.core import primitives
from kmip.core import utils

class CreateRequestPayload(primitives.Struct):
    """
    A request payload for the Create operation.

    Attributes:
        object_type: The type of the object to create.
        template_attribute: A group of attributes to set on the new object.
    """

    def __init__(self,
                 object_type=None,
                 template_attribute=None):
        """
        Construct a Create request payload structure.

        Args:
            object_type (enum): An ObjectType enumeration specifying the type
                of object to create. Optional, defaults to None. Required for
                read/write.
            template_attribute (TemplateAttribute): A TemplateAttribute
                structure containing a set of attributes to set on the new
                object. Optional, defaults to None. Required for read/write.
        """
        super(CreateRequestPayload, self).__init__(
            tag=enums.Tags.REQUEST_PAYLOAD
        )

        self._object_type = None
        self._template_attribute = None

        self.object_type = object_type
        self.template_attribute = template_attribute

    @property
    def object_type(self):
        if self._object_type:
            return self._object_type.value
        else:
            return None

    @object_type.setter
    def object_type(self, value):
        if value is None:
            self._object_type = None
        elif isinstance(value, enums.ObjectType):
            self._object_type = primitives.Enumeration(
                enums.ObjectType,
                value=value,
                tag=enums.Tags.OBJECT_TYPE
            )
        else:
            raise TypeError(
                "Object type must be an ObjectType enumeration."
            )

    @property
    def template_attribute(self):
        return self._template_attribute

    @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 structure."
            )

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

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

        Raises:
            InvalidKmipEncoding: Raised if the object type or template
                attribute is missing from the encoded payload.
        """
        super(CreateRequestPayload, self).read(
            input_buffer,
            kmip_version=kmip_version
        )
        local_buffer = utils.BytearrayStream(input_buffer.read(self.length))

        if self.is_tag_next(enums.Tags.OBJECT_TYPE, local_buffer):
            self._object_type = primitives.Enumeration(
                enums.ObjectType,
                tag=enums.Tags.OBJECT_TYPE
            )
            self._object_type.read(local_buffer, kmip_version=kmip_version)
        else:
            raise exceptions.InvalidKmipEncoding(
                "The Create request payload encoding is missing the object "
                "type."
            )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(enums.Tags.TEMPLATE_ATTRIBUTE, local_buffer):
                self._template_attribute = objects.TemplateAttribute()
                self._template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )
            else:
                raise exceptions.InvalidKmipEncoding(
                    "The Create request payload encoding is missing the "
                    "template attribute."
                )
        else:
            # NOTE (ph) For now, leave attributes natively in TemplateAttribute
            # form and just convert to the KMIP 2.0 Attributes form as needed
            # for encoding/decoding purposes. Changing the payload to require
            # the new Attributes structure will trigger a bunch of second-order
            # effects across the client and server codebases that is beyond
            # the scope of updating the Create payloads to support KMIP 2.0.
            if self.is_tag_next(enums.Tags.ATTRIBUTES, local_buffer):
                attributes = objects.Attributes()
                attributes.read(local_buffer, kmip_version=kmip_version)
                value = objects.convert_attributes_to_template_attribute(
                    attributes
                )
                self._template_attribute = value
            else:
                raise exceptions.InvalidKmipEncoding(
                    "The Create request payload encoding is missing the "
                    "attributes structure."
                )

        self.is_oversized(local_buffer)

    def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Create request payload to a buffer.

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

        Raises:
            InvalidField: Raised if the object type attribute or template
                attribute is not defined.
        """
        local_buffer = utils.BytearrayStream()

        if self._object_type:
            self._object_type.write(local_buffer, kmip_version=kmip_version)
        else:
            raise exceptions.InvalidField(
                "The Create request payload is missing the object type field."
            )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self._template_attribute:
                self._template_attribute.write(
                    local_buffer,
                    kmip_version=kmip_version
                )
            else:
                raise exceptions.InvalidField(
                    "The Create request payload is missing the template "
                    "attribute field."
                )
        else:
            # NOTE (ph) For now, leave attributes natively in TemplateAttribute
            # form and just convert to the KMIP 2.0 Attributes form as needed
            # for encoding/decoding purposes. Changing the payload to require
            # the new Attributes structure will trigger a bunch of second-order
            # effects across the client and server codebases that is beyond
            # the scope of updating the Create payloads to support KMIP 2.0.
            if self._template_attribute:
                attributes = objects.convert_template_attribute_to_attributes(
                    self._template_attribute
                )
                attributes.write(local_buffer, kmip_version=kmip_version)
            else:
                raise exceptions.InvalidField(
                    "The Create request payload is missing the template "
                    "attribute field."
                )

        self.length = local_buffer.length()
        super(CreateRequestPayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

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

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

    def __repr__(self):
        args = ", ".join([
            "object_type={}".format(self.object_type),
            "template_attribute={}".format(repr(self.template_attribute))
        ])
        return "CreateRequestPayload({})".format(args)

    def __str__(self):
        value = ", ".join(
            [
                '"object_type": {}'.format(self.object_type),
                '"template_attribute": {}'.format(self.template_attribute)
            ]
        )
        return '{' + value + '}'

class CreateResponsePayload(primitives.Struct):
    """
    A response payload for the Create operation.

    Attributes:
        object_type: The type of the object created.
        unique_identifier: The unique ID of the new object.
        template_attribute: A group of attributes that were set on the new
            object.
    """

    def __init__(self,
                 object_type=None,
                 unique_identifier=None,
                 template_attribute=None):
        """
        Construct a Create response payload structure.

        Args:
            object_type (enum): An ObjectType enumeration specifying the type
                of object created. Optional, defaults to None. Required for
                read/write.
            unique_identifier (string): The ID of the new object. Optional,
                defaults to None. Required for read/write.
            template_attribute (TemplateAttribute): A TemplateAttribute
                structure containing a set of attributes that were set on the
                new object. Optional, defaults to None.
        """
        super(CreateResponsePayload, self).__init__(
            tag=enums.Tags.RESPONSE_PAYLOAD
        )

        self._object_type = None
        self._unique_identifier = None
        self._template_attribute = None

        self.object_type = object_type
        self.unique_identifier = unique_identifier
        self.template_attribute = template_attribute

    @property
    def object_type(self):
        if self._object_type:
            return self._object_type.value
        else:
            return None

    @object_type.setter
    def object_type(self, value):
        if value is None:
            self._object_type = None
        elif isinstance(value, enums.ObjectType):
            self._object_type = primitives.Enumeration(
                enums.ObjectType,
                value=value,
                tag=enums.Tags.OBJECT_TYPE
            )
        else:
            raise TypeError(
                "Object type must be an ObjectType enumeration."
            )

    @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):
        return self._template_attribute

    @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 structure."
            )

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

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

        Raises:
            InvalidKmipEncoding: Raised if the object type or unique
                identifier is missing from the encoded payload.
        """
        super(CreateResponsePayload, self).read(
            input_buffer,
            kmip_version=kmip_version
        )
        local_buffer = utils.BytearrayStream(input_buffer.read(self.length))

        if self.is_tag_next(enums.Tags.OBJECT_TYPE, local_buffer):
            self._object_type = primitives.Enumeration(
                enums.ObjectType,
                tag=enums.Tags.OBJECT_TYPE
            )
            self._object_type.read(local_buffer, kmip_version=kmip_version)
        else:
            raise exceptions.InvalidKmipEncoding(
                "The Create response payload encoding is missing the object "
                "type."
            )

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

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self.is_tag_next(enums.Tags.TEMPLATE_ATTRIBUTE, local_buffer):
                self._template_attribute = objects.TemplateAttribute()
                self._template_attribute.read(
                    local_buffer,
                    kmip_version=kmip_version
                )

        self.is_oversized(local_buffer)

    def write(self, output_buffer, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Create response payload to a buffer.

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

        Raises:
            InvalidField: Raised if the object type attribute or unique
                identifier is not defined.
        """
        local_buffer = utils.BytearrayStream()

        if self._object_type:
            self._object_type.write(local_buffer, kmip_version=kmip_version)
        else:
            raise exceptions.InvalidField(
                "The Create response payload is missing the object type field."
            )

        if self._unique_identifier:
            self._unique_identifier.write(
                local_buffer,
                kmip_version=kmip_version
            )
        else:
            raise exceptions.InvalidField(
                "The Create response payload is missing the unique identifier "
                "field."
            )

        if kmip_version < enums.KMIPVersion.KMIP_2_0:
            if self._template_attribute:
                self._template_attribute.write(
                    local_buffer,
                    kmip_version=kmip_version
                )

        self.length = local_buffer.length()
        super(CreateResponsePayload, self).write(
            output_buffer,
            kmip_version=kmip_version
        )
        output_buffer.write(local_buffer.buffer)

    def __eq__(self, other):
        if isinstance(other, CreateResponsePayload):
            if self.object_type != other.object_type:
                return False
            elif 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, CreateResponsePayload):
            return not (self == other)
        else:
            return NotImplemented

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

    def __str__(self):
        value = ", ".join(
            [
                '"object_type": {}'.format(self.object_type),
                '"unique_identifier": "{}"'.format(self.unique_identifier),
                '"template_attribute": {}'.format(self.template_attribute)
            ]
        )
        return '{' + value + '}'


# Copyright (c) 2015 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 import attributes
from kmip.core import enums
from kmip.core import misc
from kmip.core import objects as cobjects
from kmip.core import secrets

from kmip.pie import objects as pobjects

class ObjectFactory:
    """
    A factory to convert between the Pie and core object hierarchies.
    """

    def __init__(self):
        """
        Construct an ObjectFactory.
        """
        pass

    def convert(self, obj):
        """
        Convert a Pie object into a core secret object and vice versa.

        Args:
            obj (various): A Pie or core secret object to convert into the
                opposite object space. Required.

        Raises:
            TypeError: if the object type is unrecognized or unsupported.
        """
        if isinstance(obj, pobjects.SymmetricKey):
            return self._build_core_key(obj, secrets.SymmetricKey)
        elif isinstance(obj, secrets.SymmetricKey):
            return self._build_pie_key(obj, pobjects.SymmetricKey)
        elif isinstance(obj, pobjects.PublicKey):
            return self._build_core_key(obj, secrets.PublicKey)
        elif isinstance(obj, secrets.PublicKey):
            return self._build_pie_key(obj, pobjects.PublicKey)
        elif isinstance(obj, pobjects.PrivateKey):
            return self._build_core_key(obj, secrets.PrivateKey)
        elif isinstance(obj, secrets.PrivateKey):
            return self._build_pie_key(obj, pobjects.PrivateKey)
        elif isinstance(obj, pobjects.Certificate):
            return self._build_core_certificate(obj)
        elif isinstance(obj, secrets.Certificate):
            return self._build_pie_certificate(obj)
        elif isinstance(obj, pobjects.SecretData):
            return self._build_core_secret_data(obj)
        elif isinstance(obj, secrets.SecretData):
            return self._build_pie_secret_data(obj)
        elif isinstance(obj, pobjects.OpaqueObject):
            return self._build_core_opaque_object(obj)
        elif isinstance(obj, secrets.OpaqueObject):
            return self._build_pie_opaque_object(obj)
        else:
            raise TypeError("object type unsupported and cannot be converted")

    def _build_pie_certificate(self, cert):
        certificate_type = cert.certificate_type.value
        value = cert.certificate_value.value

        if certificate_type == enums.CertificateType.X_509:
            return pobjects.X509Certificate(value)
        else:
            raise TypeError("core certificate type not supported")

    def _build_pie_key(self, key, cls):
        algorithm = key.key_block.cryptographic_algorithm.value
        length = key.key_block.cryptographic_length.value
        value = key.key_block.key_value.key_material.value
        format_type = key.key_block.key_format_type.value
        key_wrapping_data = key.key_block.key_wrapping_data

        if cls is pobjects.SymmetricKey:
            key = cls(
                algorithm,
                length,
                value,
                key_wrapping_data=self._build_key_wrapping_data(
                    key_wrapping_data
                )
            )
            if key.key_format_type != format_type:
                raise TypeError(
                    "core key format type not compatible with Pie "
                    "SymmetricKey; expected {0}, observed {1}".format(
                        key.key_format_type, format_type))
            else:
                return key
        else:
            return cls(
                algorithm,
                length,
                value,
                format_type,
                key_wrapping_data=self._build_key_wrapping_data(
                    key_wrapping_data
                )
            )

    def _build_pie_secret_data(self, secret):
        secret_data_type = secret.secret_data_type.value
        value = secret.key_block.key_value.key_material.value

        return pobjects.SecretData(value, secret_data_type)

    def _build_pie_opaque_object(self, obj):
        opaque_type = obj.opaque_data_type.value
        value = obj.opaque_data_value.value
        return pobjects.OpaqueObject(value, opaque_type)

    def _build_core_key(self, key, cls):
        algorithm = key.cryptographic_algorithm
        length = key.cryptographic_length
        value = key.value
        format_type = key.key_format_type

        key_material = cobjects.KeyMaterial(value)
        key_value = cobjects.KeyValue(key_material)
        key_wrapping_data = None
        if key.key_wrapping_data:
            key_wrapping_data = cobjects.KeyWrappingData(
                **key.key_wrapping_data
            )
        key_block = cobjects.KeyBlock(
            key_format_type=misc.KeyFormatType(format_type),
            key_compression_type=None,
            key_value=key_value,
            cryptographic_algorithm=attributes.CryptographicAlgorithm(
                algorithm),
            cryptographic_length=attributes.CryptographicLength(length),
            key_wrapping_data=key_wrapping_data
        )

        return cls(key_block)

    def _build_core_certificate(self, cert):
        return secrets.Certificate(cert.certificate_type, cert.value)

    def _build_core_secret_data(self, secret):
        secret_data_type = secret.data_type
        value = secret.value

        key_material = cobjects.KeyMaterial(value)
        key_value = cobjects.KeyValue(key_material)
        key_block = cobjects.KeyBlock(
            key_format_type=misc.KeyFormatType(enums.KeyFormatType.OPAQUE),
            key_compression_type=None,
            key_value=key_value,
            cryptographic_algorithm=None,
            cryptographic_length=None,
            key_wrapping_data=None)
        data_type = secrets.SecretData.SecretDataType(secret_data_type)

        return secrets.SecretData(data_type, key_block)

    def _build_core_opaque_object(self, obj):
        opaque_type = obj.opaque_type
        value = obj.value

        opaque_data_type = secrets.OpaqueObject.OpaqueDataType(opaque_type)
        opaque_data_value = secrets.OpaqueObject.OpaqueDataValue(value)
        return secrets.OpaqueObject(opaque_data_type, opaque_data_value)

    def _build_cryptographic_parameters(self, value):
        cryptographic_parameters = {
            'block_cipher_mode': value.block_cipher_mode,
            'padding_method': value.padding_method,
            'hashing_algorithm': value.hashing_algorithm,
            'key_role_type': value.key_role_type,
            'digital_signature_algorithm': value.digital_signature_algorithm,
            'cryptographic_algorithm': value.cryptographic_algorithm,
            'random_iv': value.random_iv,
            'iv_length': value.iv_length,
            'tag_length': value.tag_length,
            'fixed_field_length': value.fixed_field_length,
            'invocation_field_length': value.invocation_field_length,
            'counter_length': value.counter_length,
            'initial_counter_value': value.initial_counter_value
        }
        return cryptographic_parameters

    def _build_key_wrapping_data(self, value):
        if value is None:
            return None
        encryption_key_info = value.encryption_key_information
        encryption_key_information = {}
        if encryption_key_info:
            encryption_key_information = {
                'unique_identifier': encryption_key_info.unique_identifier,
                'cryptographic_parameters':
                    self._build_cryptographic_parameters(
                        encryption_key_info.cryptographic_parameters
                    )
            }
        mac_signature_key_info = value.mac_signature_key_information
        mac_signature_key_information = {}
        if mac_signature_key_info:
            mac_signature_key_information = {
                'unique_identifier': mac_signature_key_info.unique_identifier,
                'cryptographic_parameters':
                    self._build_cryptographic_parameters(
                        mac_signature_key_info.cryptographic_parameters
                    )
            }
        key_wrapping_data = {
            'wrapping_method': value.wrapping_method,
            'encryption_key_information': encryption_key_information,
            'mac_signature_key_information': mac_signature_key_information,
            'mac_signature': value.mac_signature,
            'iv_counter_nonce': value.iv_counter_nonce,
            'encoding_option': value.encoding_option
        }
        return key_wrapping_data


# 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.core import attributes
from kmip.core import enums
from kmip.core import primitives
from kmip.core import utils

class EncryptRequestPayload(primitives.Struct):
    """
    A request payload for the Encrypt operation.

    Attributes:
        unique_identifier: The unique ID of the managed object to be used for
            encryption.
        cryptographic_parameters: A collection of settings relevant for
            the encryption operation.
        data: The data to be encrypted in the form of a binary string.
        iv_counter_nonce: An IV/counter/nonce to be used with the encryption
            algorithm. Comes in the form of a binary string.
    """

    def __init__(self,
                 unique_identifier=None,
                 cryptographic_parameters=None,
                 data=None,
                 iv_counter_nonce=None):
        """
        Construct an Encrypt request payload struct.

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a symmetric key) to be used for encryption. Optional, defaults
                to None. If not included, the ID placeholder will be used.
            cryptographic_parameters (CryptographicParameters): A
                CryptographicParameters struct containing the settings for
                the encryption algorithm. Optional, defaults to None. If not
                included, the CryptographicParameters associated with the
                managed object will be used instead.
            data (bytes): The data to encrypt in binary form. Required for
                encoding and decoding.
            iv_counter_nonce (bytes): The IV/counter/nonce value to be used
                with the encryption algorithm. Optional, defaults to None.
        """
        super(EncryptRequestPayload, self).__init__(
            enums.Tags.REQUEST_PAYLOAD
        )

        self._unique_identifier = None
        self._cryptographic_parameters = None
        self._data = None
        self._iv_counter_nonce = None

        self.unique_identifier = unique_identifier
        self.cryptographic_parameters = cryptographic_parameters
        self.data = data
        self.iv_counter_nonce = iv_counter_nonce

    @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 cryptographic_parameters(self):
        return self._cryptographic_parameters

    @cryptographic_parameters.setter
    def cryptographic_parameters(self, value):
        if value is None:
            self._cryptographic_parameters = None
        elif isinstance(value, attributes.CryptographicParameters):
            self._cryptographic_parameters = value
        else:
            raise TypeError(
                "cryptographic parameters must be a CryptographicParameters "
                "struct"
            )

    @property
    def data(self):
        if self._data:
            return self._data.value
        else:
            return None

    @data.setter
    def data(self, value):
        if value is None:
            self._data = None
        elif isinstance(value, six.binary_type):
            self._data = primitives.ByteString(
                value=value,
                tag=enums.Tags.DATA
            )
        else:
            raise TypeError("data must be bytes")

    @property
    def iv_counter_nonce(self):
        if self._iv_counter_nonce:
            return self._iv_counter_nonce.value
        else:
            return None

    @iv_counter_nonce.setter
    def iv_counter_nonce(self, value):
        if value is None:
            self._iv_counter_nonce = None
        elif isinstance(value, six.binary_type):
            self._iv_counter_nonce = primitives.ByteString(
                value=value,
                tag=enums.Tags.IV_COUNTER_NONCE
            )
        else:
            raise TypeError("IV/counter/nonce must be bytes")

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Encrypt 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(EncryptRequestPayload, 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.CRYPTOGRAPHIC_PARAMETERS,
                local_stream
        ):
            self._cryptographic_parameters = \
                attributes.CryptographicParameters()
            self._cryptographic_parameters.read(
                local_stream,
                kmip_version=kmip_version
            )

        if self.is_tag_next(enums.Tags.DATA, local_stream):
            self._data = primitives.ByteString(tag=enums.Tags.DATA)
            self._data.read(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError("invalid payload missing the data attribute")

        if self.is_tag_next(enums.Tags.IV_COUNTER_NONCE, local_stream):
            self._iv_counter_nonce = primitives.ByteString(
                tag=enums.Tags.IV_COUNTER_NONCE
            )
            self._iv_counter_nonce.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 Encrypt 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
            )
        if self._cryptographic_parameters:
            self._cryptographic_parameters.write(
                local_stream,
                kmip_version=kmip_version
            )

        if self._data:
            self._data.write(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError("invalid payload missing the data attribute")

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

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

    def __eq__(self, other):
        if isinstance(other, EncryptRequestPayload):
            if self.unique_identifier != other.unique_identifier:
                return False
            elif self.cryptographic_parameters !=\
                    other.cryptographic_parameters:
                return False
            elif self.data != other.data:
                return False
            elif self.iv_counter_nonce != other.iv_counter_nonce:
                return False
            else:
                return True
        else:
            return NotImplemented

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

    def __repr__(self):
        args = ", ".join([
            "unique_identifier='{0}'".format(self.unique_identifier),
            "cryptographic_parameters={0}".format(
                repr(self.cryptographic_parameters)
            ),
            "data={0}".format(self.data),
            "iv_counter_nonce={0}".format(self.iv_counter_nonce)
        ])
        return "EncryptRequestPayload({0})".format(args)

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

class EncryptResponsePayload(primitives.Struct):
    """
    A response payload for the Encrypt operation.

    Attributes:
        unique_identifier: The unique ID of the managed object used for the
            encryption.
        data: The encrypted data in the form of a binary string.
        iv_counter_nonce: The IV/counter/nonce used with the encryption
            algorithm. Comes in the form of a binary string.
    """

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

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a symmetric key) used for encryption. Required for encoding
                and decoding.
            data (bytes): The encrypted data in binary form. Required for
                encoding and decoding.
            iv_counter_nonce (bytes): The IV/counter/nonce value used with
                the encryption algorithm if it was required and if this
                value was not originally specified by the client. Optional,
                defaults to None.
        """
        super(EncryptResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._unique_identifier = None
        self._data = None
        self._iv_counter_nonce = None

        self.unique_identifier = unique_identifier
        self.data = data
        self.iv_counter_nonce = iv_counter_nonce

    @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 data(self):
        if self._data:
            return self._data.value
        else:
            return None

    @data.setter
    def data(self, value):
        if value is None:
            self._data = None
        elif isinstance(value, six.binary_type):
            self._data = primitives.ByteString(
                value=value,
                tag=enums.Tags.DATA
            )
        else:
            raise TypeError("data must be bytes")

    @property
    def iv_counter_nonce(self):
        if self._iv_counter_nonce:
            return self._iv_counter_nonce.value
        else:
            return None

    @iv_counter_nonce.setter
    def iv_counter_nonce(self, value):
        if value is None:
            self._iv_counter_nonce = None
        elif isinstance(value, six.binary_type):
            self._iv_counter_nonce = primitives.ByteString(
                value=value,
                tag=enums.Tags.IV_COUNTER_NONCE
            )
        else:
            raise TypeError("IV/counter/nonce must be bytes")

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Encrypt 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 or data attributes
                are missing from the encoded payload.
        """
        super(EncryptResponsePayload, 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(
                "invalid payload missing the unique identifier attribute"
            )

        if self.is_tag_next(enums.Tags.DATA, local_stream):
            self._data = primitives.ByteString(tag=enums.Tags.DATA)
            self._data.read(
                local_stream,
                kmip_version=kmip_version
            )
        else:
            raise ValueError("invalid payload missing the data attribute")

        if self.is_tag_next(enums.Tags.IV_COUNTER_NONCE, local_stream):
            self._iv_counter_nonce = primitives.ByteString(
                tag=enums.Tags.IV_COUNTER_NONCE
            )
            self._iv_counter_nonce.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 Encrypt 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 encoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: Raised if the unique_identifier or data attributes
                are not defined.
        """
        local_stream = utils.BytearrayStream()

        if self._unique_identifier:
            self._unique_identifier.write(
                local_stream,
                kmip_version=kmip_version
            )
        else:
            raise ValueError(
                "invalid payload missing the unique identifier attribute"
            )

        if self._data:
            self._data.write(local_stream, kmip_version=kmip_version)
        else:
            raise ValueError("invalid payload missing the data attribute")

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

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

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

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

    def __repr__(self):
        args = ", ".join([
            "unique_identifier='{0}'".format(self.unique_identifier),
            "data={0}".format(self.data),
            "iv_counter_nonce={0}".format(self.iv_counter_nonce)
        ])
        return "EncryptResponsePayload({0})".format(args)

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