diff --git a/kmip/core/primitives.py b/kmip/core/primitives.py index c8003e3..c089ab6 100644 --- a/kmip/core/primitives.py +++ b/kmip/core/primitives.py @@ -15,6 +15,7 @@ import logging import six +import struct import sys from struct import pack, unpack @@ -357,105 +358,131 @@ class LongInteger(Base): class BigInteger(Base): - BLOCK_SIZE = 8 - SHIFT_SIZE = 64 + """ + An encodeable object representing a big integer value. - def __init__(self, value=None, tag=Tags.DEFAULT): + 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=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): + """ + 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. + + Raises: + InvalidPrimitiveLength: if the big integer encoding read in has + an invalid encoded length. + """ 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) + # 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)) - # Determine sign for padding - pad_byte = 0x00 - pad_nybl = 0x0 + sign = 1 + binary = '' - if self.value < 0: - pad_byte = 0xff - pad_nybl = 0xf + # 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 - # Compose padding bytes - pad = '' - for _ in range(self.padding_length): - pad += hex(pad_byte)[2:] + # 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') - str_rep = hex(self.value).rstrip("Ll")[2:] - if len(str_rep) % 2: - pad += hex(pad_nybl)[2] + pivot = binary.rfind('0') + binary = binary[0:pivot] + '1' + ('0' * len(binary[pivot + 1:])) - # 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)) + # Convert the value back to an integer and reapply the sign. + self.value = int(binary, 2) * sign def write(self, ostream): + """ + 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. + """ + # 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) - self.write_value(ostream) + ostream.write(hexadecimal) def validate(self): - self.__validate() + """ + Verify that the value of the BigInteger is valid. - def __validate(self): + Raises: + TypeError: if the value is not of type int or long + """ 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) + 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(Integer): diff --git a/kmip/tests/unit/core/primitives/test_big_integer.py b/kmip/tests/unit/core/primitives/test_big_integer.py index 7082e96..2037354 100644 --- a/kmip/tests/unit/core/primitives/test_big_integer.py +++ b/kmip/tests/unit/core/primitives/test_big_integer.py @@ -15,8 +15,7 @@ import testtools -from kmip.core import enums -from kmip.core import errors +from kmip.core import exceptions from kmip.core import primitives from kmip.core import utils @@ -25,208 +24,270 @@ class TestBigInteger(testtools.TestCase): def setUp(self): super(TestBigInteger, self).setUp() - self.stream = utils.BytearrayStream() - self.max_byte_long = 18446744073709551615 - self.max_long = 9223372036854775807 - self.bad_value = ( - 'Bad primitives.BigInteger.{0} after init: expected {1}, ' - 'received {2}') - self.bad_write = ( - 'Bad primitives.BigInteger write: expected {0} bytes, ' - 'received {1} bytes') - self.bad_encoding = ( - 'Bad primitives.BigInteger write: encoding mismatch') - self.bad_read = ( - 'Bad primitives.BigInteger.value read: expected {0}, ' - 'received {1}') + + # Encodings and values taken from Sections 5.1, 13.3 and 18.2 of the + # KMIP 1.1 testing documentation. + self.value_positive = int( + '74570697368583857894612671217453076717255131155396275504564761583' + '15899148268876158582639566401239193216235126746176682996459367959' + '36793366865165780165066709295778050045731105353780121783233185565' + '36420486996200625818559496541368747791032257508332162004121562017' + '72772159096834586599791505043949123930975157363117571140205992199' + '59827555693853730430222361950476764952992840295849053634702315874' + '87536235568284292445148693873502200712082861995083783995720224553' + '38838078028390162249415071016709848797960500969432640102143437177' + '60785867099769472998343832254180691121895373077720157164352949735' + '8482684822484513735382434823977') + self.value_negative = -1000 + + self.encoding_zero = utils.BytearrayStream( + b'\x42\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00' + b'\x00') + self.encoding_positive = utils.BytearrayStream( + b'\x42\x00\x00\x04\x00\x00\x01\x00\x3B\x12\x45\x5D\x53\xC1\x81\x65' + b'\x16\xC5\x18\x49\x3F\x63\x98\xAA\xFA\x72\xB1\x7D\xFA\x89\x4D\xB8' + b'\x88\xA7\xD4\x8C\x0A\x47\xF6\x25\x79\xA4\xE6\x44\xF8\x6D\xA7\x11' + b'\xFE\xC8\x50\xCD\xD9\xDB\xBD\x17\xF6\x9A\x44\x3D\x2E\xC1\xDD\x60' + b'\xD3\xC6\x18\xFA\x74\xCD\xE5\xFD\xAF\xAB\xD6\xBA\xA2\x6E\xB0\xA3' + b'\xAD\xB4\xDE\xF6\x48\x0F\xB1\x21\x8C\xD3\xB0\x83\xE2\x52\xE8\x85' + b'\xB6\xF0\x72\x9F\x98\xB2\x14\x4D\x2B\x72\x29\x3E\x1B\x11\xD7\x33' + b'\x93\xBC\x41\xF7\x5B\x15\xEE\x3D\x75\x69\xB4\x99\x5E\xD1\xA1\x44' + b'\x25\xDA\x43\x19\xB7\xB2\x6B\x0E\x8F\xEF\x17\xC3\x75\x42\xAE\x5C' + b'\x6D\x58\x49\xF8\x72\x09\x56\x7F\x39\x25\xA4\x7B\x01\x6D\x56\x48' + b'\x59\x71\x7B\xC5\x7F\xCB\x45\x22\xD0\xAA\x49\xCE\x81\x6E\x5B\xE7' + b'\xB3\x08\x81\x93\x23\x6E\xC9\xEF\xFF\x14\x08\x58\x04\x5B\x73\xC5' + b'\xD7\x9B\xAF\x38\xF7\xC6\x7F\x04\xC5\xDC\xF0\xE3\x80\x6A\xD9\x82' + b'\xD1\x25\x90\x58\xC3\x47\x3E\x84\x71\x79\xA8\x78\xF2\xC6\xB3\xBD' + b'\x96\x8F\xB9\x9E\xA4\x6E\x91\x85\x89\x2F\x36\x76\xE7\x89\x65\xC2' + b'\xAE\xD4\x87\x7B\xA3\x91\x7D\xF0\x7C\x5E\x92\x74\x74\xF1\x9E\x76' + b'\x4B\xA6\x1D\xC3\x8D\x63\xBF\x29') + self.encoding_negative = utils.BytearrayStream( + b'\x42\x00\x00\x04\x00\x00\x00\x08\xFF\xFF\xFF\xFF\xFF\xFF\xFC' + b'\x18') + self.encoding_bad_length = utils.BytearrayStream( + b'\x42\x00\x00\x04\x00\x00\x01\x01\x3B\x12\x45\x5D\x53\xC1\x81\x65' + b'\x16\xC5\x18\x49\x3F\x63\x98\xAA\xFA\x72\xB1\x7D\xFA\x89\x4D\xB8' + b'\x88\xA7\xD4\x8C\x0A\x47\xF6\x25\x79\xA4\xE6\x44\xF8\x6D\xA7\x11' + b'\xFE\xC8\x50\xCD\xD9\xDB\xBD\x17\xF6\x9A\x44\x3D\x2E\xC1\xDD\x60' + b'\xD3\xC6\x18\xFA\x74\xCD\xE5\xFD\xAF\xAB\xD6\xBA\xA2\x6E\xB0\xA3' + b'\xAD\xB4\xDE\xF6\x48\x0F\xB1\x21\x8C\xD3\xB0\x83\xE2\x52\xE8\x85' + b'\xB6\xF0\x72\x9F\x98\xB2\x14\x4D\x2B\x72\x29\x3E\x1B\x11\xD7\x33' + b'\x93\xBC\x41\xF7\x5B\x15\xEE\x3D\x75\x69\xB4\x99\x5E\xD1\xA1\x44' + b'\x25\xDA\x43\x19\xB7\xB2\x6B\x0E\x8F\xEF\x17\xC3\x75\x42\xAE\x5C' + b'\x6D\x58\x49\xF8\x72\x09\x56\x7F\x39\x25\xA4\x7B\x01\x6D\x56\x48' + b'\x59\x71\x7B\xC5\x7F\xCB\x45\x22\xD0\xAA\x49\xCE\x81\x6E\x5B\xE7' + b'\xB3\x08\x81\x93\x23\x6E\xC9\xEF\xFF\x14\x08\x58\x04\x5B\x73\xC5' + b'\xD7\x9B\xAF\x38\xF7\xC6\x7F\x04\xC5\xDC\xF0\xE3\x80\x6A\xD9\x82' + b'\xD1\x25\x90\x58\xC3\x47\x3E\x84\x71\x79\xA8\x78\xF2\xC6\xB3\xBD' + b'\x96\x8F\xB9\x9E\xA4\x6E\x91\x85\x89\x2F\x36\x76\xE7\x89\x65\xC2' + b'\xAE\xD4\x87\x7B\xA3\x91\x7D\xF0\x7C\x5E\x92\x74\x74\xF1\x9E\x76' + b'\x4B\xA6\x1D\xC3\x8D\x63\xBF\x29') def tearDown(self): super(TestBigInteger, self).tearDown() - def test_big_integer(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger(0) + def test_init(self): + """ + Test that a BigInteger can be instantiated. + """ + big_int = primitives.BigInteger(1) + self.assertEqual(1, big_int.value) - self.assertEqual(0, i.value, - self.bad_value.format('value', 0, i.value)) - self.assertEqual(1, i.length, - self.bad_value.format('length', 1, i.length)) - self.assertEqual(i.BLOCK_SIZE - 1, i.padding_length, - self.bad_value.format('padding_length', - i.BLOCK_SIZE - 1, - i.padding_length)) + def test_init_unset(self): + """ + Test that a BigInteger can be instantiated with no input. + """ + big_int = primitives.BigInteger() + self.assertEqual(0, big_int.value) - def test_big_integer_unset(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger() + def test_init_big_positive(self): + """ + Test that a BigInteger can be instantiated with large positive input. + """ + big_int = primitives.BigInteger(self.value_positive) + self.assertEqual(self.value_positive, big_int.value) - self.assertEqual(None, i.value, - self.bad_value.format('value', None, i.value)) - self.assertEqual(None, i.length, - self.bad_value.format('length', None, i.length)) - self.assertEqual(None, i.padding_length, - self.bad_value.format('padding_length', None, - i.padding_length)) + def test_init_negative(self): + """ + Test that a BigInteger can be instantiated with negative input. + """ + big_int = primitives.BigInteger(self.value_negative) + self.assertEqual(self.value_negative, big_int.value) - def test_validate_on_valid(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger() - i.value = 0 - i.length = i.BLOCK_SIZE - i.padding_length = 0 - - # Check no exception thrown - i.validate() - - def test_validate_on_valid_long(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger() - i.value = self.max_long + 1 - i.length = i.BLOCK_SIZE - i.padding_length = 0 - - # Check no exception thrown - i.validate() - - def test_validate_on_valid_unset(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger() - - # Check no exception thrown - i.validate() - - def test_validate_on_invalid_type(self): - self.skip('primitives.BigInteger implementation incomplete') - i = primitives.BigInteger() - i.value = 'test' - - self.assertRaises(errors.StateTypeError, i.validate) - - def test_write(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00' - b'\x01') - i = primitives.BigInteger(1) - i.TAG = enums.Tags.ACTIVATION_DATE - i.write(self.stream) - - result = self.stream.read() - len_exp = len(encoding) - len_rcv = len(result) - - self.assertEqual(len_exp, len_rcv, self.bad_write.format(len_exp, - len_rcv)) - self.assertEqual(encoding, result, self.bad_encoding) - - def test_write_zero(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00' - b'\x00') - i = primitives.BigInteger(0) - i.TAG = enums.Tags.ACTIVATION_DATE - i.write(self.stream) - - result = self.stream.read() - len_exp = len(encoding) - len_rcv = len(result) - - self.assertEqual(len_exp, len_rcv, self.bad_write.format(len_exp, - len_rcv)) - self.assertEqual(encoding, result, self.bad_encoding) - - def test_write_max_positive_value(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x7f\xff\xff\xff\xff\xff\xff' - b'\xff') - i = primitives.BigInteger(self.max_long) - i.TAG = enums.Tags.ACTIVATION_DATE - i.write(self.stream) - - result = self.stream.read() - len_exp = len(encoding) - len_rcv = len(result) - - self.assertEqual(len_exp, len_rcv, self.bad_write.format(len_exp, - len_rcv)) - self.assertEqual(encoding, result, self.bad_encoding) - - def test_write_min_negative_value(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\xff\xff\xff\xff\xff\xff\xff' - b'\xff') - i = primitives.BigInteger(-1) - i.TAG = enums.Tags.ACTIVATION_DATE - i.write(self.stream) - - result = self.stream.read() - len_exp = len(encoding) - len_rcv = len(result) - - self.assertEqual(len_exp, len_rcv, self.bad_write.format(len_exp, - len_rcv)) - self.assertEqual(encoding, result, self.bad_encoding) - - def test_read(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00' - b'\x01') - self.stream = utils.BytearrayStream(encoding) - i = primitives.BigInteger() - i.TAG = enums.Tags.ACTIVATION_DATE - i.read(self.stream) - - self.assertEqual(1, i.value, self.bad_read.format(1, i.value)) + def test_validate_on_invalid(self): + """ + Test that a TypeError is thrown on input of invalid type (e.g., str). + """ + self.assertRaises(TypeError, primitives.BigInteger, 'invalid') def test_read_zero(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00' - b'\x00') - self.stream = utils.BytearrayStream(encoding) - i = primitives.BigInteger() - i.TAG = enums.Tags.ACTIVATION_DATE - i.read(self.stream) + """ + Test that a BigInteger representing the value 0 can be read from a + byte stream. + """ + big_int = primitives.BigInteger() + big_int.read(self.encoding_zero) + self.assertEqual(0, big_int.value) - self.assertEqual(0, i.value, self.bad_read.format(0, i.value)) + def test_read_positive(self): + """ + Test that a BigInteger representing a big positive value can be read + from a byte stream. + """ + big_int = primitives.BigInteger() + big_int.read(self.encoding_positive) + self.assertEqual(self.value_positive, big_int.value) - def test_read_max_positive_value(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\x7f\xff\xff\xff\xff\xff\xff' - b'\xff') - self.stream = utils.BytearrayStream(encoding) - i = primitives.BigInteger() - i.TAG = enums.Tags.ACTIVATION_DATE - i.read(self.stream) - - self.assertEqual(self.max_long, i.value, - self.bad_read.format(1, i.value)) - - def test_read_min_negative_value(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x08\xff\xff\xff\xff\xff\xff\xff' - b'\xff') - self.stream = utils.BytearrayStream(encoding) - i = primitives.BigInteger() - i.TAG = enums.Tags.ACTIVATION_DATE - i.read(self.stream) - - self.assertEqual(-1, i.value, - self.bad_read.format(1, i.value)) + def test_read_negative(self): + """ + Test that a BigInteger representing a negative value can be read from + a byte stream. + """ + big_int = primitives.BigInteger() + big_int.read(self.encoding_negative) + self.assertEqual(self.value_negative, big_int.value) def test_read_on_invalid_length(self): - self.skip('primitives.BigInteger implementation incomplete') - encoding = ( - b'\x42\x00\x01\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00') - self.stream = utils.BytearrayStream(encoding) - i = primitives.BigInteger() - i.TAG = enums.Tags.ACTIVATION_DATE + """ + Test that an InvalidPrimitiveLength exception is thrown when attempting + to decode a BigInteger with an invalid length. + """ + big_int = primitives.BigInteger() + self.assertRaises( + exceptions.InvalidPrimitiveLength, big_int.read, + self.encoding_bad_length) - self.assertRaises(errors.InvalidLengthError, i.read, self.stream) + def test_write_zero(self): + """ + Test that a BigInteger representing the value 0 can be read written to + a byte stream. + """ + stream = utils.BytearrayStream() + big_int = primitives.BigInteger() + big_int.write(stream) + self.assertEqual(self.encoding_zero, stream) + + def test_write_positive(self): + """ + Test that a BigInteger representing a big positive value can be written + to a byte stream. + """ + stream = utils.BytearrayStream() + big_int = primitives.BigInteger(self.value_positive) + big_int.write(stream) + self.assertEqual(self.encoding_positive, stream) + + def test_write_negative(self): + """ + Test that a BigInteger representing a negative value can be written to + a byte stream. + """ + stream = utils.BytearrayStream() + big_int = primitives.BigInteger(self.value_negative) + big_int.write(stream) + self.assertEqual(self.encoding_negative, stream) + + def test_repr(self): + """ + Test that the representation of a BigInteger is formatted properly. + """ + long_int = primitives.BigInteger() + value = "value={0}".format(long_int.value) + tag = "tag={0}".format(long_int.tag) + self.assertEqual( + "BigInteger({0}, {1})".format(value, tag), repr(long_int)) + + def test_str(self): + """ + Test that the string representation of a BigInteger is formatted + properly. + """ + self.assertEqual("0", str(primitives.BigInteger())) + + def test_equal_on_equal(self): + """ + Test that the equality operator returns True when comparing two + BigIntegers. + """ + a = primitives.BigInteger(1) + b = primitives.BigInteger(1) + + self.assertTrue(a == b) + self.assertTrue(b == a) + + def test_equal_on_equal_and_empty(self): + """ + Test that the equality operator returns True when comparing two + BigIntegers. + """ + a = primitives.BigInteger() + b = primitives.BigInteger() + + self.assertTrue(a == b) + self.assertTrue(b == a) + + def test_equal_on_not_equal(self): + """ + Test that the equality operator returns False when comparing two + BigIntegers with different values. + """ + a = primitives.BigInteger(1) + b = primitives.BigInteger(2) + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_equal_on_type_mismatch(self): + """ + Test that the equality operator returns False when comparing a + BigInteger to a non-BigInteger object. + """ + a = primitives.BigInteger() + b = 'invalid' + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_not_equal_on_equal(self): + """ + Test that the inequality operator returns False when comparing + two BigIntegers with the same values. + """ + a = primitives.BigInteger(1) + b = primitives.BigInteger(1) + + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_not_equal_on_equal_and_empty(self): + """ + Test that the inequality operator returns False when comparing + two BigIntegers. + """ + a = primitives.BigInteger() + b = primitives.BigInteger() + + self.assertFalse(a != b) + self.assertFalse(b != a) + + def test_not_equal_on_not_equal(self): + """ + Test that the inequality operator returns True when comparing two + BigIntegers with different values. + """ + a = primitives.BigInteger(1) + b = primitives.BigInteger(2) + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_not_equal_on_type_mismatch(self): + """ + Test that the inequality operator returns True when comparing a + BigInteger to a non-BigInteger object. + """ + a = primitives.BigInteger() + b = 'invalid' + + self.assertTrue(a != b) + self.assertTrue(b != a)