From 89a6e21a066d58406086eca7ed77f1e3278d3dcc Mon Sep 17 00:00:00 2001
From: Peter Hamilton <peter.hamilton@jhuapl.edu>
Date: Thu, 27 Aug 2015 09:00:00 -0400
Subject: [PATCH] Updating support for the BigInteger primitive

This change fixes various bugs with the original BigInteger
implementation, adding in a working version of the primitive. A full
unit test suite is included.
---
 kmip/core/primitives.py                       | 185 ++++----
 .../unit/core/primitives/test_big_integer.py  | 445 ++++++++++--------
 2 files changed, 359 insertions(+), 271 deletions(-)

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)