# 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 DecryptRequestPayload(primitives.Struct):
    """
    A request payload for the Decrypt operation.

    Attributes:
        unique_identifier: The unique ID of the managed object to be used for
            decryption.
        cryptographic_parameters: A collection of settings relevant for
            the decryption operation.
        data: The data to be decrypted in the form of a binary string.
        iv_counter_nonce: An IV/counter/nonce to be used with the decryption
            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 a Decrypt request payload struct.

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a symmetric key) to be used for decryption. Optional, defaults
                to None. If not included, the ID placeholder will be used.
            cryptographic_parameters (CryptographicParameters): A
                CryptographicParameters struct containing the settings for
                the decryption algorithm. Optional, defaults to None. If not
                included, the CryptographicParameters associated with the
                managed object will be used instead.
            data (bytes): The data to decrypt in binary form. Required for
                encoding and decoding.
            iv_counter_nonce (bytes): The IV/counter/nonce value to be used
                with the decryption algorithm. Optional, defaults to None.
        """
        super(DecryptRequestPayload, 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 Decrypt 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(DecryptRequestPayload, 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 Decrypt 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(DecryptRequestPayload, self).write(
            output_stream,
            kmip_version=kmip_version
        )
        output_stream.write(local_stream.buffer)

    def __eq__(self, other):
        if isinstance(other, DecryptRequestPayload):
            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, DecryptRequestPayload):
            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 "DecryptRequestPayload({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 DecryptResponsePayload(primitives.Struct):
    """
    A response payload for the Decrypt operation.

    Attributes:
        unique_identifier: The unique ID of the managed object used for the
            decryption.
        data: The decrypted data in the form of a binary string.
    """

    def __init__(self,
                 unique_identifier=None,
                 data=None):
        """
        Construct a Decrypt response payload struct.

        Args:
            unique_identifier (string): The ID of the managed object (e.g.,
                a symmetric key) used for decryption. Required for encoding
                and decoding.
            data (bytes): The decrypted data in binary form. Required for
                encoding and decoding.
        """
        super(DecryptResponsePayload, self).__init__(
            enums.Tags.RESPONSE_PAYLOAD
        )

        self._unique_identifier = None
        self._data = None

        self.unique_identifier = unique_identifier
        self.data = data

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

    def read(self, input_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the data encoding the Decrypt 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(DecryptResponsePayload, 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")

        self.is_oversized(local_stream)

    def write(self, output_stream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the data encoding the Decrypt 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")

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

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

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

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

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


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

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

from kmip.core.primitives import Struct

from kmip.core.utils import BytearrayStream

class RevokeRequestPayload(Struct):
    """
    A request payload for the Revoke operation.

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

    Attributes:
        unique_identifier: The UUID of a managed cryptographic object
        revocation_reason: The reason why the object was revoked
        compromised_date: The date of compromise if the object was compromised
    """

    def __init__(self,
                 unique_identifier=None,
                 revocation_reason=None,
                 compromise_occurrence_date=None):
        """
        Construct a RevokeRequestPayload object.
        Args:
            unique_identifier (UniqueIdentifier): The UUID of a managed
                cryptographic object.
            revocation_reason (RevocationReason): The reason why the object was
                revoked.
            compromise_occurrence_date (DateTime): the datetime when the object
                was first believed to be compromised.
        """
        super(RevokeRequestPayload, self).__init__(
            tag=enums.Tags.REQUEST_PAYLOAD)
        self.unique_identifier = unique_identifier
        self.compromise_occurrence_date = compromise_occurrence_date
        self.revocation_reason = revocation_reason
        if self.revocation_reason is None:
            self.revocation_reason = objects.RevocationReason()
        self.validate()

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

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

        self.revocation_reason = objects.RevocationReason()
        self.revocation_reason.read(tstream, kmip_version=kmip_version)

        if self.is_tag_next(enums.Tags.COMPROMISE_OCCURRENCE_DATE, tstream):
            self.compromise_occurrence_date = primitives.DateTime(
                tag=enums.Tags.COMPROMISE_OCCURRENCE_DATE)
            self.compromise_occurrence_date.read(
                tstream,
                kmip_version=kmip_version
            )

        self.is_oversized(tstream)
        self.validate()

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

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

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

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

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

    def validate(self):
        """
        Error check the attributes of the ActivateRequestPayload object.
        """
        if self.unique_identifier is not None:
            if not isinstance(self.unique_identifier,
                              attributes.UniqueIdentifier):
                msg = "invalid unique identifier"
                raise TypeError(msg)
        if self.compromise_occurrence_date is not None:
            if not isinstance(self.compromise_occurrence_date,
                              primitives.DateTime):
                msg = "invalid compromise time"
                raise TypeError(msg)
        if not isinstance(self.revocation_reason, objects.RevocationReason):
            msg = "invalid revocation reason"
            raise TypeError(msg)

class RevokeResponsePayload(Struct):
    """
    A response payload for the Revoke operation.
    The payload contains the server response to the initial Revoke request.
    See Section 4.20 of the KMIP 1.1 specification for more information.
    Attributes:
        unique_identifier: The UUID of a managed cryptographic object.
    """
    def __init__(self,
                 unique_identifier=None):
        """
        Construct a RevokeResponsePayload object.
        Args:
            unique_identifier (UniqueIdentifier): The UUID of a managed
                cryptographic object.
        """
        super(RevokeResponsePayload, self).__init__(
            tag=enums.Tags.RESPONSE_PAYLOAD)
        if unique_identifier is None:
            self.unique_identifier = attributes.UniqueIdentifier()
        else:
            self.unique_identifier = unique_identifier
        self.validate()

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

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

        self.is_oversized(tstream)
        self.validate()

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

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

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

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


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

from kmip.core.attributes import CryptographicAlgorithm
from kmip.core.attributes import CryptographicLength

from kmip.core.enums import ObjectType

from kmip.core.misc import KeyFormatType

from kmip.core.objects import Attribute
from kmip.core.objects import KeyBlock
from kmip.core.objects import KeyMaterial
from kmip.core.objects import KeyWrappingData
from kmip.core.objects import KeyValue

from kmip.core.secrets import Certificate
from kmip.core.secrets import OpaqueObject
from kmip.core.secrets import PrivateKey
from kmip.core.secrets import PublicKey
from kmip.core.secrets import SecretData
from kmip.core.secrets import SymmetricKey
from kmip.core.secrets import Template

from kmip.core import utils
from kmip.core import exceptions

class SecretFactory(object):

    def __init__(self):
        self.base_error = exceptions.ErrorStrings.BAD_EXP_RECV
        self.template_input = self.base_error.format('Template', '{0}', '{1}',
                                                     '{2}')

    def create(self, secret_type, value=None):
        """
        Create a secret object of the specified type with the given value.

        Args:
            secret_type (ObjectType): An ObjectType enumeration specifying the
                type of secret to create.
            value (dict): A dictionary containing secret data. Optional,
                defaults to None.

        Returns:
            secret: The newly constructed secret object.

        Raises:
            TypeError: If the provided secret type is unrecognized.

        Example:
            >>> factory.create(ObjectType.SYMMETRIC_KEY)
            SymmetricKey(...)
        """
        if secret_type is ObjectType.CERTIFICATE:
            return self._create_certificate(value)
        elif secret_type is ObjectType.SYMMETRIC_KEY:
            return self._create_symmetric_key(value)
        elif secret_type is ObjectType.PUBLIC_KEY:
            return self._create_public_key(value)
        elif secret_type is ObjectType.PRIVATE_KEY:
            return self._create_private_key(value)
        elif secret_type is ObjectType.SPLIT_KEY:
            return self._create_split_key(value)
        elif secret_type is ObjectType.TEMPLATE:
            return self._create_template(value)
        elif secret_type is ObjectType.SECRET_DATA:
            return self._create_secret_data(value)
        elif secret_type is ObjectType.OPAQUE_DATA:
            return self._create_opaque_data(value)
        else:
            raise TypeError("Unrecognized secret type: {0}".format(
                secret_type))

    def _create_certificate(self, value):
        if value:
            return Certificate(
                certificate_type=value.get('certificate_type'),
                certificate_value=value.get('certificate_value')
            )
        else:
            return Certificate()

    def _create_symmetric_key(self, value):
        if value is None:
            return SymmetricKey()
        else:
            key_block = self._build_key_block(value)
            return SymmetricKey(key_block)

    def _create_public_key(self, value):
        if value is None:
            return PublicKey()
        else:
            key_block = self._build_key_block(value)
            return PublicKey(key_block)

    def _create_private_key(self, value):
        if value is None:
            return PrivateKey()
        else:
            key_block = self._build_key_block(value)
            return PrivateKey(key_block)

    def _create_split_key(self, value):
        raise NotImplementedError()

    def _create_template(self, value):
        if value is None:
            return Template()
        else:
            if not isinstance(value, list):
                msg = utils.build_er_error(Template,
                                           'constructor argument type', list,
                                           type(value))
                raise TypeError(msg)
            else:
                for val in value:
                    if not isinstance(val, Attribute):
                        msg = utils.build_er_error(Template,
                                                   'constructor argument type',
                                                   Attribute, type(val))
                        raise TypeError(msg)
            return Template(value)

    def _create_secret_data(self, value):
        if value:
            kind = SecretData.SecretDataType(value.get("secret_data_type"))
            key_block = self._build_key_block(value)
            return SecretData(kind, key_block)
        return SecretData()

    def _create_opaque_data(self, value):
        if value:
            kind = OpaqueObject.OpaqueDataType(value.get("opaque_data_type"))
            data = OpaqueObject.OpaqueDataValue(value.get("opaque_data_value"))
            return OpaqueObject(kind, data)
        return OpaqueObject()

    def _build_key_block(self, value):
        key_type = value.get('key_format_type')
        key_compression_type = value.get('key_compression_type')
        key_value = value.get('key_value')
        cryptographic_algorithm = value.get('cryptographic_algorithm')
        cryptographic_length = value.get('cryptographic_length')
        key_wrapping_data = value.get('key_wrapping_data')

        key_format_type = KeyFormatType(key_type)

        key_comp_type = None
        if key_compression_type is not None:
            key_comp_type = KeyBlock.KeyCompressionType(
                key_compression_type)

        key_material = KeyMaterial(key_value)
        key_value = KeyValue(key_material)

        crypto_algorithm = None
        if cryptographic_algorithm is not None:
            crypto_algorithm = CryptographicAlgorithm(
                cryptographic_algorithm
            )

        crypto_length = None
        if cryptographic_length is not None:
            crypto_length = CryptographicLength(cryptographic_length)

        key_wrap_data = None
        if key_wrapping_data:
            # TODO (peter-hamilton) This currently isn't used in the tests
            # TODO (peter-hamilton) but needs to be updated to properly
            # TODO (peter-hamilton) create a KeyWrappingData object.
            key_wrap_data = KeyWrappingData(**key_wrapping_data)

        key_block = KeyBlock(key_format_type,
                             key_comp_type,
                             key_value,
                             crypto_algorithm,
                             crypto_length,
                             key_wrap_data)
        return key_block


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

import logging
import os
import six

from six.moves import configparser

from kmip.core import exceptions

class KmipServerConfig(object):
    """
    A configuration management tool for the KmipServer.
    """

    def __init__(self):
        """
        Create a KmipServerConfig object.
        """
        self._logger = logging.getLogger('kmip.server.config')

        self.settings = dict()
        self.settings['enable_tls_client_auth'] = True
        self.settings['tls_cipher_suites'] = []
        self.settings['logging_level'] = logging.INFO
        self.settings['auth_plugins'] = []

        self._expected_settings = [
            'hostname',
            'port',
            'certificate_path',
            'key_path',
            'ca_path',
            'auth_suite'
        ]
        self._optional_settings = [
            'policy_path',
            'enable_tls_client_auth',
            'tls_cipher_suites',
            'logging_level',
            'database_path'
        ]

    def set_setting(self, setting, value):
        """
        Set a specific setting value.

        This will overwrite the current setting value for the specified
        setting.

        Args:
            setting (string): The name of the setting to set (e.g.,
                'certificate_path', 'hostname'). Required.
            value (misc): The value of the setting to set. Type varies based
                on setting. Required.
        Raises:
            ConfigurationError: Raised if the setting is not supported or if
                the setting value is invalid.
        """
        if setting not in self._expected_settings + self._optional_settings:
            raise exceptions.ConfigurationError(
                "Setting '{0}' is not supported.".format(setting)
            )

        if setting == 'hostname':
            self._set_hostname(value)
        elif setting == 'port':
            self._set_port(value)
        elif setting == 'certificate_path':
            self._set_certificate_path(value)
        elif setting == 'key_path':
            self._set_key_path(value)
        elif setting == 'ca_path':
            self._set_ca_path(value)
        elif setting == 'auth_suite':
            self._set_auth_suite(value)
        elif setting == 'policy_path':
            self._set_policy_path(value)
        elif setting == 'enable_tls_client_auth':
            self._set_enable_tls_client_auth(value)
        elif setting == 'tls_cipher_suites':
            self._set_tls_cipher_suites(value)
        elif setting == 'logging_level':
            self._set_logging_level(value)
        else:
            self._set_database_path(value)

    def load_settings(self, path):
        """
        Load configuration settings from the file pointed to by path.

        This will overwrite all current setting values.

        Args:
            path (string): The path to the configuration file containing
                the settings to load. Required.
        Raises:
            ConfigurationError: Raised if the path does not point to an
                existing file or if a setting value is invalid.
        """
        if not os.path.exists(path):
            raise exceptions.ConfigurationError(
                "The server configuration file ('{0}') could not be "
                "located.".format(path)
            )

        self._logger.info(
            "Loading server configuration settings from: {0}".format(path)
        )

        parser = configparser.ConfigParser()
        parser.read(path)
        self._parse_settings(parser)
        self.parse_auth_settings(parser)

    def parse_auth_settings(self, parser):
        sections = [x for x in parser.sections() if x.startswith("auth:")]
        configs = [(x, dict(parser.items(x))) for x in sections]
        self.settings['auth_plugins'] = configs

    def _parse_settings(self, parser):
        if not parser.has_section('server'):
            raise exceptions.ConfigurationError(
                "The server configuration file does not have a 'server' "
                "section."
            )

        settings = [x[0] for x in parser.items('server')]
        for s in settings:
            if s not in self._expected_settings + self._optional_settings:
                raise exceptions.ConfigurationError(
                    "Setting '{0}' is not a supported setting. Please "
                    "remove it from the configuration file.".format(s)
                )
        for setting in self._expected_settings:
            if setting not in settings:
                raise exceptions.ConfigurationError(
                    "Setting '{0}' is missing from the configuration "
                    "file.".format(setting)
                )

        if parser.has_option('server', 'hostname'):
            self._set_hostname(parser.get('server', 'hostname'))
        if parser.has_option('server', 'port'):
            self._set_port(parser.getint('server', 'port'))
        if parser.has_option('server', 'certificate_path'):
            self._set_certificate_path(parser.get(
                'server',
                'certificate_path')
            )
        if parser.has_option('server', 'key_path'):
            self._set_key_path(parser.get('server', 'key_path'))
        if parser.has_option('server', 'ca_path'):
            self._set_ca_path(parser.get('server', 'ca_path'))
        if parser.has_option('server', 'auth_suite'):
            self._set_auth_suite(parser.get('server', 'auth_suite'))
        if parser.has_option('server', 'policy_path'):
            self._set_policy_path(parser.get('server', 'policy_path'))
        if parser.has_option('server', 'enable_tls_client_auth'):
            self._set_enable_tls_client_auth(
                parser.getboolean('server', 'enable_tls_client_auth')
            )
        if parser.has_option('server', 'tls_cipher_suites'):
            self._set_tls_cipher_suites(
                parser.get('server', 'tls_cipher_suites')
            )
        if parser.has_option('server', 'logging_level'):
            self._set_logging_level(
                parser.get('server', 'logging_level')
            )
        if parser.has_option('server', 'database_path'):
            self._set_database_path(parser.get('server', 'database_path'))

    def _set_hostname(self, value):
        if isinstance(value, six.string_types):
            self.settings['hostname'] = value
        else:
            raise exceptions.ConfigurationError(
                "The hostname value must be a string."
            )

    def _set_port(self, value):
        if isinstance(value, six.integer_types):
            if 0 < value < 65535:
                self.settings['port'] = value
            else:
                raise exceptions.ConfigurationError(
                    "The port value must be an integer in the range 0 - 65535."
                )
        else:
            raise exceptions.ConfigurationError(
                "The port value must be an integer in the range 0 - 65535."
            )

    def _set_certificate_path(self, value):
        if value is None:
            self.settings['certificate_path'] = None
        elif isinstance(value, six.string_types):
            if os.path.exists(value):
                self.settings['certificate_path'] = value
            else:
                raise exceptions.ConfigurationError(
                    "The certificate path value, if specified, must be a "
                    "valid string path to a certificate file."
                )
        else:
            raise exceptions.ConfigurationError(
                "The certificate path value, if specified, must be a valid "
                "string path to a certificate file."
            )

    def _set_key_path(self, value):
        if value is None:
            self.settings['key_path'] = None
        elif isinstance(value, six.string_types):
            if os.path.exists(value):
                self.settings['key_path'] = value
            else:
                raise exceptions.ConfigurationError(
                    "The key path value, if specified, must be a valid string "
                    "path to a certificate key file."
                )
        else:
            raise exceptions.ConfigurationError(
                "The key path value, if specified, must be a valid string "
                "path to a certificate key file."
            )

    def _set_ca_path(self, value):
        if value is None:
            self.settings['ca_path'] = None
        elif isinstance(value, six.string_types):
            if os.path.exists(value):
                self.settings['ca_path'] = value
            else:
                raise exceptions.ConfigurationError(
                    "The certificate authority (CA) path value, if "
                    "specified, must be a valid string path to a CA "
                    "certificate file."
                )
        else:
            raise exceptions.ConfigurationError(
                "The certificate authority (CA) path value, if specified, "
                "must be a valid string path to a CA certificate file."
            )

    def _set_auth_suite(self, value):
        auth_suites = ['Basic', 'TLS1.2']
        if value not in auth_suites:
            raise exceptions.ConfigurationError(
                "The authentication suite must be one of the "
                "following: Basic, TLS1.2"
            )
        else:
            self.settings['auth_suite'] = value

    def _set_policy_path(self, value):
        if not value:
            self.settings['policy_path'] = None
        elif isinstance(value, six.string_types):
            self.settings['policy_path'] = value
        else:
            raise exceptions.ConfigurationError(
                "The policy path, if specified, must be a valid string path "
                "to a filesystem directory."
            )

    def _set_enable_tls_client_auth(self, value):
        if value is None:
            self.settings['enable_tls_client_auth'] = True
        elif isinstance(value, bool):
            self.settings['enable_tls_client_auth'] = value
        else:
            raise exceptions.ConfigurationError(
                "The flag enabling the TLS certificate client auth flag check "
                "must be a boolean."
            )

    def _set_tls_cipher_suites(self, value):
        if not value:
            self.settings['tls_cipher_suites'] = []
            return
        if isinstance(value, six.string_types):
            value = value.split()

        if isinstance(value, list):
            for entry in value:
                if not isinstance(entry, six.string_types):
                    raise exceptions.ConfigurationError(
                        "The TLS cipher suites must be a set of strings "
                        "representing cipher suite names."
                    )
            self.settings['tls_cipher_suites'] = list(set(value))
        else:
            raise exceptions.ConfigurationError(
                "The TLS cipher suites must be a set of strings representing "
                "cipher suite names."
            )

    def _set_logging_level(self, value):
        if value is None:
            self.settings['logging_level'] = logging.INFO
            return

        logging_levels = {
            "DEBUG":    logging.DEBUG,
            "INFO":     logging.INFO,
            "WARNING":  logging.WARNING,
            "ERROR":    logging.ERROR,
            "CRITICAL": logging.CRITICAL
        }
        if value in logging_levels.values():
            self.settings['logging_level'] = value
        elif isinstance(value, six.string_types):
            level = logging_levels.get(value.upper())
            if level:
                self.settings['logging_level'] = level
            else:
                raise exceptions.ConfigurationError(
                    "The logging level must be a string representing a valid "
                    "logging level."
                )
        else:
            raise exceptions.ConfigurationError(
                "The logging level must be a string representing a valid "
                "logging level."
            )

    def _set_database_path(self, value):
        if not value:
            self.settings['database_path'] = None
        elif isinstance(value, six.string_types):
            self.settings['database_path'] = value
        else:
            raise exceptions.ConfigurationError(
                "The database path, if specified, must be a valid path to a "
                "SQLite database file."
            )


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

from kmip.core import enums
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

import sqlalchemy.types as types

Base = declarative_base()

def attribute_append_factory(index_attribute):
    def attribute_append(list_container, list_attribute, initiator):
        index = getattr(list_container, index_attribute)
        list_attribute.index = index
        setattr(list_container, index_attribute, index + 1)
        return list_attribute
    return attribute_append

class UsageMaskType(types.TypeDecorator):
    """
    Converts a list of enums.CryptographicUsageMask Enums in an integer
    bitmask. This allows the database to only store an integer instead of a
    list of enumbs. This also does the reverse of converting an integer bit
    mask into a list enums.CryptographicUsageMask Enums.
    """

    impl = types.Integer

    def process_bind_param(self, value, dialect):
        """
        Returns the integer value of the usage mask bitmask. This value is
        stored in the database.

        Args:
            value(list<enums.CryptographicUsageMask>): list of enums in the
            usage mask
            dialect(string): SQL dialect
        """
        bitmask = 0x00
        for e in value:
            bitmask = bitmask | e.value
        return bitmask

    def process_result_value(self, value, dialect):
        """
        Returns a new list of enums.CryptographicUsageMask Enums. This converts
        the integer value into the list of enums.

        Args:
            value(int): The integer value stored in the database that is used
                to create the list of enums.CryptographicUsageMask Enums.
            dialect(string): SQL dialect
        """
        masks = list()
        if value:
            for e in enums.CryptographicUsageMask:
                if e.value & value:
                    masks.append(e)
        return masks

class EnumType(types.TypeDecorator):
    """
    Converts a Python enum to an integer before storing it in the database.
    This also does the reverse of converting an integer into an enum object.
    This allows enums to be stored in a database.
    """

    impl = types.Integer

    def __init__(self, cls):
        """
        Create a new EnumType. This new EnumType requires a class object in the
        constructor. The class is used to construct new instances of the Enum
        when the integer value is retrieved from the database.

        Args:
            cls(class): An Enum class used to create new instances from integer
                values.
        """
        super(EnumType, self).__init__()
        self._cls = cls

    def process_bind_param(self, value, dialect):
        """
        Returns the integer value of the Enum. This value is stored in the
        database.

        Args:
            value(Enum): An Enum instance whose integer value is to be stored.
            dialect(string): SQL dialect
        """
        if value:
            return value.value
        else:
            return -1

    def process_result_value(self, value, dialect):
        """
        Returns a new Enum representing the value stored in the database. The
        Enum class type of the returned object is that of the cls parameter in
        the __init__ call.

        Args:
            value(int): The integer value stored in the database that is used
                to create the Enum
            dialect(string): SQL dialect
        """
        if value == -1:
            return None
        return self._cls(value)

class ManagedObjectName(Base):

    __tablename__ = 'managed_object_names'
    id = Column('id', Integer, primary_key=True)
    mo_uid = Column('mo_uid', Integer, ForeignKey('managed_objects.uid'))
    name = Column('name', String)
    index = Column('name_index', Integer)
    name_type = Column('name_type', EnumType(enums.NameType))

    mo = relationship('ManagedObject', back_populates='_names')

    def __init__(self, name, index=0,
                 name_type=enums.NameType.UNINTERPRETED_TEXT_STRING):
        self.name = name
        self.index = index
        self.name_type = name_type

    def __repr__(self):
        return ("<ManagedObjectName(name='%s', index='%d', type='%s')>" %
                (self.name, self.index, self.name_type))

    def __eq__(self, other):
        if isinstance(other, ManagedObjectName):
            if self.name != other.name:
                return False
            elif self.index != other.index:
                return False
            elif self.name_type != other.name_type:
                return False
            else:
                return True
        else:
            return NotImplemented

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


# 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 enum as enumeration
import logging
import six
import struct
import sys
import time

from struct import pack, unpack

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

class Base(object):
    TAG_SIZE = 3
    TYPE_SIZE = 1
    LENGTH_SIZE = 4

    def __init__(self, tag=enums.Tags.DEFAULT, type=enums.Types.DEFAULT):
        self.tag = tag
        self.type = type
        self.length = None

    # TODO (peter-hamilton) Convert this into a classmethod, class name can be
    #                       obtained from cls parameter that replaces self
    def is_oversized(self, stream):
        extra = len(stream.peek())
        if extra > 0:
            raise exceptions.StreamNotEmptyError(Base.__name__, extra)

    def read_tag(self, istream):
        # Read in the bytes for the tag
        tts = istream.read(self.TAG_SIZE)
        tag = unpack('!I', b'\x00' + tts[0:self.TAG_SIZE])[0]

        enum_tag = enums.Tags(tag)

        # Verify that the tag matches for the current object
        if enum_tag is not self.tag:
            raise exceptions.ReadValueError(
                Base.__name__,
                'tag',
                hex(self.tag.value),
                hex(tag)
            )

    def read_type(self, istream):
        # Read in the bytes for the type
        tts = istream.read(self.TYPE_SIZE)
        num_bytes = len(tts)
        if num_bytes != self.TYPE_SIZE:
            min_bytes = 'a minimum of {0} bytes'.format(self.TYPE_SIZE)
            raise exceptions.ReadValueError(
                Base.__name__,
                'type',
                min_bytes,
                '{0} bytes'.format(num_bytes)
            )
        typ = unpack('!B', tts)[0]

        enum_typ = enums.Types(typ)

        if enum_typ is not self.type:
            raise exceptions.ReadValueError(
                Base.__name__,
                'type',
                self.type.value,
                typ
            )

    def read_length(self, istream):
        # Read in the bytes for the length
        lst = istream.read(self.LENGTH_SIZE)
        num_bytes = len(lst)
        if num_bytes != self.LENGTH_SIZE:
            min_bytes = 'a minimum of {0} bytes'.format(self.LENGTH_SIZE)
            raise exceptions.ReadValueError(
                Base.__name__,
                'length',
                min_bytes,
                '{0} bytes'.format(num_bytes)
            )
        self.length = unpack('!I', lst)[0]

    def read_value(self, istream):
        raise NotImplementedError()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        self.read_tag(istream)
        self.read_type(istream)
        self.read_length(istream)

    def write_tag(self, ostream):
        # Write the tag to the output stream
        ostream.write(pack('!I', self.tag.value)[1:])

    def write_type(self, ostream):
        if type(self.type) is not enums.Types:
            msg = exceptions.ErrorStrings.BAD_EXP_RECV
            raise TypeError(msg.format(Base.__name__, 'type',
                                       enums.Types, type(self.type)))
        ostream.write(pack('!B', self.type.value))

    def write_length(self, ostream):
        if type(self.length) is not int:
            msg = exceptions.ErrorStrings.BAD_EXP_RECV
            raise TypeError(msg.format(Base.__name__, 'length',
                                       int, type(self.length)))
        num_bytes = utils.count_bytes(self.length)
        if num_bytes > self.LENGTH_SIZE:
            raise exceptions.WriteOverflowError(
                Base.__name__,
                'length',
                self.LENGTH_SIZE,
                num_bytes
            )
        ostream.write(pack('!I', self.length))

    def write_value(self, ostream):
        raise NotImplementedError()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        self.write_tag(ostream)
        self.write_type(ostream)
        self.write_length(ostream)

    def validate(self):
        raise NotImplementedError()

    @staticmethod
    def is_tag_next(tag, stream):
        next_tag = stream.peek(Base.TAG_SIZE)
        if len(next_tag) != Base.TAG_SIZE:
            return False
        next_tag = unpack('!I', b'\x00' + next_tag)[0]
        if next_tag == tag.value:
            return True
        else:
            return False

    @staticmethod
    def is_type_next(kmip_type, stream):
        tag_type_size = Base.TAG_SIZE + Base.TYPE_SIZE
        tt = stream.peek(tag_type_size)

        if len(tt) != tag_type_size:
            return False

        typ = unpack('!B', tt[Base.TAG_SIZE:])[0]

        if typ == kmip_type.value:
            return True
        else:
            return False

class Struct(Base):

    def __init__(self, tag=enums.Tags.DEFAULT):
        super(Struct, self).__init__(tag, type=enums.Types.STRUCTURE)

    # NOTE (peter-hamilton) If seen, should indicate repr needs to be defined
    def __repr__(self):
        return "Struct()"

class Integer(Base):
    LENGTH = 4

    # Set for signed 32-bit integers
    MIN = -2147483648
    MAX = 2147483647

    def __init__(self, value=None, tag=enums.Tags.DEFAULT, signed=True):
        super(Integer, self).__init__(tag, type=enums.Types.INTEGER)

        self.value = value
        if self.value is None:
            self.value = 0

        self.length = self.LENGTH
        self.padding_length = self.LENGTH
        if signed:
            self.pack_string = '!i'
        else:
            self.pack_string = '!I'

        self.validate()

    def read_value(self, istream):
        if self.length is not self.LENGTH:
            raise exceptions.ReadValueError(
                Integer.__name__,
                'length',
                self.LENGTH,
                self.length
            )

        self.value = unpack(self.pack_string, istream.read(self.length))[0]
        pad = unpack(self.pack_string, istream.read(self.padding_length))[0]

        if pad != 0:
            raise exceptions.ReadValueError(
                Integer.__name__,
                'pad',
                0,
                pad
            )
        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(Integer, self).read(istream, kmip_version=kmip_version)
        self.read_value(istream)

    def write_value(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        ostream.write(pack(self.pack_string, self.value))
        ostream.write(pack(self.pack_string, 0))

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(Integer, self).write(ostream, kmip_version=kmip_version)
        self.write_value(ostream, kmip_version=kmip_version)

    def validate(self):
        """
        Verify that the value of the Integer object is valid.

        Raises:
            TypeError: if the value is not of type int or long
            ValueError: if the value cannot be represented by a signed 32-bit
                integer
        """
        if self.value is not None:
            if type(self.value) not in six.integer_types:
                raise TypeError('expected (one of): {0}, observed: {1}'.format(
                    six.integer_types, type(self.value)))
            else:
                if self.value > Integer.MAX:
                    raise ValueError('integer value greater than accepted max')
                elif self.value < Integer.MIN:
                    raise ValueError('integer value less than accepted min')

    def __repr__(self):
        return "{0}(value={1})".format(type(self).__name__, self.value)

    def __str__(self):
        return str(self.value)

    def __eq__(self, other):
        if isinstance(other, Integer):
            return self.value == other.value
        else:
            return NotImplemented

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

    def __lt__(self, other):
        if isinstance(other, Integer):
            return self.value < other.value
        else:
            return NotImplemented

    def __gt__(self, other):
        if isinstance(other, Integer):
            return self.value > other.value
        else:
            return NotImplemented

    def __le__(self, other):
        if isinstance(other, Integer):
            return self.__eq__(other) or self.__lt__(other)
        else:
            return NotImplemented

    def __ge__(self, other):
        if isinstance(other, Integer):
            return self.__eq__(other) or self.__gt__(other)
        else:
            return NotImplemented

class LongInteger(Base):
    """
    An encodeable object representing a long integer value.

    A LongInteger is one of the KMIP primitive object types. It is encoded as
    a signed, big-endian, 64-bit integer. For more information, see Section
    9.1 of the KMIP 1.1 specification.
    """

    LENGTH = 8

    # Bounds for signed 64-bit integers
    MIN = -9223372036854775808
    MAX = 9223372036854775807

    def __init__(self, value=0, tag=enums.Tags.DEFAULT):
        """
        Create a LongInteger.

        Args:
            value (int): The value of the LongInteger. Optional, defaults to 0.
            tag (Tags): An enumeration defining the tag of the LongInteger.
                Optional, defaults to Tags.DEFAULT.
        """
        super(LongInteger, self).__init__(tag, type=enums.Types.LONG_INTEGER)
        self.value = value
        self.length = LongInteger.LENGTH

        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the encoding of the LongInteger from the input stream.

        Args:
            istream (stream): A buffer containing the encoded bytes of a
                LongInteger. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            InvalidPrimitiveLength: if the long integer encoding read in has
                an invalid encoded length.
        """
        super(LongInteger, self).read(istream, kmip_version=kmip_version)

        if self.length is not LongInteger.LENGTH:
            raise exceptions.InvalidPrimitiveLength(
                "invalid long integer length read; "
                "expected: {0}, observed: {1}".format(
                    LongInteger.LENGTH, self.length))

        self.value = unpack('!q', istream.read(self.length))[0]
        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the encoding of the LongInteger to the output stream.

        Args:
            ostream (stream): A buffer to contain the encoded bytes of a
                LongInteger. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        super(LongInteger, self).write(ostream, kmip_version=kmip_version)
        ostream.write(pack('!q', self.value))

    def validate(self):
        """
        Verify that the value of the LongInteger is valid.

        Raises:
            TypeError: if the value is not of type int or long
            ValueError: if the value cannot be represented by a signed 64-bit
                integer
        """
        if self.value is not None:
            if not isinstance(self.value, six.integer_types):
                raise TypeError('expected (one of): {0}, observed: {1}'.format(
                    six.integer_types, type(self.value)))
            else:
                if self.value > LongInteger.MAX:
                    raise ValueError(
                        'long integer value greater than accepted max')
                elif self.value < LongInteger.MIN:
                    raise ValueError(
                        'long integer value less than accepted min')

    def __repr__(self):
        return "LongInteger(value={0}, tag={1})".format(self.value, self.tag)

    def __str__(self):
        return str(self.value)

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

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

class BigInteger(Base):
    """
    An encodeable object representing a big integer value.

    A BigInteger is one of the KMIP primitive object types. It is encoded as
    a signed, big-endian, integer of arbitrary size. For more information, see
    Section 9.1 of the KMIP 1.1 specification.
    """

    def __init__(self, value=0, tag=enums.Tags.DEFAULT):
        super(BigInteger, self).__init__(tag, type=enums.Types.BIG_INTEGER)
        self.value = value

        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the encoding of the BigInteger from the input stream.

        Args:
            istream (stream): A buffer containing the encoded bytes of the
                value of a BigInteger. Usually a BytearrayStream object.
                Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            InvalidPrimitiveLength: if the big integer encoding read in has
                an invalid encoded length.
        """
        super(BigInteger, self).read(istream, kmip_version=kmip_version)

        # Check for a valid length before even trying to parse the value.
        if self.length % 8:
            raise exceptions.InvalidPrimitiveLength(
                "invalid big integer length read; "
                "expected: multiple of 8, observed: {0}".format(self.length))

        sign = 1
        binary = ''

        # Read the value byte by byte and convert it into binary, padding each
        # byte as needed.
        for _ in range(self.length):
            byte = struct.unpack('!B', istream.read(1))[0]
            bits = "{0:b}".format(byte)
            pad = len(bits) % 8
            if pad:
                bits = ('0' * (8 - pad)) + bits
            binary += bits

        # If the value is negative, convert via two's complement.
        if binary[0] == '1':
            sign = -1
            binary = binary.replace('1', 'i')
            binary = binary.replace('0', '1')
            binary = binary.replace('i', '0')

            pivot = binary.rfind('0')
            binary = binary[0:pivot] + '1' + ('0' * len(binary[pivot + 1:]))

        # Convert the value back to an integer and reapply the sign.
        self.value = int(binary, 2) * sign

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the encoding of the BigInteger to the output stream.

        Args:
            ostream (Stream): A buffer to contain the encoded bytes of a
                BigInteger object. Usually a BytearrayStream object.
                Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        # Convert the value to binary and pad it as needed.
        binary = "{0:b}".format(abs(self.value))
        binary = ("0" * (64 - (len(binary) % 64))) + binary

        # If the value is negative, convert via two's complement.
        if self.value < 0:
            binary = binary.replace('1', 'i')
            binary = binary.replace('0', '1')
            binary = binary.replace('i', '0')

            pivot = binary.rfind('0')
            binary = binary[0:pivot] + '1' + ('0' * len(binary[pivot + 1:]))

        # Convert each byte to hex and build the hex string for the value.
        hexadecimal = b''
        for i in range(0, len(binary), 8):
            byte = binary[i:i + 8]
            byte = int(byte, 2)
            hexadecimal += struct.pack('!B', byte)

        self.length = len(hexadecimal)
        super(BigInteger, self).write(ostream, kmip_version=kmip_version)
        ostream.write(hexadecimal)

    def validate(self):
        """
        Verify that the value of the BigInteger is valid.

        Raises:
            TypeError: if the value is not of type int or long
        """
        if self.value is not None:
            if not isinstance(self.value, six.integer_types):
                raise TypeError('expected (one of): {0}, observed: {1}'.format(
                    six.integer_types, type(self.value)))

    def __repr__(self):
        return "BigInteger(value={0}, tag={1})".format(self.value, self.tag)

    def __str__(self):
        return str(self.value)

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

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

class Enumeration(Base):
    """
    An encodeable object representing an enumeration.

    An Enumeration is one of the KMIP primitive object types. It is encoded as
    an unsigned, big-endian, 32-bit integer. For more information, see Section
    9.1 of the KMIP 1.1 specification.
    """
    LENGTH = 4

    # Bounds for unsigned 32-bit integers
    MIN = 0
    MAX = 4294967296

    def __init__(self, enum, value=None, tag=enums.Tags.DEFAULT):
        """
        Create an Enumeration.

        Args:
            enum (class): The enumeration class of which value is a member
                (e.g., Tags). Required.
            value (int): The value of the Enumeration, must be an integer
                (e.g., Tags.DEFAULT). Optional, defaults to None.
            tag (Tags): An enumeration defining the tag of the Enumeration.
                Optional, defaults to Tags.DEFAULT.
        """
        super(Enumeration, self).__init__(tag, enums.Types.ENUMERATION)

        self.value = value
        self.enum = enum
        self.length = Enumeration.LENGTH

        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the encoding of the Enumeration from the input stream.

        Args:
            istream (stream): A buffer containing the encoded bytes of an
                Enumeration. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            InvalidPrimitiveLength: if the Enumeration encoding read in has an
                invalid encoded length.
            InvalidPaddingBytes: if the Enumeration encoding read in does not
                use zeroes for its padding bytes.
        """
        super(Enumeration, self).read(istream, kmip_version=kmip_version)

        # Check for a valid length before even trying to parse the value.
        if self.length != Enumeration.LENGTH:
            raise exceptions.InvalidPrimitiveLength(
                "enumeration length must be {0}".format(Enumeration.LENGTH))

        # Decode the Enumeration value and the padding bytes.
        value = unpack('!I', istream.read(Enumeration.LENGTH))[0]
        self.value = self.enum(value)
        pad = unpack('!I', istream.read(Enumeration.LENGTH))[0]

        # Verify that the padding bytes are zero bytes.
        if pad != 0:
            raise exceptions.InvalidPaddingBytes("padding bytes must be zero")

        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the encoding of the Enumeration to the output stream.

        Args:
            ostream (stream): A buffer to contain the encoded bytes of an
                Enumeration. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Enumeration, self).write(ostream, kmip_version=kmip_version)
        ostream.write(pack('!I', self.value.value))
        ostream.write(pack('!I', 0))

    def validate(self):
        """
        Verify that the value of the Enumeration is valid.

        Raises:
            TypeError: if the enum is not of type Enum
            ValueError: if the value is not of the expected Enum subtype or if
                the value cannot be represented by an unsigned 32-bit integer
        """
        if not isinstance(self.enum, enumeration.EnumMeta):
            raise TypeError(
                'enumeration type {0} must be of type EnumMeta'.format(
                    self.enum))
        if self.value is not None:
            if not isinstance(self.value, self.enum):
                raise TypeError(
                    'enumeration {0} must be of type {1}'.format(
                        self.value, self.enum))
            if type(self.value.value) not in six.integer_types:
                raise TypeError('enumeration value must be an int')
            else:
                if self.value.value > Enumeration.MAX:
                    raise ValueError(
                        'enumeration value greater than accepted max')
                elif self.value.value < Enumeration.MIN:
                    raise ValueError(
                        'enumeration value less than accepted min')

    def __repr__(self):
        enum = "enum={0}".format(self.enum.__name__)
        value = "value={0}".format(self.value)
        tag = "tag={0}".format(self.tag)
        return "Enumeration({0}, {1}, {2})".format(enum, value, tag)

    def __str__(self):
        return str(self.value)

    def __eq__(self, other):
        if isinstance(other, Enumeration):
            return ((self.enum == other.enum) and (self.value == other.value))
        else:
            return NotImplemented

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

class Boolean(Base):
    """
    An encodeable object representing a boolean value.

    A Boolean is one of the KMIP primitive object types. It is encoded as an
    unsigned, big-endian, 8-byte value, capable of taking the values True (1)
    or False (0). For more information, see Section 9.1 of the KMIP 1.1
    specification.
    """
    LENGTH = 8

    def __init__(self, value=True, tag=enums.Tags.DEFAULT):
        """
        Create a Boolean object.

        Args:
            value (bool): The value of the Boolean. Optional, defaults to True.
            tag (Tags): An enumeration defining the tag of the Boolean object.
                Optional, defaults to Tags.DEFAULT.
        """
        super(Boolean, self).__init__(tag, type=enums.Types.BOOLEAN)
        self.logger = logging.getLogger(__name__)
        self.value = value
        self.length = self.LENGTH

        self.validate()

    def read_value(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the value of the Boolean object from the input stream.

        Args:
            istream (Stream): A buffer containing the encoded bytes of the
                value of a Boolean object. Usually a BytearrayStream object.
                Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            ValueError: if the read boolean value is not a 0 or 1.
        """
        try:
            value = unpack('!Q', istream.read(self.LENGTH))[0]
        except Exception:
            self.logger.error("Error reading boolean value from buffer")
            raise

        if value == 1:
            self.value = True
        elif value == 0:
            self.value = False
        else:
            raise ValueError("expected: 0 or 1, observed: {0}".format(value))

        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the encoding of the Boolean object from the input stream.

        Args:
            istream (Stream): A buffer containing the encoded bytes of a
                Boolean object. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Boolean, self).read(istream, kmip_version=kmip_version)
        self.read_value(istream, kmip_version=kmip_version)

    def write_value(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the value of the Boolean object to the output stream.

        Args:
            ostream (Stream): A buffer to contain the encoded bytes of the
                value of a Boolean object. Usually a BytearrayStream object.
                Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        try:
            ostream.write(pack('!Q', self.value))
        except Exception:
            self.logger.error("Error writing boolean value to buffer")
            raise

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the encoding of the Boolean object to the output stream.

        Args:
            ostream (Stream): A buffer to contain the encoded bytes of a
                Boolean object. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Boolean, self).write(ostream, kmip_version=kmip_version)
        self.write_value(ostream, kmip_version=kmip_version)

    def validate(self):
        """
        Verify that the value of the Boolean object is valid.

        Raises:
            TypeError: if the value is not of type bool.
        """
        if self.value:
            if not isinstance(self.value, bool):
                raise TypeError("expected: {0}, observed: {1}".format(
                    bool, type(self.value)))

    def __repr__(self):
        return "{0}(value={1})".format(type(self).__name__, repr(self.value))

    def __str__(self):
        return "{0}".format(repr(self.value))

    def __eq__(self, other):
        if isinstance(other, Boolean):
            return self.value == other.value
        else:
            return NotImplemented

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

class TextString(Base):
    PADDING_SIZE = 8
    BYTE_FORMAT = '!c'

    def __init__(self, value=None, tag=enums.Tags.DEFAULT):
        super(TextString, self).__init__(tag, type=enums.Types.TEXT_STRING)

        if value is None:
            self.value = ''
        else:
            self.value = value

        self.validate()

        if self.value is not None:
            self.length = len(self.value)
            self.padding_length = self.PADDING_SIZE - (self.length %
                                                       self.PADDING_SIZE)
            if self.padding_length == self.PADDING_SIZE:
                self.padding_length = 0
        else:
            self.length = None
            self.padding_length = None

    def read_value(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        # Read string text
        self.value = ''
        for _ in range(self.length):
            c = unpack(self.BYTE_FORMAT, istream.read(1))[0]
            if sys.version >= '3':
                c = c.decode()
            self.value += c

        # Read padding and check content
        self.padding_length = self.PADDING_SIZE - (self.length %
                                                   self.PADDING_SIZE)
        if self.padding_length < self.PADDING_SIZE:
            for _ in range(self.padding_length):
                pad = unpack('!B', istream.read(1))[0]
                if pad != 0:
                    raise exceptions.ReadValueError(
                        TextString.__name__,
                        'pad',
                        0,
                        pad
                    )

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(TextString, self).read(istream, kmip_version=kmip_version)
        self.read_value(istream, kmip_version=kmip_version)
        self.validate()

    def write_value(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        # Write string to stream
        for char in self.value:
            ostream.write(pack(self.BYTE_FORMAT, char.encode()))

        # Write padding to stream
        for _ in range(self.padding_length):
            ostream.write(pack('!B', 0))

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(TextString, self).write(ostream, kmip_version=kmip_version)
        self.write_value(ostream, kmip_version=kmip_version)

    def validate(self):
        self.__validate()

    def __validate(self):
        if self.value is not None:
            if not isinstance(self.value, six.string_types):
                msg = exceptions.ErrorStrings.BAD_EXP_RECV
                raise TypeError(msg.format('TextString', 'value', str,
                                           type(self.value)))

    def __repr__(self):
        return "{0}(value={1})".format(type(self).__name__, repr(self.value))

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

    def __eq__(self, other):
        if isinstance(other, TextString):
            return self.value == other.value
        else:
            return NotImplemented

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

class ByteString(Base):
    PADDING_SIZE = 8
    BYTE_FORMAT = '!B'

    def __init__(self, value=None, tag=enums.Tags.DEFAULT):
        super(ByteString, self).__init__(tag, type=enums.Types.BYTE_STRING)

        if value is None:
            self.value = bytes()
        else:
            self.value = bytes(value)

        self.validate()

        if self.value is not None:
            self.length = len(self.value)
            self.padding_length = self.PADDING_SIZE - (self.length %
                                                       self.PADDING_SIZE)
            if self.padding_length == self.PADDING_SIZE:
                self.padding_length = 0
        else:
            self.length = None
            self.padding_length = None

    def read_value(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        # Read bytes into bytearray
        data = bytearray()
        for _ in range(self.length):
            data.append(istream.read(1)[0])
        self.value = bytes(data)

        # Read padding and check content
        self.padding_length = self.PADDING_SIZE - (self.length %
                                                   self.PADDING_SIZE)
        if self.padding_length == self.PADDING_SIZE:
            self.padding_length = 0

        if self.padding_length < self.PADDING_SIZE:
            for _ in range(self.padding_length):
                pad = unpack('!B', istream.read(1))[0]
                if pad != 0:
                    raise exceptions.ReadValueError(
                        TextString.__name__,
                        'pad',
                        0,
                        pad
                    )

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(ByteString, self).read(istream, kmip_version=kmip_version)
        self.read_value(istream, kmip_version=kmip_version)

    def write_value(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        # Write bytes to stream
        data = bytearray(self.value)
        for byte in data:
            ostream.write(pack(self.BYTE_FORMAT, byte))

        # Write padding to stream
        for _ in range(self.padding_length):
            ostream.write(pack('!B', 0))

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        super(ByteString, self).write(ostream, kmip_version=kmip_version)
        self.write_value(ostream, kmip_version=kmip_version)

    def validate(self):
        self.__validate()

    def __validate(self):
        # TODO (peter-hamilton) Test is pointless, value is always bytes. Fix.
        if self.value is not None:
            data_type = type(self.value)
            if data_type is not bytes:
                msg = exceptions.ErrorStrings.BAD_EXP_RECV
                raise TypeError(msg.format('ByteString', 'value', bytes,
                                           data_type))

    def __repr__(self):
        return "{0}(value={1})".format(type(self).__name__, repr(self.value))

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

    def __eq__(self, other):
        if isinstance(other, ByteString):
            return self.value == other.value
        else:
            return NotImplemented

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

class DateTime(LongInteger):
    """
    An encodeable object representing a date/time value.

    A DateTime is one of the KMIP primitive object types. It is encoded as
    a signed, big-endian, 64-bit integer, representing a POSIX time value as
    the number of seconds since the Epoch (1970 January 1, 00:00:00 UTC). For
    more information, see Section 9.1 of the KMIP 1.1 specification.
    """

    def __init__(self, value=None, tag=enums.Tags.DEFAULT):
        """
        Create a DateTime.

        Args:
            value (int): The value of the DateTime in number of seconds since
                the Epoch. See the time package for additional information.
                Optional, defaults to the current time.
            tag (Tags): An enumeration defining the tag of the LongInteger.
                Optional, defaults to Tags.DEFAULT.
        """
        if value is None:
            value = int(time.time())
        super(DateTime, self).__init__(value, tag)
        self.type = enums.Types.DATE_TIME

    def __repr__(self):
        return "DateTime(value={0}, tag={1})".format(self.value, self.tag)

    def __str__(self):
        return time.asctime(time.gmtime(self.value))

class Interval(Base):
    """
    An encodeable object representing an interval of time.

    An Interval is one of the KMIP primitive object types. It is encoded as
    an unsigned, big-endian, 32-bit integer, where the value has a resolution
    of one second. For more information, see Section 9.1 of the KMIP 1.1
    specification.
    """
    LENGTH = 4

    # Bounds for unsigned 32-bit integers
    MIN = 0
    MAX = 4294967296

    def __init__(self, value=0, tag=enums.Tags.DEFAULT):
        super(Interval, self).__init__(tag, type=enums.Types.INTERVAL)

        self.value = value
        self.length = Interval.LENGTH

        self.validate()

    def read(self, istream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Read the encoding of the Interval from the input stream.

        Args:
            istream (stream): A buffer containing the encoded bytes of the
                value of an Interval. Usually a BytearrayStream object.
                Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be decoded. Optional,
                defaults to KMIP 1.0.

        Raises:
            InvalidPrimitiveLength: if the Interval encoding read in has an
                invalid encoded length.
            InvalidPaddingBytes: if the Interval encoding read in does not use
                zeroes for its padding bytes.
        """
        super(Interval, self).read(istream, kmip_version=kmip_version)

        # Check for a valid length before even trying to parse the value.
        if self.length != Interval.LENGTH:
            raise exceptions.InvalidPrimitiveLength(
                "interval length must be {0}".format(Interval.LENGTH))

        # Decode the Interval value and the padding bytes.
        self.value = unpack('!I', istream.read(Interval.LENGTH))[0]
        pad = unpack('!I', istream.read(Interval.LENGTH))[0]

        # Verify that the padding bytes are zero bytes.
        if pad != 0:
            raise exceptions.InvalidPaddingBytes("padding bytes must be zero")

        self.validate()

    def write(self, ostream, kmip_version=enums.KMIPVersion.KMIP_1_0):
        """
        Write the encoding of the Interval to the output stream.

        Args:
            ostream (stream): A buffer to contain the encoded bytes of an
                Interval. Usually a BytearrayStream object. Required.
            kmip_version (KMIPVersion): An enumeration defining the KMIP
                version with which the object will be encoded. Optional,
                defaults to KMIP 1.0.
        """
        super(Interval, self).write(ostream, kmip_version=kmip_version)
        ostream.write(pack('!I', self.value))
        ostream.write(pack('!I', 0))

    def validate(self):
        """
        Verify that the value of the Interval is valid.

        Raises:
            TypeError: if the value is not of type int or long
            ValueError: if the value cannot be represented by an unsigned
                32-bit integer
        """
        if self.value is not None:
            if type(self.value) not in six.integer_types:
                raise TypeError('expected (one of): {0}, observed: {1}'.format(
                    six.integer_types, type(self.value)))
            else:
                if self.value > Interval.MAX:
                    raise ValueError(
                        'interval value greater than accepted max')
                elif self.value < Interval.MIN:
                    raise ValueError('interval value less than accepted min')

    def __repr__(self):
        value = "value={0}".format(self.value)
        tag = "tag={0}".format(self.tag)
        return "Interval({0}, {1})".format(value, tag)

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

    def __eq__(self, other):
        if isinstance(other, Interval):
            return self.value == other.value
        else:
            return NotImplemented

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