# 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 logging import six import sys from struct import pack, unpack from enum import Enum from kmip.core.enums import Types from kmip.core.enums import Tags from kmip.core.errors import ErrorStrings from kmip.core import errors from kmip.core import utils class Base(object): TAG_SIZE = 3 TYPE_SIZE = 1 LENGTH_SIZE = 4 def __init__(self, tag=Tags.DEFAULT, type=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 errors.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 = Tags(tag) # Verify that the tag matches for the current object if enum_tag is not self.tag: raise errors.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 errors.ReadValueError(Base.__name__, 'type', min_bytes, '{0} bytes'.format(num_bytes)) typ = unpack('!B', tts)[0] enum_typ = Types(typ) if enum_typ is not self.type: raise errors.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 errors.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): 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 Types: msg = ErrorStrings.BAD_EXP_RECV raise TypeError(msg.format(Base.__name__, 'type', Types, type(self.type))) ostream.write(pack('!B', self.type.value)) def write_length(self, ostream): if type(self.length) is not int: msg = 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 errors.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): 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=Tags.DEFAULT): super(Struct, self).__init__(tag, type=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=Tags.DEFAULT, signed=True): super(Integer, self).__init__(tag, type=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 errors.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 is not 0: raise errors.ReadValueError(Integer.__name__, 'pad', 0, pad) self.validate() def read(self, istream): super(Integer, self).read(istream) self.read_value(istream) def write_value(self, ostream): ostream.write(pack(self.pack_string, self.value)) ostream.write(pack(self.pack_string, 0)) def write(self, ostream): super(Integer, self).write(ostream) self.write_value(ostream) 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__, repr(self.value)) def __str__(self): return "{0}".format(repr(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 class LongInteger(Base): LENGTH = 8 def __init__(self, value=None, tag=Tags.DEFAULT): super(LongInteger, self).__init__(tag, type=Types.LONG_INTEGER) self.value = value self.length = self.LENGTH self.validate() def read_value(self, istream): if self.length is not self.LENGTH: raise errors.ReadValueError(LongInteger.__name__, 'length', self.LENGTH, self.length) self.value = unpack('!q', istream.read(self.length))[0] self.validate() def read(self, istream): super(LongInteger, self).read(istream) self.read_value(istream) def write_value(self, ostream): ostream.write(pack('!q', self.value)) def write(self, ostream): super(LongInteger, self).write(ostream) self.write_value(ostream) def validate(self): self.__validate() def __validate(self): if self.value is not None: data_type = type(self.value) if data_type not in six.integer_types: raise errors.StateTypeError( LongInteger.__name__, "{0}".format(six.integer_types), data_type) num_bytes = utils.count_bytes(self.value) if num_bytes > self.length: raise errors.StateOverflowError( LongInteger.__name__, 'value', self.length, num_bytes) def __repr__(self): return '' % (self.value) class BigInteger(Base): BLOCK_SIZE = 8 SHIFT_SIZE = 64 def __init__(self, value=None, tag=Tags.DEFAULT): super(BigInteger, self).__init__(tag, type=Types.BIG_INTEGER) self.value = value if self.value is not None: self.real_length = utils.count_bytes(self.value) self.padding_length = self.BLOCK_SIZE - (self.length % self.BLOCK_SIZE) if self.padding_length == self.BLOCK_SIZE: self.padding_length = 0 else: self.length = None self.padding_length = None self.validate() def read_value(self, istream): if (self.length < self.BLOCK_SIZE) or (self.length % self.BLOCK_SIZE): raise errors.InvalidLengthError(BigInteger.__name__, ('multiple' 'of {0}'.format(self.BLOCK_SIZE)), self.length) self.value = 0 num_blocks = self.length / self.BLOCK_SIZE # Read first block as signed data self.value = unpack('!q', str(istream.read(self.BLOCK_SIZE)))[0] # Shift current value and add on next unsigned block for _ in range(num_blocks - 1): self.value = self.value << self.SHIFT_SIZE stream_data = istream.read(self.BLOCK_SIZE) self.value += unpack('!Q', stream_data)[0] self.validate() def read(self, istream): super(BigInteger, self).read(istream) self.read_value(istream) def write_value(self, ostream): # 1. Determine the sign of the value (+/-); save it. # 2. Extend hex of value with 0s until encoding is right size (8x). # 3. Write out each block of the encoding as signed, 2s complement: # pack('!q', sign * block) # Determine sign for padding pad_byte = 0x00 pad_nybl = 0x0 if self.value < 0: pad_byte = 0xff pad_nybl = 0xf # Compose padding bytes pad = '' for _ in range(self.padding_length): pad += hex(pad_byte)[2:] str_rep = hex(self.value).rstrip("Ll")[2:] if len(str_rep) % 2: pad += hex(pad_nybl)[2] # Compose value for block-based write str_rep = pad + str_rep num_blocks = len(str_rep) / self.BLOCK_SIZE # Write first block as signed data block = int(str_rep[0:self.BLOCK_SIZE], 16) ostream.write(pack('!q', block)) # Write remaining blocks as unsigned data for i in range(1, num_blocks): block = str_rep[(self.BLOCK_SIZE * i):(self.BLOCK_SIZE * (i + 1))] block = int(block, 16) ostream.write(pack('!Q', block)) def write(self, ostream): super(BigInteger, self).write(ostream) self.write_value(ostream) def validate(self): self.__validate() def __validate(self): if self.value is not None: data_type = type(self.value) if data_type not in six.integer_types: raise errors.StateTypeError( BigInteger.__name__, "{0}".format(six.integer_types), data_type) num_bytes = utils.count_bytes(self.length) if num_bytes > self.LENGTH_SIZE: raise errors.StateOverflowError( BigInteger.__name__, 'length', self.LENGTH_SIZE, num_bytes) class Enumeration(Integer): ENUM_TYPE = None def __init__(self, value=None, tag=Tags.DEFAULT): self.enum = value self.validate() if self.enum is None: super(Enumeration, self).__init__(None, tag, False) else: super(Enumeration, self).__init__(self.enum.value, tag, False) self.type = Types.ENUMERATION def read(self, istream): super(Enumeration, self).read(istream) self.enum = self.ENUM_TYPE(self.value) self.validate() def write(self, ostream): super(Enumeration, self).write(ostream) def validate(self): self.__validate() def __validate(self): if self.enum is not None: if not isinstance(self.enum, Enum): raise TypeError("expected {0}, observed {1}".format( type(self.enum), Enum)) def __repr__(self): return "{0}(value={1})".format(type(self).__name__, self.enum) def __str__(self): return "{0}.{1}".format(type(self.enum).__name__, self.enum.name) 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=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=Types.BOOLEAN) self.logger = logging.getLogger(__name__) self.value = value self.length = self.LENGTH self.validate() def read_value(self, istream): """ 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. Raises: ValueError: if the read boolean value is not a 0 or 1. """ try: value = unpack('!Q', istream.read(self.LENGTH))[0] except: 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): """ 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. """ super(Boolean, self).read(istream) self.read_value(istream) def write_value(self, ostream): """ 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. """ try: ostream.write(pack('!Q', self.value)) except: self.logger.error("Error writing boolean value to buffer") raise def write(self, ostream): """ 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. """ super(Boolean, self).write(ostream) self.write_value(ostream) 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=Tags.DEFAULT): super(TextString, self).__init__(tag, type=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): # 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 is not 0: raise errors.ReadValueError(TextString.__name__, 'pad', 0, pad) def read(self, istream): super(TextString, self).read(istream) self.read_value(istream) self.validate() def write_value(self, ostream): # Write string to stream for char in self.value: if sys.version < '3': c = char else: c = char.encode() ostream.write(pack(self.BYTE_FORMAT, c)) # Write padding to stream for _ in range(self.padding_length): ostream.write(pack('!B', 0)) def write(self, ostream): super(TextString, self).write(ostream) self.write_value(ostream) def validate(self): self.__validate() def __validate(self): if self.value is not None: data_type = type(self.value) if data_type is not str: msg = ErrorStrings.BAD_EXP_RECV raise TypeError(msg.format('TextString', 'value', str, data_type)) 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, 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=Tags.DEFAULT): super(ByteString, self).__init__(tag, type=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): # 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 is not 0: raise errors.ReadValueError(TextString.__name__, 'pad', 0, pad) def read(self, istream): super(ByteString, self).read(istream) self.read_value(istream) def write_value(self, ostream): # 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): super(ByteString, self).write(ostream) self.write_value(ostream) 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 = 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): def __init__(self, value=None, tag=Tags.DEFAULT): super(DateTime, self).__init__(value, tag) self.type = Types.DATE_TIME class Interval(Integer): def __init__(self, value=None, tag=Tags.DEFAULT): super(Interval, self).__init__(value, tag) self.type = Types.INTERVAL