def test_put_bitstream_copy_self(self): """ Test using the put_bitstream_copy method with the same BitStream object as origin and destination. """ bitstream = BitStream() # Generate a random string of bits, ie: ('0' | '1')* num_bits = 50 bits = "" for i in range(0,num_bits): bits += random.choice(('0','1')) # inefficient, but ok for a test. # Put those bits in the BitStream... bitstream.put_bit_dump_string(bits) # ... copy the bitstream into itself at any point: bitstream.seek(random.randint(0,50)) bitstream.put_bitstream_copy(bitstream) # Check that the bitstream was unchanged by the previous operation: # (overwriting data with the same data is the same as doing nothing, # except that the current position is changed to the end of the stream) self.assertEquals(bitstream.get_length(),num_bits) self.assertEquals(bitstream.get_current_pos(),num_bits) bitstream.seek(0) read_bits = bitstream.get_bit_dump_string(bitstream.get_length()) self.assertEqual(read_bits, bits)
def test_put_bitstream_copy_self(self): """ Test using the put_bitstream_copy method with the same BitStream object as origin and destination. """ bitstream = BitStream() # Generate a random string of bits, ie: ('0' | '1')* num_bits = 50 bits = "" for i in range(0, num_bits): bits += random.choice( ('0', '1')) # inefficient, but ok for a test. # Put those bits in the BitStream... bitstream.put_bit_dump_string(bits) # ... copy the bitstream into itself at any point: bitstream.seek(random.randint(0, 50)) bitstream.put_bitstream_copy(bitstream) # Check that the bitstream was unchanged by the previous operation: # (overwriting data with the same data is the same as doing nothing, # except that the current position is changed to the end of the stream) self.assertEquals(bitstream.get_length(), num_bits) self.assertEquals(bitstream.get_current_pos(), num_bits) bitstream.seek(0) read_bits = bitstream.get_bit_dump_string(bitstream.get_length()) self.assertEqual(read_bits, bits)
def test_string_character_zero(self): """ Test that we are handling character zero ('\\0') correctly. """ bitstream = BitStream() bitstream.put_string("Evil \0String.") # 13 chars/bytes self.assertEquals(bitstream.get_length(), 13 * 8) bitstream.seek(0) self.assertEquals(bitstream.get_string(bitstream.get_length()), "Evil \0String.")
def test_string_character_zero(self): """ Test that we are handling character zero ('\\0') correctly. """ bitstream = BitStream() bitstream.put_string("Evil \0String.") # 13 chars/bytes self.assertEquals(bitstream.get_length(),13*8) bitstream.seek(0) self.assertEquals(bitstream.get_string(bitstream.get_length()), "Evil \0String.")
def test_multiformat_write_multiformat_read(self): """ This test writes numeric, byte and string data to a stream and then reads the whole stream as binary, hex and base64 data, ensuring that the output is the expected one in each case. """ # Write a number, 2 bytes and a string bitstream = BitStream() bitstream.put_num(10438341575639894917, 64) bitstream.put_byte(230) bitstream.put_byte(191) bitstream.put_string("ÄäÜüßTestЯБГДЖЙŁĄŻStringĘĆŃŚŹてす" \ "とアイウエオカキク4234ケコサシスセソタチツテ") # Read in binary, hex and base64 formats expected_bits = \ "1001000011011100011100000101101110111100001101010000101110000101" \ "1110011010111111110000111000010011000011101001001100001110011100" \ "1100001110111100110000111001111101010100011001010111001101110100" \ "1101000010101111110100001001000111010000100100111101000010010100" \ "1101000010010110110100001001100111000101100000011100010010000100" \ "1100010110111011010100110111010001110010011010010110111001100111" \ "1100010010011000110001001000011011000101100000111100010110011010" \ "1100010110111001111000111000000110100110111000111000000110011001" \ "1110001110000001101010001110111110111101101100011110111110111101" \ "1011001011101111101111011011001111101111101111011011010011101111" \ "1011110110110101111011111011110110110110111011111011110110110111" \ "1110111110111101101110000011010000110010001100110011010011101111" \ "1011110110111001111011111011110110111010111011111011110110111011" \ "1110111110111101101111001110111110111101101111011110111110111101" \ "1011111011101111101111011011111111101111101111101000000011101111" \ "1011111010000001111011111011111010000010111011111011111010000011" expected_hex = \ "90dc705bbc350b85e6bfc384c3a4c39cc3bcc39f54657374d0afd091d093d094" \ "d096d099c581c484c5bb537472696e67c498c486c583c59ac5b9e381a6e38199" \ "e381a8efbdb1efbdb2efbdb3efbdb4efbdb5efbdb6efbdb7efbdb834323334ef" \ "bdb9efbdbaefbdbbefbdbcefbdbdefbdbeefbdbfefbe80efbe81efbe82efbe83" expected_base64 = \ "kNxwW7w1C4Xmv8OEw6TDnMO8w59UZXN00K/QkdCT0JTQltCZxYHEhMW7U3RyaW5n" \ "xJjEhsWDxZrFueOBpuOBmeOBqO+9se+9su+9s++9tO+9te+9tu+9t++9uDQyMzTv" \ "vbnvvbrvvbvvvbzvvb3vvb7vvb/vvoDvvoHvvoLvvoM=" bitstream.seek(0) self.assertEquals( \ bitstream.get_bit_dump_string(bitstream.get_length()), expected_bits) bitstream.seek(0) self.assertEquals(bitstream.get_hex(bitstream.get_length()).lower(), expected_hex) bitstream.seek(0) self.assertEquals(bitstream.get_base64(bitstream.get_length()), expected_base64)
def test_multiformat_write_multiformat_read(self): """ This test writes numeric, byte and string data to a stream and then reads the whole stream as binary, hex and base64 data, ensuring that the output is the expected one in each case. """ # Write a number, 2 bytes and a string bitstream = BitStream() bitstream.put_num(10438341575639894917, 64) bitstream.put_byte(230) bitstream.put_byte(191) bitstream.put_string("ÄäÜüßTestЯБГДЖЙŁĄŻStringĘĆŃŚŹてす" \ "とアイウエオカキク4234ケコサシスセソタチツテ") # Read in binary, hex and base64 formats expected_bits = \ "1001000011011100011100000101101110111100001101010000101110000101" \ "1110011010111111110000111000010011000011101001001100001110011100" \ "1100001110111100110000111001111101010100011001010111001101110100" \ "1101000010101111110100001001000111010000100100111101000010010100" \ "1101000010010110110100001001100111000101100000011100010010000100" \ "1100010110111011010100110111010001110010011010010110111001100111" \ "1100010010011000110001001000011011000101100000111100010110011010" \ "1100010110111001111000111000000110100110111000111000000110011001" \ "1110001110000001101010001110111110111101101100011110111110111101" \ "1011001011101111101111011011001111101111101111011011010011101111" \ "1011110110110101111011111011110110110110111011111011110110110111" \ "1110111110111101101110000011010000110010001100110011010011101111" \ "1011110110111001111011111011110110111010111011111011110110111011" \ "1110111110111101101111001110111110111101101111011110111110111101" \ "1011111011101111101111011011111111101111101111101000000011101111" \ "1011111010000001111011111011111010000010111011111011111010000011" expected_hex = \ "90dc705bbc350b85e6bfc384c3a4c39cc3bcc39f54657374d0afd091d093d094" \ "d096d099c581c484c5bb537472696e67c498c486c583c59ac5b9e381a6e38199" \ "e381a8efbdb1efbdb2efbdb3efbdb4efbdb5efbdb6efbdb7efbdb834323334ef" \ "bdb9efbdbaefbdbbefbdbcefbdbdefbdbeefbdbfefbe80efbe81efbe82efbe83" expected_base64 = \ "kNxwW7w1C4Xmv8OEw6TDnMO8w59UZXN00K/QkdCT0JTQltCZxYHEhMW7U3RyaW5n" \ "xJjEhsWDxZrFueOBpuOBmeOBqO+9se+9su+9s++9tO+9te+9tu+9t++9uDQyMzTv" \ "vbnvvbrvvbvvvbzvvb3vvb7vvb/vvoDvvoHvvoLvvoM=" bitstream.seek(0) self.assertEquals( \ bitstream.get_bit_dump_string(bitstream.get_length()), expected_bits) bitstream.seek(0) self.assertEquals( bitstream.get_hex(bitstream.get_length()).lower(), expected_hex) bitstream.seek(0) self.assertEquals(bitstream.get_base64(bitstream.get_length()), expected_base64)
def test_byte_basic(self): """ Test basic put_byte and get_byte behavior. """ bitstream = BitStream() # Put a couple of bytes in the stream: bytes = [ 12, # 00001100 222, # 11011110 145, # 10010001 42, # 00101010 0, # 00000000 255] # 11111111 for byte in bytes: bitstream.put_byte(byte) # Sanity check: self.assertEquals(bitstream.get_length(),len(bytes)*8) self.assertEquals(bitstream.get_current_pos(),len(bytes)*8) # Read the bytes back from the stream bitstream.seek(0) for byte in bytes: self.assertEquals(bitstream.get_byte(), byte) # Read some bits from the stream, interpreting them as bytes, but # without restricting ourselves to 8-bit aligned bytes # e.g. read the "byte" defined by bits #4 to #12 bitstream.seek(4) self.assertEquals(bitstream.get_byte(), 205) # 11001101 bitstream.seek(19) self.assertEquals(bitstream.get_byte(), 137) # 10001001
def test_string_ascii(self): """ Test basic put_string and get_string behavior, using ASCII strings. """ bitstream = BitStream() string1 = "Test string " # 12 chars/bytes string2 = "using only ASCII characters (0-126):\n\t" # 38 chars/bytes string3 = "Hello World!" # 12 chars/bytes # Store our message in 3 writes bitstream.put_string(string1) bitstream.put_string(string2) bitstream.put_string(string3) # Sanity check: self.assertEquals(bitstream.get_length(), (12 + 38 + 12) * 8) self.assertEquals(bitstream.get_current_pos(), (12 + 38 + 12) * 8) # Retrieve our message in 2 reads bitstream.seek(0) self.assertEquals( bitstream.get_string(29 * 8), # read 29 bytes "Test string using only ASCII ") self.assertEquals( bitstream.get_string(33 * 8), # read 33 bytes "characters (0-126):\n\tHello World!")
def test_stress(self): """ Stress test BitStream by writings lots of numbers of different bit sizes and reading them back. """ # num_writes = 10000 num_writes = 1000 min_bit_length = 1 max_bit_length = 8192 # Generate the numbers nums = [] total_bit_length = 0 for i in range(0, num_writes): bit_length = random.randint(min_bit_length, max_bit_length) total_bit_length += bit_length val = random.randint(0, 2**bit_length - 1) nums.append({"bit_length" : bit_length, "value" : val}) # Write them to a BitStream object bitstream = BitStream() for num in nums: bitstream.put_num(num["value"], num["bit_length"]) # Check the BitStream length and current position self.assertEquals(bitstream.get_length(),total_bit_length) self.assertEquals(bitstream.get_current_pos(),total_bit_length) # Read all numbers back bitstream.seek(0) for num in nums: self.assertEquals(bitstream.get_num(num["bit_length"]),num["value"])
def test_stress(self): """ Stress test BitStream by writings lots of numbers of different bit sizes and reading them back. """ # num_writes = 10000 num_writes = 1000 min_bit_length = 1 max_bit_length = 8192 # Generate the numbers nums = [] total_bit_length = 0 for i in range(0, num_writes): bit_length = random.randint(min_bit_length, max_bit_length) total_bit_length += bit_length val = random.randint(0, 2**bit_length - 1) nums.append({"bit_length": bit_length, "value": val}) # Write them to a BitStream object bitstream = BitStream() for num in nums: bitstream.put_num(num["value"], num["bit_length"]) # Check the BitStream length and current position self.assertEquals(bitstream.get_length(), total_bit_length) self.assertEquals(bitstream.get_current_pos(), total_bit_length) # Read all numbers back bitstream.seek(0) for num in nums: self.assertEquals(bitstream.get_num(num["bit_length"]), num["value"])
def test_bitstream_constructor(self): """ Test that creating an empty bitstream succeeds. """ bitstream = BitStream() # An empty bitstream has length 0 ... self.assertEqual(bitstream.get_length(), 0) # ...and has its current position marker at position 0. self.assertEqual(bitstream.get_current_pos(), 0)
def test_put_bitstream_copy(self): """ Test the basic functionality of the put_bitstream_copy method. """ bitstream1 = BitStream() bitstream1.put_string("This is bitstream1") bitstream2 = BitStream() bitstream2.put_string("This is bitstream2") bitstream3 = BitStream() # bitstream3 remains empty bitstream4 = BitStream() bitstream4.put_string("This is bitstream4") # copy the full contents of bitstream2 to the end of bitstream1 bitstream2.seek(0) bitstream1.put_bitstream_copy(bitstream2) self.assertEquals(bitstream2.get_current_pos(), bitstream2.get_length()) # check the contents of bitstream1 bitstream1.seek(0) self.assertEquals(bitstream1.get_string(bitstream1.get_length()), "This is bitstream1This is bitstream2") # copy the full contents of bitstream3 (aka. nothing) to the end of # bitstream4 bitstream3.seek(0) bitstream4.put_bitstream_copy(bitstream3) # check the contents of bitstream4 bitstream4.seek(0) self.assertEquals(bitstream4.get_string(bitstream4.get_length()), "This is bitstream4") # copy the contents of bitstream4 from the position 8 onwards bitstream4.seek(8 * 8) bitstream1.put_bitstream_copy(bitstream4) self.assertEquals(bitstream4.get_current_pos(), bitstream4.get_length()) # check the contents of bitstream1 bitstream1.seek(0) self.assertEquals(bitstream1.get_string(bitstream1.get_length()), "This is bitstream1This is bitstream2bitstream4")
def test_put_bitstream_copy(self): """ Test the basic functionality of the put_bitstream_copy method. """ bitstream1 = BitStream() bitstream1.put_string("This is bitstream1") bitstream2 = BitStream() bitstream2.put_string("This is bitstream2") bitstream3 = BitStream() # bitstream3 remains empty bitstream4 = BitStream() bitstream4.put_string("This is bitstream4") # copy the full contents of bitstream2 to the end of bitstream1 bitstream2.seek(0) bitstream1.put_bitstream_copy(bitstream2) self.assertEquals(bitstream2.get_current_pos(), bitstream2.get_length()) # check the contents of bitstream1 bitstream1.seek(0) self.assertEquals(bitstream1.get_string(bitstream1.get_length()), "This is bitstream1This is bitstream2") # copy the full contents of bitstream3 (aka. nothing) to the end of # bitstream4 bitstream3.seek(0) bitstream4.put_bitstream_copy(bitstream3) # check the contents of bitstream4 bitstream4.seek(0) self.assertEquals(bitstream4.get_string(bitstream4.get_length()), "This is bitstream4") # copy the contents of bitstream4 from the position 8 onwards bitstream4.seek(8*8) bitstream1.put_bitstream_copy(bitstream4) self.assertEquals(bitstream4.get_current_pos(), bitstream4.get_length()) # check the contents of bitstream1 bitstream1.seek(0) self.assertEquals(bitstream1.get_string(bitstream1.get_length()), "This is bitstream1This is bitstream2bitstream4")
def test_base64_basic(self): """ This method tests put_base64's and get_base64's basic functionality. """ bitstream = BitStream() # We use the Base64 Test Vectors defined in RFC4648. # http://www.ietf.org/rfc/rfc4648.txt test_vectors = [("",""), ("f","Zg=="), ("fo","Zm8="), ("foo","Zm9v"), ("foob","Zm9vYg=="), ("fooba","Zm9vYmE="), ("foobar","Zm9vYmFy")] # STEP 1: # For each test vector, we write its value to the bitstream as a string # then read it as base64 data. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val)*8 bitstream.put_string(str_val) bitstream.seek(0) self.assertEquals(bitstream.get_base64(vector_bit_length), base64_val) bitstream.seek(0) # NOTE that we are overwriting multiple times our bitstream, this is # also a feature of BitStream we are testing in this test case. # STEP 2: # For each test vector, we write its value to the bitstream as base64 # data, then read it as string *and* base64 data. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val)*8 bitstream.put_base64(base64_val) bitstream.seek(0) self.assertEquals(bitstream.get_string(vector_bit_length), str_val) bitstream.seek(0) self.assertEquals(bitstream.get_base64(vector_bit_length), base64_val) bitstream.seek(0) # STEP 3: # For each test vector, we write its value to a NEW bitstream as base64 # data, and make sure the length of the stream is the expected one. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val)*8 new_bs = BitStream() new_bs.put_base64(base64_val) self.assertEquals(new_bs.get_length(), vector_bit_length) self.assertEquals(new_bs.get_current_pos(), vector_bit_length)
def test_string_unicode(self): """ Test basic put_string and get_string support for python unicode objects. """ bitstream = BitStream() unicode_string = u"ÄäÜüßЯБГДЖЙŁĄŻĘĆŃŚŹてすとアイウエオカキクケコサシスセソタチツテ" bitstream.put_string(unicode_string) bitstream.seek(0) # Note: the string is read back as a "normal" UTF-8 string, unicode # type information is not stored in the bitstream. self.assertEquals(bitstream.get_string(bitstream.get_length()), "ÄäÜüßЯБГДЖЙŁĄŻĘĆŃŚŹてすとアイウエオカキクケコサシスセソタチツテ")
def test_string_utf8(self): """ Test basic put_string and get_string support for UTF-8 strings. """ bitstream = BitStream() string1 = "ÄäÜüß" # 5 chars, 10 bytes string2 = "ЯБГДЖЙŁĄŻĘĆŃŚŹ" # 14 chars, 28 bytes string3 = "てすとアイウエオカキクケコサシスセソタチツテ" # 22 chars, 66 bytes # Store our message in 3 writes bitstream.put_string(string1) bitstream.put_string(string2) bitstream.put_string(string3) # Sanity check: self.assertEquals(bitstream.get_length(), (10 + 28 + 66) * 8) self.assertEquals(bitstream.get_current_pos(), (10 + 28 + 66) * 8) # Retrieve the whole message bitstream.seek(0) self.assertEquals(bitstream.get_string(bitstream.get_length()), "ÄäÜüßЯБГДЖЙŁĄŻĘĆŃŚŹてすとアイウエオカキクケコサシスセソタチツテ")
def test_string_utf8(self): """ Test basic put_string and get_string support for UTF-8 strings. """ bitstream = BitStream() string1 = "ÄäÜüß" # 5 chars, 10 bytes string2 = "ЯБГДЖЙŁĄŻĘĆŃŚŹ" # 14 chars, 28 bytes string3 = "てすとアイウエオカキクケコサシスセソタチツテ" # 22 chars, 66 bytes # Store our message in 3 writes bitstream.put_string(string1) bitstream.put_string(string2) bitstream.put_string(string3) # Sanity check: self.assertEquals(bitstream.get_length(),(10+28+66)*8) self.assertEquals(bitstream.get_current_pos(),(10+28+66)*8) # Retrieve the whole message bitstream.seek(0) self.assertEquals(bitstream.get_string(bitstream.get_length()), "ÄäÜüßЯБГДЖЙŁĄŻĘĆŃŚŹてすとアイウエオカキクケコサシスセソタチツテ")
def test_base64_basic(self): """ This method tests put_base64's and get_base64's basic functionality. """ bitstream = BitStream() # We use the Base64 Test Vectors defined in RFC4648. # http://www.ietf.org/rfc/rfc4648.txt test_vectors = [("", ""), ("f", "Zg=="), ("fo", "Zm8="), ("foo", "Zm9v"), ("foob", "Zm9vYg=="), ("fooba", "Zm9vYmE="), ("foobar", "Zm9vYmFy")] # STEP 1: # For each test vector, we write its value to the bitstream as a string # then read it as base64 data. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val) * 8 bitstream.put_string(str_val) bitstream.seek(0) self.assertEquals(bitstream.get_base64(vector_bit_length), base64_val) bitstream.seek(0) # NOTE that we are overwriting multiple times our bitstream, this is # also a feature of BitStream we are testing in this test case. # STEP 2: # For each test vector, we write its value to the bitstream as base64 # data, then read it as string *and* base64 data. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val) * 8 bitstream.put_base64(base64_val) bitstream.seek(0) self.assertEquals(bitstream.get_string(vector_bit_length), str_val) bitstream.seek(0) self.assertEquals(bitstream.get_base64(vector_bit_length), base64_val) bitstream.seek(0) # STEP 3: # For each test vector, we write its value to a NEW bitstream as base64 # data, and make sure the length of the stream is the expected one. for (str_val, base64_val) in test_vectors: vector_bit_length = len(str_val) * 8 new_bs = BitStream() new_bs.put_base64(base64_val) self.assertEquals(new_bs.get_length(), vector_bit_length) self.assertEquals(new_bs.get_current_pos(), vector_bit_length)
def test_put_hex_invalid_format(self): """ Test that put_hex raises an exception when passed any string not of the form ('0'-'9'|'a'-'f'|'A'-'F')* """ bitstream = BitStream() # Try some fixed examples self.assertRaises(ValueError, bitstream.put_hex, "aF92G2") self.assertRaises(ValueError, bitstream.put_hex, "Hello") self.assertRaises(ValueError, bitstream.put_hex, "3354 F") self.assertRaises(ValueError, bitstream.put_hex, "ケコサシス") # Generate a random string conforming to ('0'-'9'|'a'-'f'|'A'-'F')* # except for one (ascii) character. valid_hex_digits = "0123456789abcdefABCDEF" num_digits = 50 pos_wrong_char = random.randint(1, 48) r_invalid_hex_string = "" for i in range(0, pos_wrong_char): r_invalid_hex_string += random.choice(valid_hex_digits) non_hex_char = "0" while (non_hex_char in valid_hex_digits): non_hex_char = \ random.choice(string.letters + string.digits + string.whitespace) r_invalid_hex_string += non_hex_char for i in range(0, num_digits - pos_wrong_char - 1): r_invalid_hex_string += random.choice(valid_hex_digits) # Try the randomly generated example self.assertRaises(ValueError, bitstream.put_hex, r_invalid_hex_string) # Calls that throw exceptions should not alter the contents or position # of the bitstream: self.assertEquals(bitstream.get_length(), 0) self.assertEquals(bitstream.get_current_pos(), 0)
def test_put_hex_invalid_format(self): """ Test that put_hex raises an exception when passed any string not of the form ('0'-'9'|'a'-'f'|'A'-'F')* """ bitstream = BitStream() # Try some fixed examples self.assertRaises(ValueError, bitstream.put_hex, "aF92G2") self.assertRaises(ValueError, bitstream.put_hex, "Hello") self.assertRaises(ValueError, bitstream.put_hex, "3354 F") self.assertRaises(ValueError, bitstream.put_hex, "ケコサシス") # Generate a random string conforming to ('0'-'9'|'a'-'f'|'A'-'F')* # except for one (ascii) character. valid_hex_digits = "0123456789abcdefABCDEF" num_digits = 50 pos_wrong_char = random.randint(1,48) r_invalid_hex_string = "" for i in range(0,pos_wrong_char): r_invalid_hex_string += random.choice(valid_hex_digits) non_hex_char = "0" while(non_hex_char in valid_hex_digits): non_hex_char = \ random.choice(string.letters + string.digits + string.whitespace) r_invalid_hex_string += non_hex_char for i in range(0,num_digits - pos_wrong_char - 1): r_invalid_hex_string += random.choice(valid_hex_digits) # Try the randomly generated example self.assertRaises(ValueError, bitstream.put_hex, r_invalid_hex_string) # Calls that throw exceptions should not alter the contents or position # of the bitstream: self.assertEquals(bitstream.get_length(),0) self.assertEquals(bitstream.get_current_pos(),0)
def test_num_basic(self): """ Test basic put_num and get_num behavior """ bitstream = BitStream() # Some parameters num_sizes = [4, 8, 16, 32, 64, 128, 256, 2839] num_writes = 10 nums = [] # Add num_writes integers of each size to nums: for i in range(0, len(num_sizes)): max_val = 2**num_sizes[i] - 1 nums.append([]) for j in range(0, num_writes): # append a random number between 0 and max_val, inclusive nums[i].append(random.randint(0, max_val)) # Write all values in nums to the stream for i in range(0, len(num_sizes)): for j in range(0, num_writes): bitstream.put_num(nums[i][j], num_sizes[i]) # Go back to start of the stream bitstream.seek(0) # Sanity check: expected_length = 0 for num_size in num_sizes: expected_length += num_size * num_writes self.assertEqual(bitstream.get_length(), expected_length) # Read them back and compare for i in range(0, len(num_sizes)): for j in range(0, num_writes): n = bitstream.get_num(num_sizes[i]) self.assertEqual(n, nums[i][j])
def test_num_basic(self): """ Test basic put_num and get_num behavior """ bitstream = BitStream() # Some parameters num_sizes = [4,8,16,32,64,128,256,2839] num_writes = 10 nums = [] # Add num_writes integers of each size to nums: for i in range(0,len(num_sizes)): max_val = 2**num_sizes[i] - 1 nums.append([]) for j in range(0,num_writes): # append a random number between 0 and max_val, inclusive nums[i].append(random.randint(0, max_val)) # Write all values in nums to the stream for i in range(0,len(num_sizes)): for j in range(0,num_writes): bitstream.put_num(nums[i][j], num_sizes[i]) # Go back to start of the stream bitstream.seek(0) # Sanity check: expected_length = 0 for num_size in num_sizes: expected_length += num_size * num_writes self.assertEqual(bitstream.get_length(), expected_length) # Read them back and compare for i in range(0,len(num_sizes)): for j in range(0,num_writes): n = bitstream.get_num(num_sizes[i]) self.assertEqual(n, nums[i][j])
def test_put_bit_dump_string_invalid_format(self): """ Test that put_bit_dump_string raises an exception when passed any string not of the form ('0'|'1')* """ bitstream = BitStream() # Try some fixed examples self.assertRaises(ValueError, bitstream.put_bit_dump_string, "23234") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "Hello") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "0101 0") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "ケコサシス") # Generate a random string conforming to ('0'|'1')* except for one # (ascii) character. num_bits = 50 pos_wrong_char = random.randint(1,48) r_invalid_bit_string = "" for i in range(0,pos_wrong_char): r_invalid_bit_string += random.choice(('0','1')) r_invalid_bit_string += \ random.choice(string.letters + "23456789_-/\\" + string.whitespace) for i in range(0,num_bits - pos_wrong_char - 1): r_invalid_bit_string += random.choice(('0','1')) # Try the randomly generated example self.assertRaises(ValueError, bitstream.put_bit_dump_string, r_invalid_bit_string) # Calls that throw exceptions should not alter the contents or position # of the bitstream: self.assertEquals(bitstream.get_length(),0) self.assertEquals(bitstream.get_current_pos(),0)
def test_put_bit_dump_string_invalid_format(self): """ Test that put_bit_dump_string raises an exception when passed any string not of the form ('0'|'1')* """ bitstream = BitStream() # Try some fixed examples self.assertRaises(ValueError, bitstream.put_bit_dump_string, "23234") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "Hello") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "0101 0") self.assertRaises(ValueError, bitstream.put_bit_dump_string, "ケコサシス") # Generate a random string conforming to ('0'|'1')* except for one # (ascii) character. num_bits = 50 pos_wrong_char = random.randint(1, 48) r_invalid_bit_string = "" for i in range(0, pos_wrong_char): r_invalid_bit_string += random.choice(('0', '1')) r_invalid_bit_string += \ random.choice(string.letters + "23456789_-/\\" + string.whitespace) for i in range(0, num_bits - pos_wrong_char - 1): r_invalid_bit_string += random.choice(('0', '1')) # Try the randomly generated example self.assertRaises(ValueError, bitstream.put_bit_dump_string, r_invalid_bit_string) # Calls that throw exceptions should not alter the contents or position # of the bitstream: self.assertEquals(bitstream.get_length(), 0) self.assertEquals(bitstream.get_current_pos(), 0)
def test_string_ascii(self): """ Test basic put_string and get_string behavior, using ASCII strings. """ bitstream = BitStream() string1 = "Test string " # 12 chars/bytes string2 = "using only ASCII characters (0-126):\n\t" # 38 chars/bytes string3 = "Hello World!" # 12 chars/bytes # Store our message in 3 writes bitstream.put_string(string1) bitstream.put_string(string2) bitstream.put_string(string3) # Sanity check: self.assertEquals(bitstream.get_length(),(12+38+12)*8) self.assertEquals(bitstream.get_current_pos(),(12+38+12)*8) # Retrieve our message in 2 reads bitstream.seek(0) self.assertEquals(bitstream.get_string(29*8), # read 29 bytes "Test string using only ASCII ") self.assertEquals(bitstream.get_string(33*8), # read 33 bytes "characters (0-126):\n\tHello World!")
def test_byte_basic(self): """ Test basic put_byte and get_byte behavior. """ bitstream = BitStream() # Put a couple of bytes in the stream: bytes = [ 12, # 00001100 222, # 11011110 145, # 10010001 42, # 00101010 0, # 00000000 255 ] # 11111111 for byte in bytes: bitstream.put_byte(byte) # Sanity check: self.assertEquals(bitstream.get_length(), len(bytes) * 8) self.assertEquals(bitstream.get_current_pos(), len(bytes) * 8) # Read the bytes back from the stream bitstream.seek(0) for byte in bytes: self.assertEquals(bitstream.get_byte(), byte) # Read some bits from the stream, interpreting them as bytes, but # without restricting ourselves to 8-bit aligned bytes # e.g. read the "byte" defined by bits #4 to #12 bitstream.seek(4) self.assertEquals(bitstream.get_byte(), 205) # 11001101 bitstream.seek(19) self.assertEquals(bitstream.get_byte(), 137) # 10001001
def encrypt_bitstream(self, bitstream, pad_to=None, task_monitor=None): """ Encrypts the given bitstream into a ciphertext object. Arguments: bitstream::BitStream-- A stream of bits to encrypt (see BitStream utility class). pad_to::int -- Minimum size (in bytes) of the resulting ciphertext. Data will be padded before encryption to match this size. task_monitor::TaskMonitor -- A task monitor for this task. Returns: ciphertext:Ciphertext -- A ciphertext object encapsulating the encrypted data. """ random = StrongRandom() ## PART 1 # First, format the bitstream as per Ciphertext.py Note 001, # previous to encryption. # [size (64 bits) | message (size bits) | padding (X bits) ] ## formated_bitstream = BitStream() # The first 64 encode the size of the actual data in bits SIZE_BLOCK_LENGTH = 64 size_in_bits = bitstream.get_length() if(size_in_bits >= 2**SIZE_BLOCK_LENGTH): raise ValueError("The size of the bitstream to encrypt is larger " \ "than 16 Exabits. The current format for " \ "PloneVote ciphertext only allows encrypting a " \ "maximum of 16 Exabits of information.") formated_bitstream.put_num(size_in_bits, SIZE_BLOCK_LENGTH) # We then copy the contents of the original bitstream bitstream.seek(0) formated_bitstream.put_bitstream_copy(bitstream) # Finally, we append random data until we reach the desired pad_to # length unpadded_length = formated_bitstream.get_length() if(pad_to != None and (pad_to * 8) > unpadded_length): full_length = pad_to * 8 else: full_length = unpadded_length padding_left = full_length - unpadded_length while(padding_left > 1024): padding_bits = random.randint(1, 2**1024) formated_bitstream.put_num(padding_bits,1024) padding_left -= 1024 if(padding_left > 0): padding_bits = random.randint(1, 2**padding_left) formated_bitstream.put_num(padding_bits, padding_left) padding_left = 0 ## PART 2 # We encrypt the formated bitsteam using ElGamal into a Ciphertext # object. # See "Handbook of Applied Cryptography" Algorithm 8.18 ## # block_size is the size of each block of bits to encrypt # since we can only encrypt messages in [0, p - 1] # we should use (nbits - 1) as the block size, where # 2**(nbits - 1) < p < 2**nbits block_size = self.cryptosystem.get_nbits() - 1 prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() # We pull data from the bitstream one block at a time and encrypt it formated_bitstream.seek(0) ciphertext = \ Ciphertext(self.cryptosystem.get_nbits(), self.get_fingerprint()) plaintext_bits_left = formated_bitstream.get_length() # Check if we have a task monitor and register with it if(task_monitor != None): # We will do two tick()s per block to encrypt: one for generating # the gamma component of the ciphertext block and another for the # delta component (those are the two time intensive steps, # because of exponentiation). ticks = math.ceil((1.0 * plaintext_bits_left) / block_size) * 2 encrypt_task_mon = \ task_monitor.new_subtask("Encrypt data", expected_ticks = ticks) while(plaintext_bits_left > 0): # get next block (message, m, etc) to encrypt if(plaintext_bits_left >= block_size): block = formated_bitstream.get_num(block_size) plaintext_bits_left -= block_size else: block = formated_bitstream.get_num(plaintext_bits_left) # Encrypt as if the stream was filled with random data past its # end, this avoids introducing a 0's gap during decryption to # bitstream displacement = block_size - plaintext_bits_left block = block << displacement padding = random.randint(0, 2**displacement - 1) assert (padding // 2**displacement == 0), \ "padding should be at most displacement bits long" block = block | padding plaintext_bits_left = 0 # Select a random integer k, 1 <= k <= p − 2 k = random.randint(1, prime - 2) # Compute gamma and delta gamma = pow(generator, k, prime) if(task_monitor != None): encrypt_task_mon.tick() delta = (block * pow(self._key, k, prime)) % prime if(task_monitor != None): encrypt_task_mon.tick() # Add this encrypted data portion to the ciphertext object ciphertext.append(gamma, delta) # return the ciphertext object return ciphertext
def from_file(cls, filename, SerializerClass=serialize.XMLSerializer): """ Loads an instance of Ciphertext from the given file. Arguments: filename::string -- The name of a file containing the ciphertext in serialized form. SerializerClass::class -- The class that provides the deserialization. XMLSerializer by default. Must inherit from serialize.BaseSerializer and provide an adequate deserialize_from_file method. Note that often the same class used to serialize the data must be used to deserialize it. (see utilities/serialize.py documentation for more information) Throws: InvalidPloneVoteCryptoFileError -- If the file is not a valid PloneVoteCryptoLib stored ciphertext file. """ # Create a new serializer object for the Ciphertext structure definition serializer = SerializerClass(Ciphertext_serialize_structure_definition) # Deserialize the Ciphertext instance from file try: data = serializer.deserialize_from_file(filename) except serialize.InvalidSerializeDataError as e: # Convert the exception to an InvalidPloneVoteCryptoFileError raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "following error occurred while trying to deserialize the " \ "file contents: %s" % (filename, str(e))) # Get the values from the deserialized data try: nbits = int(data["PloneVoteCiphertext"]["nbits"]) except ValueError: raise InvalidPloneVoteCryptoFileError(filename, "File \"%s\" does not contain a valid ciphertext. The " \ "stored value for nbits is not a valid (decimal) integer." \ % filename) fingerprint_str = data["PloneVoteCiphertext"]["PKFingerprint"] enc_data_str = data["PloneVoteCiphertext"]["EncryptedData"] # Construct a new Ciphertext object with the given nbits and fingerprint ciphertext = cls(nbits, fingerprint_str) # Load the encrypted data bitstream = BitStream() bitstream.put_base64(enc_data_str) bitstream.seek(0) length = bitstream.get_length() # number of gamma and delta blocks in the bitstream: blocks = length // (nbits * 2) for i in range(0, blocks): gamma_val = bitstream.get_num(nbits) delta_val = bitstream.get_num(nbits) ciphertext.append(gamma_val, delta_val) # Return the ciphertext return ciphertext
def encrypt_bitstream(self, bitstream, pad_to=None, task_monitor=None): """ Encrypts the given bitstream into a ciphertext object. Arguments: bitstream::BitStream-- A stream of bits to encrypt (see BitStream utility class). pad_to::int -- Minimum size (in bytes) of the resulting ciphertext. Data will be padded before encryption to match this size. task_monitor::TaskMonitor -- A task monitor for this task. Returns: ciphertext:Ciphertext -- A ciphertext object encapsulating the encrypted data. """ random = StrongRandom() ## PART 1 # First, format the bitstream as per Ciphertext.py Note 001, # previous to encryption. # [size (64 bits) | message (size bits) | padding (X bits) ] ## formated_bitstream = BitStream() # The first 64 encode the size of the actual data in bits SIZE_BLOCK_LENGTH = 64 size_in_bits = bitstream.get_length() if(size_in_bits >= 2**SIZE_BLOCK_LENGTH): raise ValueError("The size of the bitstream to encrypt is larger " \ "than 16 Exabits. The current format for " \ "PloneVote ciphertext only allows encrypting a " \ "maximum of 16 Exabits of information.") formated_bitstream.put_num(size_in_bits, SIZE_BLOCK_LENGTH) # We then copy the contents of the original bitstream bitstream.seek(0) formated_bitstream.put_bitstream_copy(bitstream) # Finally, we append random data until we reach the desired pad_to # length unpadded_length = formated_bitstream.get_length() if(pad_to != None and (pad_to * 8) > unpadded_length): full_length = pad_to * 8 else: full_length = unpadded_length padding_left = full_length - unpadded_length while(padding_left > 1024): padding_bits = random.randint(1, 2**1024) formated_bitstream.put_num(padding_bits,1024) padding_left -= 1024 if(padding_left > 0): padding_bits = random.randint(1, 2**padding_left) formated_bitstream.put_num(padding_bits, padding_left) padding_left = 0 ## PART 2 # We encrypt the formated bitsteam using ElGamal into a Ciphertext # object. # See "Handbook of Applied Cryptography" Algorithm 8.18 ## # block_size is the size of each block of bits to encrypt # since we can only encrypt messages in [0, p - 1] # we should use (nbits - 1) as the block size, where # 2**(nbits - 1) < p < 2**nbits block_size = self.cryptosystem.get_nbits() - 1 prime = self.cryptosystem.get_prime() generator = self.cryptosystem.get_generator() # We pull data from the bitstream one block at a time and encrypt it formated_bitstream.seek(0) ciphertext = \ Ciphertext(self.cryptosystem.get_nbits(), self.get_fingerprint()) plaintext_bits_left = formated_bitstream.get_length() # Check if we have a task monitor and register with it if(task_monitor != None): # We will do two tick()s per block to encrypt: one for generating # the gamma component of the ciphertext block and another for the # delta component (those are the two time intensive steps, # because of exponentiation). ticks = math.ceil((1.0 * plaintext_bits_left) / block_size) * 2 encrypt_task_mon = \ task_monitor.new_subtask("Encrypt data", expected_ticks = ticks) while(plaintext_bits_left > 0): # get next block (message, m, etc) to encrypt if(plaintext_bits_left >= block_size): block = formated_bitstream.get_num(block_size) plaintext_bits_left -= block_size else: block = formated_bitstream.get_num(plaintext_bits_left) # Encrypt as if the stream was filled with random data past its # end, this avoids introducing a 0's gap during decryption to # bitstream displacement = block_size - plaintext_bits_left block = block << displacement padding = random.randint(0, 2**displacement - 1) assert (padding / 2**displacement == 0), \ "padding should be at most displacement bits long" block = block | padding plaintext_bits_left = 0 # Select a random integer k, 1 <= k <= p − 2 k = random.randint(1, prime - 2) # Compute gamma and delta gamma = pow(generator, k, prime) if(task_monitor != None): encrypt_task_mon.tick() delta = (block * pow(self._key, k, prime)) % prime if(task_monitor != None): encrypt_task_mon.tick() # Add this encrypted data portion to the ciphertext object ciphertext.append(gamma, delta) # return the ciphertext object return ciphertext