diff options
Diffstat (limited to 'python/bitstring')
-rw-r--r-- | python/bitstring/PKG-INFO | 122 | ||||
-rw-r--r-- | python/bitstring/README.txt | 99 | ||||
-rw-r--r-- | python/bitstring/bitstring.py | 4234 | ||||
-rw-r--r-- | python/bitstring/doc/bitstring_manual.pdf | bin | 0 -> 439327 bytes | |||
-rw-r--r-- | python/bitstring/release_notes.txt | 1523 | ||||
-rw-r--r-- | python/bitstring/setup.py | 44 | ||||
-rw-r--r-- | python/bitstring/test/smalltestfile | 1 | ||||
-rw-r--r-- | python/bitstring/test/test.m1v | bin | 0 -> 125300 bytes | |||
-rw-r--r-- | python/bitstring/test/test_bitarray.py | 310 | ||||
-rw-r--r-- | python/bitstring/test/test_bits.py | 378 | ||||
-rw-r--r-- | python/bitstring/test/test_bitstore.py | 37 | ||||
-rw-r--r-- | python/bitstring/test/test_bitstream.py | 3940 | ||||
-rw-r--r-- | python/bitstring/test/test_bitstring.py | 97 | ||||
-rw-r--r-- | python/bitstring/test/test_constbitstream.py | 121 |
14 files changed, 10906 insertions, 0 deletions
diff --git a/python/bitstring/PKG-INFO b/python/bitstring/PKG-INFO new file mode 100644 index 000000000..1036c45d7 --- /dev/null +++ b/python/bitstring/PKG-INFO @@ -0,0 +1,122 @@ +Metadata-Version: 1.1 +Name: bitstring +Version: 3.1.3 +Summary: Simple construction, analysis and modification of binary data. +Home-page: http://python-bitstring.googlecode.com +Author: Scott Griffiths +Author-email: scott@griffiths.name +License: The MIT License: http://www.opensource.org/licenses/mit-license.php +Download-URL: http://python-bitstring.googlecode.com +Description: ================
+ bitstring module
+ ================
+
+ **bitstring** is a pure Python module designed to help make
+ the creation and analysis of binary data as simple and natural as possible.
+
+ Bitstrings can be constructed from integers (big and little endian), hex,
+ octal, binary, strings or files. They can be sliced, joined, reversed,
+ inserted into, overwritten, etc. with simple functions or slice notation.
+ They can also be read from, searched and replaced, and navigated in,
+ similar to a file or stream.
+
+ bitstring is open source software, and has been released under the MIT
+ licence.
+
+ This version supports Python 2.6 and later (including Python 3).
+ For Python 2.4 and 2.5 you should instead download version 1.0.
+
+ Documentation
+ -------------
+ The manual for the bitstring module is available here
+ <http://packages.python.org/bitstring>. It contains a walk-through of all
+ the features and a complete reference section.
+
+ It is also available as a PDF as part of the source download.
+
+ Installation
+ ------------
+ If you have downloaded and unzipped the package then you need to run the
+ ``setup.py`` script with the 'install' argument::
+
+ python setup.py install
+
+ You may need to run this with root privileges on Unix-like systems.
+
+
+ If you haven't yet downloaded the package then you can just try::
+
+ easy_install bitstring
+
+ or ::
+
+ pip install bitstring
+
+
+ Simple Examples
+ ---------------
+ Creation::
+
+ >>> a = BitArray(bin='00101')
+ >>> b = Bits(a_file_object)
+ >>> c = BitArray('0xff, 0b101, 0o65, uint:6=22')
+ >>> d = pack('intle:16, hex=a, 0b1', 100, a='0x34f')
+ >>> e = pack('<16h', *range(16))
+
+ Different interpretations, slicing and concatenation::
+
+ >>> a = BitArray('0x1af')
+ >>> a.hex, a.bin, a.uint
+ ('1af', '000110101111', 431)
+ >>> a[10:3:-1].bin
+ '1110101'
+ >>> 3*a + '0b100'
+ BitArray('0o0657056705674')
+
+ Reading data sequentially::
+
+ >>> b = BitStream('0x160120f')
+ >>> b.read(12).hex
+ '160'
+ >>> b.pos = 0
+ >>> b.read('uint:12')
+ 352
+ >>> b.readlist('uint:12, bin:3')
+ [288, '111']
+
+ Searching, inserting and deleting::
+
+ >>> c = BitArray('0b00010010010010001111') # c.hex == '0x1248f'
+ >>> c.find('0x48')
+ (8,)
+ >>> c.replace('0b001', '0xabc')
+ >>> c.insert('0b0000')
+ >>> del c[12:16]
+
+ Unit Tests
+ ----------
+
+ The 400+ unit tests should all pass for Python 2.6 and later.
+
+ ----
+
+ The bitstring module has been released as open source under the MIT License.
+ Copyright (c) 2014 Scott Griffiths
+
+ For more information see the project's homepage on Google Code:
+ <http://python-bitstring.googlecode.com>
+
+ +Platform: all +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.0 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/python/bitstring/README.txt b/python/bitstring/README.txt new file mode 100644 index 000000000..491c2f8cf --- /dev/null +++ b/python/bitstring/README.txt @@ -0,0 +1,99 @@ +================
+bitstring module
+================
+
+**bitstring** is a pure Python module designed to help make
+the creation and analysis of binary data as simple and natural as possible.
+
+Bitstrings can be constructed from integers (big and little endian), hex,
+octal, binary, strings or files. They can be sliced, joined, reversed,
+inserted into, overwritten, etc. with simple functions or slice notation.
+They can also be read from, searched and replaced, and navigated in,
+similar to a file or stream.
+
+bitstring is open source software, and has been released under the MIT
+licence.
+
+This version supports Python 2.6 and later (including Python 3).
+For Python 2.4 and 2.5 you should instead download version 1.0.
+
+Documentation
+-------------
+The manual for the bitstring module is available here
+<http://packages.python.org/bitstring>. It contains a walk-through of all
+the features and a complete reference section.
+
+It is also available as a PDF as part of the source download.
+
+Installation
+------------
+If you have downloaded and unzipped the package then you need to run the
+``setup.py`` script with the 'install' argument::
+
+ python setup.py install
+
+You may need to run this with root privileges on Unix-like systems.
+
+
+If you haven't yet downloaded the package then you can just try::
+
+ easy_install bitstring
+
+or ::
+
+ pip install bitstring
+
+
+Simple Examples
+---------------
+Creation::
+
+ >>> a = BitArray(bin='00101')
+ >>> b = Bits(a_file_object)
+ >>> c = BitArray('0xff, 0b101, 0o65, uint:6=22')
+ >>> d = pack('intle:16, hex=a, 0b1', 100, a='0x34f')
+ >>> e = pack('<16h', *range(16))
+
+Different interpretations, slicing and concatenation::
+
+ >>> a = BitArray('0x1af')
+ >>> a.hex, a.bin, a.uint
+ ('1af', '000110101111', 431)
+ >>> a[10:3:-1].bin
+ '1110101'
+ >>> 3*a + '0b100'
+ BitArray('0o0657056705674')
+
+Reading data sequentially::
+
+ >>> b = BitStream('0x160120f')
+ >>> b.read(12).hex
+ '160'
+ >>> b.pos = 0
+ >>> b.read('uint:12')
+ 352
+ >>> b.readlist('uint:12, bin:3')
+ [288, '111']
+
+Searching, inserting and deleting::
+
+ >>> c = BitArray('0b00010010010010001111') # c.hex == '0x1248f'
+ >>> c.find('0x48')
+ (8,)
+ >>> c.replace('0b001', '0xabc')
+ >>> c.insert('0b0000')
+ >>> del c[12:16]
+
+Unit Tests
+----------
+
+The 400+ unit tests should all pass for Python 2.6 and later.
+
+----
+
+The bitstring module has been released as open source under the MIT License.
+Copyright (c) 2014 Scott Griffiths
+
+For more information see the project's homepage on Google Code:
+<http://python-bitstring.googlecode.com>
+
diff --git a/python/bitstring/bitstring.py b/python/bitstring/bitstring.py new file mode 100644 index 000000000..86f969c7f --- /dev/null +++ b/python/bitstring/bitstring.py @@ -0,0 +1,4234 @@ +#!/usr/bin/env python +# cython: profile=True +""" +This package defines classes that simplify bit-wise creation, manipulation and +interpretation of data. + +Classes: + +Bits -- An immutable container for binary data. +BitArray -- A mutable container for binary data. +ConstBitStream -- An immutable container with streaming methods. +BitStream -- A mutable container with streaming methods. + + Bits (base class) + / \ + + mutating methods / \ + streaming methods + / \ + BitArray ConstBitStream + \ / + \ / + \ / + BitStream + +Functions: + +pack -- Create a BitStream from a format string. + +Exceptions: + +Error -- Module exception base class. +CreationError -- Error during creation. +InterpretError -- Inappropriate interpretation of binary data. +ByteAlignError -- Whole byte position or length needed. +ReadError -- Reading or peeking past the end of a bitstring. + +http://python-bitstring.googlecode.com +""" + +__licence__ = """ +The MIT License + +Copyright (c) 2006-2014 Scott Griffiths (scott@griffiths.name) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +__version__ = "3.1.3" + +__author__ = "Scott Griffiths" + +import numbers +import copy +import sys +import re +import binascii +import mmap +import os +import struct +import operator +import collections + +byteorder = sys.byteorder + +bytealigned = False +"""Determines whether a number of methods default to working only on byte boundaries.""" + +# Maximum number of digits to use in __str__ and __repr__. +MAX_CHARS = 250 + +# Maximum size of caches used for speed optimisations. +CACHE_SIZE = 1000 + +class Error(Exception): + """Base class for errors in the bitstring module.""" + + def __init__(self, *params): + self.msg = params[0] if params else '' + self.params = params[1:] + + def __str__(self): + if self.params: + return self.msg.format(*self.params) + return self.msg + + +class ReadError(Error, IndexError): + """Reading or peeking past the end of a bitstring.""" + + def __init__(self, *params): + Error.__init__(self, *params) + + +class InterpretError(Error, ValueError): + """Inappropriate interpretation of binary data.""" + + def __init__(self, *params): + Error.__init__(self, *params) + + +class ByteAlignError(Error): + """Whole-byte position or length needed.""" + + def __init__(self, *params): + Error.__init__(self, *params) + + +class CreationError(Error, ValueError): + """Inappropriate argument during bitstring creation.""" + + def __init__(self, *params): + Error.__init__(self, *params) + + +class ConstByteStore(object): + """Stores raw bytes together with a bit offset and length. + + Used internally - not part of public interface. + """ + + __slots__ = ('offset', '_rawarray', 'bitlength') + + def __init__(self, data, bitlength=None, offset=None): + """data is either a bytearray or a MmapByteArray""" + self._rawarray = data + if offset is None: + offset = 0 + if bitlength is None: + bitlength = 8 * len(data) - offset + self.offset = offset + self.bitlength = bitlength + + def getbit(self, pos): + assert 0 <= pos < self.bitlength + byte, bit = divmod(self.offset + pos, 8) + return bool(self._rawarray[byte] & (128 >> bit)) + + def getbyte(self, pos): + """Direct access to byte data.""" + return self._rawarray[pos] + + def getbyteslice(self, start, end): + """Direct access to byte data.""" + c = self._rawarray[start:end] + return c + + @property + def bytelength(self): + if not self.bitlength: + return 0 + sb = self.offset // 8 + eb = (self.offset + self.bitlength - 1) // 8 + return eb - sb + 1 + + def __copy__(self): + return ByteStore(self._rawarray[:], self.bitlength, self.offset) + + def _appendstore(self, store): + """Join another store on to the end of this one.""" + if not store.bitlength: + return + # Set new array offset to the number of bits in the final byte of current array. + store = offsetcopy(store, (self.offset + self.bitlength) % 8) + if store.offset: + # first do the byte with the join. + joinval = (self._rawarray.pop() & (255 ^ (255 >> store.offset)) | + (store.getbyte(0) & (255 >> store.offset))) + self._rawarray.append(joinval) + self._rawarray.extend(store._rawarray[1:]) + else: + self._rawarray.extend(store._rawarray) + self.bitlength += store.bitlength + + def _prependstore(self, store): + """Join another store on to the start of this one.""" + if not store.bitlength: + return + # Set the offset of copy of store so that it's final byte + # ends in a position that matches the offset of self, + # then join self on to the end of it. + store = offsetcopy(store, (self.offset - store.bitlength) % 8) + assert (store.offset + store.bitlength) % 8 == self.offset % 8 + bit_offset = self.offset % 8 + if bit_offset: + # first do the byte with the join. + store.setbyte(-1, (store.getbyte(-1) & (255 ^ (255 >> bit_offset)) | \ + (self._rawarray[self.byteoffset] & (255 >> bit_offset)))) + store._rawarray.extend(self._rawarray[self.byteoffset + 1: self.byteoffset + self.bytelength]) + else: + store._rawarray.extend(self._rawarray[self.byteoffset: self.byteoffset + self.bytelength]) + self._rawarray = store._rawarray + self.offset = store.offset + self.bitlength += store.bitlength + + @property + def byteoffset(self): + return self.offset // 8 + + @property + def rawbytes(self): + return self._rawarray + + +class ByteStore(ConstByteStore): + """Adding mutating methods to ConstByteStore + + Used internally - not part of public interface. + """ + __slots__ = () + + def setbit(self, pos): + assert 0 <= pos < self.bitlength + byte, bit = divmod(self.offset + pos, 8) + self._rawarray[byte] |= (128 >> bit) + + def unsetbit(self, pos): + assert 0 <= pos < self.bitlength + byte, bit = divmod(self.offset + pos, 8) + self._rawarray[byte] &= ~(128 >> bit) + + def invertbit(self, pos): + assert 0 <= pos < self.bitlength + byte, bit = divmod(self.offset + pos, 8) + self._rawarray[byte] ^= (128 >> bit) + + def setbyte(self, pos, value): + self._rawarray[pos] = value + + def setbyteslice(self, start, end, value): + self._rawarray[start:end] = value + + +def offsetcopy(s, newoffset): + """Return a copy of a ByteStore with the newoffset. + + Not part of public interface. + """ + assert 0 <= newoffset < 8 + if not s.bitlength: + return copy.copy(s) + else: + if newoffset == s.offset % 8: + return ByteStore(s.getbyteslice(s.byteoffset, s.byteoffset + s.bytelength), s.bitlength, newoffset) + newdata = [] + d = s._rawarray + assert newoffset != s.offset % 8 + if newoffset < s.offset % 8: + # We need to shift everything left + shiftleft = s.offset % 8 - newoffset + # First deal with everything except for the final byte + for x in range(s.byteoffset, s.byteoffset + s.bytelength - 1): + newdata.append(((d[x] << shiftleft) & 0xff) +\ + (d[x + 1] >> (8 - shiftleft))) + bits_in_last_byte = (s.offset + s.bitlength) % 8 + if not bits_in_last_byte: + bits_in_last_byte = 8 + if bits_in_last_byte > shiftleft: + newdata.append((d[s.byteoffset + s.bytelength - 1] << shiftleft) & 0xff) + else: # newoffset > s._offset % 8 + shiftright = newoffset - s.offset % 8 + newdata.append(s.getbyte(0) >> shiftright) + for x in range(s.byteoffset + 1, s.byteoffset + s.bytelength): + newdata.append(((d[x - 1] << (8 - shiftright)) & 0xff) +\ + (d[x] >> shiftright)) + bits_in_last_byte = (s.offset + s.bitlength) % 8 + if not bits_in_last_byte: + bits_in_last_byte = 8 + if bits_in_last_byte + shiftright > 8: + newdata.append((d[s.byteoffset + s.bytelength - 1] << (8 - shiftright)) & 0xff) + new_s = ByteStore(bytearray(newdata), s.bitlength, newoffset) + assert new_s.offset == newoffset + return new_s + + +def equal(a, b): + """Return True if ByteStores a == b. + + Not part of public interface. + """ + # We want to return False for inequality as soon as possible, which + # means we get lots of special cases. + # First the easy one - compare lengths: + a_bitlength = a.bitlength + b_bitlength = b.bitlength + if a_bitlength != b_bitlength: + return False + if not a_bitlength: + assert b_bitlength == 0 + return True + # Make 'a' the one with the smaller offset + if (a.offset % 8) > (b.offset % 8): + a, b = b, a + # and create some aliases + a_bitoff = a.offset % 8 + b_bitoff = b.offset % 8 + a_byteoffset = a.byteoffset + b_byteoffset = b.byteoffset + a_bytelength = a.bytelength + b_bytelength = b.bytelength + da = a._rawarray + db = b._rawarray + + # If they are pointing to the same data, they must be equal + if da is db and a.offset == b.offset: + return True + + if a_bitoff == b_bitoff: + bits_spare_in_last_byte = 8 - (a_bitoff + a_bitlength) % 8 + if bits_spare_in_last_byte == 8: + bits_spare_in_last_byte = 0 + # Special case for a, b contained in a single byte + if a_bytelength == 1: + a_val = ((da[a_byteoffset] << a_bitoff) & 0xff) >> (8 - a_bitlength) + b_val = ((db[b_byteoffset] << b_bitoff) & 0xff) >> (8 - b_bitlength) + return a_val == b_val + # Otherwise check first byte + if da[a_byteoffset] & (0xff >> a_bitoff) != db[b_byteoffset] & (0xff >> b_bitoff): + return False + # then everything up to the last + b_a_offset = b_byteoffset - a_byteoffset + for x in range(1 + a_byteoffset, a_byteoffset + a_bytelength - 1): + if da[x] != db[b_a_offset + x]: + return False + # and finally the last byte + return (da[a_byteoffset + a_bytelength - 1] >> bits_spare_in_last_byte == + db[b_byteoffset + b_bytelength - 1] >> bits_spare_in_last_byte) + + assert a_bitoff != b_bitoff + # This is how much we need to shift a to the right to compare with b: + shift = b_bitoff - a_bitoff + # Special case for b only one byte long + if b_bytelength == 1: + assert a_bytelength == 1 + a_val = ((da[a_byteoffset] << a_bitoff) & 0xff) >> (8 - a_bitlength) + b_val = ((db[b_byteoffset] << b_bitoff) & 0xff) >> (8 - b_bitlength) + return a_val == b_val + # Special case for a only one byte long + if a_bytelength == 1: + assert b_bytelength == 2 + a_val = ((da[a_byteoffset] << a_bitoff) & 0xff) >> (8 - a_bitlength) + b_val = ((db[b_byteoffset] << 8) + db[b_byteoffset + 1]) << b_bitoff + b_val &= 0xffff + b_val >>= 16 - b_bitlength + return a_val == b_val + + # Compare first byte of b with bits from first byte of a + if (da[a_byteoffset] & (0xff >> a_bitoff)) >> shift != db[b_byteoffset] & (0xff >> b_bitoff): + return False + # Now compare every full byte of b with bits from 2 bytes of a + for x in range(1, b_bytelength - 1): + # Construct byte from 2 bytes in a to compare to byte in b + b_val = db[b_byteoffset + x] + a_val = ((da[a_byteoffset + x - 1] << 8) + da[a_byteoffset + x]) >> shift + a_val &= 0xff + if a_val != b_val: + return False + + # Now check bits in final byte of b + final_b_bits = (b.offset + b_bitlength) % 8 + if not final_b_bits: + final_b_bits = 8 + b_val = db[b_byteoffset + b_bytelength - 1] >> (8 - final_b_bits) + final_a_bits = (a.offset + a_bitlength) % 8 + if not final_a_bits: + final_a_bits = 8 + if b.bytelength > a_bytelength: + assert b_bytelength == a_bytelength + 1 + a_val = da[a_byteoffset + a_bytelength - 1] >> (8 - final_a_bits) + a_val &= 0xff >> (8 - final_b_bits) + return a_val == b_val + assert a_bytelength == b_bytelength + a_val = da[a_byteoffset + a_bytelength - 2] << 8 + a_val += da[a_byteoffset + a_bytelength - 1] + a_val >>= (8 - final_a_bits) + a_val &= 0xff >> (8 - final_b_bits) + return a_val == b_val + + +class MmapByteArray(object): + """Looks like a bytearray, but from an mmap. + + Not part of public interface. + """ + + __slots__ = ('filemap', 'filelength', 'source', 'byteoffset', 'bytelength') + + def __init__(self, source, bytelength=None, byteoffset=None): + self.source = source + source.seek(0, os.SEEK_END) + self.filelength = source.tell() + if byteoffset is None: + byteoffset = 0 + if bytelength is None: + bytelength = self.filelength - byteoffset + self.byteoffset = byteoffset + self.bytelength = bytelength + self.filemap = mmap.mmap(source.fileno(), 0, access=mmap.ACCESS_READ) + + def __getitem__(self, key): + try: + start = key.start + stop = key.stop + except AttributeError: + try: + assert 0 <= key < self.bytelength + return ord(self.filemap[key + self.byteoffset]) + except TypeError: + # for Python 3 + return self.filemap[key + self.byteoffset] + else: + if start is None: + start = 0 + if stop is None: + stop = self.bytelength + assert key.step is None + assert 0 <= start < self.bytelength + assert 0 <= stop <= self.bytelength + s = slice(start + self.byteoffset, stop + self.byteoffset) + return bytearray(self.filemap.__getitem__(s)) + + def __len__(self): + return self.bytelength + + +# This creates a dictionary for every possible byte with the value being +# the key with its bits reversed. +BYTE_REVERSAL_DICT = dict() + +# For Python 2.x/ 3.x coexistence +# Yes this is very very hacky. +try: + xrange + for i in range(256): + BYTE_REVERSAL_DICT[i] = chr(int("{0:08b}".format(i)[::-1], 2)) +except NameError: + for i in range(256): + BYTE_REVERSAL_DICT[i] = bytes([int("{0:08b}".format(i)[::-1], 2)]) + from io import IOBase as file + xrange = range + basestring = str + +# Python 2.x octals start with '0', in Python 3 it's '0o' +LEADING_OCT_CHARS = len(oct(1)) - 1 + +def tidy_input_string(s): + """Return string made lowercase and with all whitespace removed.""" + s = ''.join(s.split()).lower() + return s + +INIT_NAMES = ('uint', 'int', 'ue', 'se', 'sie', 'uie', 'hex', 'oct', 'bin', 'bits', + 'uintbe', 'intbe', 'uintle', 'intle', 'uintne', 'intne', + 'float', 'floatbe', 'floatle', 'floatne', 'bytes', 'bool', 'pad') + +TOKEN_RE = re.compile(r'(?P<name>' + '|'.join(INIT_NAMES) + + r')((:(?P<len>[^=]+)))?(=(?P<value>.*))?$', re.IGNORECASE) +DEFAULT_UINT = re.compile(r'(?P<len>[^=]+)?(=(?P<value>.*))?$', re.IGNORECASE) + +MULTIPLICATIVE_RE = re.compile(r'(?P<factor>.*)\*(?P<token>.+)') + +# Hex, oct or binary literals +LITERAL_RE = re.compile(r'(?P<name>0(x|o|b))(?P<value>.+)', re.IGNORECASE) + +# An endianness indicator followed by one or more struct.pack codes +STRUCT_PACK_RE = re.compile(r'(?P<endian><|>|@)?(?P<fmt>(?:\d*[bBhHlLqQfd])+)$') + +# A number followed by a single character struct.pack code +STRUCT_SPLIT_RE = re.compile(r'\d*[bBhHlLqQfd]') + +# These replicate the struct.pack codes +# Big-endian +REPLACEMENTS_BE = {'b': 'intbe:8', 'B': 'uintbe:8', + 'h': 'intbe:16', 'H': 'uintbe:16', + 'l': 'intbe:32', 'L': 'uintbe:32', + 'q': 'intbe:64', 'Q': 'uintbe:64', + 'f': 'floatbe:32', 'd': 'floatbe:64'} +# Little-endian +REPLACEMENTS_LE = {'b': 'intle:8', 'B': 'uintle:8', + 'h': 'intle:16', 'H': 'uintle:16', + 'l': 'intle:32', 'L': 'uintle:32', + 'q': 'intle:64', 'Q': 'uintle:64', + 'f': 'floatle:32', 'd': 'floatle:64'} + +# Size in bytes of all the pack codes. +PACK_CODE_SIZE = {'b': 1, 'B': 1, 'h': 2, 'H': 2, 'l': 4, 'L': 4, + 'q': 8, 'Q': 8, 'f': 4, 'd': 8} + +_tokenname_to_initialiser = {'hex': 'hex', '0x': 'hex', '0X': 'hex', 'oct': 'oct', + '0o': 'oct', '0O': 'oct', 'bin': 'bin', '0b': 'bin', + '0B': 'bin', 'bits': 'auto', 'bytes': 'bytes', 'pad': 'pad'} + +def structparser(token): + """Parse struct-like format string token into sub-token list.""" + m = STRUCT_PACK_RE.match(token) + if not m: + return [token] + else: + endian = m.group('endian') + if endian is None: + return [token] + # Split the format string into a list of 'q', '4h' etc. + formatlist = re.findall(STRUCT_SPLIT_RE, m.group('fmt')) + # Now deal with mulitiplicative factors, 4h -> hhhh etc. + fmt = ''.join([f[-1] * int(f[:-1]) if len(f) != 1 else + f for f in formatlist]) + if endian == '@': + # Native endianness + if byteorder == 'little': + endian = '<' + else: + assert byteorder == 'big' + endian = '>' + if endian == '<': + tokens = [REPLACEMENTS_LE[c] for c in fmt] + else: + assert endian == '>' + tokens = [REPLACEMENTS_BE[c] for c in fmt] + return tokens + +def tokenparser(fmt, keys=None, token_cache={}): + """Divide the format string into tokens and parse them. + + Return stretchy token and list of [initialiser, length, value] + initialiser is one of: hex, oct, bin, uint, int, se, ue, 0x, 0o, 0b etc. + length is None if not known, as is value. + + If the token is in the keyword dictionary (keys) then it counts as a + special case and isn't messed with. + + tokens must be of the form: [factor*][initialiser][:][length][=value] + + """ + try: + return token_cache[(fmt, keys)] + except KeyError: + token_key = (fmt, keys) + # Very inefficient expanding of brackets. + fmt = expand_brackets(fmt) + # Split tokens by ',' and remove whitespace + # The meta_tokens can either be ordinary single tokens or multiple + # struct-format token strings. + meta_tokens = (''.join(f.split()) for f in fmt.split(',')) + return_values = [] + stretchy_token = False + for meta_token in meta_tokens: + # See if it has a multiplicative factor + m = MULTIPLICATIVE_RE.match(meta_token) + if not m: + factor = 1 + else: + factor = int(m.group('factor')) + meta_token = m.group('token') + # See if it's a struct-like format + tokens = structparser(meta_token) + ret_vals = [] + for token in tokens: + if keys and token in keys: + # Don't bother parsing it, it's a keyword argument + ret_vals.append([token, None, None]) + continue + value = length = None + if token == '': + continue + # Match literal tokens of the form 0x... 0o... and 0b... + m = LITERAL_RE.match(token) + if m: + name = m.group('name') + value = m.group('value') + ret_vals.append([name, length, value]) + continue + # Match everything else: + m1 = TOKEN_RE.match(token) + if not m1: + # and if you don't specify a 'name' then the default is 'uint': + m2 = DEFAULT_UINT.match(token) + if not m2: + raise ValueError("Don't understand token '{0}'.".format(token)) + if m1: + name = m1.group('name') + length = m1.group('len') + if m1.group('value'): + value = m1.group('value') + else: + assert m2 + name = 'uint' + length = m2.group('len') + if m2.group('value'): + value = m2.group('value') + if name == 'bool': + if length is not None: + raise ValueError("You can't specify a length with bool tokens - they are always one bit.") + length = 1 + if length is None and name not in ('se', 'ue', 'sie', 'uie'): + stretchy_token = True + if length is not None: + # Try converting length to int, otherwise check it's a key. + try: + length = int(length) + if length < 0: + raise Error + # For the 'bytes' token convert length to bits. + if name == 'bytes': + length *= 8 + except Error: + raise ValueError("Can't read a token with a negative length.") + except ValueError: + if not keys or length not in keys: + raise ValueError("Don't understand length '{0}' of token.".format(length)) + ret_vals.append([name, length, value]) + # This multiplies by the multiplicative factor, but this means that + # we can't allow keyword values as multipliers (e.g. n*uint:8). + # The only way to do this would be to return the factor in some fashion + # (we can't use the key's value here as it would mean that we couldn't + # sensibly continue to cache the function's results. (TODO). + return_values.extend(ret_vals * factor) + return_values = [tuple(x) for x in return_values] + if len(token_cache) < CACHE_SIZE: + token_cache[token_key] = stretchy_token, return_values + return stretchy_token, return_values + +# Looks for first number*( +BRACKET_RE = re.compile(r'(?P<factor>\d+)\*\(') + +def expand_brackets(s): + """Remove whitespace and expand all brackets.""" + s = ''.join(s.split()) + while True: + start = s.find('(') + if start == -1: + break + count = 1 # Number of hanging open brackets + p = start + 1 + while p < len(s): + if s[p] == '(': + count += 1 + if s[p] == ')': + count -= 1 + if not count: + break + p += 1 + if count: + raise ValueError("Unbalanced parenthesis in '{0}'.".format(s)) + if start == 0 or s[start - 1] != '*': + s = s[0:start] + s[start + 1:p] + s[p + 1:] + else: + m = BRACKET_RE.search(s) + if m: + factor = int(m.group('factor')) + matchstart = m.start('factor') + s = s[0:matchstart] + (factor - 1) * (s[start + 1:p] + ',') + s[start + 1:p] + s[p + 1:] + else: + raise ValueError("Failed to parse '{0}'.".format(s)) + return s + + +# This converts a single octal digit to 3 bits. +OCT_TO_BITS = ['{0:03b}'.format(i) for i in xrange(8)] + +# A dictionary of number of 1 bits contained in binary representation of any byte +BIT_COUNT = dict(zip(xrange(256), [bin(i).count('1') for i in xrange(256)])) + + +class Bits(object): + """A container holding an immutable sequence of bits. + + For a mutable container use the BitArray class instead. + + Methods: + + all() -- Check if all specified bits are set to 1 or 0. + any() -- Check if any of specified bits are set to 1 or 0. + count() -- Count the number of bits set to 1 or 0. + cut() -- Create generator of constant sized chunks. + endswith() -- Return whether the bitstring ends with a sub-string. + find() -- Find a sub-bitstring in the current bitstring. + findall() -- Find all occurrences of a sub-bitstring in the current bitstring. + join() -- Join bitstrings together using current bitstring. + rfind() -- Seek backwards to find a sub-bitstring. + split() -- Create generator of chunks split by a delimiter. + startswith() -- Return whether the bitstring starts with a sub-bitstring. + tobytes() -- Return bitstring as bytes, padding if needed. + tofile() -- Write bitstring to file, padding if needed. + unpack() -- Interpret bits using format string. + + Special methods: + + Also available are the operators [], ==, !=, +, *, ~, <<, >>, &, |, ^. + + Properties: + + bin -- The bitstring as a binary string. + bool -- For single bit bitstrings, interpret as True or False. + bytes -- The bitstring as a bytes object. + float -- Interpret as a floating point number. + floatbe -- Interpret as a big-endian floating point number. + floatle -- Interpret as a little-endian floating point number. + floatne -- Interpret as a native-endian floating point number. + hex -- The bitstring as a hexadecimal string. + int -- Interpret as a two's complement signed integer. + intbe -- Interpret as a big-endian signed integer. + intle -- Interpret as a little-endian signed integer. + intne -- Interpret as a native-endian signed integer. + len -- Length of the bitstring in bits. + oct -- The bitstring as an octal string. + se -- Interpret as a signed exponential-Golomb code. + ue -- Interpret as an unsigned exponential-Golomb code. + sie -- Interpret as a signed interleaved exponential-Golomb code. + uie -- Interpret as an unsigned interleaved exponential-Golomb code. + uint -- Interpret as a two's complement unsigned integer. + uintbe -- Interpret as a big-endian unsigned integer. + uintle -- Interpret as a little-endian unsigned integer. + uintne -- Interpret as a native-endian unsigned integer. + + """ + + __slots__ = ('_datastore') + + def __init__(self, auto=None, length=None, offset=None, **kwargs): + """Either specify an 'auto' initialiser: + auto -- a string of comma separated tokens, an integer, a file object, + a bytearray, a boolean iterable or another bitstring. + + Or initialise via **kwargs with one (and only one) of: + bytes -- raw data as a string, for example read from a binary file. + bin -- binary string representation, e.g. '0b001010'. + hex -- hexadecimal string representation, e.g. '0x2ef' + oct -- octal string representation, e.g. '0o777'. + uint -- an unsigned integer. + int -- a signed integer. + float -- a floating point number. + uintbe -- an unsigned big-endian whole byte integer. + intbe -- a signed big-endian whole byte integer. + floatbe - a big-endian floating point number. + uintle -- an unsigned little-endian whole byte integer. + intle -- a signed little-endian whole byte integer. + floatle -- a little-endian floating point number. + uintne -- an unsigned native-endian whole byte integer. + intne -- a signed native-endian whole byte integer. + floatne -- a native-endian floating point number. + se -- a signed exponential-Golomb code. + ue -- an unsigned exponential-Golomb code. + sie -- a signed interleaved exponential-Golomb code. + uie -- an unsigned interleaved exponential-Golomb code. + bool -- a boolean (True or False). + filename -- a file which will be opened in binary read-only mode. + + Other keyword arguments: + length -- length of the bitstring in bits, if needed and appropriate. + It must be supplied for all integer and float initialisers. + offset -- bit offset to the data. These offset bits are + ignored and this is mainly intended for use when + initialising using 'bytes' or 'filename'. + + """ + pass + + def __new__(cls, auto=None, length=None, offset=None, _cache={}, **kwargs): + # For instances auto-initialised with a string we intern the + # instance for re-use. + try: + if isinstance(auto, basestring): + try: + return _cache[auto] + except KeyError: + x = object.__new__(Bits) + try: + _, tokens = tokenparser(auto) + except ValueError as e: + raise CreationError(*e.args) + x._datastore = ConstByteStore(bytearray(0), 0, 0) + for token in tokens: + x._datastore._appendstore(Bits._init_with_token(*token)._datastore) + assert x._assertsanity() + if len(_cache) < CACHE_SIZE: + _cache[auto] = x + return x + if isinstance(auto, Bits): + return auto + except TypeError: + pass + x = super(Bits, cls).__new__(cls) + x._initialise(auto, length, offset, **kwargs) + return x + + def _initialise(self, auto, length, offset, **kwargs): + if length is not None and length < 0: + raise CreationError("bitstring length cannot be negative.") + if offset is not None and offset < 0: + raise CreationError("offset must be >= 0.") + if auto is not None: + self._initialise_from_auto(auto, length, offset) + return + if not kwargs: + # No initialisers, so initialise with nothing or zero bits + if length is not None and length != 0: + data = bytearray((length + 7) // 8) + self._setbytes_unsafe(data, length, 0) + return + self._setbytes_unsafe(bytearray(0), 0, 0) + return + k, v = kwargs.popitem() + try: + init_without_length_or_offset[k](self, v) + if length is not None or offset is not None: + raise CreationError("Cannot use length or offset with this initialiser.") + except KeyError: + try: + init_with_length_only[k](self, v, length) + if offset is not None: + raise CreationError("Cannot use offset with this initialiser.") + except KeyError: + if offset is None: + offset = 0 + try: + init_with_length_and_offset[k](self, v, length, offset) + except KeyError: + raise CreationError("Unrecognised keyword '{0}' used to initialise.", k) + + def _initialise_from_auto(self, auto, length, offset): + if offset is None: + offset = 0 + self._setauto(auto, length, offset) + return + + def __copy__(self): + """Return a new copy of the Bits for the copy module.""" + # Note that if you want a new copy (different ID), use _copy instead. + # The copy can return self as it's immutable. + return self + + def __lt__(self, other): + raise TypeError("unorderable type: {0}".format(type(self).__name__)) + + def __gt__(self, other): + raise TypeError("unorderable type: {0}".format(type(self).__name__)) + + def __le__(self, other): + raise TypeError("unorderable type: {0}".format(type(self).__name__)) + + def __ge__(self, other): + raise TypeError("unorderable type: {0}".format(type(self).__name__)) + + def __add__(self, bs): + """Concatenate bitstrings and return new bitstring. + + bs -- the bitstring to append. + + """ + bs = Bits(bs) + if bs.len <= self.len: + s = self._copy() + s._append(bs) + else: + s = bs._copy() + s = self.__class__(s) + s._prepend(self) + return s + + def __radd__(self, bs): + """Append current bitstring to bs and return new bitstring. + + bs -- the string for the 'auto' initialiser that will be appended to. + + """ + bs = self._converttobitstring(bs) + return bs.__add__(self) + + def __getitem__(self, key): + """Return a new bitstring representing a slice of the current bitstring. + + Indices are in units of the step parameter (default 1 bit). + Stepping is used to specify the number of bits in each item. + + >>> print BitArray('0b00110')[1:4] + '0b011' + >>> print BitArray('0x00112233')[1:3:8] + '0x1122' + + """ + length = self.len + try: + step = key.step if key.step is not None else 1 + except AttributeError: + # single element + if key < 0: + key += length + if not 0 <= key < length: + raise IndexError("Slice index out of range.") + # Single bit, return True or False + return self._datastore.getbit(key) + else: + if step != 1: + # convert to binary string and use string slicing + bs = self.__class__() + bs._setbin_unsafe(self._getbin().__getitem__(key)) + return bs + start, stop = 0, length + if key.start is not None: + start = key.start + if key.start < 0: + start += stop + if key.stop is not None: + stop = key.stop + if key.stop < 0: + stop += length + start = max(start, 0) + stop = min(stop, length) + if start < stop: + return self._slice(start, stop) + else: + return self.__class__() + + def __len__(self): + """Return the length of the bitstring in bits.""" + return self._getlength() + + def __str__(self): + """Return approximate string representation of bitstring for printing. + + Short strings will be given wholly in hexadecimal or binary. Longer + strings may be part hexadecimal and part binary. Very long strings will + be truncated with '...'. + + """ + length = self.len + if not length: + return '' + if length > MAX_CHARS * 4: + # Too long for hex. Truncate... + return ''.join(('0x', self._readhex(MAX_CHARS * 4, 0), '...')) + # If it's quite short and we can't do hex then use bin + if length < 32 and length % 4 != 0: + return '0b' + self.bin + # If we can use hex then do so + if not length % 4: + return '0x' + self.hex + # Otherwise first we do as much as we can in hex + # then add on 1, 2 or 3 bits on at the end + bits_at_end = length % 4 + return ''.join(('0x', self._readhex(length - bits_at_end, 0), + ', ', '0b', + self._readbin(bits_at_end, length - bits_at_end))) + + def __repr__(self): + """Return representation that could be used to recreate the bitstring. + + If the returned string is too long it will be truncated. See __str__(). + + """ + length = self.len + if isinstance(self._datastore._rawarray, MmapByteArray): + offsetstring = '' + if self._datastore.byteoffset or self._offset: + offsetstring = ", offset=%d" % (self._datastore._rawarray.byteoffset * 8 + self._offset) + lengthstring = ", length=%d" % length + return "{0}(filename='{1}'{2}{3})".format(self.__class__.__name__, + self._datastore._rawarray.source.name, lengthstring, offsetstring) + else: + s = self.__str__() + lengthstring = '' + if s.endswith('...'): + lengthstring = " # length={0}".format(length) + return "{0}('{1}'){2}".format(self.__class__.__name__, s, lengthstring) + + def __eq__(self, bs): + """Return True if two bitstrings have the same binary representation. + + >>> BitArray('0b1110') == '0xe' + True + + """ + try: + bs = Bits(bs) + except TypeError: + return False + return equal(self._datastore, bs._datastore) + + def __ne__(self, bs): + """Return False if two bitstrings have the same binary representation. + + >>> BitArray('0b111') == '0x7' + False + + """ + return not self.__eq__(bs) + + def __invert__(self): + """Return bitstring with every bit inverted. + + Raises Error if the bitstring is empty. + + """ + if not self.len: + raise Error("Cannot invert empty bitstring.") + s = self._copy() + s._invert_all() + return s + + def __lshift__(self, n): + """Return bitstring with bits shifted by n to the left. + + n -- the number of bits to shift. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot shift by a negative amount.") + if not self.len: + raise ValueError("Cannot shift an empty bitstring.") + n = min(n, self.len) + s = self._slice(n, self.len) + s._append(Bits(n)) + return s + + def __rshift__(self, n): + """Return bitstring with bits shifted by n to the right. + + n -- the number of bits to shift. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot shift by a negative amount.") + if not self.len: + raise ValueError("Cannot shift an empty bitstring.") + if not n: + return self._copy() + s = self.__class__(length=min(n, self.len)) + s._append(self[:-n]) + return s + + def __mul__(self, n): + """Return bitstring consisting of n concatenations of self. + + Called for expression of the form 'a = b*3'. + n -- The number of concatenations. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot multiply by a negative integer.") + if not n: + return self.__class__() + s = self._copy() + s._imul(n) + return s + + def __rmul__(self, n): + """Return bitstring consisting of n concatenations of self. + + Called for expressions of the form 'a = 3*b'. + n -- The number of concatenations. Must be >= 0. + + """ + return self.__mul__(n) + + def __and__(self, bs): + """Bit-wise 'and' between two bitstrings. Returns new bitstring. + + bs -- The bitstring to '&' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for & operator.") + s = self._copy() + s._iand(bs) + return s + + def __rand__(self, bs): + """Bit-wise 'and' between two bitstrings. Returns new bitstring. + + bs -- the bitstring to '&' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + return self.__and__(bs) + + def __or__(self, bs): + """Bit-wise 'or' between two bitstrings. Returns new bitstring. + + bs -- The bitstring to '|' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for | operator.") + s = self._copy() + s._ior(bs) + return s + + def __ror__(self, bs): + """Bit-wise 'or' between two bitstrings. Returns new bitstring. + + bs -- The bitstring to '|' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + return self.__or__(bs) + + def __xor__(self, bs): + """Bit-wise 'xor' between two bitstrings. Returns new bitstring. + + bs -- The bitstring to '^' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for ^ operator.") + s = self._copy() + s._ixor(bs) + return s + + def __rxor__(self, bs): + """Bit-wise 'xor' between two bitstrings. Returns new bitstring. + + bs -- The bitstring to '^' with. + + Raises ValueError if the two bitstrings have differing lengths. + + """ + return self.__xor__(bs) + + def __contains__(self, bs): + """Return whether bs is contained in the current bitstring. + + bs -- The bitstring to search for. + + """ + # Don't want to change pos + try: + pos = self._pos + except AttributeError: + pass + found = Bits.find(self, bs, bytealigned=False) + try: + self._pos = pos + except AttributeError: + pass + return bool(found) + + def __hash__(self): + """Return an integer hash of the object.""" + # We can't in general hash the whole bitstring (it could take hours!) + # So instead take some bits from the start and end. + if self.len <= 160: + # Use the whole bitstring. + shorter = self + else: + # Take 10 bytes from start and end + shorter = self[:80] + self[-80:] + h = 0 + for byte in shorter.tobytes(): + try: + h = (h << 4) + ord(byte) + except TypeError: + # Python 3 + h = (h << 4) + byte + g = h & 0xf0000000 + if g & (1 << 31): + h ^= (g >> 24) + h ^= g + return h % 1442968193 + + # This is only used in Python 2.x... + def __nonzero__(self): + """Return True if any bits are set to 1, otherwise return False.""" + return self.any(True) + + # ...whereas this is used in Python 3.x + __bool__ = __nonzero__ + + def _assertsanity(self): + """Check internal self consistency as a debugging aid.""" + assert self.len >= 0 + assert 0 <= self._offset, "offset={0}".format(self._offset) + assert (self.len + self._offset + 7) // 8 == self._datastore.bytelength + self._datastore.byteoffset + return True + + @classmethod + def _init_with_token(cls, name, token_length, value): + if token_length is not None: + token_length = int(token_length) + if token_length == 0: + return cls() + # For pad token just return the length in zero bits + if name == 'pad': + return cls(token_length) + + if value is None: + if token_length is None: + error = "Token has no value ({0}=???).".format(name) + else: + error = "Token has no value ({0}:{1}=???).".format(name, token_length) + raise ValueError(error) + try: + b = cls(**{_tokenname_to_initialiser[name]: value}) + except KeyError: + if name in ('se', 'ue', 'sie', 'uie'): + b = cls(**{name: int(value)}) + elif name in ('uint', 'int', 'uintbe', 'intbe', 'uintle', 'intle', 'uintne', 'intne'): + b = cls(**{name: int(value), 'length': token_length}) + elif name in ('float', 'floatbe', 'floatle', 'floatne'): + b = cls(**{name: float(value), 'length': token_length}) + elif name == 'bool': + if value in (1, 'True', '1'): + b = cls(bool=True) + elif value in (0, 'False', '0'): + b = cls(bool=False) + else: + raise CreationError("bool token can only be 'True' or 'False'.") + else: + raise CreationError("Can't parse token name {0}.", name) + if token_length is not None and b.len != token_length: + msg = "Token with length {0} packed with value of length {1} ({2}:{3}={4})." + raise CreationError(msg, token_length, b.len, name, token_length, value) + return b + + def _clear(self): + """Reset the bitstring to an empty state.""" + self._datastore = ByteStore(bytearray(0)) + + def _setauto(self, s, length, offset): + """Set bitstring from a bitstring, file, bool, integer, iterable or string.""" + # As s can be so many different things it's important to do the checks + # in the correct order, as some types are also other allowed types. + # So basestring must be checked before Iterable + # and bytes/bytearray before Iterable but after basestring! + if isinstance(s, Bits): + if length is None: + length = s.len - offset + self._setbytes_unsafe(s._datastore.rawbytes, length, s._offset + offset) + return + if isinstance(s, file): + if offset is None: + offset = 0 + if length is None: + length = os.path.getsize(s.name) * 8 - offset + byteoffset, offset = divmod(offset, 8) + bytelength = (length + byteoffset * 8 + offset + 7) // 8 - byteoffset + m = MmapByteArray(s, bytelength, byteoffset) + if length + byteoffset * 8 + offset > m.filelength * 8: + raise CreationError("File is not long enough for specified " + "length and offset.") + self._datastore = ConstByteStore(m, length, offset) + return + if length is not None: + raise CreationError("The length keyword isn't applicable to this initialiser.") + if offset: + raise CreationError("The offset keyword isn't applicable to this initialiser.") + if isinstance(s, basestring): + bs = self._converttobitstring(s) + assert bs._offset == 0 + self._setbytes_unsafe(bs._datastore.rawbytes, bs.length, 0) + return + if isinstance(s, (bytes, bytearray)): + self._setbytes_unsafe(bytearray(s), len(s) * 8, 0) + return + if isinstance(s, numbers.Integral): + # Initialise with s zero bits. + if s < 0: + msg = "Can't create bitstring of negative length {0}." + raise CreationError(msg, s) + data = bytearray((s + 7) // 8) + self._datastore = ByteStore(data, s, 0) + return + if isinstance(s, collections.Iterable): + # Evaluate each item as True or False and set bits to 1 or 0. + self._setbin_unsafe(''.join(str(int(bool(x))) for x in s)) + return + raise TypeError("Cannot initialise bitstring from {0}.".format(type(s))) + + def _setfile(self, filename, length, offset): + """Use file as source of bits.""" + source = open(filename, 'rb') + if offset is None: + offset = 0 + if length is None: + length = os.path.getsize(source.name) * 8 - offset + byteoffset, offset = divmod(offset, 8) + bytelength = (length + byteoffset * 8 + offset + 7) // 8 - byteoffset + m = MmapByteArray(source, bytelength, byteoffset) + if length + byteoffset * 8 + offset > m.filelength * 8: + raise CreationError("File is not long enough for specified " + "length and offset.") + self._datastore = ConstByteStore(m, length, offset) + + def _setbytes_safe(self, data, length=None, offset=0): + """Set the data from a string.""" + data = bytearray(data) + if length is None: + # Use to the end of the data + length = len(data)*8 - offset + self._datastore = ByteStore(data, length, offset) + else: + if length + offset > len(data) * 8: + msg = "Not enough data present. Need {0} bits, have {1}." + raise CreationError(msg, length + offset, len(data) * 8) + if length == 0: + self._datastore = ByteStore(bytearray(0)) + else: + self._datastore = ByteStore(data, length, offset) + + def _setbytes_unsafe(self, data, length, offset): + """Unchecked version of _setbytes_safe.""" + self._datastore = ByteStore(data[:], length, offset) + assert self._assertsanity() + + def _readbytes(self, length, start): + """Read bytes and return them. Note that length is in bits.""" + assert length % 8 == 0 + assert start + length <= self.len + if not (start + self._offset) % 8: + return bytes(self._datastore.getbyteslice((start + self._offset) // 8, + (start + self._offset + length) // 8)) + return self._slice(start, start + length).tobytes() + + def _getbytes(self): + """Return the data as an ordinary string.""" + if self.len % 8: + raise InterpretError("Cannot interpret as bytes unambiguously - " + "not multiple of 8 bits.") + return self._readbytes(self.len, 0) + + def _setuint(self, uint, length=None): + """Reset the bitstring to have given unsigned int interpretation.""" + try: + if length is None: + # Use the whole length. Deliberately not using .len here. + length = self._datastore.bitlength + except AttributeError: + # bitstring doesn't have a _datastore as it hasn't been created! + pass + # TODO: All this checking code should be hoisted out of here! + if length is None or length == 0: + raise CreationError("A non-zero length must be specified with a " + "uint initialiser.") + if uint >= (1 << length): + msg = "{0} is too large an unsigned integer for a bitstring of length {1}. "\ + "The allowed range is [0, {2}]." + raise CreationError(msg, uint, length, (1 << length) - 1) + if uint < 0: + raise CreationError("uint cannot be initialsed by a negative number.") + s = hex(uint)[2:] + s = s.rstrip('L') + if len(s) & 1: + s = '0' + s + try: + data = bytes.fromhex(s) + except AttributeError: + # the Python 2.x way + data = binascii.unhexlify(s) + # Now add bytes as needed to get the right length. + extrabytes = ((length + 7) // 8) - len(data) + if extrabytes > 0: + data = b'\x00' * extrabytes + data + offset = 8 - (length % 8) + if offset == 8: + offset = 0 + self._setbytes_unsafe(bytearray(data), length, offset) + + def _readuint(self, length, start): + """Read bits and interpret as an unsigned int.""" + if not length: + raise InterpretError("Cannot interpret a zero length bitstring " + "as an integer.") + offset = self._offset + startbyte = (start + offset) // 8 + endbyte = (start + offset + length - 1) // 8 + + b = binascii.hexlify(bytes(self._datastore.getbyteslice(startbyte, endbyte + 1))) + assert b + i = int(b, 16) + final_bits = 8 - ((start + offset + length) % 8) + if final_bits != 8: + i >>= final_bits + i &= (1 << length) - 1 + return i + + def _getuint(self): + """Return data as an unsigned int.""" + return self._readuint(self.len, 0) + + def _setint(self, int_, length=None): + """Reset the bitstring to have given signed int interpretation.""" + # If no length given, and we've previously been given a length, use it. + if length is None and hasattr(self, 'len') and self.len != 0: + length = self.len + if length is None or length == 0: + raise CreationError("A non-zero length must be specified with an int initialiser.") + if int_ >= (1 << (length - 1)) or int_ < -(1 << (length - 1)): + raise CreationError("{0} is too large a signed integer for a bitstring of length {1}. " + "The allowed range is [{2}, {3}].", int_, length, -(1 << (length - 1)), + (1 << (length - 1)) - 1) + if int_ >= 0: + self._setuint(int_, length) + return + # TODO: We should decide whether to just use the _setuint, or to do the bit flipping, + # based upon which will be quicker. If the -ive number is less than half the maximum + # possible then it's probably quicker to do the bit flipping... + + # Do the 2's complement thing. Add one, set to minus number, then flip bits. + int_ += 1 + self._setuint(-int_, length) + self._invert_all() + + def _readint(self, length, start): + """Read bits and interpret as a signed int""" + ui = self._readuint(length, start) + if not ui >> (length - 1): + # Top bit not set, number is positive + return ui + # Top bit is set, so number is negative + tmp = (~(ui - 1)) & ((1 << length) - 1) + return -tmp + + def _getint(self): + """Return data as a two's complement signed int.""" + return self._readint(self.len, 0) + + def _setuintbe(self, uintbe, length=None): + """Set the bitstring to a big-endian unsigned int interpretation.""" + if length is not None and length % 8 != 0: + raise CreationError("Big-endian integers must be whole-byte. " + "Length = {0} bits.", length) + self._setuint(uintbe, length) + + def _readuintbe(self, length, start): + """Read bits and interpret as a big-endian unsigned int.""" + if length % 8: + raise InterpretError("Big-endian integers must be whole-byte. " + "Length = {0} bits.", length) + return self._readuint(length, start) + + def _getuintbe(self): + """Return data as a big-endian two's complement unsigned int.""" + return self._readuintbe(self.len, 0) + + def _setintbe(self, intbe, length=None): + """Set bitstring to a big-endian signed int interpretation.""" + if length is not None and length % 8 != 0: + raise CreationError("Big-endian integers must be whole-byte. " + "Length = {0} bits.", length) + self._setint(intbe, length) + + def _readintbe(self, length, start): + """Read bits and interpret as a big-endian signed int.""" + if length % 8: + raise InterpretError("Big-endian integers must be whole-byte. " + "Length = {0} bits.", length) + return self._readint(length, start) + + def _getintbe(self): + """Return data as a big-endian two's complement signed int.""" + return self._readintbe(self.len, 0) + + def _setuintle(self, uintle, length=None): + if length is not None and length % 8 != 0: + raise CreationError("Little-endian integers must be whole-byte. " + "Length = {0} bits.", length) + self._setuint(uintle, length) + self._reversebytes(0, self.len) + + def _readuintle(self, length, start): + """Read bits and interpret as a little-endian unsigned int.""" + if length % 8: + raise InterpretError("Little-endian integers must be whole-byte. " + "Length = {0} bits.", length) + assert start + length <= self.len + absolute_pos = start + self._offset + startbyte, offset = divmod(absolute_pos, 8) + val = 0 + if not offset: + endbyte = (absolute_pos + length - 1) // 8 + chunksize = 4 # for 'L' format + while endbyte - chunksize + 1 >= startbyte: + val <<= 8 * chunksize + val += struct.unpack('<L', bytes(self._datastore.getbyteslice(endbyte + 1 - chunksize, endbyte + 1)))[0] + endbyte -= chunksize + for b in xrange(endbyte, startbyte - 1, -1): + val <<= 8 + val += self._datastore.getbyte(b) + else: + data = self._slice(start, start + length) + assert data.len % 8 == 0 + data._reversebytes(0, self.len) + for b in bytearray(data.bytes): + val <<= 8 + val += b + return val + + def _getuintle(self): + return self._readuintle(self.len, 0) + + def _setintle(self, intle, length=None): + if length is not None and length % 8 != 0: + raise CreationError("Little-endian integers must be whole-byte. " + "Length = {0} bits.", length) + self._setint(intle, length) + self._reversebytes(0, self.len) + + def _readintle(self, length, start): + """Read bits and interpret as a little-endian signed int.""" + ui = self._readuintle(length, start) + if not ui >> (length - 1): + # Top bit not set, number is positive + return ui + # Top bit is set, so number is negative + tmp = (~(ui - 1)) & ((1 << length) - 1) + return -tmp + + def _getintle(self): + return self._readintle(self.len, 0) + + def _setfloat(self, f, length=None): + # If no length given, and we've previously been given a length, use it. + if length is None and hasattr(self, 'len') and self.len != 0: + length = self.len + if length is None or length == 0: + raise CreationError("A non-zero length must be specified with a " + "float initialiser.") + if length == 32: + b = struct.pack('>f', f) + elif length == 64: + b = struct.pack('>d', f) + else: + raise CreationError("floats can only be 32 or 64 bits long, " + "not {0} bits", length) + self._setbytes_unsafe(bytearray(b), length, 0) + + def _readfloat(self, length, start): + """Read bits and interpret as a float.""" + if not (start + self._offset) % 8: + startbyte = (start + self._offset) // 8 + if length == 32: + f, = struct.unpack('>f', bytes(self._datastore.getbyteslice(startbyte, startbyte + 4))) + elif length == 64: + f, = struct.unpack('>d', bytes(self._datastore.getbyteslice(startbyte, startbyte + 8))) + else: + if length == 32: + f, = struct.unpack('>f', self._readbytes(32, start)) + elif length == 64: + f, = struct.unpack('>d', self._readbytes(64, start)) + try: + return f + except NameError: + raise InterpretError("floats can only be 32 or 64 bits long, not {0} bits", length) + + def _getfloat(self): + """Interpret the whole bitstring as a float.""" + return self._readfloat(self.len, 0) + + def _setfloatle(self, f, length=None): + # If no length given, and we've previously been given a length, use it. + if length is None and hasattr(self, 'len') and self.len != 0: + length = self.len + if length is None or length == 0: + raise CreationError("A non-zero length must be specified with a " + "float initialiser.") + if length == 32: + b = struct.pack('<f', f) + elif length == 64: + b = struct.pack('<d', f) + else: + raise CreationError("floats can only be 32 or 64 bits long, " + "not {0} bits", length) + self._setbytes_unsafe(bytearray(b), length, 0) + + def _readfloatle(self, length, start): + """Read bits and interpret as a little-endian float.""" + startbyte, offset = divmod(start + self._offset, 8) + if not offset: + if length == 32: + f, = struct.unpack('<f', bytes(self._datastore.getbyteslice(startbyte, startbyte + 4))) + elif length == 64: + f, = struct.unpack('<d', bytes(self._datastore.getbyteslice(startbyte, startbyte + 8))) + else: + if length == 32: + f, = struct.unpack('<f', self._readbytes(32, start)) + elif length == 64: + f, = struct.unpack('<d', self._readbytes(64, start)) + try: + return f + except NameError: + raise InterpretError("floats can only be 32 or 64 bits long, " + "not {0} bits", length) + + def _getfloatle(self): + """Interpret the whole bitstring as a little-endian float.""" + return self._readfloatle(self.len, 0) + + def _setue(self, i): + """Initialise bitstring with unsigned exponential-Golomb code for integer i. + + Raises CreationError if i < 0. + + """ + if i < 0: + raise CreationError("Cannot use negative initialiser for unsigned " + "exponential-Golomb.") + if not i: + self._setbin_unsafe('1') + return + tmp = i + 1 + leadingzeros = -1 + while tmp > 0: + tmp >>= 1 + leadingzeros += 1 + remainingpart = i + 1 - (1 << leadingzeros) + binstring = '0' * leadingzeros + '1' + Bits(uint=remainingpart, + length=leadingzeros).bin + self._setbin_unsafe(binstring) + + def _readue(self, pos): + """Return interpretation of next bits as unsigned exponential-Golomb code. + + Raises ReadError if the end of the bitstring is encountered while + reading the code. + + """ + oldpos = pos + try: + while not self[pos]: + pos += 1 + except IndexError: + raise ReadError("Read off end of bitstring trying to read code.") + leadingzeros = pos - oldpos + codenum = (1 << leadingzeros) - 1 + if leadingzeros > 0: + if pos + leadingzeros + 1 > self.len: + raise ReadError("Read off end of bitstring trying to read code.") + codenum += self._readuint(leadingzeros, pos + 1) + pos += leadingzeros + 1 + else: + assert codenum == 0 + pos += 1 + return codenum, pos + + def _getue(self): + """Return data as unsigned exponential-Golomb code. + + Raises InterpretError if bitstring is not a single exponential-Golomb code. + + """ + try: + value, newpos = self._readue(0) + if value is None or newpos != self.len: + raise ReadError + except ReadError: + raise InterpretError("Bitstring is not a single exponential-Golomb code.") + return value + + def _setse(self, i): + """Initialise bitstring with signed exponential-Golomb code for integer i.""" + if i > 0: + u = (i * 2) - 1 + else: + u = -2 * i + self._setue(u) + + def _getse(self): + """Return data as signed exponential-Golomb code. + + Raises InterpretError if bitstring is not a single exponential-Golomb code. + + """ + try: + value, newpos = self._readse(0) + if value is None or newpos != self.len: + raise ReadError + except ReadError: + raise InterpretError("Bitstring is not a single exponential-Golomb code.") + return value + + def _readse(self, pos): + """Return interpretation of next bits as a signed exponential-Golomb code. + + Advances position to after the read code. + + Raises ReadError if the end of the bitstring is encountered while + reading the code. + + """ + codenum, pos = self._readue(pos) + m = (codenum + 1) // 2 + if not codenum % 2: + return -m, pos + else: + return m, pos + + def _setuie(self, i): + """Initialise bitstring with unsigned interleaved exponential-Golomb code for integer i. + + Raises CreationError if i < 0. + + """ + if i < 0: + raise CreationError("Cannot use negative initialiser for unsigned " + "interleaved exponential-Golomb.") + self._setbin_unsafe('1' if i == 0 else '0' + '0'.join(bin(i + 1)[3:]) + '1') + + def _readuie(self, pos): + """Return interpretation of next bits as unsigned interleaved exponential-Golomb code. + + Raises ReadError if the end of the bitstring is encountered while + reading the code. + + """ + try: + codenum = 1 + while not self[pos]: + pos += 1 + codenum <<= 1 + codenum += self[pos] + pos += 1 + pos += 1 + except IndexError: + raise ReadError("Read off end of bitstring trying to read code.") + codenum -= 1 + return codenum, pos + + def _getuie(self): + """Return data as unsigned interleaved exponential-Golomb code. + + Raises InterpretError if bitstring is not a single exponential-Golomb code. + + """ + try: + value, newpos = self._readuie(0) + if value is None or newpos != self.len: + raise ReadError + except ReadError: + raise InterpretError("Bitstring is not a single interleaved exponential-Golomb code.") + return value + + def _setsie(self, i): + """Initialise bitstring with signed interleaved exponential-Golomb code for integer i.""" + if not i: + self._setbin_unsafe('1') + else: + self._setuie(abs(i)) + self._append(Bits([i < 0])) + + def _getsie(self): + """Return data as signed interleaved exponential-Golomb code. + + Raises InterpretError if bitstring is not a single exponential-Golomb code. + + """ + try: + value, newpos = self._readsie(0) + if value is None or newpos != self.len: + raise ReadError + except ReadError: + raise InterpretError("Bitstring is not a single interleaved exponential-Golomb code.") + return value + + def _readsie(self, pos): + """Return interpretation of next bits as a signed interleaved exponential-Golomb code. + + Advances position to after the read code. + + Raises ReadError if the end of the bitstring is encountered while + reading the code. + + """ + codenum, pos = self._readuie(pos) + if not codenum: + return 0, pos + try: + if self[pos]: + return -codenum, pos + 1 + else: + return codenum, pos + 1 + except IndexError: + raise ReadError("Read off end of bitstring trying to read code.") + + def _setbool(self, value): + # We deliberately don't want to have implicit conversions to bool here. + # If we did then it would be difficult to deal with the 'False' string. + if value in (1, 'True'): + self._setbytes_unsafe(bytearray(b'\x80'), 1, 0) + elif value in (0, 'False'): + self._setbytes_unsafe(bytearray(b'\x00'), 1, 0) + else: + raise CreationError('Cannot initialise boolean with {0}.', value) + + def _getbool(self): + if self.length != 1: + msg = "For a bool interpretation a bitstring must be 1 bit long, not {0} bits." + raise InterpretError(msg, self.length) + return self[0] + + def _readbool(self, pos): + return self[pos], pos + 1 + + def _setbin_safe(self, binstring): + """Reset the bitstring to the value given in binstring.""" + binstring = tidy_input_string(binstring) + # remove any 0b if present + binstring = binstring.replace('0b', '') + self._setbin_unsafe(binstring) + + def _setbin_unsafe(self, binstring): + """Same as _setbin_safe, but input isn't sanity checked. binstring mustn't start with '0b'.""" + length = len(binstring) + # pad with zeros up to byte boundary if needed + boundary = ((length + 7) // 8) * 8 + padded_binstring = binstring + '0' * (boundary - length)\ + if len(binstring) < boundary else binstring + try: + bytelist = [int(padded_binstring[x:x + 8], 2) + for x in xrange(0, len(padded_binstring), 8)] + except ValueError: + raise CreationError("Invalid character in bin initialiser {0}.", binstring) + self._setbytes_unsafe(bytearray(bytelist), length, 0) + + def _readbin(self, length, start): + """Read bits and interpret as a binary string.""" + if not length: + return '' + # Get the byte slice containing our bit slice + startbyte, startoffset = divmod(start + self._offset, 8) + endbyte = (start + self._offset + length - 1) // 8 + b = self._datastore.getbyteslice(startbyte, endbyte + 1) + # Convert to a string of '0' and '1's (via a hex string an and int!) + try: + c = "{:0{}b}".format(int(binascii.hexlify(b), 16), 8*len(b)) + except TypeError: + # Hack to get Python 2.6 working + c = "{0:0{1}b}".format(int(binascii.hexlify(str(b)), 16), 8*len(b)) + # Finally chop off any extra bits. + return c[startoffset:startoffset + length] + + def _getbin(self): + """Return interpretation as a binary string.""" + return self._readbin(self.len, 0) + + def _setoct(self, octstring): + """Reset the bitstring to have the value given in octstring.""" + octstring = tidy_input_string(octstring) + # remove any 0o if present + octstring = octstring.replace('0o', '') + binlist = [] + for i in octstring: + try: + if not 0 <= int(i) < 8: + raise ValueError + binlist.append(OCT_TO_BITS[int(i)]) + except ValueError: + raise CreationError("Invalid symbol '{0}' in oct initialiser.", i) + self._setbin_unsafe(''.join(binlist)) + + def _readoct(self, length, start): + """Read bits and interpret as an octal string.""" + if length % 3: + raise InterpretError("Cannot convert to octal unambiguously - " + "not multiple of 3 bits.") + if not length: + return '' + # Get main octal bit by converting from int. + # Strip starting 0 or 0o depending on Python version. + end = oct(self._readuint(length, start))[LEADING_OCT_CHARS:] + if end.endswith('L'): + end = end[:-1] + middle = '0' * (length // 3 - len(end)) + return middle + end + + def _getoct(self): + """Return interpretation as an octal string.""" + return self._readoct(self.len, 0) + + def _sethex(self, hexstring): + """Reset the bitstring to have the value given in hexstring.""" + hexstring = tidy_input_string(hexstring) + # remove any 0x if present + hexstring = hexstring.replace('0x', '') + length = len(hexstring) + if length % 2: + hexstring += '0' + try: + try: + data = bytearray.fromhex(hexstring) + except TypeError: + # Python 2.6 needs a unicode string (a bug). 2.7 and 3.x work fine. + data = bytearray.fromhex(unicode(hexstring)) + except ValueError: + raise CreationError("Invalid symbol in hex initialiser.") + self._setbytes_unsafe(data, length * 4, 0) + + def _readhex(self, length, start): + """Read bits and interpret as a hex string.""" + if length % 4: + raise InterpretError("Cannot convert to hex unambiguously - " + "not multiple of 4 bits.") + if not length: + return '' + # This monstrosity is the only thing I could get to work for both 2.6 and 3.1. + # TODO: Is utf-8 really what we mean here? + s = str(binascii.hexlify(self._slice(start, start + length).tobytes()).decode('utf-8')) + # If there's one nibble too many then cut it off + return s[:-1] if (length // 4) % 2 else s + + def _gethex(self): + """Return the hexadecimal representation as a string prefixed with '0x'. + + Raises an InterpretError if the bitstring's length is not a multiple of 4. + + """ + return self._readhex(self.len, 0) + + def _getoffset(self): + return self._datastore.offset + + def _getlength(self): + """Return the length of the bitstring in bits.""" + return self._datastore.bitlength + + def _ensureinmemory(self): + """Ensure the data is held in memory, not in a file.""" + self._setbytes_unsafe(self._datastore.getbyteslice(0, self._datastore.bytelength), + self.len, self._offset) + + @classmethod + def _converttobitstring(cls, bs, offset=0, cache={}): + """Convert bs to a bitstring and return it. + + offset gives the suggested bit offset of first significant + bit, to optimise append etc. + + """ + if isinstance(bs, Bits): + return bs + try: + return cache[(bs, offset)] + except KeyError: + if isinstance(bs, basestring): + b = cls() + try: + _, tokens = tokenparser(bs) + except ValueError as e: + raise CreationError(*e.args) + if tokens: + b._append(Bits._init_with_token(*tokens[0])) + b._datastore = offsetcopy(b._datastore, offset) + for token in tokens[1:]: + b._append(Bits._init_with_token(*token)) + assert b._assertsanity() + assert b.len == 0 or b._offset == offset + if len(cache) < CACHE_SIZE: + cache[(bs, offset)] = b + return b + except TypeError: + # Unhashable type + pass + return cls(bs) + + def _copy(self): + """Create and return a new copy of the Bits (always in memory).""" + s_copy = self.__class__() + s_copy._setbytes_unsafe(self._datastore.getbyteslice(0, self._datastore.bytelength), + self.len, self._offset) + return s_copy + + def _slice(self, start, end): + """Used internally to get a slice, without error checking.""" + if end == start: + return self.__class__() + offset = self._offset + startbyte, newoffset = divmod(start + offset, 8) + endbyte = (end + offset - 1) // 8 + bs = self.__class__() + bs._setbytes_unsafe(self._datastore.getbyteslice(startbyte, endbyte + 1), end - start, newoffset) + return bs + + def _readtoken(self, name, pos, length): + """Reads a token from the bitstring and returns the result.""" + if length is not None and int(length) > self.length - pos: + raise ReadError("Reading off the end of the data. " + "Tried to read {0} bits when only {1} available.".format(int(length), self.length - pos)) + try: + val = name_to_read[name](self, length, pos) + return val, pos + length + except KeyError: + if name == 'pad': + return None, pos + length + raise ValueError("Can't parse token {0}:{1}".format(name, length)) + except TypeError: + # This is for the 'ue', 'se' and 'bool' tokens. They will also return the new pos. + return name_to_read[name](self, pos) + + def _append(self, bs): + """Append a bitstring to the current bitstring.""" + self._datastore._appendstore(bs._datastore) + + def _prepend(self, bs): + """Prepend a bitstring to the current bitstring.""" + self._datastore._prependstore(bs._datastore) + + def _reverse(self): + """Reverse all bits in-place.""" + # Reverse the contents of each byte + n = [BYTE_REVERSAL_DICT[b] for b in self._datastore.rawbytes] + # Then reverse the order of the bytes + n.reverse() + # The new offset is the number of bits that were unused at the end. + newoffset = 8 - (self._offset + self.len) % 8 + if newoffset == 8: + newoffset = 0 + self._setbytes_unsafe(bytearray().join(n), self.length, newoffset) + + def _truncatestart(self, bits): + """Truncate bits from the start of the bitstring.""" + assert 0 <= bits <= self.len + if not bits: + return + if bits == self.len: + self._clear() + return + bytepos, offset = divmod(self._offset + bits, 8) + self._setbytes_unsafe(self._datastore.getbyteslice(bytepos, self._datastore.bytelength), self.len - bits, + offset) + assert self._assertsanity() + + def _truncateend(self, bits): + """Truncate bits from the end of the bitstring.""" + assert 0 <= bits <= self.len + if not bits: + return + if bits == self.len: + self._clear() + return + newlength_in_bytes = (self._offset + self.len - bits + 7) // 8 + self._setbytes_unsafe(self._datastore.getbyteslice(0, newlength_in_bytes), self.len - bits, + self._offset) + assert self._assertsanity() + + def _insert(self, bs, pos): + """Insert bs at pos.""" + assert 0 <= pos <= self.len + if pos > self.len // 2: + # Inserting nearer end, so cut off end. + end = self._slice(pos, self.len) + self._truncateend(self.len - pos) + self._append(bs) + self._append(end) + else: + # Inserting nearer start, so cut off start. + start = self._slice(0, pos) + self._truncatestart(pos) + self._prepend(bs) + self._prepend(start) + try: + self._pos = pos + bs.len + except AttributeError: + pass + assert self._assertsanity() + + def _overwrite(self, bs, pos): + """Overwrite with bs at pos.""" + assert 0 <= pos < self.len + if bs is self: + # Just overwriting with self, so do nothing. + assert pos == 0 + return + firstbytepos = (self._offset + pos) // 8 + lastbytepos = (self._offset + pos + bs.len - 1) // 8 + bytepos, bitoffset = divmod(self._offset + pos, 8) + if firstbytepos == lastbytepos: + mask = ((1 << bs.len) - 1) << (8 - bs.len - bitoffset) + self._datastore.setbyte(bytepos, self._datastore.getbyte(bytepos) & (~mask)) + d = offsetcopy(bs._datastore, bitoffset) + self._datastore.setbyte(bytepos, self._datastore.getbyte(bytepos) | (d.getbyte(0) & mask)) + else: + # Do first byte + mask = (1 << (8 - bitoffset)) - 1 + self._datastore.setbyte(bytepos, self._datastore.getbyte(bytepos) & (~mask)) + d = offsetcopy(bs._datastore, bitoffset) + self._datastore.setbyte(bytepos, self._datastore.getbyte(bytepos) | (d.getbyte(0) & mask)) + # Now do all the full bytes + self._datastore.setbyteslice(firstbytepos + 1, lastbytepos, d.getbyteslice(1, lastbytepos - firstbytepos)) + # and finally the last byte + bitsleft = (self._offset + pos + bs.len) % 8 + if not bitsleft: + bitsleft = 8 + mask = (1 << (8 - bitsleft)) - 1 + self._datastore.setbyte(lastbytepos, self._datastore.getbyte(lastbytepos) & mask) + self._datastore.setbyte(lastbytepos, + self._datastore.getbyte(lastbytepos) | (d.getbyte(d.bytelength - 1) & ~mask)) + assert self._assertsanity() + + def _delete(self, bits, pos): + """Delete bits at pos.""" + assert 0 <= pos <= self.len + assert pos + bits <= self.len + if not pos: + # Cutting bits off at the start. + self._truncatestart(bits) + return + if pos + bits == self.len: + # Cutting bits off at the end. + self._truncateend(bits) + return + if pos > self.len - pos - bits: + # More bits before cut point than after it, so do bit shifting + # on the final bits. + end = self._slice(pos + bits, self.len) + assert self.len - pos > 0 + self._truncateend(self.len - pos) + self._append(end) + return + # More bits after the cut point than before it. + start = self._slice(0, pos) + self._truncatestart(pos + bits) + self._prepend(start) + return + + def _reversebytes(self, start, end): + """Reverse bytes in-place.""" + # Make the start occur on a byte boundary + # TODO: We could be cleverer here to avoid changing the offset. + newoffset = 8 - (start % 8) + if newoffset == 8: + newoffset = 0 + self._datastore = offsetcopy(self._datastore, newoffset) + # Now just reverse the byte data + toreverse = bytearray(self._datastore.getbyteslice((newoffset + start) // 8, (newoffset + end) // 8)) + toreverse.reverse() + self._datastore.setbyteslice((newoffset + start) // 8, (newoffset + end) // 8, toreverse) + + def _set(self, pos): + """Set bit at pos to 1.""" + assert 0 <= pos < self.len + self._datastore.setbit(pos) + + def _unset(self, pos): + """Set bit at pos to 0.""" + assert 0 <= pos < self.len + self._datastore.unsetbit(pos) + + def _invert(self, pos): + """Flip bit at pos 1<->0.""" + assert 0 <= pos < self.len + self._datastore.invertbit(pos) + + def _invert_all(self): + """Invert every bit.""" + set = self._datastore.setbyte + get = self._datastore.getbyte + for p in xrange(self._datastore.byteoffset, self._datastore.byteoffset + self._datastore.bytelength): + set(p, 256 + ~get(p)) + + def _ilshift(self, n): + """Shift bits by n to the left in place. Return self.""" + assert 0 < n <= self.len + self._append(Bits(n)) + self._truncatestart(n) + return self + + def _irshift(self, n): + """Shift bits by n to the right in place. Return self.""" + assert 0 < n <= self.len + self._prepend(Bits(n)) + self._truncateend(n) + return self + + def _imul(self, n): + """Concatenate n copies of self in place. Return self.""" + assert n >= 0 + if not n: + self._clear() + return self + m = 1 + old_len = self.len + while m * 2 < n: + self._append(self) + m *= 2 + self._append(self[0:(n - m) * old_len]) + return self + + def _inplace_logical_helper(self, bs, f): + """Helper function containing most of the __ior__, __iand__, __ixor__ code.""" + # Give the two bitstrings the same offset (modulo 8) + self_byteoffset, self_bitoffset = divmod(self._offset, 8) + bs_byteoffset, bs_bitoffset = divmod(bs._offset, 8) + if bs_bitoffset != self_bitoffset: + if not self_bitoffset: + bs._datastore = offsetcopy(bs._datastore, 0) + else: + self._datastore = offsetcopy(self._datastore, bs_bitoffset) + a = self._datastore.rawbytes + b = bs._datastore.rawbytes + for i in xrange(len(a)): + a[i] = f(a[i + self_byteoffset], b[i + bs_byteoffset]) + return self + + def _ior(self, bs): + return self._inplace_logical_helper(bs, operator.ior) + + def _iand(self, bs): + return self._inplace_logical_helper(bs, operator.iand) + + def _ixor(self, bs): + return self._inplace_logical_helper(bs, operator.xor) + + def _readbits(self, length, start): + """Read some bits from the bitstring and return newly constructed bitstring.""" + return self._slice(start, start + length) + + def _validate_slice(self, start, end): + """Validate start and end and return them as positive bit positions.""" + if start is None: + start = 0 + elif start < 0: + start += self.len + if end is None: + end = self.len + elif end < 0: + end += self.len + if not 0 <= end <= self.len: + raise ValueError("end is not a valid position in the bitstring.") + if not 0 <= start <= self.len: + raise ValueError("start is not a valid position in the bitstring.") + if end < start: + raise ValueError("end must not be less than start.") + return start, end + + def unpack(self, fmt, **kwargs): + """Interpret the whole bitstring using fmt and return list. + + fmt -- A single string or a list of strings with comma separated tokens + describing how to interpret the bits in the bitstring. Items + can also be integers, for reading new bitstring of the given length. + kwargs -- A dictionary or keyword-value pairs - the keywords used in the + format string will be replaced with their given value. + + Raises ValueError if the format is not understood. If not enough bits + are available then all bits to the end of the bitstring will be used. + + See the docstring for 'read' for token examples. + + """ + return self._readlist(fmt, 0, **kwargs)[0] + + def _readlist(self, fmt, pos, **kwargs): + tokens = [] + stretchy_token = None + if isinstance(fmt, basestring): + fmt = [fmt] + # Not very optimal this, but replace integers with 'bits' tokens + # TODO: optimise + for i, f in enumerate(fmt): + if isinstance(f, numbers.Integral): + fmt[i] = "bits:{0}".format(f) + for f_item in fmt: + stretchy, tkns = tokenparser(f_item, tuple(sorted(kwargs.keys()))) + if stretchy: + if stretchy_token: + raise Error("It's not possible to have more than one 'filler' token.") + stretchy_token = stretchy + tokens.extend(tkns) + if not stretchy_token: + lst = [] + for name, length, _ in tokens: + if length in kwargs: + length = kwargs[length] + if name == 'bytes': + length *= 8 + if name in kwargs and length is None: + # Using default 'uint' - the name is really the length. + value, pos = self._readtoken('uint', pos, kwargs[name]) + lst.append(value) + continue + value, pos = self._readtoken(name, pos, length) + if value is not None: # Don't append pad tokens + lst.append(value) + return lst, pos + stretchy_token = False + bits_after_stretchy_token = 0 + for token in tokens: + name, length, _ = token + if length in kwargs: + length = kwargs[length] + if name == 'bytes': + length *= 8 + if name in kwargs and length is None: + # Default 'uint'. + length = kwargs[name] + if stretchy_token: + if name in ('se', 'ue', 'sie', 'uie'): + raise Error("It's not possible to parse a variable" + "length token after a 'filler' token.") + else: + if length is None: + raise Error("It's not possible to have more than " + "one 'filler' token.") + bits_after_stretchy_token += length + if length is None and name not in ('se', 'ue', 'sie', 'uie'): + assert not stretchy_token + stretchy_token = token + bits_left = self.len - pos + return_values = [] + for token in tokens: + name, length, _ = token + if token is stretchy_token: + # Set length to the remaining bits + length = max(bits_left - bits_after_stretchy_token, 0) + if length in kwargs: + length = kwargs[length] + if name == 'bytes': + length *= 8 + if name in kwargs and length is None: + # Default 'uint' + length = kwargs[name] + if length is not None: + bits_left -= length + value, pos = self._readtoken(name, pos, length) + if value is not None: + return_values.append(value) + return return_values, pos + + def _findbytes(self, bytes_, start, end, bytealigned): + """Quicker version of find when everything's whole byte + and byte aligned. + + """ + assert self._datastore.offset == 0 + assert bytealigned is True + # Extract data bytes from bitstring to be found. + bytepos = (start + 7) // 8 + found = False + p = bytepos + finalpos = end // 8 + increment = max(1024, len(bytes_) * 10) + buffersize = increment + len(bytes_) + while p < finalpos: + # Read in file or from memory in overlapping chunks and search the chunks. + buf = bytearray(self._datastore.getbyteslice(p, min(p + buffersize, finalpos))) + pos = buf.find(bytes_) + if pos != -1: + found = True + p += pos + break + p += increment + if not found: + return () + return (p * 8,) + + def _findregex(self, reg_ex, start, end, bytealigned): + """Find first occurrence of a compiled regular expression. + + Note that this doesn't support arbitrary regexes, in particular they + must match a known length. + + """ + p = start + length = len(reg_ex.pattern) + # We grab overlapping chunks of the binary representation and + # do an ordinary string search within that. + increment = max(4096, length * 10) + buffersize = increment + length + while p < end: + buf = self._readbin(min(buffersize, end - p), p) + # Test using regular expressions... + m = reg_ex.search(buf) + if m: + pos = m.start() + # pos = buf.find(targetbin) + # if pos != -1: + # if bytealigned then we only accept byte aligned positions. + if not bytealigned or (p + pos) % 8 == 0: + return (p + pos,) + if bytealigned: + # Advance to just beyond the non-byte-aligned match and try again... + p += pos + 1 + continue + p += increment + # Not found, return empty tuple + return () + + def find(self, bs, start=None, end=None, bytealigned=None): + """Find first occurrence of substring bs. + + Returns a single item tuple with the bit position if found, or an + empty tuple if not found. The bit position (pos property) will + also be set to the start of the substring if it is found. + + bs -- The bitstring to find. + start -- The bit position to start the search. Defaults to 0. + end -- The bit position one past the last bit to search. + Defaults to self.len. + bytealigned -- If True the bitstring will only be + found on byte boundaries. + + Raises ValueError if bs is empty, if start < 0, if end > self.len or + if end < start. + + >>> BitArray('0xc3e').find('0b1111') + (6,) + + """ + bs = Bits(bs) + if not bs.len: + raise ValueError("Cannot find an empty bitstring.") + start, end = self._validate_slice(start, end) + if bytealigned is None: + bytealigned = globals()['bytealigned'] + if bytealigned and not bs.len % 8 and not self._datastore.offset: + p = self._findbytes(bs.bytes, start, end, bytealigned) + else: + p = self._findregex(re.compile(bs._getbin()), start, end, bytealigned) + # If called from a class that has a pos, set it + try: + self._pos = p[0] + except (AttributeError, IndexError): + pass + return p + + def findall(self, bs, start=None, end=None, count=None, bytealigned=None): + """Find all occurrences of bs. Return generator of bit positions. + + bs -- The bitstring to find. + start -- The bit position to start the search. Defaults to 0. + end -- The bit position one past the last bit to search. + Defaults to self.len. + count -- The maximum number of occurrences to find. + bytealigned -- If True the bitstring will only be found on + byte boundaries. + + Raises ValueError if bs is empty, if start < 0, if end > self.len or + if end < start. + + Note that all occurrences of bs are found, even if they overlap. + + """ + if count is not None and count < 0: + raise ValueError("In findall, count must be >= 0.") + bs = Bits(bs) + start, end = self._validate_slice(start, end) + if bytealigned is None: + bytealigned = globals()['bytealigned'] + c = 0 + if bytealigned and not bs.len % 8 and not self._datastore.offset: + # Use the quick find method + f = self._findbytes + x = bs._getbytes() + else: + f = self._findregex + x = re.compile(bs._getbin()) + while True: + + p = f(x, start, end, bytealigned) + if not p: + break + if count is not None and c >= count: + return + c += 1 + try: + self._pos = p[0] + except AttributeError: + pass + yield p[0] + if bytealigned: + start = p[0] + 8 + else: + start = p[0] + 1 + if start >= end: + break + return + + def rfind(self, bs, start=None, end=None, bytealigned=None): + """Find final occurrence of substring bs. + + Returns a single item tuple with the bit position if found, or an + empty tuple if not found. The bit position (pos property) will + also be set to the start of the substring if it is found. + + bs -- The bitstring to find. + start -- The bit position to end the reverse search. Defaults to 0. + end -- The bit position one past the first bit to reverse search. + Defaults to self.len. + bytealigned -- If True the bitstring will only be found on byte + boundaries. + + Raises ValueError if bs is empty, if start < 0, if end > self.len or + if end < start. + + """ + bs = Bits(bs) + start, end = self._validate_slice(start, end) + if bytealigned is None: + bytealigned = globals()['bytealigned'] + if not bs.len: + raise ValueError("Cannot find an empty bitstring.") + # Search chunks starting near the end and then moving back + # until we find bs. + increment = max(8192, bs.len * 80) + buffersize = min(increment + bs.len, end - start) + pos = max(start, end - buffersize) + while True: + found = list(self.findall(bs, start=pos, end=pos + buffersize, + bytealigned=bytealigned)) + if not found: + if pos == start: + return () + pos = max(start, pos - increment) + continue + return (found[-1],) + + def cut(self, bits, start=None, end=None, count=None): + """Return bitstring generator by cutting into bits sized chunks. + + bits -- The size in bits of the bitstring chunks to generate. + start -- The bit position to start the first cut. Defaults to 0. + end -- The bit position one past the last bit to use in the cut. + Defaults to self.len. + count -- If specified then at most count items are generated. + Default is to cut as many times as possible. + + """ + start, end = self._validate_slice(start, end) + if count is not None and count < 0: + raise ValueError("Cannot cut - count must be >= 0.") + if bits <= 0: + raise ValueError("Cannot cut - bits must be >= 0.") + c = 0 + while count is None or c < count: + c += 1 + nextchunk = self._slice(start, min(start + bits, end)) + if nextchunk.len != bits: + return + assert nextchunk._assertsanity() + yield nextchunk + start += bits + return + + def split(self, delimiter, start=None, end=None, count=None, + bytealigned=None): + """Return bitstring generator by splittling using a delimiter. + + The first item returned is the initial bitstring before the delimiter, + which may be an empty bitstring. + + delimiter -- The bitstring used as the divider. + start -- The bit position to start the split. Defaults to 0. + end -- The bit position one past the last bit to use in the split. + Defaults to self.len. + count -- If specified then at most count items are generated. + Default is to split as many times as possible. + bytealigned -- If True splits will only occur on byte boundaries. + + Raises ValueError if the delimiter is empty. + + """ + delimiter = Bits(delimiter) + if not delimiter.len: + raise ValueError("split delimiter cannot be empty.") + start, end = self._validate_slice(start, end) + if bytealigned is None: + bytealigned = globals()['bytealigned'] + if count is not None and count < 0: + raise ValueError("Cannot split - count must be >= 0.") + if count == 0: + return + if bytealigned and not delimiter.len % 8 and not self._datastore.offset: + # Use the quick find method + f = self._findbytes + x = delimiter._getbytes() + else: + f = self._findregex + x = re.compile(delimiter._getbin()) + found = f(x, start, end, bytealigned) + if not found: + # Initial bits are the whole bitstring being searched + yield self._slice(start, end) + return + # yield the bytes before the first occurrence of the delimiter, even if empty + yield self._slice(start, found[0]) + startpos = pos = found[0] + c = 1 + while count is None or c < count: + pos += delimiter.len + found = f(x, pos, end, bytealigned) + if not found: + # No more occurrences, so return the rest of the bitstring + yield self._slice(startpos, end) + return + c += 1 + yield self._slice(startpos, found[0]) + startpos = pos = found[0] + # Have generated count bitstrings, so time to quit. + return + + def join(self, sequence): + """Return concatenation of bitstrings joined by self. + + sequence -- A sequence of bitstrings. + + """ + s = self.__class__() + i = iter(sequence) + try: + s._append(Bits(next(i))) + while True: + n = next(i) + s._append(self) + s._append(Bits(n)) + except StopIteration: + pass + return s + + def tobytes(self): + """Return the bitstring as bytes, padding with zero bits if needed. + + Up to seven zero bits will be added at the end to byte align. + + """ + d = offsetcopy(self._datastore, 0).rawbytes + # Need to ensure that unused bits at end are set to zero + unusedbits = 8 - self.len % 8 + if unusedbits != 8: + d[-1] &= (0xff << unusedbits) + return bytes(d) + + def tofile(self, f): + """Write the bitstring to a file object, padding with zero bits if needed. + + Up to seven zero bits will be added at the end to byte align. + + """ + # If the bitstring is file based then we don't want to read it all + # in to memory. + chunksize = 1024 * 1024 # 1 MB chunks + if not self._offset: + a = 0 + bytelen = self._datastore.bytelength + p = self._datastore.getbyteslice(a, min(a + chunksize, bytelen - 1)) + while len(p) == chunksize: + f.write(p) + a += chunksize + p = self._datastore.getbyteslice(a, min(a + chunksize, bytelen - 1)) + f.write(p) + # Now the final byte, ensuring that unused bits at end are set to 0. + bits_in_final_byte = self.len % 8 + if not bits_in_final_byte: + bits_in_final_byte = 8 + f.write(self[-bits_in_final_byte:].tobytes()) + else: + # Really quite inefficient... + a = 0 + b = a + chunksize * 8 + while b <= self.len: + f.write(self._slice(a, b)._getbytes()) + a += chunksize * 8 + b += chunksize * 8 + if a != self.len: + f.write(self._slice(a, self.len).tobytes()) + + def startswith(self, prefix, start=None, end=None): + """Return whether the current bitstring starts with prefix. + + prefix -- The bitstring to search for. + start -- The bit position to start from. Defaults to 0. + end -- The bit position to end at. Defaults to self.len. + + """ + prefix = Bits(prefix) + start, end = self._validate_slice(start, end) + if end < start + prefix.len: + return False + end = start + prefix.len + return self._slice(start, end) == prefix + + def endswith(self, suffix, start=None, end=None): + """Return whether the current bitstring ends with suffix. + + suffix -- The bitstring to search for. + start -- The bit position to start from. Defaults to 0. + end -- The bit position to end at. Defaults to self.len. + + """ + suffix = Bits(suffix) + start, end = self._validate_slice(start, end) + if start + suffix.len > end: + return False + start = end - suffix.len + return self._slice(start, end) == suffix + + def all(self, value, pos=None): + """Return True if one or many bits are all set to value. + + value -- If value is True then checks for bits set to 1, otherwise + checks for bits set to 0. + pos -- An iterable of bit positions. Negative numbers are treated in + the same way as slice indices. Defaults to the whole bitstring. + + """ + value = bool(value) + length = self.len + if pos is None: + pos = xrange(self.len) + for p in pos: + if p < 0: + p += length + if not 0 <= p < length: + raise IndexError("Bit position {0} out of range.".format(p)) + if not self._datastore.getbit(p) is value: + return False + return True + + def any(self, value, pos=None): + """Return True if any of one or many bits are set to value. + + value -- If value is True then checks for bits set to 1, otherwise + checks for bits set to 0. + pos -- An iterable of bit positions. Negative numbers are treated in + the same way as slice indices. Defaults to the whole bitstring. + + """ + value = bool(value) + length = self.len + if pos is None: + pos = xrange(self.len) + for p in pos: + if p < 0: + p += length + if not 0 <= p < length: + raise IndexError("Bit position {0} out of range.".format(p)) + if self._datastore.getbit(p) is value: + return True + return False + + def count(self, value): + """Return count of total number of either zero or one bits. + + value -- If True then bits set to 1 are counted, otherwise bits set + to 0 are counted. + + >>> Bits('0xef').count(1) + 7 + + """ + if not self.len: + return 0 + # count the number of 1s (from which it's easy to work out the 0s). + # Don't count the final byte yet. + count = sum(BIT_COUNT[self._datastore.getbyte(i)] for i in xrange(self._datastore.bytelength - 1)) + # adjust for bits at start that aren't part of the bitstring + if self._offset: + count -= BIT_COUNT[self._datastore.getbyte(0) >> (8 - self._offset)] + # and count the last 1 - 8 bits at the end. + endbits = self._datastore.bytelength * 8 - (self._offset + self.len) + count += BIT_COUNT[self._datastore.getbyte(self._datastore.bytelength - 1) >> endbits] + return count if value else self.len - count + + # Create native-endian functions as aliases depending on the byteorder + if byteorder == 'little': + _setfloatne = _setfloatle + _readfloatne = _readfloatle + _getfloatne = _getfloatle + _setuintne = _setuintle + _readuintne = _readuintle + _getuintne = _getuintle + _setintne = _setintle + _readintne = _readintle + _getintne = _getintle + else: + _setfloatne = _setfloat + _readfloatne = _readfloat + _getfloatne = _getfloat + _setuintne = _setuintbe + _readuintne = _readuintbe + _getuintne = _getuintbe + _setintne = _setintbe + _readintne = _readintbe + _getintne = _getintbe + + _offset = property(_getoffset) + + len = property(_getlength, + doc="""The length of the bitstring in bits. Read only. + """) + length = property(_getlength, + doc="""The length of the bitstring in bits. Read only. + """) + bool = property(_getbool, + doc="""The bitstring as a bool (True or False). Read only. + """) + hex = property(_gethex, + doc="""The bitstring as a hexadecimal string. Read only. + """) + bin = property(_getbin, + doc="""The bitstring as a binary string. Read only. + """) + oct = property(_getoct, + doc="""The bitstring as an octal string. Read only. + """) + bytes = property(_getbytes, + doc="""The bitstring as a bytes object. Read only. + """) + int = property(_getint, + doc="""The bitstring as a two's complement signed int. Read only. + """) + uint = property(_getuint, + doc="""The bitstring as a two's complement unsigned int. Read only. + """) + float = property(_getfloat, + doc="""The bitstring as a floating point number. Read only. + """) + intbe = property(_getintbe, + doc="""The bitstring as a two's complement big-endian signed int. Read only. + """) + uintbe = property(_getuintbe, + doc="""The bitstring as a two's complement big-endian unsigned int. Read only. + """) + floatbe = property(_getfloat, + doc="""The bitstring as a big-endian floating point number. Read only. + """) + intle = property(_getintle, + doc="""The bitstring as a two's complement little-endian signed int. Read only. + """) + uintle = property(_getuintle, + doc="""The bitstring as a two's complement little-endian unsigned int. Read only. + """) + floatle = property(_getfloatle, + doc="""The bitstring as a little-endian floating point number. Read only. + """) + intne = property(_getintne, + doc="""The bitstring as a two's complement native-endian signed int. Read only. + """) + uintne = property(_getuintne, + doc="""The bitstring as a two's complement native-endian unsigned int. Read only. + """) + floatne = property(_getfloatne, + doc="""The bitstring as a native-endian floating point number. Read only. + """) + ue = property(_getue, + doc="""The bitstring as an unsigned exponential-Golomb code. Read only. + """) + se = property(_getse, + doc="""The bitstring as a signed exponential-Golomb code. Read only. + """) + uie = property(_getuie, + doc="""The bitstring as an unsigned interleaved exponential-Golomb code. Read only. + """) + sie = property(_getsie, + doc="""The bitstring as a signed interleaved exponential-Golomb code. Read only. + """) + + +# Dictionary that maps token names to the function that reads them. +name_to_read = {'uint': Bits._readuint, + 'uintle': Bits._readuintle, + 'uintbe': Bits._readuintbe, + 'uintne': Bits._readuintne, + 'int': Bits._readint, + 'intle': Bits._readintle, + 'intbe': Bits._readintbe, + 'intne': Bits._readintne, + 'float': Bits._readfloat, + 'floatbe': Bits._readfloat, # floatbe is a synonym for float + 'floatle': Bits._readfloatle, + 'floatne': Bits._readfloatne, + 'hex': Bits._readhex, + 'oct': Bits._readoct, + 'bin': Bits._readbin, + 'bits': Bits._readbits, + 'bytes': Bits._readbytes, + 'ue': Bits._readue, + 'se': Bits._readse, + 'uie': Bits._readuie, + 'sie': Bits._readsie, + 'bool': Bits._readbool, + } + +# Dictionaries for mapping init keywords with init functions. +init_with_length_and_offset = {'bytes': Bits._setbytes_safe, + 'filename': Bits._setfile, + } + +init_with_length_only = {'uint': Bits._setuint, + 'int': Bits._setint, + 'float': Bits._setfloat, + 'uintbe': Bits._setuintbe, + 'intbe': Bits._setintbe, + 'floatbe': Bits._setfloat, + 'uintle': Bits._setuintle, + 'intle': Bits._setintle, + 'floatle': Bits._setfloatle, + 'uintne': Bits._setuintne, + 'intne': Bits._setintne, + 'floatne': Bits._setfloatne, + } + +init_without_length_or_offset = {'bin': Bits._setbin_safe, + 'hex': Bits._sethex, + 'oct': Bits._setoct, + 'ue': Bits._setue, + 'se': Bits._setse, + 'uie': Bits._setuie, + 'sie': Bits._setsie, + 'bool': Bits._setbool, + } + + +class BitArray(Bits): + """A container holding a mutable sequence of bits. + + Subclass of the immutable Bits class. Inherits all of its + methods (except __hash__) and adds mutating methods. + + Mutating methods: + + append() -- Append a bitstring. + byteswap() -- Change byte endianness in-place. + insert() -- Insert a bitstring. + invert() -- Flip bit(s) between one and zero. + overwrite() -- Overwrite a section with a new bitstring. + prepend() -- Prepend a bitstring. + replace() -- Replace occurrences of one bitstring with another. + reverse() -- Reverse bits in-place. + rol() -- Rotate bits to the left. + ror() -- Rotate bits to the right. + set() -- Set bit(s) to 1 or 0. + + Methods inherited from Bits: + + all() -- Check if all specified bits are set to 1 or 0. + any() -- Check if any of specified bits are set to 1 or 0. + count() -- Count the number of bits set to 1 or 0. + cut() -- Create generator of constant sized chunks. + endswith() -- Return whether the bitstring ends with a sub-string. + find() -- Find a sub-bitstring in the current bitstring. + findall() -- Find all occurrences of a sub-bitstring in the current bitstring. + join() -- Join bitstrings together using current bitstring. + rfind() -- Seek backwards to find a sub-bitstring. + split() -- Create generator of chunks split by a delimiter. + startswith() -- Return whether the bitstring starts with a sub-bitstring. + tobytes() -- Return bitstring as bytes, padding if needed. + tofile() -- Write bitstring to file, padding if needed. + unpack() -- Interpret bits using format string. + + Special methods: + + Mutating operators are available: [], <<=, >>=, +=, *=, &=, |= and ^= + in addition to the inherited [], ==, !=, +, *, ~, <<, >>, &, | and ^. + + Properties: + + bin -- The bitstring as a binary string. + bool -- For single bit bitstrings, interpret as True or False. + bytepos -- The current byte position in the bitstring. + bytes -- The bitstring as a bytes object. + float -- Interpret as a floating point number. + floatbe -- Interpret as a big-endian floating point number. + floatle -- Interpret as a little-endian floating point number. + floatne -- Interpret as a native-endian floating point number. + hex -- The bitstring as a hexadecimal string. + int -- Interpret as a two's complement signed integer. + intbe -- Interpret as a big-endian signed integer. + intle -- Interpret as a little-endian signed integer. + intne -- Interpret as a native-endian signed integer. + len -- Length of the bitstring in bits. + oct -- The bitstring as an octal string. + pos -- The current bit position in the bitstring. + se -- Interpret as a signed exponential-Golomb code. + ue -- Interpret as an unsigned exponential-Golomb code. + sie -- Interpret as a signed interleaved exponential-Golomb code. + uie -- Interpret as an unsigned interleaved exponential-Golomb code. + uint -- Interpret as a two's complement unsigned integer. + uintbe -- Interpret as a big-endian unsigned integer. + uintle -- Interpret as a little-endian unsigned integer. + uintne -- Interpret as a native-endian unsigned integer. + + """ + + __slots__ = () + + # As BitArray objects are mutable, we shouldn't allow them to be hashed. + __hash__ = None + + def __init__(self, auto=None, length=None, offset=None, **kwargs): + """Either specify an 'auto' initialiser: + auto -- a string of comma separated tokens, an integer, a file object, + a bytearray, a boolean iterable or another bitstring. + + Or initialise via **kwargs with one (and only one) of: + bytes -- raw data as a string, for example read from a binary file. + bin -- binary string representation, e.g. '0b001010'. + hex -- hexadecimal string representation, e.g. '0x2ef' + oct -- octal string representation, e.g. '0o777'. + uint -- an unsigned integer. + int -- a signed integer. + float -- a floating point number. + uintbe -- an unsigned big-endian whole byte integer. + intbe -- a signed big-endian whole byte integer. + floatbe - a big-endian floating point number. + uintle -- an unsigned little-endian whole byte integer. + intle -- a signed little-endian whole byte integer. + floatle -- a little-endian floating point number. + uintne -- an unsigned native-endian whole byte integer. + intne -- a signed native-endian whole byte integer. + floatne -- a native-endian floating point number. + se -- a signed exponential-Golomb code. + ue -- an unsigned exponential-Golomb code. + sie -- a signed interleaved exponential-Golomb code. + uie -- an unsigned interleaved exponential-Golomb code. + bool -- a boolean (True or False). + filename -- a file which will be opened in binary read-only mode. + + Other keyword arguments: + length -- length of the bitstring in bits, if needed and appropriate. + It must be supplied for all integer and float initialisers. + offset -- bit offset to the data. These offset bits are + ignored and this is intended for use when + initialising using 'bytes' or 'filename'. + + """ + # For mutable BitArrays we always read in files to memory: + if not isinstance(self._datastore, ByteStore): + self._ensureinmemory() + + def __new__(cls, auto=None, length=None, offset=None, **kwargs): + x = super(BitArray, cls).__new__(cls) + y = Bits.__new__(BitArray, auto, length, offset, **kwargs) + x._datastore = y._datastore + return x + + def __iadd__(self, bs): + """Append bs to current bitstring. Return self. + + bs -- the bitstring to append. + + """ + self.append(bs) + return self + + def __copy__(self): + """Return a new copy of the BitArray.""" + s_copy = BitArray() + if not isinstance(self._datastore, ByteStore): + # Let them both point to the same (invariant) array. + # If either gets modified then at that point they'll be read into memory. + s_copy._datastore = self._datastore + else: + s_copy._datastore = copy.copy(self._datastore) + return s_copy + + def __setitem__(self, key, value): + """Set item or range to new value. + + Indices are in units of the step parameter (default 1 bit). + Stepping is used to specify the number of bits in each item. + + If the length of the bitstring is changed then pos will be moved + to after the inserted section, otherwise it will remain unchanged. + + >>> s = BitArray('0xff') + >>> s[0:1:4] = '0xe' + >>> print s + '0xef' + >>> s[4:4] = '0x00' + >>> print s + '0xe00f' + + """ + try: + # A slice + start, step = 0, 1 + if key.step is not None: + step = key.step + except AttributeError: + # single element + if key < 0: + key += self.len + if not 0 <= key < self.len: + raise IndexError("Slice index out of range.") + if isinstance(value, numbers.Integral): + if not value: + self._unset(key) + return + if value in (1, -1): + self._set(key) + return + raise ValueError("Cannot set a single bit with integer {0}.".format(value)) + value = Bits(value) + if value.len == 1: + # TODO: this can't be optimal + if value[0]: + self._set(key) + else: + self._unset(key) + else: + self._delete(1, key) + self._insert(value, key) + return + else: + if step != 1: + # convert to binary string and use string slicing + # TODO: Horribly inefficent + temp = list(self._getbin()) + v = list(Bits(value)._getbin()) + temp.__setitem__(key, v) + self._setbin_unsafe(''.join(temp)) + return + + # If value is an integer then we want to set the slice to that + # value rather than initialise a new bitstring of that length. + if not isinstance(value, numbers.Integral): + try: + # TODO: Better way than calling constructor here? + value = Bits(value) + except TypeError: + raise TypeError("Bitstring, integer or string expected. " + "Got {0}.".format(type(value))) + if key.start is not None: + start = key.start + if key.start < 0: + start += self.len + if start < 0: + start = 0 + stop = self.len + if key.stop is not None: + stop = key.stop + if key.stop < 0: + stop += self.len + if start > stop: + # The standard behaviour for lists is to just insert at the + # start position if stop < start and step == 1. + stop = start + if isinstance(value, numbers.Integral): + if value >= 0: + value = self.__class__(uint=value, length=stop - start) + else: + value = self.__class__(int=value, length=stop - start) + stop = min(stop, self.len) + start = max(start, 0) + start = min(start, stop) + if (stop - start) == value.len: + if not value.len: + return + if step >= 0: + self._overwrite(value, start) + else: + self._overwrite(value.__getitem__(slice(None, None, 1)), start) + else: + # TODO: A delete then insert is wasteful - it could do unneeded shifts. + # Could be either overwrite + insert or overwrite + delete. + self._delete(stop - start, start) + if step >= 0: + self._insert(value, start) + else: + self._insert(value.__getitem__(slice(None, None, 1)), start) + # pos is now after the inserted piece. + return + + def __delitem__(self, key): + """Delete item or range. + + Indices are in units of the step parameter (default 1 bit). + Stepping is used to specify the number of bits in each item. + + >>> a = BitArray('0x001122') + >>> del a[1:2:8] + >>> print a + 0x0022 + + """ + try: + # A slice + start = 0 + step = key.step if key.step is not None else 1 + except AttributeError: + # single element + if key < 0: + key += self.len + if not 0 <= key < self.len: + raise IndexError("Slice index out of range.") + self._delete(1, key) + return + else: + if step != 1: + # convert to binary string and use string slicing + # TODO: Horribly inefficent + temp = list(self._getbin()) + temp.__delitem__(key) + self._setbin_unsafe(''.join(temp)) + return + stop = key.stop + if key.start is not None: + start = key.start + if key.start < 0 and stop is None: + start += self.len + if start < 0: + start = 0 + if stop is None: + stop = self.len + if start > stop: + return + stop = min(stop, self.len) + start = max(start, 0) + start = min(start, stop) + self._delete(stop - start, start) + return + + def __ilshift__(self, n): + """Shift bits by n to the left in place. Return self. + + n -- the number of bits to shift. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot shift by a negative amount.") + if not self.len: + raise ValueError("Cannot shift an empty bitstring.") + if not n: + return self + n = min(n, self.len) + return self._ilshift(n) + + def __irshift__(self, n): + """Shift bits by n to the right in place. Return self. + + n -- the number of bits to shift. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot shift by a negative amount.") + if not self.len: + raise ValueError("Cannot shift an empty bitstring.") + if not n: + return self + n = min(n, self.len) + return self._irshift(n) + + def __imul__(self, n): + """Concatenate n copies of self in place. Return self. + + Called for expressions of the form 'a *= 3'. + n -- The number of concatenations. Must be >= 0. + + """ + if n < 0: + raise ValueError("Cannot multiply by a negative integer.") + return self._imul(n) + + def __ior__(self, bs): + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for |= operator.") + return self._ior(bs) + + def __iand__(self, bs): + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for &= operator.") + return self._iand(bs) + + def __ixor__(self, bs): + bs = Bits(bs) + if self.len != bs.len: + raise ValueError("Bitstrings must have the same length " + "for ^= operator.") + return self._ixor(bs) + + def replace(self, old, new, start=None, end=None, count=None, + bytealigned=None): + """Replace all occurrences of old with new in place. + + Returns number of replacements made. + + old -- The bitstring to replace. + new -- The replacement bitstring. + start -- Any occurrences that start before this will not be replaced. + Defaults to 0. + end -- Any occurrences that finish after this will not be replaced. + Defaults to self.len. + count -- The maximum number of replacements to make. Defaults to + replace all occurrences. + bytealigned -- If True replacements will only be made on byte + boundaries. + + Raises ValueError if old is empty or if start or end are + out of range. + + """ + old = Bits(old) + new = Bits(new) + if not old.len: + raise ValueError("Empty bitstring cannot be replaced.") + start, end = self._validate_slice(start, end) + if bytealigned is None: + bytealigned = globals()['bytealigned'] + # Adjust count for use in split() + if count is not None: + count += 1 + sections = self.split(old, start, end, count, bytealigned) + lengths = [s.len for s in sections] + if len(lengths) == 1: + # Didn't find anything to replace. + return 0 # no replacements done + if new is self: + # Prevent self assignment woes + new = copy.copy(self) + positions = [lengths[0] + start] + for l in lengths[1:-1]: + # Next position is the previous one plus the length of the next section. + positions.append(positions[-1] + l) + # We have all the positions that need replacements. We do them + # in reverse order so that they won't move around as we replace. + positions.reverse() + try: + # Need to calculate new pos, if this is a bitstream + newpos = self._pos + for p in positions: + self[p:p + old.len] = new + if old.len != new.len: + diff = new.len - old.len + for p in positions: + if p >= newpos: + continue + if p + old.len <= newpos: + newpos += diff + else: + newpos = p + self._pos = newpos + except AttributeError: + for p in positions: + self[p:p + old.len] = new + assert self._assertsanity() + return len(lengths) - 1 + + def insert(self, bs, pos=None): + """Insert bs at bit position pos. + + bs -- The bitstring to insert. + pos -- The bit position to insert at. + + Raises ValueError if pos < 0 or pos > self.len. + + """ + bs = Bits(bs) + if not bs.len: + return self + if bs is self: + bs = self.__copy__() + if pos is None: + try: + pos = self._pos + except AttributeError: + raise TypeError("insert require a bit position for this type.") + if pos < 0: + pos += self.len + if not 0 <= pos <= self.len: + raise ValueError("Invalid insert position.") + self._insert(bs, pos) + + def overwrite(self, bs, pos=None): + """Overwrite with bs at bit position pos. + + bs -- The bitstring to overwrite with. + pos -- The bit position to begin overwriting from. + + Raises ValueError if pos < 0 or pos + bs.len > self.len + + """ + bs = Bits(bs) + if not bs.len: + return + if pos is None: + try: + pos = self._pos + except AttributeError: + raise TypeError("overwrite require a bit position for this type.") + if pos < 0: + pos += self.len + if pos < 0 or pos + bs.len > self.len: + raise ValueError("Overwrite exceeds boundary of bitstring.") + self._overwrite(bs, pos) + try: + self._pos = pos + bs.len + except AttributeError: + pass + + def append(self, bs): + """Append a bitstring to the current bitstring. + + bs -- The bitstring to append. + + """ + # The offset is a hint to make bs easily appendable. + bs = self._converttobitstring(bs, offset=(self.len + self._offset) % 8) + self._append(bs) + + def prepend(self, bs): + """Prepend a bitstring to the current bitstring. + + bs -- The bitstring to prepend. + + """ + bs = Bits(bs) + self._prepend(bs) + + def reverse(self, start=None, end=None): + """Reverse bits in-place. + + start -- Position of first bit to reverse. Defaults to 0. + end -- One past the position of the last bit to reverse. + Defaults to self.len. + + Using on an empty bitstring will have no effect. + + Raises ValueError if start < 0, end > self.len or end < start. + + """ + start, end = self._validate_slice(start, end) + if start == 0 and end == self.len: + self._reverse() + return + s = self._slice(start, end) + s._reverse() + self[start:end] = s + + def set(self, value, pos=None): + """Set one or many bits to 1 or 0. + + value -- If True bits are set to 1, otherwise they are set to 0. + pos -- Either a single bit position or an iterable of bit positions. + Negative numbers are treated in the same way as slice indices. + Defaults to the entire bitstring. + + Raises IndexError if pos < -self.len or pos >= self.len. + + """ + f = self._set if value else self._unset + if pos is None: + pos = xrange(self.len) + try: + length = self.len + for p in pos: + if p < 0: + p += length + if not 0 <= p < length: + raise IndexError("Bit position {0} out of range.".format(p)) + f(p) + except TypeError: + # Single pos + if pos < 0: + pos += self.len + if not 0 <= pos < length: + raise IndexError("Bit position {0} out of range.".format(pos)) + f(pos) + + def invert(self, pos=None): + """Invert one or many bits from 0 to 1 or vice versa. + + pos -- Either a single bit position or an iterable of bit positions. + Negative numbers are treated in the same way as slice indices. + + Raises IndexError if pos < -self.len or pos >= self.len. + + """ + if pos is None: + self._invert_all() + return + if not isinstance(pos, collections.Iterable): + pos = (pos,) + length = self.len + + for p in pos: + if p < 0: + p += length + if not 0 <= p < length: + raise IndexError("Bit position {0} out of range.".format(p)) + self._invert(p) + + def ror(self, bits, start=None, end=None): + """Rotate bits to the right in-place. + + bits -- The number of bits to rotate by. + start -- Start of slice to rotate. Defaults to 0. + end -- End of slice to rotate. Defaults to self.len. + + Raises ValueError if bits < 0. + + """ + if not self.len: + raise Error("Cannot rotate an empty bitstring.") + if bits < 0: + raise ValueError("Cannot rotate right by negative amount.") + start, end = self._validate_slice(start, end) + bits %= (end - start) + if not bits: + return + rhs = self._slice(end - bits, end) + self._delete(bits, end - bits) + self._insert(rhs, start) + + def rol(self, bits, start=None, end=None): + """Rotate bits to the left in-place. + + bits -- The number of bits to rotate by. + start -- Start of slice to rotate. Defaults to 0. + end -- End of slice to rotate. Defaults to self.len. + + Raises ValueError if bits < 0. + + """ + if not self.len: + raise Error("Cannot rotate an empty bitstring.") + if bits < 0: + raise ValueError("Cannot rotate left by negative amount.") + start, end = self._validate_slice(start, end) + bits %= (end - start) + if not bits: + return + lhs = self._slice(start, start + bits) + self._delete(bits, start) + self._insert(lhs, end - bits) + + def byteswap(self, fmt=None, start=None, end=None, repeat=True): + """Change the endianness in-place. Return number of repeats of fmt done. + + fmt -- A compact structure string, an integer number of bytes or + an iterable of integers. Defaults to 0, which byte reverses the + whole bitstring. + start -- Start bit position, defaults to 0. + end -- End bit position, defaults to self.len. + repeat -- If True (the default) the byte swapping pattern is repeated + as much as possible. + + """ + start, end = self._validate_slice(start, end) + if fmt is None or fmt == 0: + # reverse all of the whole bytes. + bytesizes = [(end - start) // 8] + elif isinstance(fmt, numbers.Integral): + if fmt < 0: + raise ValueError("Improper byte length {0}.".format(fmt)) + bytesizes = [fmt] + elif isinstance(fmt, basestring): + m = STRUCT_PACK_RE.match(fmt) + if not m: + raise ValueError("Cannot parse format string {0}.".format(fmt)) + # Split the format string into a list of 'q', '4h' etc. + formatlist = re.findall(STRUCT_SPLIT_RE, m.group('fmt')) + # Now deal with multiplicative factors, 4h -> hhhh etc. + bytesizes = [] + for f in formatlist: + if len(f) == 1: + bytesizes.append(PACK_CODE_SIZE[f]) + else: + bytesizes.extend([PACK_CODE_SIZE[f[-1]]] * int(f[:-1])) + elif isinstance(fmt, collections.Iterable): + bytesizes = fmt + for bytesize in bytesizes: + if not isinstance(bytesize, numbers.Integral) or bytesize < 0: + raise ValueError("Improper byte length {0}.".format(bytesize)) + else: + raise TypeError("Format must be an integer, string or iterable.") + + repeats = 0 + totalbitsize = 8 * sum(bytesizes) + if not totalbitsize: + return 0 + if repeat: + # Try to repeat up to the end of the bitstring. + finalbit = end + else: + # Just try one (set of) byteswap(s). + finalbit = start + totalbitsize + for patternend in xrange(start + totalbitsize, finalbit + 1, totalbitsize): + bytestart = patternend - totalbitsize + for bytesize in bytesizes: + byteend = bytestart + bytesize * 8 + self._reversebytes(bytestart, byteend) + bytestart += bytesize * 8 + repeats += 1 + return repeats + + def clear(self): + """Remove all bits, reset to zero length.""" + self._clear() + + def copy(self): + """Return a copy of the bitstring.""" + return self._copy() + + int = property(Bits._getint, Bits._setint, + doc="""The bitstring as a two's complement signed int. Read and write. + """) + uint = property(Bits._getuint, Bits._setuint, + doc="""The bitstring as a two's complement unsigned int. Read and write. + """) + float = property(Bits._getfloat, Bits._setfloat, + doc="""The bitstring as a floating point number. Read and write. + """) + intbe = property(Bits._getintbe, Bits._setintbe, + doc="""The bitstring as a two's complement big-endian signed int. Read and write. + """) + uintbe = property(Bits._getuintbe, Bits._setuintbe, + doc="""The bitstring as a two's complement big-endian unsigned int. Read and write. + """) + floatbe = property(Bits._getfloat, Bits._setfloat, + doc="""The bitstring as a big-endian floating point number. Read and write. + """) + intle = property(Bits._getintle, Bits._setintle, + doc="""The bitstring as a two's complement little-endian signed int. Read and write. + """) + uintle = property(Bits._getuintle, Bits._setuintle, + doc="""The bitstring as a two's complement little-endian unsigned int. Read and write. + """) + floatle = property(Bits._getfloatle, Bits._setfloatle, + doc="""The bitstring as a little-endian floating point number. Read and write. + """) + intne = property(Bits._getintne, Bits._setintne, + doc="""The bitstring as a two's complement native-endian signed int. Read and write. + """) + uintne = property(Bits._getuintne, Bits._setuintne, + doc="""The bitstring as a two's complement native-endian unsigned int. Read and write. + """) + floatne = property(Bits._getfloatne, Bits._setfloatne, + doc="""The bitstring as a native-endian floating point number. Read and write. + """) + ue = property(Bits._getue, Bits._setue, + doc="""The bitstring as an unsigned exponential-Golomb code. Read and write. + """) + se = property(Bits._getse, Bits._setse, + doc="""The bitstring as a signed exponential-Golomb code. Read and write. + """) + uie = property(Bits._getuie, Bits._setuie, + doc="""The bitstring as an unsigned interleaved exponential-Golomb code. Read and write. + """) + sie = property(Bits._getsie, Bits._setsie, + doc="""The bitstring as a signed interleaved exponential-Golomb code. Read and write. + """) + hex = property(Bits._gethex, Bits._sethex, + doc="""The bitstring as a hexadecimal string. Read and write. + """) + bin = property(Bits._getbin, Bits._setbin_safe, + doc="""The bitstring as a binary string. Read and write. + """) + oct = property(Bits._getoct, Bits._setoct, + doc="""The bitstring as an octal string. Read and write. + """) + bool = property(Bits._getbool, Bits._setbool, + doc="""The bitstring as a bool (True or False). Read and write. + """) + bytes = property(Bits._getbytes, Bits._setbytes_safe, + doc="""The bitstring as a ordinary string. Read and write. + """) + + + +class ConstBitStream(Bits): + """A container or stream holding an immutable sequence of bits. + + For a mutable container use the BitStream class instead. + + Methods inherited from Bits: + + all() -- Check if all specified bits are set to 1 or 0. + any() -- Check if any of specified bits are set to 1 or 0. + count() -- Count the number of bits set to 1 or 0. + cut() -- Create generator of constant sized chunks. + endswith() -- Return whether the bitstring ends with a sub-string. + find() -- Find a sub-bitstring in the current bitstring. + findall() -- Find all occurrences of a sub-bitstring in the current bitstring. + join() -- Join bitstrings together using current bitstring. + rfind() -- Seek backwards to find a sub-bitstring. + split() -- Create generator of chunks split by a delimiter. + startswith() -- Return whether the bitstring starts with a sub-bitstring. + tobytes() -- Return bitstring as bytes, padding if needed. + tofile() -- Write bitstring to file, padding if needed. + unpack() -- Interpret bits using format string. + + Other methods: + + bytealign() -- Align to next byte boundary. + peek() -- Peek at and interpret next bits as a single item. + peeklist() -- Peek at and interpret next bits as a list of items. + read() -- Read and interpret next bits as a single item. + readlist() -- Read and interpret next bits as a list of items. + + Special methods: + + Also available are the operators [], ==, !=, +, *, ~, <<, >>, &, |, ^. + + Properties: + + bin -- The bitstring as a binary string. + bool -- For single bit bitstrings, interpret as True or False. + bytepos -- The current byte position in the bitstring. + bytes -- The bitstring as a bytes object. + float -- Interpret as a floating point number. + floatbe -- Interpret as a big-endian floating point number. + floatle -- Interpret as a little-endian floating point number. + floatne -- Interpret as a native-endian floating point number. + hex -- The bitstring as a hexadecimal string. + int -- Interpret as a two's complement signed integer. + intbe -- Interpret as a big-endian signed integer. + intle -- Interpret as a little-endian signed integer. + intne -- Interpret as a native-endian signed integer. + len -- Length of the bitstring in bits. + oct -- The bitstring as an octal string. + pos -- The current bit position in the bitstring. + se -- Interpret as a signed exponential-Golomb code. + ue -- Interpret as an unsigned exponential-Golomb code. + sie -- Interpret as a signed interleaved exponential-Golomb code. + uie -- Interpret as an unsigned interleaved exponential-Golomb code. + uint -- Interpret as a two's complement unsigned integer. + uintbe -- Interpret as a big-endian unsigned integer. + uintle -- Interpret as a little-endian unsigned integer. + uintne -- Interpret as a native-endian unsigned integer. + + """ + + __slots__ = ('_pos') + + def __init__(self, auto=None, length=None, offset=None, **kwargs): + """Either specify an 'auto' initialiser: + auto -- a string of comma separated tokens, an integer, a file object, + a bytearray, a boolean iterable or another bitstring. + + Or initialise via **kwargs with one (and only one) of: + bytes -- raw data as a string, for example read from a binary file. + bin -- binary string representation, e.g. '0b001010'. + hex -- hexadecimal string representation, e.g. '0x2ef' + oct -- octal string representation, e.g. '0o777'. + uint -- an unsigned integer. + int -- a signed integer. + float -- a floating point number. + uintbe -- an unsigned big-endian whole byte integer. + intbe -- a signed big-endian whole byte integer. + floatbe - a big-endian floating point number. + uintle -- an unsigned little-endian whole byte integer. + intle -- a signed little-endian whole byte integer. + floatle -- a little-endian floating point number. + uintne -- an unsigned native-endian whole byte integer. + intne -- a signed native-endian whole byte integer. + floatne -- a native-endian floating point number. + se -- a signed exponential-Golomb code. + ue -- an unsigned exponential-Golomb code. + sie -- a signed interleaved exponential-Golomb code. + uie -- an unsigned interleaved exponential-Golomb code. + bool -- a boolean (True or False). + filename -- a file which will be opened in binary read-only mode. + + Other keyword arguments: + length -- length of the bitstring in bits, if needed and appropriate. + It must be supplied for all integer and float initialisers. + offset -- bit offset to the data. These offset bits are + ignored and this is intended for use when + initialising using 'bytes' or 'filename'. + + """ + self._pos = 0 + + def __new__(cls, auto=None, length=None, offset=None, **kwargs): + x = super(ConstBitStream, cls).__new__(cls) + x._initialise(auto, length, offset, **kwargs) + return x + + def _setbytepos(self, bytepos): + """Move to absolute byte-aligned position in stream.""" + self._setbitpos(bytepos * 8) + + def _getbytepos(self): + """Return the current position in the stream in bytes. Must be byte aligned.""" + if self._pos % 8: + raise ByteAlignError("Not byte aligned in _getbytepos().") + return self._pos // 8 + + def _setbitpos(self, pos): + """Move to absolute postion bit in bitstream.""" + if pos < 0: + raise ValueError("Bit position cannot be negative.") + if pos > self.len: + raise ValueError("Cannot seek past the end of the data.") + self._pos = pos + + def _getbitpos(self): + """Return the current position in the stream in bits.""" + return self._pos + + def _clear(self): + Bits._clear(self) + self._pos = 0 + + def __copy__(self): + """Return a new copy of the ConstBitStream for the copy module.""" + # Note that if you want a new copy (different ID), use _copy instead. + # The copy can use the same datastore as it's immutable. + s = ConstBitStream() + s._datastore = self._datastore + # Reset the bit position, don't copy it. + s._pos = 0 + return s + + def __add__(self, bs): + """Concatenate bitstrings and return new bitstring. + + bs -- the bitstring to append. + + """ + s = Bits.__add__(self, bs) + s._pos = 0 + return s + + def read(self, fmt): + """Interpret next bits according to the format string and return result. + + fmt -- Token string describing how to interpret the next bits. + + Token examples: 'int:12' : 12 bits as a signed integer + 'uint:8' : 8 bits as an unsigned integer + 'float:64' : 8 bytes as a big-endian float + 'intbe:16' : 2 bytes as a big-endian signed integer + 'uintbe:16' : 2 bytes as a big-endian unsigned integer + 'intle:32' : 4 bytes as a little-endian signed integer + 'uintle:32' : 4 bytes as a little-endian unsigned integer + 'floatle:64': 8 bytes as a little-endian float + 'intne:24' : 3 bytes as a native-endian signed integer + 'uintne:24' : 3 bytes as a native-endian unsigned integer + 'floatne:32': 4 bytes as a native-endian float + 'hex:80' : 80 bits as a hex string + 'oct:9' : 9 bits as an octal string + 'bin:1' : single bit binary string + 'ue' : next bits as unsigned exp-Golomb code + 'se' : next bits as signed exp-Golomb code + 'uie' : next bits as unsigned interleaved exp-Golomb code + 'sie' : next bits as signed interleaved exp-Golomb code + 'bits:5' : 5 bits as a bitstring + 'bytes:10' : 10 bytes as a bytes object + 'bool' : 1 bit as a bool + 'pad:3' : 3 bits of padding to ignore - returns None + + fmt may also be an integer, which will be treated like the 'bits' token. + + The position in the bitstring is advanced to after the read items. + + Raises ReadError if not enough bits are available. + Raises ValueError if the format is not understood. + + """ + if isinstance(fmt, numbers.Integral): + if fmt < 0: + raise ValueError("Cannot read negative amount.") + if fmt > self.len - self._pos: + raise ReadError("Cannot read {0} bits, only {1} available.", + fmt, self.len - self._pos) + bs = self._slice(self._pos, self._pos + fmt) + self._pos += fmt + return bs + p = self._pos + _, token = tokenparser(fmt) + if len(token) != 1: + self._pos = p + raise ValueError("Format string should be a single token, not {0} " + "tokens - use readlist() instead.".format(len(token))) + name, length, _ = token[0] + if length is None: + length = self.len - self._pos + value, self._pos = self._readtoken(name, self._pos, length) + return value + + def readlist(self, fmt, **kwargs): + """Interpret next bits according to format string(s) and return list. + + fmt -- A single string or list of strings with comma separated tokens + describing how to interpret the next bits in the bitstring. Items + can also be integers, for reading new bitstring of the given length. + kwargs -- A dictionary or keyword-value pairs - the keywords used in the + format string will be replaced with their given value. + + The position in the bitstring is advanced to after the read items. + + Raises ReadError is not enough bits are available. + Raises ValueError if the format is not understood. + + See the docstring for 'read' for token examples. 'pad' tokens are skipped + and not added to the returned list. + + >>> h, b1, b2 = s.readlist('hex:20, bin:5, bin:3') + >>> i, bs1, bs2 = s.readlist(['uint:12', 10, 10]) + + """ + value, self._pos = self._readlist(fmt, self._pos, **kwargs) + return value + + def readto(self, bs, bytealigned=None): + """Read up to and including next occurrence of bs and return result. + + bs -- The bitstring to find. An integer is not permitted. + bytealigned -- If True the bitstring will only be + found on byte boundaries. + + Raises ValueError if bs is empty. + Raises ReadError if bs is not found. + + """ + if isinstance(bs, numbers.Integral): + raise ValueError("Integers cannot be searched for") + bs = Bits(bs) + oldpos = self._pos + p = self.find(bs, self._pos, bytealigned=bytealigned) + if not p: + raise ReadError("Substring not found") + self._pos += bs.len + return self._slice(oldpos, self._pos) + + def peek(self, fmt): + """Interpret next bits according to format string and return result. + + fmt -- Token string describing how to interpret the next bits. + + The position in the bitstring is not changed. If not enough bits are + available then all bits to the end of the bitstring will be used. + + Raises ReadError if not enough bits are available. + Raises ValueError if the format is not understood. + + See the docstring for 'read' for token examples. + + """ + pos_before = self._pos + value = self.read(fmt) + self._pos = pos_before + return value + + def peeklist(self, fmt, **kwargs): + """Interpret next bits according to format string(s) and return list. + + fmt -- One or more strings with comma separated tokens describing + how to interpret the next bits in the bitstring. + kwargs -- A dictionary or keyword-value pairs - the keywords used in the + format string will be replaced with their given value. + + The position in the bitstring is not changed. If not enough bits are + available then all bits to the end of the bitstring will be used. + + Raises ReadError if not enough bits are available. + Raises ValueError if the format is not understood. + + See the docstring for 'read' for token examples. + + """ + pos = self._pos + return_values = self.readlist(fmt, **kwargs) + self._pos = pos + return return_values + + def bytealign(self): + """Align to next byte and return number of skipped bits. + + Raises ValueError if the end of the bitstring is reached before + aligning to the next byte. + + """ + skipped = (8 - (self._pos % 8)) % 8 + self.pos += self._offset + skipped + assert self._assertsanity() + return skipped + + pos = property(_getbitpos, _setbitpos, + doc="""The position in the bitstring in bits. Read and write. + """) + bitpos = property(_getbitpos, _setbitpos, + doc="""The position in the bitstring in bits. Read and write. + """) + bytepos = property(_getbytepos, _setbytepos, + doc="""The position in the bitstring in bytes. Read and write. + """) + + + + + +class BitStream(ConstBitStream, BitArray): + """A container or stream holding a mutable sequence of bits + + Subclass of the ConstBitStream and BitArray classes. Inherits all of + their methods. + + Methods: + + all() -- Check if all specified bits are set to 1 or 0. + any() -- Check if any of specified bits are set to 1 or 0. + append() -- Append a bitstring. + bytealign() -- Align to next byte boundary. + byteswap() -- Change byte endianness in-place. + count() -- Count the number of bits set to 1 or 0. + cut() -- Create generator of constant sized chunks. + endswith() -- Return whether the bitstring ends with a sub-string. + find() -- Find a sub-bitstring in the current bitstring. + findall() -- Find all occurrences of a sub-bitstring in the current bitstring. + insert() -- Insert a bitstring. + invert() -- Flip bit(s) between one and zero. + join() -- Join bitstrings together using current bitstring. + overwrite() -- Overwrite a section with a new bitstring. + peek() -- Peek at and interpret next bits as a single item. + peeklist() -- Peek at and interpret next bits as a list of items. + prepend() -- Prepend a bitstring. + read() -- Read and interpret next bits as a single item. + readlist() -- Read and interpret next bits as a list of items. + replace() -- Replace occurrences of one bitstring with another. + reverse() -- Reverse bits in-place. + rfind() -- Seek backwards to find a sub-bitstring. + rol() -- Rotate bits to the left. + ror() -- Rotate bits to the right. + set() -- Set bit(s) to 1 or 0. + split() -- Create generator of chunks split by a delimiter. + startswith() -- Return whether the bitstring starts with a sub-bitstring. + tobytes() -- Return bitstring as bytes, padding if needed. + tofile() -- Write bitstring to file, padding if needed. + unpack() -- Interpret bits using format string. + + Special methods: + + Mutating operators are available: [], <<=, >>=, +=, *=, &=, |= and ^= + in addition to [], ==, !=, +, *, ~, <<, >>, &, | and ^. + + Properties: + + bin -- The bitstring as a binary string. + bool -- For single bit bitstrings, interpret as True or False. + bytepos -- The current byte position in the bitstring. + bytes -- The bitstring as a bytes object. + float -- Interpret as a floating point number. + floatbe -- Interpret as a big-endian floating point number. + floatle -- Interpret as a little-endian floating point number. + floatne -- Interpret as a native-endian floating point number. + hex -- The bitstring as a hexadecimal string. + int -- Interpret as a two's complement signed integer. + intbe -- Interpret as a big-endian signed integer. + intle -- Interpret as a little-endian signed integer. + intne -- Interpret as a native-endian signed integer. + len -- Length of the bitstring in bits. + oct -- The bitstring as an octal string. + pos -- The current bit position in the bitstring. + se -- Interpret as a signed exponential-Golomb code. + ue -- Interpret as an unsigned exponential-Golomb code. + sie -- Interpret as a signed interleaved exponential-Golomb code. + uie -- Interpret as an unsigned interleaved exponential-Golomb code. + uint -- Interpret as a two's complement unsigned integer. + uintbe -- Interpret as a big-endian unsigned integer. + uintle -- Interpret as a little-endian unsigned integer. + uintne -- Interpret as a native-endian unsigned integer. + + """ + + __slots__ = () + + # As BitStream objects are mutable, we shouldn't allow them to be hashed. + __hash__ = None + + def __init__(self, auto=None, length=None, offset=None, **kwargs): + """Either specify an 'auto' initialiser: + auto -- a string of comma separated tokens, an integer, a file object, + a bytearray, a boolean iterable or another bitstring. + + Or initialise via **kwargs with one (and only one) of: + bytes -- raw data as a string, for example read from a binary file. + bin -- binary string representation, e.g. '0b001010'. + hex -- hexadecimal string representation, e.g. '0x2ef' + oct -- octal string representation, e.g. '0o777'. + uint -- an unsigned integer. + int -- a signed integer. + float -- a floating point number. + uintbe -- an unsigned big-endian whole byte integer. + intbe -- a signed big-endian whole byte integer. + floatbe - a big-endian floating point number. + uintle -- an unsigned little-endian whole byte integer. + intle -- a signed little-endian whole byte integer. + floatle -- a little-endian floating point number. + uintne -- an unsigned native-endian whole byte integer. + intne -- a signed native-endian whole byte integer. + floatne -- a native-endian floating point number. + se -- a signed exponential-Golomb code. + ue -- an unsigned exponential-Golomb code. + sie -- a signed interleaved exponential-Golomb code. + uie -- an unsigned interleaved exponential-Golomb code. + bool -- a boolean (True or False). + filename -- a file which will be opened in binary read-only mode. + + Other keyword arguments: + length -- length of the bitstring in bits, if needed and appropriate. + It must be supplied for all integer and float initialisers. + offset -- bit offset to the data. These offset bits are + ignored and this is intended for use when + initialising using 'bytes' or 'filename'. + + """ + self._pos = 0 + # For mutable BitStreams we always read in files to memory: + if not isinstance(self._datastore, ByteStore): + self._ensureinmemory() + + def __new__(cls, auto=None, length=None, offset=None, **kwargs): + x = super(BitStream, cls).__new__(cls) + x._initialise(auto, length, offset, **kwargs) + return x + + def __copy__(self): + """Return a new copy of the BitStream.""" + s_copy = BitStream() + s_copy._pos = 0 + if not isinstance(self._datastore, ByteStore): + # Let them both point to the same (invariant) array. + # If either gets modified then at that point they'll be read into memory. + s_copy._datastore = self._datastore + else: + s_copy._datastore = ByteStore(self._datastore._rawarray[:], + self._datastore.bitlength, + self._datastore.offset) + return s_copy + + def prepend(self, bs): + """Prepend a bitstring to the current bitstring. + + bs -- The bitstring to prepend. + + """ + bs = self._converttobitstring(bs) + self._prepend(bs) + self._pos += bs.len + + +def pack(fmt, *values, **kwargs): + """Pack the values according to the format string and return a new BitStream. + + fmt -- A single string or a list of strings with comma separated tokens + describing how to create the BitStream. + values -- Zero or more values to pack according to the format. + kwargs -- A dictionary or keyword-value pairs - the keywords used in the + format string will be replaced with their given value. + + Token examples: 'int:12' : 12 bits as a signed integer + 'uint:8' : 8 bits as an unsigned integer + 'float:64' : 8 bytes as a big-endian float + 'intbe:16' : 2 bytes as a big-endian signed integer + 'uintbe:16' : 2 bytes as a big-endian unsigned integer + 'intle:32' : 4 bytes as a little-endian signed integer + 'uintle:32' : 4 bytes as a little-endian unsigned integer + 'floatle:64': 8 bytes as a little-endian float + 'intne:24' : 3 bytes as a native-endian signed integer + 'uintne:24' : 3 bytes as a native-endian unsigned integer + 'floatne:32': 4 bytes as a native-endian float + 'hex:80' : 80 bits as a hex string + 'oct:9' : 9 bits as an octal string + 'bin:1' : single bit binary string + 'ue' / 'uie': next bits as unsigned exp-Golomb code + 'se' / 'sie': next bits as signed exp-Golomb code + 'bits:5' : 5 bits as a bitstring object + 'bytes:10' : 10 bytes as a bytes object + 'bool' : 1 bit as a bool + 'pad:3' : 3 zero bits as padding + + >>> s = pack('uint:12, bits', 100, '0xffe') + >>> t = pack(['bits', 'bin:3'], s, '111') + >>> u = pack('uint:8=a, uint:8=b, uint:55=a', a=6, b=44) + + """ + tokens = [] + if isinstance(fmt, basestring): + fmt = [fmt] + try: + for f_item in fmt: + _, tkns = tokenparser(f_item, tuple(sorted(kwargs.keys()))) + tokens.extend(tkns) + except ValueError as e: + raise CreationError(*e.args) + value_iter = iter(values) + s = BitStream() + try: + for name, length, value in tokens: + # If the value is in the kwd dictionary then it takes precedence. + if value in kwargs: + value = kwargs[value] + # If the length is in the kwd dictionary then use that too. + if length in kwargs: + length = kwargs[length] + # Also if we just have a dictionary name then we want to use it + if name in kwargs and length is None and value is None: + s.append(kwargs[name]) + continue + if length is not None: + length = int(length) + if value is None and name != 'pad': + # Take the next value from the ones provided + value = next(value_iter) + s._append(BitStream._init_with_token(name, length, value)) + except StopIteration: + raise CreationError("Not enough parameters present to pack according to the " + "format. {0} values are needed.", len(tokens)) + try: + next(value_iter) + except StopIteration: + # Good, we've used up all the *values. + return s + raise CreationError("Too many parameters present to pack according to the format.") + + +# Aliases for backward compatibility +ConstBitArray = Bits +BitString = BitStream + +__all__ = ['ConstBitArray', 'ConstBitStream', 'BitStream', 'BitArray', + 'Bits', 'BitString', 'pack', 'Error', 'ReadError', + 'InterpretError', 'ByteAlignError', 'CreationError', 'bytealigned'] diff --git a/python/bitstring/doc/bitstring_manual.pdf b/python/bitstring/doc/bitstring_manual.pdf Binary files differnew file mode 100644 index 000000000..dc17385b7 --- /dev/null +++ b/python/bitstring/doc/bitstring_manual.pdf diff --git a/python/bitstring/release_notes.txt b/python/bitstring/release_notes.txt new file mode 100644 index 000000000..8cad4ca9d --- /dev/null +++ b/python/bitstring/release_notes.txt @@ -0,0 +1,1523 @@ +--------------------------------
+bitstring module version history
+--------------------------------
+
+---------------------------------------
+March 4th 2014: version 3.1.3 released
+---------------------------------------
+This is another bug fix release.
+
+* Fix for problem with prepend for bitstrings with byte offsets in their data store.
+
+---------------------------------------
+April 18th 2013: version 3.1.2 released
+---------------------------------------
+This is another bug fix release.
+
+* Fix for problem where unpacking bytes would by eight times too long
+
+---------------------------------------
+March 21st 2013: version 3.1.1 released
+---------------------------------------
+This is a bug fix release.
+
+* Fix for problem where concatenating bitstrings sometimes modified method's arguments
+
+------------------------------------------
+February 26th 2013: version 3.1.0 released
+------------------------------------------
+This is a minor release with a couple of new features and some bug fixes.
+
+New 'pad' token
+---------------
+
+This token can be used in reads and when packing/unpacking to indicate that
+you don't care about the contents of these bits. Any padding bits will just
+be skipped over when reading/unpacking or zero-filled when packing.
+
+ >>> a, b = s.readlist('pad:5, uint:3, pad:1, uint:3')
+
+Here only two items are returned in the list - the padding bits are ignored.
+
+New clear and copy convenience methods
+--------------------------------------
+
+These methods have been introduced in Python 3.3 for lists and bytearrays,
+as more obvious ways of clearing and copying, and we mirror that change here.
+
+t = s.copy() is equivalent to t = s[:], and s.clear() is equivalent to del s[:].
+
+Other changes
+-------------
+
+* Some bug fixes.
+
+-----------------------------------------
+February 7th 2012: version 3.0.2 released
+-----------------------------------------
+This is a minor update that fixes a few bugs.
+
+* Fix for subclasses of bitstring classes behaving strangely (Issue 121).
+* Fix for excessive memory usage in rare cases (Issue 120).
+* Fixes for slicing edge cases.
+
+There has also been a reorganisation of the code to return it to a single
+'bitstring.py' file rather than the package that has been used for the past
+several releases. This change shouldn't affect users directly.
+
+------------------------------------------
+November 21st 2011: version 3.0.1 released
+------------------------------------------
+This release fixed a small but very visible bug in bitstring printing.
+
+------------------------------------------
+November 21st 2011: version 3.0.0 released
+------------------------------------------
+This is a major release which breaks backward compatibility in a few places.
+
+Backwardly incompatible changes
+===============================
+
+Hex, oct and bin properties don't have leading 0x, 0o and 0b
+------------------------------------------------------------
+
+If you ask for the hex, octal or binary representations of a bitstring then
+they will no longer be prefixed with '0x', 0o' or '0b'. This was done as it
+was noticed that the first thing a lot of user code does after getting these
+representations was to cut off the first two characters before further
+processing.
+
+ >>> a = BitArray('0x123')
+ >>> a.hex, a.oct, a.bin
+ ('123', '0443', '000100100011')
+
+Previously this would have returned ('0x123', '0o0443', '0b000100100011')
+
+This change might require some recoding, but it should all be simplifications.
+
+ConstBitArray renamed to Bits
+-----------------------------
+
+Previously Bits was an alias for ConstBitStream (for backward compatibility).
+This has now changed so that Bits and BitArray loosely correspond to the
+built-in types bytes and bytearray.
+
+If you were using streaming/reading methods on a Bits object then you will
+have to change it to a ConstBitStream.
+
+The ConstBitArray name is kept as an alias for Bits.
+
+Stepping in slices has conventional meaning
+-------------------------------------------
+
+The step parameter in __getitem__, __setitem__ and __delitem__ used to act
+as a multiplier for the start and stop parameters. No one seemed to use it
+though and so it has now reverted to the convential meaning for containers.
+
+If you are using step then recoding is simple: s[a:b:c] becomes s[a*c:b*c].
+
+Some examples of the new usage:
+
+ >>> s = BitArray('0x0000')
+ s[::4] = [1, 1, 1, 1]
+ >>> s.hex
+ '8888'
+ >>> del s[8::2]
+ >>> s.hex
+ '880'
+
+
+New features
+============
+
+New readto method
+-----------------
+
+This method is a mix between a find and a read - it searches for a bitstring
+and then reads up to and including it. For example:
+
+ >>> s = ConstBitStream('0x47000102034704050647')
+ >>> s.readto('0x47', bytealigned=True)
+ BitStream('0x47')
+ >>> s.readto('0x47', bytealigned=True)
+ BitStream('0x0001020347')
+ >>> s.readto('0x47', bytealigned=True)
+ BitStream('0x04050647')
+
+pack function accepts an iterable as its format
+-----------------------------------------------
+
+Previously only a string was accepted as the format in the pack function.
+This was an oversight as it broke the symmetry between pack and unpack.
+Now you can use formats like this:
+
+ fmt = ['hex:8', 'bin:3']
+ a = pack(fmt, '47', '001')
+ a.unpack(fmt)
+
+
+--------------------------------------
+June 18th 2011: version 2.2.0 released
+--------------------------------------
+This is a minor upgrade with a couple of new features.
+
+New interleaved exponential-Golomb interpretations
+--------------------------------------------------
+
+New bit interpretations for interleaved exponential-Golomb (as used in the
+Dirac video codec) are supplied via 'uie' and 'sie':
+
+ >>> s = BitArray(uie=41)
+ >>> s.uie
+ 41
+ >>> s.bin
+ '0b00010001001'
+
+These are pretty similar to the non-interleaved versions - see the manual
+for more details. Credit goes to Paul Sargent for the patch.
+
+New package-level bytealigned variable
+--------------------------------------
+
+A number of methods take a 'bytealigned' parameter to indicate that they
+should only work on byte boundaries (e.g. find, replace, split). Previously
+this parameter defaulted to 'False'. Instead it now defaults to
+'bitstring.bytealigned', which itself defaults to 'False', but can be changed
+to modify the default behaviour of the methods. For example:
+
+ >>> a = BitArray('0x00 ff 0f ff')
+ >>> a.find('0x0f')
+ (4,) # found first not on a byte boundary
+ >>> a.find('0x0f', bytealigned=True)
+ (16,) # forced looking only on byte boundaries
+ >>> bitstring.bytealigned = True # Change default behaviour
+ >>> a.find('0x0f')
+ (16,)
+ >>> a.find('0x0f', bytealigned=False)
+ (4,)
+
+If you're only working with bytes then this can help avoid some errors and
+save some typing!
+
+Other changes
+-------------
+
+* Fix for Python 3.2, correcting for a change to the binascii module.
+* Fix for bool initialisation from 0 or 1.
+* Efficiency improvements, including interning strategy.
+
+------------------------------------------
+February 23rd 2011: version 2.1.1 released
+------------------------------------------
+This is a release to fix a couple of bugs that were introduced in 2.1.0.
+
+* Bug fix: Reading using the 'bytes' token had been broken (Issue 102).
+* Fixed problem using some methods on ConstBitArrays.
+* Better exception handling for tokens missing values.
+* Some performance improvements.
+
+-----------------------------------------
+January 23rd 2011: version 2.1.0 released
+-----------------------------------------
+
+New class hierarchy introduced with simpler classes
+---------------------------------------------------
+Previously there were just two classes, the immutable Bits which was the base
+class for the mutable BitString class. Both of these classes have the concept
+of a bit position, from which reads etc. take place so that the bitstring could
+be treated as if it were a file or stream.
+
+Two simpler classes have now been added which are purely bit containers and
+don't have a bit position. These are called ConstBitArray and BitArray. As you
+can guess the former is an immutable version of the latter.
+
+The other classes have also been renamed to better reflect their capabilities.
+Instead of BitString you can use BitStream, and instead of Bits you can use
+ConstBitStream. The old names are kept as aliases for backward compatibility.
+
+The classes hierarchy is:
+
+ ConstBitArray
+ / \
+ / \
+ BitArray ConstBitStream (formerly Bits)
+ \ /
+ \ /
+ BitStream (formerly BitString)
+
+
+Other changes
+-------------
+A lot of internal reorganisation has taken place since the previous version,
+most of which won't be noticed by the end user. Some things you might see are:
+
+* New package structure. Previous versions have been a single file for the
+ module and another for the unit tests. The module is now split into many
+ more files so it can't be used just by copying bitstring.py any more.
+* To run the unit tests there is now a script called runtests.py in the test
+ directory.
+* File based bitstring are now implemented in terms of an mmap. This should
+ be just an implementation detail, but unfortunately for 32-bit versions of
+ Python this creates a limit of 4GB on the files that can be used. The work
+ around is either to get a 64-bit Python, or just stick with version 2.0.
+* The ConstBitArray and ConstBitStream classes no longer copy byte data when
+ a slice or a read takes place, they just take a reference. This is mostly
+ a very nice optimisation, but there are occassions where it could have an
+ adverse effect. For example if a very large bitstring is created, a small
+ slice taken and the original deleted. The byte data from the large
+ bitstring would still be retained in memory.
+* Optimisations. Once again this version should be faster than the last.
+ The module is still pure Python but some of the reorganisation was to make
+ it more feasible to put some of the code into Cython or similar, so
+ hopefully more speed will be on the way.
+
+--------------------------------------
+July 26th 2010: version 2.0.3 released
+--------------------------------------
+* Bug fix: Using peek and read for a single bit now returns a new bitstring
+ as was intended, rather than the old behaviour of returning a bool.
+* Removed HTML docs from source archive - better to use the online version.
+
+--------------------------------------
+July 25th 2010: version 2.0.2 released
+--------------------------------------
+This is a major release, with a number of backwardly incompatible changes.
+The main change is the removal of many methods, all of which have simple
+alternatives. Other changes are quite minor but may need some recoding.
+
+There are a few new features, most of which have been made to help the
+stream-lining of the API. As always there are performance improvements and
+some API changes were made purely with future performance in mind.
+
+The backwardly incompatible changes are:
+-----------------------------------------
+* Methods removed.
+
+About half of the class methods have been removed from the API. They all have
+simple alternatives, so what remains is more powerful and easier to remember.
+The removed methods are listed here on the left, with their equivalent
+replacements on the right:
+
+s.advancebit() -> s.pos += 1
+s.advancebits(bits) -> s.pos += bits
+s.advancebyte() -> s.pos += 8
+s.advancebytes(bytes) -> s.pos += 8*bytes
+s.allunset([a, b]) -> s.all(False, [a, b])
+s.anyunset([a, b]) -> s.any(False, [a, b])
+s.delete(bits, pos) -> del s[pos:pos+bits]
+s.peekbit() -> s.peek(1)
+s.peekbitlist(a, b) -> s.peeklist([a, b])
+s.peekbits(bits) -> s.peek(bits)
+s.peekbyte() -> s.peek(8)
+s.peekbytelist(a, b) -> s.peeklist([8*a, 8*b])
+s.peekbytes(bytes) -> s.peek(8*bytes)
+s.readbit() -> s.read(1)
+s.readbitlist(a, b) -> s.readlist([a, b])
+s.readbits(bits) -> s.read(bits)
+s.readbyte() -> s.read(8)
+s.readbytelist(a, b) -> s.readlist([8*a, 8*b])
+s.readbytes(bytes) -> s.read(8*bytes)
+s.retreatbit() -> s.pos -= 1
+s.retreatbits(bits) -> s.pos -= bits
+s.retreatbyte() -> s.pos -= 8
+s.retreatbytes(bytes) -> s.pos -= 8*bytes
+s.reversebytes(start, end) -> s.byteswap(0, start, end)
+s.seek(pos) -> s.pos = pos
+s.seekbyte(bytepos) -> s.bytepos = bytepos
+s.slice(start, end, step) -> s[start:end:step]
+s.tell() -> s.pos
+s.tellbyte() -> s.bytepos
+s.truncateend(bits) -> del s[-bits:]
+s.truncatestart(bits) -> del s[:bits]
+s.unset([a, b]) -> s.set(False, [a, b])
+
+Many of these methods have been deprecated for the last few releases, but
+there are some new removals too. Any recoding needed should be quite
+straightforward, so while I apologise for the hassle, I had to take the
+opportunity to streamline and rationalise what was becoming a bit of an
+overblown API.
+
+* set / unset methods combined.
+
+The set/unset methods have been combined in a single method, which now
+takes a boolean as its first argument:
+
+s.set([a, b]) -> s.set(1, [a, b])
+s.unset([a, b]) -> s.set(0, [a, b])
+s.allset([a, b]) -> s.all(1, [a, b])
+s.allunset([a, b]) -> s.all(0, [a, b])
+s.anyset([a, b]) -> s.any(1, [a, b])
+s.anyunset([a, b]) -> s.any(0, [a, b])
+
+* all / any only accept iterables.
+
+The all and any methods (previously called allset, allunset, anyset and
+anyunset) no longer accept a single bit position. The recommended way of
+testing a single bit is just to index it, for example instead of:
+
+>>> if s.all(True, i):
+
+just use
+
+>>> if s[i]:
+
+If you really want to you can of course use an iterable with a single
+element, such as 's.any(False, [i])', but it's clearer just to write
+'not s[i]'.
+
+* Exception raised on reading off end of bitstring.
+
+If a read or peek goes beyond the end of the bitstring then a ReadError
+will be raised. The previous behaviour was that the rest of the bitstring
+would be returned and no exception raised.
+
+* BitStringError renamed to Error.
+
+The base class for errors in the bitstring module is now just Error, so
+it will likely appears in your code as bitstring.Error instead of
+the rather repetitive bitstring.BitStringError.
+
+* Single bit slices and reads return a bool.
+
+A single index slice (such as s[5]) will now return a bool (i.e. True or
+False) rather than a single bit bitstring. This is partly to reflect the
+style of the bytearray type, which returns an integer for single items, but
+mostly to avoid common errors like:
+
+>>> if s[0]:
+... do_something()
+
+While the intent of this code snippet is quite clear (i.e. do_something if
+the first bit of s is set) under the old rules s[0] would be true as long
+as s wasn't empty. That's because any one-bit bitstring was true as it was a
+non-empty container. Under the new rule s[0] is True if s starts with a '1'
+bit and False if s starts with a '0' bit.
+
+The change does not affect reads and peeks, so s.peek(1) will still return
+a single bit bitstring, which leads on to the next item...
+
+* Empty bitstrings or bitstrings with only zero bits are considered False.
+
+Previously a bitstring was False if it had no elements, otherwise it was True.
+This is standard behaviour for containers, but wasn't very useful for a container
+of just 0s and 1s. The new behaviour means that the bitstring is False if it
+has no 1 bits. This means that code like this:
+
+>>> if s.peek(1):
+... do_something()
+
+should work as you'd expect. It also means that Bits(1000), Bits(0x00) and
+Bits('uint:12=0') are all also False. If you need to check for the emptiness of
+a bitstring then instead check the len property:
+
+if s -> if s.len
+if not s -> if not s.len
+
+* Length and offset disallowed for some initialisers.
+
+Previously you could create bitstring using expressions like:
+
+>>> s = Bits(hex='0xabcde', offset=4, length=13)
+
+This has now been disallowed, and the offset and length parameters may only
+be used when initialising with bytes or a file. To replace the old behaviour
+you could instead use
+
+>>> s = Bits(hex='0xabcde')[4:17]
+
+* Renamed 'format' parameter 'fmt'.
+
+Methods with a 'format' parameter have had it renamed to 'fmt', to prevent
+hiding the built-in 'format'. Affects methods unpack, read, peek, readlist,
+peeklist and byteswap and the pack function.
+
+* Iterables instead of *format accepted for some methods.
+
+This means that for the affected methods (unpack, readlist and peeklist) you
+will need to use an iterable to specify multiple items. This is easier to
+show than to describe, so instead of
+
+>>> a, b, c, d = s.readlist('uint:12', 'hex:4', 'bin:7')
+
+you would instead write
+
+>>> a, b, c, d = s.readlist(['uint:12', 'hex:4', 'bin:7'])
+
+Note that you could still use the single string 'uint:12, hex:4, bin:7' if
+you preferred.
+
+* Bool auto-initialisation removed.
+
+You can no longer use True and False to initialise single bit bitstrings.
+The reasoning behind this is that as bool is a subclass of int, it really is
+bad practice to have Bits(False) be different to Bits(0) and to have Bits(True)
+different to Bits(1).
+
+If you have used bool auto-initialisation then you will have to be careful to
+replace it as the bools will now be interpreted as ints, so Bits(False) will
+be empty (a bitstring of length 0), and Bits(True) will be a single zero bit
+(a bitstring of length 1). Sorry for the confusion, but I think this will
+prevent bigger problems in the future.
+
+There are a few alternatives for creating a single bit bitstring. My favourite
+it to use a list with a single item:
+
+Bits(False) -> Bits([0])
+Bits(True) -> Bits([1])
+
+* New creation from file strategy
+
+Previously if you created a bitstring from a file, either by auto-initialising
+with a file object or using the filename parameter, the file would not be read
+into memory unless you tried to modify it, at which point the whole file would
+be read.
+
+The new behaviour depends on whether you create a Bits or a BitString from the
+file. If you create a Bits (which is immutable) then the file will never be
+read into memory. This allows very large files to be opened for examination
+even if they could never fit in memory.
+
+If however you create a BitString, the whole of the referenced file will be read
+to store in memory. If the file is very big this could take a long time, or fail,
+but the idea is that in saying you want the mutable BitString you are implicitly
+saying that you want to make changes and so (for now) we need to load it into
+memory.
+
+The new strategy is a bit more predictable in terms of performance than the old.
+The main point to remember is that if you want to open a file and don't plan to
+alter the bitstring then use the Bits class rather than BitString.
+
+Just to be clear, in neither case will the contents of the file ever be changed -
+if you want to output the modified BitString then use the tofile method, for
+example.
+
+* find and rfind return a tuple instead of a bool.
+
+If a find is unsuccessful then an empty tuple is returned (which is False in a
+boolean sense) otherwise a single item tuple with the bit position is returned
+(which is True in a boolean sense). You shouldn't need to recode unless you
+explicitly compared the result of a find to True or False, for example this
+snippet doesn't need to be altered:
+
+>>> if s.find('0x23'):
+... print(s.bitpos)
+
+but you could now instead use
+
+>>> found = s.find('0x23')
+>>> if found:
+... print(found[0])
+
+The reason for returning the bit position in a tuple is so that finding at
+position zero can still be True - it's the tuple (0,) - whereas not found can
+be False - the empty tuple ().
+
+The new features in this release are:
+-------------------------------------
+* New count method.
+
+This method just counts the number of 1 or 0 bits in the bitstring.
+
+>>> s = Bits('0x31fff4')
+>>> s.count(1)
+16
+
+* read and peek methods accept integers.
+
+The read, readlist, peek and peeklist methods now accept integers as parameters
+to mean "read this many bits and return a bitstring". This has allowed a number
+of methods to be removed from this release, so for example instead of:
+
+>>> a, b, c = s.readbits(5, 6, 7)
+>>> if s.peekbit():
+... do_something()
+
+you should write:
+
+>>> a, b, c = s.readlist([5, 6, 7])
+>>> if s.peek(1):
+... do_something()
+
+* byteswap used to reverse all bytes.
+
+The byteswap method now allows a format specifier of 0 (the default) to signify
+that all of the whole bytes should be reversed. This means that calling just
+byteswap() is almost equivalent to the now removed bytereverse() method (a small
+difference is that byteswap won't raise an exception if the bitstring isn't a
+whole number of bytes long).
+
+* Auto initialise with bytearray or (for Python 3 only) bytes.
+
+So rather than writing:
+
+>>> a = Bits(bytes=some_bytearray)
+
+you can just write
+
+>>> a = Bits(some_bytearray)
+
+This also works for the bytes type, but only if you're using Python 3.
+For Python 2 it's not possible to distinguish between a bytes object and a
+str. For this reason this method should be used with some caution as it will
+make you code behave differently with the different major Python versions.
+
+>>> b = Bits(b'abcd\x23\x00') # Only Python 3!
+
+* set, invert, all and any default to whole bitstring.
+
+This means that you can for example write:
+
+>>> a = BitString(100) # 100 zero bits
+>>> a.set(1) # set all bits to 1
+>>> a.all(1) # are all bits set to 1?
+True
+>>> a.any(0) # are any set to 0?
+False
+>>> a.invert() # invert every bit
+
+* New exception types.
+
+As well as renaming BitStringError to just Error
+there are also new exceptions which use Error as a base class.
+
+These can be caught in preference to Error if you need finer control.
+The new exceptions sometimes also derive from built-in exceptions:
+
+ByteAlignError(Error) - whole byte position or length needed.
+
+ReadError(Error, IndexError) - reading or peeking off the end of
+the bitstring.
+
+CreationError(Error, ValueError) - inappropriate argument during
+bitstring creation.
+
+InterpretError(Error, ValueError) - inappropriate interpretation of
+binary data.
+
+
+--------------------------------------------------------------
+March 18th 2010: version 1.3.0 for Python 2.6 and 3.x released
+--------------------------------------------------------------
+New features:
+
+* byteswap method for changing endianness.
+
+Changes the endianness in-place according to a format string or
+integer(s) giving the byte pattern. See the manual for details.
+
+>>> s = BitString('0x00112233445566')
+>>> s.byteswap(2)
+3
+>>> s
+BitString('0x11003322554466')
+>>> s.byteswap('h')
+3
+>>> s
+BitString('0x00112233445566')
+>>> s.byteswap([2, 5])
+1
+>>> s
+BitString('0x11006655443322')
+
+* Multiplicative factors in bitstring creation and reading.
+
+For example:
+
+>>> s = Bits('100*0x123')
+
+* Token grouping using parenthesis.
+
+For example:
+
+>>> s = Bits('3*(uint:6=3, 0b1)')
+
+* Negative slice indices allowed.
+
+The start and end parameters of many methods may now be negative, with the
+same meaning as for negative slice indices. Affects all methods with these
+parameters.
+
+* Sequence ABCs used.
+
+The Bits class now derives from collections.Sequence, while the BitString
+class derives from collections.MutableSequence.
+
+* Keywords allowed in readlist, peeklist and unpack.
+
+Keywords for token lengths are now permitted when reading. So for example,
+you can write
+
+>>> s = bitstring.pack('4*(uint:n)', 2, 3, 4, 5, n=7)
+>>> s.unpack('4*(uint:n)', n=7)
+[2, 3, 4, 5]
+
+* start and end parameters added to rol and ror.
+
+* join function accepts other iterables.
+
+Also its parameter has changed from 'bitstringlist' to 'sequence'. This is
+technically a backward incompatibility in the unlikely event that you are
+referring to the parameter by name.
+
+* __init__ method accepts keywords.
+
+Rather than a long list of initialisers the __init__ methods now use a
+**kwargs dictionary for all initialisers except 'auto'. This should have no
+effect, except that this is a small backward incompatibility if you use
+positional arguments when initialising with anything other than auto
+(which would be rather unusual).
+
+* More optimisations.
+
+* Bug fixed in replace method (it could fail if start != 0).
+
+----------------------------------------------------------------
+January 19th 2010: version 1.2.0 for Python 2.6 and 3.x released
+----------------------------------------------------------------
+
+* New 'Bits' class.
+
+Introducing a brand new class, Bits, representing an immutable sequence of
+bits.
+
+The Bits class is the base class for the mutable BitString. The differences
+between Bits and BitStrings are:
+
+1) Bits are immutable, so once they have been created their value cannot change.
+This of course means that mutating methods (append, replace, del etc.) are not
+available for Bits.
+
+2) Bits are hashable, so they can be used in sets and as keys in dictionaries.
+
+3) Bits are potentially more efficient than BitStrings, both in terms of
+computation and memory. The current implementation is only marginally
+more efficient though - this should improve in future versions.
+
+You can switch from Bits to a BitString or vice versa by constructing a new
+object from the old.
+
+>>> s = Bits('0xabcd')
+>>> t = BitString(s)
+>>> t.append('0xe')
+>>> u = Bits(t)
+
+The relationship between Bits and BitString is supposed to loosely mirror that
+between bytes and bytearray in Python 3.
+
+* Deprecation messages turned on.
+
+A number of methods have been flagged for removal in version 2. Deprecation
+warnings will now be given, which include an alternative way to do the same
+thing. All of the deprecated methods have simpler equivalent alternatives.
+
+>>> t = s.slice(0, 2)
+__main__:1: DeprecationWarning: Call to deprecated function slice.
+Instead of 's.slice(a, b, c)' use 's[a:b:c]'.
+
+The deprecated methods are: advancebit, advancebits, advancebyte, advancebytes,
+retreatbit, retreatbits, retreatbyte, retreatbytes, tell, seek, slice, delete,
+tellbyte, seekbyte, truncatestart and truncateend.
+
+* Initialise from bool.
+
+Booleans have been added to the list of types that can 'auto'
+initialise a bitstring.
+
+>>> zerobit = BitString(False)
+>>> onebit = BitString(True)
+
+* Improved efficiency.
+
+More methods have been speeded up, in particular some deletions and insertions.
+
+* Bug fixes.
+
+A rare problem with truncating the start of bitstrings was fixed.
+
+A possible problem outputting the final byte in tofile() was fixed.
+
+-----------------------------------------------------------------
+December 22nd 2009: version 1.1.3 for Python 2.6 and 3.x released
+-----------------------------------------------------------------
+
+This version hopefully fixes an installation problem for platforms with
+case-sensitive file systems. There are no new features or other bug fixes.
+
+-----------------------------------------------------------------
+December 18th 2009: version 1.1.2 for Python 2.6 and 3.x released
+-----------------------------------------------------------------
+
+This is a minor update with (almost) no new features.
+
+* Improved efficiency.
+
+The speed of many typical operations has been increased, some substantially.
+
+* Initialise from integer.
+
+A BitString of '0' bits can be created using just an integer to give the length
+in bits. So instead of
+
+>>> s = BitString(length=100)
+
+you can write just
+
+>>> s = BitString(100)
+
+This matches the behaviour of bytearrays and (in Python 3) bytes.
+
+* A defect related to using the set / unset functions on BitStrings initialised
+from a file has been fixed.
+
+-----------------------------------------------------------------
+November 24th 2009: version 1.1.0 for Python 2.6 and 3.x released
+-----------------------------------------------------------------
+Note that this version will not work for Python 2.4 or 2.5. There may be an
+update for these Python versions some time next year, but it's not a priorty
+quite yet. Also note that only one version is now provided, which works for
+Python 2.6 and 3.x (done with the minimum of hackery!)
+
+* Improved efficiency.
+
+A fair number of functions have improved efficiency, some quite dramatically.
+
+* New bit setting and checking functions.
+
+Although these functions don't do anything that couldn't be done before, they
+do make some common use cases much more efficient. If you need to set or check
+single bits then these are the functions you need.
+
+set / unset : Set bit(s) to 1 or 0 respectively.
+allset / allunset : Check if all bits are 1 or all 0.
+anyset / anyunset : Check if any bits are 1 or any 0.
+
+>>> s = BitString(length=1000)
+>>> s.set((10, 100, 44, 12, 1))
+>>> s.allunset((2, 22, 222))
+True
+>>> s.anyset(range(7, 77))
+True
+
+* New rotate functions.
+
+ror / rol : Rotate bits to the right or left respectively.
+
+>>> s = BitString('0b100000000')
+>>> s.ror(2)
+>>> s.bin
+'0b001000000'
+>>> s.rol(5)
+>>> s.bin
+'0b000000100'
+
+* Floating point interpretations.
+
+New float initialisations and interpretations are available. These only work
+for BitStrings of length 32 or 64 bits.
+
+>>> s = BitString(float=0.2, length=64)
+>>> s.float
+0.200000000000000001
+>>> t = bitstring.pack('<3f', -0.4, 1e34, 17.0)
+>>> t.hex
+'0xcdccccbedf84f67700008841'
+
+* 'bytes' token reintroduced.
+
+This token returns a bytes object (equivalent to a str in Python 2.6).
+
+>>> s = BitString('0x010203')
+>>> s.unpack('bytes:2, bytes:1')
+['\x01\x02', '\x03']
+
+* 'uint' is now the default token type.
+
+So for example these are equivalent:
+
+a, b = s.readlist('uint:12, uint:12')
+a, b = s.readlist('12, 12')
+
+--------------------------------------------------------
+October 10th 2009: version 1.0.1 for Python 3.x released
+--------------------------------------------------------
+This is a straight port of version 1.0.0 to Python 3.
+
+For changes since the last Python 3 release read all the way down in this
+document to version 0.4.3.
+
+This version will also work for Python 2.6, but there's no advantage to using
+it over the 1.0.0 release. It won't work for anything before 2.6.
+
+-------------------------------------------------------
+October 9th 2009: version 1.0.0 for Python 2.x released
+-------------------------------------------------------
+Version 1 is here!
+
+This is the first release not to carry the 'beta' tag. It contains a couple of
+minor new features but is principally a release to fix the API. If you've been
+using an older version then you almost certainly will have to recode a bit. If
+you're not ready to do that then you may wish to delay updating.
+
+So the bad news is that there are lots of small changes to the API. The good
+news is that all the changes are pretty trivial, the new API is cleaner and
+more 'Pythonic', and that by making it version 1.0 I'm promising not to
+tweak it again for some time.
+
+** API Changes **
+
+* New read / peek functions for returning multiple items.
+
+The functions read, readbits, readbytes, peek, peekbits and peekbytes now only
+ever return a single item, never a list.
+
+The new functions readlist, readbitlist, readbytelist, peeklist, peekbitlist
+and peekbytelist can be used to read multiple items and will always return a
+list.
+
+So a line like:
+
+>>> a, b = s.read('uint:12, hex:32')
+
+becomes
+
+>>> a, b = s.readlist('uint:12, hex:32')
+
+* Renaming / removing functions.
+
+Functions have been renamed as follows:
+
+seekbit -> seek
+tellbit -> tell
+reversebits -> reverse
+deletebits -> delete
+tostring -> tobytes
+
+and a couple have been removed altogether:
+
+deletebytes - use delete instead.
+empty - use 'not s' rather than 's.empty()'.
+
+* Renaming parameters.
+
+The parameters 'startbit' and 'endbit' have been renamed 'start' and 'end'.
+This affects the functions slice, find, findall, rfind, reverse, cut and split.
+
+The parameter 'bitpos' has been renamed to 'pos'. The affects the functions
+seek, tell, insert, overwrite and delete.
+
+* Mutating methods return None rather than self.
+
+This means that you can't chain functions together so
+
+>>> s.append('0x00').prepend('0xff')
+>>> t = s.reverse()
+
+Needs to be rewritten
+
+>>> s.append('0x00')
+>>> s.prepend('0xff)
+>>> s.reverse()
+>>> t = s
+
+Affects truncatestart, truncateend, insert, overwrite, delete, append,
+prepend, reverse and reversebytes.
+
+* Properties renamed.
+
+The 'data' property has been renamed to 'bytes'. Also if the BitString is not a
+whole number of bytes then a ValueError exception will be raised when using
+'bytes' as a 'getter'.
+
+Properties 'len' and 'pos' have been added to replace 'length' and 'bitpos',
+although the longer names have not been removed so you can continue to use them
+if you prefer.
+
+* Other changes.
+
+The unpack function now always returns a list, never a single item.
+
+BitStrings are now 'unhashable', so calling hash on one or making a set will
+fail.
+
+The colon separating the token name from its length is now mandatory. So for
+example BitString('uint12=100') becomes BitString('uint:12=100').
+
+Removed support for the 'bytes' token in format strings. Instead of
+s.read('bytes:4') use s.read('bits:32').
+
+** New features **
+
+* Added endswith and startswith functions.
+
+These do much as you'd expect; they return True or False depending on whether
+the BitString starts or ends with the parameter.
+
+>>> BitString('0xef342').startswith('0b11101')
+True
+
+----------------------------------------------------------
+September 11th 2009: version 0.5.2 for Python 2.x released
+----------------------------------------------------------
+Finally some tools for dealing with endianness!
+
+* New interpretations are now available for whole-byte BitStrings that treat
+them as big, little, or native-endian.
+
+>>> big = BitString(intbe=1, length=16) # or BitString('intbe:16=1') if you prefer.
+>>> little = BitString(intle=1, length=16)
+>>> print big.hex, little.hex
+0x0001 0x0100
+>>> print big.intbe, little.intle
+1 1
+
+* 'Struct'-like compact format codes
+
+To save some typing when using pack, unpack, read and peek, compact format
+codes based on those used in the struct and array modules have been added.
+These must start with a character indicating the endianness (>, < or @ for
+big, little and native-endian), followed by characters giving the format:
+
+b 1-byte signed int
+B 1-byte unsigned int
+h 2-byte signed int
+H 2-byte unsigned int
+l 4-byte signed int
+L 4-byte unsigned int
+q 8-byte signed int
+Q 8-byte unsigned int
+
+For example:
+
+>>> s = bitstring.pack('<4h', 0, 1, 2, 3)
+
+creates a BitString with four little-endian 2-byte integers. While
+
+>>> x, y, z = s.read('>hhl')
+
+reads them back as two big-endian two-byte integers and one four-byte big
+endian integer.
+
+Of course you can combine this new format with the old ones however you like:
+
+>>> s.unpack('<h, intle:24, uint:5, bin')
+[0, 131073, 0, '0b0000000001100000000']
+
+-------------------------------------------------------
+August 26th 2009: version 0.5.1 for Python 2.x released
+-------------------------------------------------------
+
+This update introduces pack and unpack functions for creating and dissembling
+BitStrings.
+
+* New pack() and unpack() functions.
+
+The module level pack function provides a flexible new method for creating
+BitStrings. Tokens for BitString 'literals' can be used in the same way as in
+the constructor.
+
+>>> from bitstring import BitString, pack
+>>> a = pack('0b11, 0xff, 0o77, int:5=-1, se=33')
+
+You can also leave placeholders in the format, which will be filled in by
+the values provided.
+
+>>> b = pack('uint:10, hex:4', 33, 'f')
+
+Finally you can use a dictionary or keywords.
+
+>>> c = pack('bin=a, hex=b, bin=a', a='010', b='ef')
+
+The unpack function is similar to the read function except that it always
+unpacks from the start of the BitString.
+
+>>> x, y = b.unpack('uint:10, hex')
+
+If a token is given without a length (as above) then it will expand to fill the
+remaining bits in the BitString. This also now works with read() and peek().
+
+* New tostring() and tofile() functions.
+
+The tostring() function just returns the data as a string, with up to seven
+zero bits appended to byte align. The tofile() function does the same except
+writes to a file object.
+
+>>> f = open('myfile', 'wb')
+>>> BitString('0x1234ff').tofile(f)
+
+* Other changes.
+
+The use of '=' is now mandatory in 'auto' initialisers. Tokens like 'uint12 100' will
+no longer work. Also the use of a ':' before the length is encouraged, but not yet
+mandated. So the previous example should be written as 'uint:12=100'.
+
+The 'auto' initialiser will now take a file object.
+
+>>> f = open('myfile', 'rb')
+>>> s = BitString(f)
+
+-----------------------------------------------------
+July 19th 2009: version 0.5.0 for Python 2.x released
+-----------------------------------------------------
+
+This update breaks backward compatibility in a couple of areas. The only one
+you probably need to be concerned about is the change to the default for
+bytealigned in find, replace, split, etc.
+
+See the user manual for more details on each of these items.
+
+* Expanded abilities of 'auto' initialiser.
+
+More types can be initialised through the 'auto' initialiser. For example
+instead of
+
+>>> a = BitString(uint=44, length=16)
+
+you can write
+
+>>> a = BitString('uint16=44')
+
+Also, different comma-separated tokens will be joined together, e.g.
+
+>>> b = BitString('0xff') + 'int8=-5'
+
+can be written
+
+>>> b = BitString('0xff, int8=-5')
+
+* New formatted read() and peek() functions.
+
+These takes a format string similar to that used in the auto initialiser.
+If only one token is provided then a single value is returned, otherwise a
+list of values is returned.
+
+>>> start_code, width, height = s.read('hex32, uint12, uint12')
+
+is equivalent to
+
+>>> start_code = s.readbits(32).hex
+>>> width = s.readbits(12).uint
+>>> height = s.readbits(12).uint
+
+The tokens are:
+
+ int n : n bits as an unsigned integer.
+ uint n : n bits as a signed integer.
+ hex n : n bits as a hexadecimal string.
+ oct n : n bits as an octal string.
+ bin n : n bits as a binary string.
+ ue : next bits as an unsigned exp-Golomb.
+ se : next bits as a signed exp-Golomb.
+ bits n : n bits as a new BitString.
+ bytes n : n bytes as a new BitString.
+
+See the user manual for more details.
+
+* hex() and oct() functions removed.
+
+The special functions for hex() and oct() have been removed. Please use the
+hex and oct properties instead.
+
+>>> hex(s)
+
+becomes
+
+>>> s.hex
+
+* join made a member function.
+
+The join function must now be called on a BitString object, which will be
+used to join the list together. You may need to recode slightly:
+
+>>> s = bitstring.join('0x34', '0b1001', '0b1')
+
+becomes
+
+>>> s = BitString().join('0x34', '0b1001', '0b1')
+
+* More than one value allowed in readbits, readbytes, peekbits and peekbytes
+
+If you specify more than one bit or byte length then a list of BitStrings will
+be returned.
+
+>>> a, b, c = s.readbits(10, 5, 5)
+
+is equivalent to
+
+>>> a = readbits(10)
+>>> b = readbits(5)
+>>> c = readbits(5)
+
+* bytealigned defaults to False, and is at the end of the parameter list
+
+Functions that have a bytealigned paramater have changed so that it now
+defaults to False rather than True. Also its position in the parameter list
+has changed to be at the end. You may need to recode slightly (sorry!)
+
+* readue and readse functions have been removed
+
+Instead you should use the new read function with a 'ue' or 'se' token:
+
+>>> i = s.readue()
+
+becomes
+
+>>> i = s.read('ue')
+
+This is more flexible as you can read multiple items in one go, plus you can
+now also use the peek function with ue and se.
+
+* Minor bugs fixed.
+
+See the issue tracker for more details.
+
+-----------------------------------------------------
+June 15th 2009: version 0.4.3 for Python 2.x released
+-----------------------------------------------------
+
+This is a minor update. This release is the first to bundle the bitstring
+manual. This is a PDF and you can find it in the docs directory.
+
+Changes in version 0.4.3
+
+* New 'cut' function
+
+This function returns a generator for constant sized chunks of a BitString.
+
+>>> for byte in s.cut(8):
+... do_something_with(byte)
+
+You can also specify a startbit and endbit, as well as a count, which limits
+the number of items generated:
+
+>>> first100TSPackets = list(s.cut(188*8, count=100))
+
+* 'slice' function now equivalent to __getitem__.
+
+This means that a step can also be given to the slice function so that the
+following are now the same thing, and it's just a personal preference which
+to use:
+
+>>> s1 = s[a:b:c]
+>>> s2 = s.slice(a, b, c)
+
+* findall gets a 'count' parameter.
+
+So now
+
+>>> list(a.findall(s, count=n))
+
+is equivalent to
+
+>>> list(a.findall(s))[:n]
+
+except that it won't need to generate the whole list and so is much more
+efficient.
+
+* Changes to 'split'.
+
+The split function now has a 'count' parameter rather than 'maxsplit'. This
+makes the interface closer to that for cut, replace and findall. The final item
+generated is now no longer the whole of the rest of the BitString.
+
+* A couple of minor bugs were fixed. See the issue tracker for details.
+
+----------------------------------------------------
+May 25th 2009: version 0.4.2 for Python 2.x released
+----------------------------------------------------
+
+This is a minor update, and almost doesn't break compatibility with version
+0.4.0, but with the slight exception of findall() returning a generator,
+detailed below.
+
+Changes in version 0.4.2
+
+* Stepping in slices
+
+The use of the step parameter (also known as the stride) in slices has been
+added. Its use is a little non-standard as it effectively gives a multiplicative
+factor to apply to the start and stop parameters, rather than skipping over
+bits.
+
+For example this makes it much more convenient if you want to give slices in
+terms of bytes instead of bits. Instead of writing s[a*8:b*8] you can use
+s[a:b:8].
+
+When using a step the BitString is effectively truncated to a multiple of the
+step, so s[::8] is equal to s if s is an integer number of bytes, otherwise it
+is truncated by up to 7 bits. So the final seven complete 16-bit words could be
+written as s[-7::16]
+
+Negative slices are also allowed, and should do what you'd expect. So for
+example s[::-1] returns a bit-reversed copy of s (which is similar to
+s.reversebits(), which does the same operation on s in-place). As another
+example, to get the first 10 bytes in reverse byte order you could use
+s_bytereversed = s[0:10:-8].
+
+* Removed restrictions on offset
+
+You can now specify an offset of greater than 7 bits when creating a BitString,
+and the use of offset is also now permitted when using the filename initialiser.
+This is useful when you want to create a BitString from the middle of a file
+without having to read the file into memory.
+
+>>> f = BitString(filename='reallybigfile', offset=8000000, length=32)
+
+* Integers can be assigned to slices
+
+You can now assign an integer to a slice of a BitString. If the integer doesn't
+fit in the size of slice given then a ValueError exception is raised. So this
+is now allowed and works as expected:
+
+>>> s[8:16] = 106
+
+and is equivalent to
+
+>>> s[8:16] = BitString(uint=106, length=8)
+
+* Less exceptions raised
+
+Some changes have been made to slicing so that less exceptions are raised,
+bringing the interface closer to that for lists. So for example trying to delete
+past the end of the BitString will now just delete to the end, rather than
+raising a ValueError.
+
+* Initialisation from lists and tuples
+
+A new option for the auto initialiser is to pass it a list or tuple. The items
+in the list or tuple are evaluated as booleans and the bits in the BitString are
+set to 1 for True items and 0 for False items. This can be used anywhere the
+auto initialiser can currently be used. For example:
+
+>>> a = BitString([True, 7, False, 0, ()]) # 0b11000
+>>> b = a + ['Yes', ''] # Adds '0b10'
+>>> (True, True, False) in a
+True
+
+* Miscellany
+
+reversebits() now has optional startbit and endbit parameters.
+
+As an optimisation findall() will return a generator, rather than a list. If you
+still want the whole list then of course you can just call list() on the
+generator.
+
+Improved efficiency of rfind().
+
+A couple of minor bugs were fixed. See the issue tracker for details.
+
+-----------------------------------------------------
+April 23rd 2009: Python 3 only version 0.4.1 released
+-----------------------------------------------------
+
+This version is just a port of version 0.4.0 to Python 3. All the unit tests
+pass, but beyond that only limited ad hoc testing has been done and so it
+should be considered an experimental release. That said, the unit test
+coverage is very good - I'm just not sure if anyone even wants a Python 3
+version!
+
+---------------------------------------
+April 11th 2009: version 0.4.0 released
+---------------------------------------
+Changes in version 0.4.0
+
+* New functions
+
+Added rfind(), findall(), replace(). These do pretty much what you'd expect -
+see the docstrings or the wiki for more information.
+
+* More special functions
+
+Some missing functions were added: __repr__, __contains__, __rand__,
+__ror__, _rxor__ and __delitem__.
+
+* Miscellany
+
+A couple of small bugs were fixed (see the issue tracker).
+
+----
+
+There are some small backward incompatibilities relative to version 0.3.2:
+
+* Combined find() and findbytealigned()
+
+findbytealigned() has been removed, and becomes part of find(). The default
+start position has changed on both find() and split() to be the start of the
+BitString. You may need to recode:
+
+>>> s1.find(bs)
+>>> s2.findbytealigned(bs)
+>>> s2.split(bs)
+
+becomes
+
+>>> s1.find(bs, bytealigned=False, startbit=s1.bitpos)
+>>> s2.find(bs, startbit=s1.bitpos) # bytealigned defaults to True
+>>> s2.split(bs, startbit=s2.bitpos)
+
+* Reading off end of BitString no longer raises exception.
+
+Previously a read or peek function that encountered the end of the BitString
+would raise a ValueError. It will now instead return the remainder of the
+BitString, which could be an empty BitString. This is closer to the file
+object interface.
+
+* Removed visibility of offset.
+
+The offset property was previously read-only, and has now been removed from
+public view altogether. As it is used internally for efficiency reasons you
+shouldn't really have needed to use it. If you do then use the _offset parameter
+instead (with caution).
+
+---------------------------------------
+March 11th 2009: version 0.3.2 released
+---------------------------------------
+Changes in version 0.3.2
+
+* Better performance
+
+A number of functions (especially find() and findbytealigned()) have been sped
+up considerably.
+
+* Bit-wise operations
+
+Added support for bit-wise AND (&), OR (|) and XOR (^). For example:
+
+>>> a = BitString('0b00111')
+>>> print a & '0b10101'
+0b00101
+
+* Miscellany
+
+Added seekbit() and seekbyte() functions. These complement the 'advance' and
+'retreat' functions, although you can still just use bitpos and bytepos
+properties directly.
+
+>>> a.seekbit(100) # Equivalent to a.bitpos = 100
+
+Allowed comparisons between BitString objects and strings. For example this
+will now work:
+
+>>> a = BitString('0b00001111')
+>>> a == '0x0f'
+True
+
+------------------------------------------
+February 26th 2009: version 0.3.1 released
+------------------------------------------
+Changes in version 0.3.1
+
+This version only adds features and fixes bugs relative to 0.3.0, and doesn't
+break backwards compatibility.
+
+* Octal interpretation and initialisation
+
+The oct property now joins bin and hex. Just prefix octal numbers with '0o'.
+
+>>> a = BitString('0o755')
+>>> print a.bin
+0b111101101
+
+* Simpler copying
+
+Rather than using b = copy.copy(a) to create a copy of a BitString, now you
+can just use b = BitString(a).
+
+* More special methods
+
+Lots of new special methods added, for example bit-shifting via << and >>,
+equality testing via == and !=, bit inversion (~) and concatenation using *.
+
+Also __setitem__ is now supported so BitString objects can be modified using
+standard index notation.
+
+* Proper installer
+
+Finally got round to writing the distutils script. To install just
+python setup.py install.
+
+------------------------------------------
+February 15th 2009: version 0.3.0 released
+------------------------------------------
+Changes in version 0.3.0
+
+* Simpler initialisation from binary and hexadecimal
+
+The first argument in the BitString constructor is now called auto and will
+attempt to interpret the type of a string. Prefix binary numbers with '0b'
+and hexadecimals with '0x'.
+
+>>> a = BitString('0b0') # single zero bit
+>>> b = BitString('0xffff') # two bytes
+
+Previously the first argument was data, so if you relied on this then you
+will need to recode:
+
+>>> a = BitString('\x00\x00\x01\xb3') # Don't do this any more!
+
+becomes
+
+>>> a = BitString(data='\x00\x00\x01\xb3')
+
+or just
+
+>>> a = BitString('0x000001b3')
+
+This new notation can also be used in functions that take a BitString as an
+argument. For example:
+
+>>> a = BitString('0x0011') + '0xff'
+>>> a.insert('0b001', 6)
+>>> a.find('0b1111')
+
+* BitString made more mutable
+
+The functions append, deletebits, insert, overwrite, truncatestart and
+truncateend now modify the BitString that they act upon. This allows for
+cleaner and more efficient code, but you may need to rewrite slightly if you
+depended upon the old behaviour:
+
+>>> a = BitString(hex='0xffff')
+>>> a = a.append(BitString(hex='0x00'))
+>>> b = a.deletebits(10, 10)
+
+becomes:
+
+>>> a = BitString('0xffff')
+>>> a.append('0x00')
+>>> b = copy.copy(a)
+>>> b.deletebits(10, 10)
+
+Thanks to Frank Aune for suggestions in this and other areas.
+
+* Changes to printing
+
+The binary interpretation of a BitString is now prepended with '0b'. This is
+in keeping with the Python 2.6 (and 3.0) bin function. The prefix is optional
+when initialising using 'bin='.
+
+Also, if you just print a BitString with no interpretation it will pick
+something appropriate - hex if it is an integer number of bytes, otherwise
+binary. If the BitString representation is very long it will be truncated
+by '...' so it is only an approximate interpretation.
+
+>>> a = BitString('0b0011111')
+>>> print a
+0b0011111
+>>> a += '0b0'
+>>> print a
+0x3e
+
+* More convenience functions
+
+Some missing functions such as advancebit and deletebytes have been added. Also
+a number of peek functions make an appearance as have prepend and reversebits.
+See the Tutorial for more details.
+
+-----------------------------------------
+January 13th 2009: version 0.2.0 released
+-----------------------------------------
+Some fairly minor updates, not really deserving of a whole version point update.
+------------------------------------------
+December 29th 2008: version 0.1.0 released
+------------------------------------------
+First release!
diff --git a/python/bitstring/setup.py b/python/bitstring/setup.py new file mode 100644 index 000000000..9f088dda9 --- /dev/null +++ b/python/bitstring/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +from distutils.core import setup +# from distutils.extension import Extension +# from Cython.Distutils import build_ext +import sys + +kwds = {'long_description': open('README.txt').read()} + +if sys.version_info[:2] < (2, 6): + raise Exception('This version of bitstring needs Python 2.6 or later. ' + 'For Python 2.4 / 2.5 please use bitstring version 1.0 instead.') + +# macros = [('PYREX_WITHOUT_ASSERTIONS', None)] +# ext_modules = [Extension('bitstring', ["bitstring.pyx"], define_macros=macros)] + +setup(name='bitstring', + version='3.1.3', + description='Simple construction, analysis and modification of binary data.', + author='Scott Griffiths', + author_email='scott@griffiths.name', + url='http://python-bitstring.googlecode.com', + download_url='http://python-bitstring.googlecode.com', + license='The MIT License: http://www.opensource.org/licenses/mit-license.php', + # cmdclass = {'build_ext': build_ext}, + # ext_modules = ext_modules, + py_modules=['bitstring'], + platforms='all', + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.0', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + **kwds + ) + diff --git a/python/bitstring/test/smalltestfile b/python/bitstring/test/smalltestfile new file mode 100644 index 000000000..be687ec35 --- /dev/null +++ b/python/bitstring/test/smalltestfile @@ -0,0 +1 @@ +#Eg‰«Íï
\ No newline at end of file diff --git a/python/bitstring/test/test.m1v b/python/bitstring/test/test.m1v Binary files differnew file mode 100644 index 000000000..2da3ece11 --- /dev/null +++ b/python/bitstring/test/test.m1v diff --git a/python/bitstring/test/test_bitarray.py b/python/bitstring/test/test_bitarray.py new file mode 100644 index 000000000..b80f90617 --- /dev/null +++ b/python/bitstring/test/test_bitarray.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python +""" +Unit tests for the bitarray module. +""" + +import unittest +import sys + +sys.path.insert(0, '..') +import bitstring +from bitstring import BitArray + +class All(unittest.TestCase): + def testCreationFromUint(self): + s = BitArray(uint=15, length=6) + self.assertEqual(s.bin, '001111') + s = BitArray(uint=0, length=1) + self.assertEqual(s.bin, '0') + s.uint = 1 + self.assertEqual(s.uint, 1) + s = BitArray(length=8) + s.uint = 0 + self.assertEqual(s.uint, 0) + s.uint = 255 + self.assertEqual(s.uint, 255) + self.assertEqual(s.len, 8) + self.assertRaises(bitstring.CreationError, s._setuint, 256) + + def testCreationFromOct(self): + s = BitArray(oct='7') + self.assertEqual(s.oct, '7') + self.assertEqual(s.bin, '111') + s.append('0o1') + self.assertEqual(s.bin, '111001') + s.oct = '12345670' + self.assertEqual(s.length, 24) + self.assertEqual(s.bin, '001010011100101110111000') + s = BitArray('0o123') + self.assertEqual(s.oct, '123') + + +class NoPosAttribute(unittest.TestCase): + def testReplace(self): + s = BitArray('0b01') + s.replace('0b1', '0b11') + self.assertEqual(s, '0b011') + + def testDelete(self): + s = BitArray('0b000000001') + del s[-1:] + self.assertEqual(s, '0b00000000') + + def testInsert(self): + s = BitArray('0b00') + s.insert('0xf', 1) + self.assertEqual(s, '0b011110') + + def testInsertParameters(self): + s = BitArray('0b111') + self.assertRaises(TypeError, s.insert, '0x4') + + def testOverwrite(self): + s = BitArray('0b01110') + s.overwrite('0b000', 1) + self.assertEqual(s, '0b00000') + + def testOverwriteParameters(self): + s = BitArray('0b0000') + self.assertRaises(TypeError, s.overwrite, '0b111') + + def testPrepend(self): + s = BitArray('0b0') + s.prepend([1]) + self.assertEqual(s, [1, 0]) + + def testRol(self): + s = BitArray('0b0001') + s.rol(1) + self.assertEqual(s, '0b0010') + + def testRor(self): + s = BitArray('0b1000') + s.ror(1) + self.assertEqual(s, '0b0100') + + def testSetItem(self): + s = BitArray('0b000100') + s[4:5] = '0xf' + self.assertEqual(s, '0b000111110') + s[0:1] = [1] + self.assertEqual(s, '0b100111110') + + +class Bugs(unittest.TestCase): + def testAddingNonsense(self): + a = BitArray([0]) + a += '0' # a uint of length 0 - so nothing gets added. + self.assertEqual(a, [0]) + self.assertRaises(ValueError, a.__iadd__, '3') + self.assertRaises(ValueError, a.__iadd__, 'se') + self.assertRaises(ValueError, a.__iadd__, 'float:32') + + def testPrependAfterCreationFromDataWithOffset(self): + s1 = BitArray(bytes=b'\x00\x00\x07\xff\xf0\x00', offset=21, length=15) + self.assertFalse(s1.any(0)) + s1.prepend('0b0') + self.assertEqual(s1.bin, '0111111111111111') + s1.prepend('0b0') + self.assertEqual(s1.bin, '00111111111111111') + + +class ByteAligned(unittest.TestCase): + def testDefault(self, defaultbytealigned=bitstring.bytealigned): + self.assertFalse(defaultbytealigned) + + def testChangingIt(self): + bitstring.bytealigned = True + self.assertTrue(bitstring.bytealigned) + bitstring.bytealigned = False + + def testNotByteAligned(self): + bitstring.bytealigned = False + a = BitArray('0x00 ff 0f f') + l = list(a.findall('0xff')) + self.assertEqual(l, [8, 20]) + p = a.find('0x0f')[0] + self.assertEqual(p, 4) + p = a.rfind('0xff')[0] + self.assertEqual(p, 20) + s = list(a.split('0xff')) + self.assertEqual(s, ['0x00', '0xff0', '0xff']) + a.replace('0xff', '') + self.assertEqual(a, '0x000') + + def testByteAligned(self): + bitstring.bytealigned = True + a = BitArray('0x00 ff 0f f') + l = list(a.findall('0xff')) + self.assertEqual(l, [8]) + p = a.find('0x0f')[0] + self.assertEqual(p, 16) + p = a.rfind('0xff')[0] + self.assertEqual(p, 8) + s = list(a.split('0xff')) + self.assertEqual(s, ['0x00', '0xff0ff']) + a.replace('0xff', '') + self.assertEqual(a, '0x000ff') + + +class SliceAssignment(unittest.TestCase): + + def testSliceAssignmentSingleBit(self): + a = BitArray('0b000') + a[2] = '0b1' + self.assertEqual(a.bin, '001') + a[0] = BitArray(bin='1') + self.assertEqual(a.bin, '101') + a[-1] = '0b0' + self.assertEqual(a.bin, '100') + a[-3] = '0b0' + self.assertEqual(a.bin, '000') + + def testSliceAssignmentSingleBitErrors(self): + a = BitArray('0b000') + self.assertRaises(IndexError, a.__setitem__, -4, '0b1') + self.assertRaises(IndexError, a.__setitem__, 3, '0b1') + self.assertRaises(TypeError, a.__setitem__, 1, 1.3) + + def testSliceAssignmentMulipleBits(self): + a = BitArray('0b0') + a[0] = '0b110' + self.assertEqual(a.bin, '110') + a[0] = '0b000' + self.assertEqual(a.bin, '00010') + a[0:3] = '0b111' + self.assertEqual(a.bin, '11110') + a[-2:] = '0b011' + self.assertEqual(a.bin, '111011') + a[:] = '0x12345' + self.assertEqual(a.hex, '12345') + a[:] = '' + self.assertFalse(a) + + def testSliceAssignmentMultipleBitsErrors(self): + a = BitArray() + self.assertRaises(IndexError, a.__setitem__, 0, '0b00') + a += '0b1' + a[0:2] = '0b11' + self.assertEqual(a, '0b11') + + def testDelSliceStep(self): + a = BitArray(bin='100111101001001110110100101') + del a[::2] + self.assertEqual(a.bin, '0110010101100') + del a[3:9:3] + self.assertEqual(a.bin, '01101101100') + del a[2:7:1] + self.assertEqual(a.bin, '011100') + del a[::99] + self.assertEqual(a.bin, '11100') + del a[::1] + self.assertEqual(a.bin, '') + + def testDelSliceNegativeStep(self): + a = BitArray('0b0001011101101100100110000001') + del a[5:23:-3] + self.assertEqual(a.bin, '0001011101101100100110000001') + del a[25:3:-3] + self.assertEqual(a.bin, '00011101010000100001') + del a[:6:-7] + self.assertEqual(a.bin, '000111010100010000') + del a[15::-2] + self.assertEqual(a.bin, '0010000000') + del a[::-1] + self.assertEqual(a.bin, '') + + def testDelSliceErrors(self): + a = BitArray(10) + del a[5:3] + self.assertEqual(a, 10) + del a[3:5:-1] + self.assertEqual(a, 10) + + def testDelSingleElement(self): + a = BitArray('0b0010011') + del a[-1] + self.assertEqual(a.bin, '001001') + del a[2] + self.assertEqual(a.bin, '00001') + try: + del a[5] + self.assertTrue(False) + except IndexError: + pass + + def testSetSliceStep(self): + a = BitArray(bin='0000000000') + a[::2] = '0b11111' + self.assertEqual(a.bin, '1010101010') + a[4:9:3] = [0, 0] + self.assertEqual(a.bin, '1010001010') + a[7:3:-1] = [1, 1, 1, 0] + self.assertEqual(a.bin, '1010011110') + a[7:1:-2] = [0, 0, 1] + self.assertEqual(a.bin, '1011001010') + a[::-5] = [1, 1] + self.assertEqual(a.bin, '1011101011') + a[::-1] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1] + self.assertEqual(a.bin, '1000000000') + + def testSetSliceErrors(self): + a = BitArray(8) + try: + a[::3] = [1] + self.assertTrue(False) + except ValueError: + pass + class A(object): pass + try: + a[1:2] = A() + self.assertTrue(False) + except TypeError: + pass + try: + a[1:4:-1] = [1, 2] + self.assertTrue(False) + except ValueError: + pass + + +class Subclassing(unittest.TestCase): + + def testIsInstance(self): + class SubBits(BitArray): pass + a = SubBits() + self.assertTrue(isinstance(a, SubBits)) + + def testClassType(self): + class SubBits(BitArray): pass + self.assertEqual(SubBits().__class__, SubBits) + + +class Clear(unittest.TestCase): + + def testClear(self): + s = BitArray('0xfff') + s.clear() + self.assertEqual(s.len, 0) + + +class Copy(unittest.TestCase): + + def testCopyMethod(self): + s = BitArray(9) + t = s.copy() + self.assertEqual(s, t) + t[0] = True + self.assertEqual(t.bin, '100000000') + self.assertEqual(s.bin, '000000000') + + +class ModifiedByAddingBug(unittest.TestCase): + + def testAdding(self): + a = BitArray('0b0') + b = BitArray('0b11') + c = a + b + self.assertEqual(c, '0b011') + self.assertEqual(a, '0b0') + self.assertEqual(b, '0b11')
\ No newline at end of file diff --git a/python/bitstring/test/test_bits.py b/python/bitstring/test/test_bits.py new file mode 100644 index 000000000..402c03899 --- /dev/null +++ b/python/bitstring/test/test_bits.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python + +import unittest +import sys + +sys.path.insert(0, '..') +import bitstring +from bitstring import MmapByteArray +from bitstring import Bits, BitArray, ConstByteStore, ByteStore + +class Creation(unittest.TestCase): + def testCreationFromBytes(self): + s = Bits(bytes=b'\xa0\xff') + self.assertEqual((s.len, s.hex), (16, 'a0ff')) + s = Bits(bytes=b'abc', length=0) + self.assertEqual(s, '') + + def testCreationFromBytesErrors(self): + self.assertRaises(bitstring.CreationError, Bits, bytes=b'abc', length=25) + + def testCreationFromDataWithOffset(self): + s1 = Bits(bytes=b'\x0b\x1c\x2f', offset=0, length=20) + s2 = Bits(bytes=b'\xa0\xb1\xC2', offset=4) + self.assertEqual((s2.len, s2.hex), (20, '0b1c2')) + self.assertEqual((s1.len, s1.hex), (20, '0b1c2')) + self.assertTrue(s1 == s2) + + def testCreationFromHex(self): + s = Bits(hex='0xA0ff') + self.assertEqual((s.len, s.hex), (16, 'a0ff')) + s = Bits(hex='0x0x0X') + self.assertEqual((s.length, s.hex), (0, '')) + + def testCreationFromHexWithWhitespace(self): + s = Bits(hex=' \n0 X a 4e \r3 \n') + self.assertEqual(s.hex, 'a4e3') + + def testCreationFromHexErrors(self): + self.assertRaises(bitstring.CreationError, Bits, hex='0xx0') + self.assertRaises(bitstring.CreationError, Bits, hex='0xX0') + self.assertRaises(bitstring.CreationError, Bits, hex='0Xx0') + self.assertRaises(bitstring.CreationError, Bits, hex='-2e') + # These really should fail, but it's awkward and not a big deal... +# self.assertRaises(bitstring.CreationError, Bits, '0x2', length=2) +# self.assertRaises(bitstring.CreationError, Bits, '0x3', offset=1) + + def testCreationFromBin(self): + s = Bits(bin='1010000011111111') + self.assertEqual((s.length, s.hex), (16, 'a0ff')) + s = Bits(bin='00')[:1] + self.assertEqual(s.bin, '0') + s = Bits(bin=' 0000 \n 0001\r ') + self.assertEqual(s.bin, '00000001') + + def testCreationFromBinWithWhitespace(self): + s = Bits(bin=' \r\r\n0 B 00 1 1 \t0 ') + self.assertEqual(s.bin, '00110') + + def testCreationFromOctErrors(self): + s = Bits('0b00011') + self.assertRaises(bitstring.InterpretError, s._getoct) + self.assertRaises(bitstring.CreationError, s._setoct, '8') + + def testCreationFromUintWithOffset(self): + self.assertRaises(bitstring.Error, Bits, uint=12, length=8, offset=1) + + def testCreationFromUintErrors(self): + self.assertRaises(bitstring.CreationError, Bits, uint=-1, length=10) + self.assertRaises(bitstring.CreationError, Bits, uint=12) + self.assertRaises(bitstring.CreationError, Bits, uint=4, length=2) + self.assertRaises(bitstring.CreationError, Bits, uint=0, length=0) + self.assertRaises(bitstring.CreationError, Bits, uint=12, length=-12) + + def testCreationFromInt(self): + s = Bits(int=0, length=4) + self.assertEqual(s.bin, '0000') + s = Bits(int=1, length=2) + self.assertEqual(s.bin, '01') + s = Bits(int=-1, length=11) + self.assertEqual(s.bin, '11111111111') + s = Bits(int=12, length=7) + self.assertEqual(s.int, 12) + s = Bits(int=-243, length=108) + self.assertEqual((s.int, s.length), (-243, 108)) + for length in range(6, 10): + for value in range(-17, 17): + s = Bits(int=value, length=length) + self.assertEqual((s.int, s.length), (value, length)) + s = Bits(int=10, length=8) + + def testCreationFromIntErrors(self): + self.assertRaises(bitstring.CreationError, Bits, int=-1, length=0) + self.assertRaises(bitstring.CreationError, Bits, int=12) + self.assertRaises(bitstring.CreationError, Bits, int=4, length=3) + self.assertRaises(bitstring.CreationError, Bits, int=-5, length=3) + + def testCreationFromSe(self): + for i in range(-100, 10): + s = Bits(se=i) + self.assertEqual(s.se, i) + + def testCreationFromSeWithOffset(self): + self.assertRaises(bitstring.CreationError, Bits, se=-13, offset=1) + + def testCreationFromSeErrors(self): + self.assertRaises(bitstring.CreationError, Bits, se=-5, length=33) + s = Bits(bin='001000') + self.assertRaises(bitstring.InterpretError, s._getse) + + def testCreationFromUe(self): + [self.assertEqual(Bits(ue=i).ue, i) for i in range(0, 20)] + + def testCreationFromUeWithOffset(self): + self.assertRaises(bitstring.CreationError, Bits, ue=104, offset=2) + + def testCreationFromUeErrors(self): + self.assertRaises(bitstring.CreationError, Bits, ue=-1) + self.assertRaises(bitstring.CreationError, Bits, ue=1, length=12) + s = Bits(bin='10') + self.assertRaises(bitstring.InterpretError, s._getue) + + def testCreationFromBool(self): + a = Bits('bool=1') + self.assertEqual(a, 'bool=1') + b = Bits('bool=0') + self.assertEqual(b, [0]) + c = bitstring.pack('2*bool', 0, 1) + self.assertEqual(c, '0b01') + + def testCreationKeywordError(self): + self.assertRaises(bitstring.CreationError, Bits, squirrel=5) + + def testDataStoreType(self): + a = Bits('0xf') + self.assertEqual(type(a._datastore), bitstring.ConstByteStore) + + +class Initialisation(unittest.TestCase): + def testEmptyInit(self): + a = Bits() + self.assertEqual(a, '') + + def testNoPos(self): + a = Bits('0xabcdef') + try: + a.pos + except AttributeError: + pass + else: + assert False + + def testFind(self): + a = Bits('0xabcd') + r = a.find('0xbc') + self.assertEqual(r[0], 4) + r = a.find('0x23462346246', bytealigned=True) + self.assertFalse(r) + + def testRfind(self): + a = Bits('0b11101010010010') + b = a.rfind('0b010') + self.assertEqual(b[0], 11) + + def testFindAll(self): + a = Bits('0b0010011') + b = list(a.findall([1])) + self.assertEqual(b, [2, 5, 6]) + + +class Cut(unittest.TestCase): + def testCut(self): + s = Bits(30) + for t in s.cut(3): + self.assertEqual(t, [0] * 3) + + +class InterleavedExpGolomb(unittest.TestCase): + def testCreation(self): + s1 = Bits(uie=0) + s2 = Bits(uie=1) + self.assertEqual(s1, [1]) + self.assertEqual(s2, [0, 0, 1]) + s1 = Bits(sie=0) + s2 = Bits(sie=-1) + s3 = Bits(sie=1) + self.assertEqual(s1, [1]) + self.assertEqual(s2, [0, 0, 1, 1]) + self.assertEqual(s3, [0, 0, 1, 0]) + + def testCreationFromProperty(self): + s = BitArray() + s.uie = 45 + self.assertEqual(s.uie, 45) + s.sie = -45 + self.assertEqual(s.sie, -45) + + def testInterpretation(self): + for x in range(101): + self.assertEqual(Bits(uie=x).uie, x) + for x in range(-100, 100): + self.assertEqual(Bits(sie=x).sie, x) + + def testErrors(self): + for f in ['sie=100, 0b1001', '0b00', 'uie=100, 0b1001']: + s = Bits(f) + self.assertRaises(bitstring.InterpretError, s._getsie) + self.assertRaises(bitstring.InterpretError, s._getuie) + self.assertRaises(ValueError, Bits, 'uie=-10') + + +class FileBased(unittest.TestCase): + def setUp(self): + self.a = Bits(filename='smalltestfile') + self.b = Bits(filename='smalltestfile', offset=16) + self.c = Bits(filename='smalltestfile', offset=20, length=16) + self.d = Bits(filename='smalltestfile', offset=20, length=4) + + def testCreationWithOffset(self): + self.assertEqual(self.a, '0x0123456789abcdef') + self.assertEqual(self.b, '0x456789abcdef') + self.assertEqual(self.c, '0x5678') + + def testBitOperators(self): + x = self.b[4:20] + self.assertEqual(x, '0x5678') + self.assertEqual((x & self.c).hex, self.c.hex) + self.assertEqual(self.c ^ self.b[4:20], 16) + self.assertEqual(self.a[23:36] | self.c[3:], self.c[3:]) + + def testAddition(self): + h = self.d + '0x1' + x = self.a[20:24] + self.c[-4:] + self.c[8:12] + self.assertEqual(x, '0x587') + x = self.b + x + self.assertEqual(x.hex, '456789abcdef587') + x = BitArray(x) + del x[12:24] + self.assertEqual(x, '0x456abcdef587') + +class Mmap(unittest.TestCase): + def setUp(self): + self.f = open('smalltestfile', 'rb') + + def tearDown(self): + self.f.close() + + def testByteArrayEquivalence(self): + a = MmapByteArray(self.f) + self.assertEqual(a.bytelength, 8) + self.assertEqual(len(a), 8) + self.assertEqual(a[0], 0x01) + self.assertEqual(a[1], 0x23) + self.assertEqual(a[7], 0xef) + self.assertEqual(a[0:1], bytearray([1])) + self.assertEqual(a[:], bytearray([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef])) + self.assertEqual(a[2:4], bytearray([0x45, 0x67])) + + def testWithLength(self): + a = MmapByteArray(self.f, 3) + self.assertEqual(a[0], 0x01) + self.assertEqual(len(a), 3) + + def testWithOffset(self): + a = MmapByteArray(self.f, None, 5) + self.assertEqual(len(a), 3) + self.assertEqual(a[0], 0xab) + + def testWithLengthAndOffset(self): + a = MmapByteArray(self.f, 3, 3) + self.assertEqual(len(a), 3) + self.assertEqual(a[0], 0x67) + self.assertEqual(a[:], bytearray([0x67, 0x89, 0xab])) + + +class Comparisons(unittest.TestCase): + def testUnorderable(self): + a = Bits(5) + b = Bits(5) + self.assertRaises(TypeError, a.__lt__, b) + self.assertRaises(TypeError, a.__gt__, b) + self.assertRaises(TypeError, a.__le__, b) + self.assertRaises(TypeError, a.__ge__, b) + + +class Subclassing(unittest.TestCase): + + def testIsInstance(self): + class SubBits(bitstring.Bits): pass + a = SubBits() + self.assertTrue(isinstance(a, SubBits)) + + def testClassType(self): + class SubBits(bitstring.Bits): pass + self.assertEqual(SubBits().__class__, SubBits) + + +class LongBoolConversion(unittest.TestCase): + + def testLongBool(self): + a = Bits(1000) + b = bool(a) + self.assertTrue(b is False) + + +# Some basic tests for the private ByteStore classes + +class ConstByteStoreCreation(unittest.TestCase): + + def testProperties(self): + a = ConstByteStore(bytearray(b'abc')) + self.assertEqual(a.bytelength, 3) + self.assertEqual(a.offset, 0) + self.assertEqual(a.bitlength, 24) + self.assertEqual(a._rawarray, b'abc') + + def testGetBit(self): + a = ConstByteStore(bytearray([0x0f])) + self.assertEqual(a.getbit(0), False) + self.assertEqual(a.getbit(3), False) + self.assertEqual(a.getbit(4), True) + self.assertEqual(a.getbit(7), True) + + b = ConstByteStore(bytearray([0x0f]), 7, 1) + self.assertEqual(b.getbit(2), False) + self.assertEqual(b.getbit(3), True) + + def testGetByte(self): + a = ConstByteStore(bytearray(b'abcde'), 1, 13) + self.assertEqual(a.getbyte(0), 97) + self.assertEqual(a.getbyte(1), 98) + self.assertEqual(a.getbyte(4), 101) + + +class PadToken(unittest.TestCase): + + def testCreation(self): + a = Bits('pad:10') + self.assertEqual(a, Bits(10)) + b = Bits('pad:0') + self.assertEqual(b, Bits()) + c = Bits('0b11, pad:1, 0b111') + self.assertEqual(c, Bits('0b110111')) + + def testPack(self): + s = bitstring.pack('0b11, pad:3=5, 0b1') + self.assertEqual(s.bin, '110001') + d = bitstring.pack('pad:c', c=12) + self.assertEqual(d, Bits(12)) + e = bitstring.pack('0xf, uint:12, pad:1, bin, pad:4, 0b10', 0, '111') + self.assertEqual(e.bin, '11110000000000000111000010') + + def testUnpack(self): + s = Bits('0b111000111') + x, y = s.unpack('3, pad:3, 3') + self.assertEqual((x, y), (7, 7)) + x, y = s.unpack('2, pad:2, bin') + self.assertEqual((x, y), (3, '00111')) + x = s.unpack('pad:1, pad:2, pad:3') + self.assertEqual(x, []) + + +class ModifiedByAddingBug(unittest.TestCase): + + def testAdding(self): + a = Bits('0b0') + b = Bits('0b11') + c = a + b + self.assertEqual(c, '0b011') + self.assertEqual(a, '0b0') + self.assertEqual(b, '0b11') + + def testAdding2(self): + a = Bits(100) + b = Bits(101) + c = a + b + self.assertEqual(a, 100) + self.assertEqual(b, 101) + self.assertEqual(c, 201) diff --git a/python/bitstring/test/test_bitstore.py b/python/bitstring/test/test_bitstore.py new file mode 100644 index 000000000..9f5c9036e --- /dev/null +++ b/python/bitstring/test/test_bitstore.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +import unittest +import sys +sys.path.insert(0, '..') +from bitstring import ByteStore, ConstByteStore, equal, offsetcopy + + +class OffsetCopy(unittest.TestCase): + def testStraightCopy(self): + s = ByteStore(bytearray([10, 5, 1]), 24, 0) + t = offsetcopy(s, 0) + self.assertEqual(t._rawarray, bytearray([10, 5, 1])) + + def testOffsetIncrease(self): + s = ByteStore(bytearray([1, 1, 1]), 24, 0) + t = offsetcopy(s, 4) + self.assertEqual(t.bitlength, 24) + self.assertEqual(t.offset, 4) + self.assertEqual(t._rawarray, bytearray([0, 16, 16, 16])) + + +class Equals(unittest.TestCase): + + def testBothSingleByte(self): + s = ByteStore(bytearray([128]), 3, 0) + t = ByteStore(bytearray([64]), 3, 1) + u = ByteStore(bytearray([32]), 3, 2) + self.assertTrue(equal(s, t)) + self.assertTrue(equal(s, u)) + self.assertTrue(equal(u, t)) + + def testOneSingleByte(self): + s = ByteStore(bytearray([1, 0]), 2, 7) + t = ByteStore(bytearray([64]), 2, 1) + self.assertTrue(equal(s, t)) + self.assertTrue(equal(t, s))
\ No newline at end of file diff --git a/python/bitstring/test/test_bitstream.py b/python/bitstring/test/test_bitstream.py new file mode 100644 index 000000000..f94193d32 --- /dev/null +++ b/python/bitstring/test/test_bitstream.py @@ -0,0 +1,3940 @@ +#!/usr/bin/env python + +import unittest +import sys +sys.path.insert(0, '..') +import bitstring +import copy +import os +import collections +from bitstring import BitStream, ConstBitStream, pack +from bitstring import ByteStore, offsetcopy + + +class FlexibleInitialisation(unittest.TestCase): + def testFlexibleInitialisation(self): + a = BitStream('uint:8=12') + c = BitStream(' uint : 8 = 12') + self.assertTrue(a == c == BitStream(uint=12, length=8)) + self.assertEqual(a.uint, 12) + a = BitStream(' int:2= -1') + b = BitStream('int :2 = -1') + c = BitStream(' int: 2 =-1 ') + self.assertTrue(a == b == c == BitStream(int=-1, length=2)) + + def testFlexibleInitialisation2(self): + h = BitStream('hex=12') + o = BitStream('oct=33') + b = BitStream('bin=10') + self.assertEqual(h, '0x12') + self.assertEqual(o, '0o33') + self.assertEqual(b, '0b10') + + def testFlexibleInitialisation3(self): + for s in ['se=-1', ' se = -1 ', 'se = -1']: + a = BitStream(s) + self.assertEqual(a.se, -1) + for s in ['ue=23', 'ue =23', 'ue = 23']: + a = BitStream(s) + self.assertEqual(a.ue, 23) + + def testMultipleStringInitialisation(self): + a = BitStream('0b1 , 0x1') + self.assertEqual(a, '0b10001') + a = BitStream('ue=5, ue=1, se=-2') + self.assertEqual(a.read('ue'), 5) + self.assertEqual(a.read('ue'), 1) + self.assertEqual(a.read('se'), -2) + b = BitStream('uint:32 = 12, 0b11') + 'int:100=-100, 0o44' + self.assertEqual(b.read(32).uint, 12) + self.assertEqual(b.read(2).bin, '11') + self.assertEqual(b.read(100).int, -100) + + +class Reading(unittest.TestCase): + def testReadBits(self): + s = BitStream(bytes=b'\x4d\x55') + self.assertEqual(s.read(4).hex, '4') + self.assertEqual(s.read(8).hex, 'd5') + self.assertEqual(s.read(1), [0]) + self.assertEqual(s.read(3).bin, '101') + self.assertFalse(s.read(0)) + + def testReadByte(self): + s = BitStream(hex='4d55') + self.assertEqual(s.read(8).hex, '4d') + self.assertEqual(s.read(8).hex, '55') + + def testReadBytes(self): + s = BitStream(hex='0x112233448811') + self.assertEqual(s.read(3 * 8).hex, '112233') + self.assertRaises(ValueError, s.read, -2 * 8) + s.bitpos += 1 + self.assertEqual(s.read(2 * 8).bin, '1000100100010000') + + def testReadUE(self): + self.assertRaises(bitstring.InterpretError, BitStream('')._getue) + # The numbers 0 to 8 as unsigned Exponential-Golomb codes + s = BitStream(bin='1 010 011 00100 00101 00110 00111 0001000 0001001') + self.assertEqual(s.pos, 0) + for i in range(9): + self.assertEqual(s.read('ue'), i) + self.assertRaises(bitstring.ReadError, s.read, 'ue') + + def testReadSE(self): + s = BitStream(bin='010 00110 0001010 0001000 00111') + self.assertEqual(s.read('se'), 1) + self.assertEqual(s.read('se'), 3) + self.assertEqual(s.readlist(3 * ['se']), [5, 4, -3]) + + +class Find(unittest.TestCase): + def testFind1(self): + s = ConstBitStream(bin='0b0000110110000') + self.assertTrue(s.find(BitStream(bin='11011'), False)) + self.assertEqual(s.bitpos, 4) + self.assertEqual(s.read(5).bin, '11011') + s.bitpos = 0 + self.assertFalse(s.find('0b11001', False)) + + def testFind2(self): + s = BitStream(bin='0') + self.assertTrue(s.find(s, False)) + self.assertEqual(s.pos, 0) + self.assertFalse(s.find('0b00', False)) + self.assertRaises(ValueError, s.find, BitStream(), False) + + def testFindWithOffset(self): + s = BitStream(hex='0x112233')[4:] + self.assertTrue(s.find('0x23', False)) + self.assertEqual(s.pos, 8) + + def testFindCornerCases(self): + s = BitStream(bin='000111000111') + self.assertTrue(s.find('0b000')) + self.assertEqual(s.pos, 0) + self.assertTrue(s.find('0b000')) + self.assertEqual(s.pos, 0) + self.assertTrue(s.find('0b0111000111')) + self.assertEqual(s.pos, 2) + self.assertTrue(s.find('0b000', start=2)) + self.assertEqual(s.pos, 6) + self.assertTrue(s.find('0b111', start=6)) + self.assertEqual(s.pos, 9) + s.pos += 2 + self.assertTrue(s.find('0b1', start=s.pos)) + + def testFindBytes(self): + s = BitStream('0x010203040102ff') + self.assertFalse(s.find('0x05', bytealigned=True)) + self.assertTrue(s.find('0x02', bytealigned=True)) + self.assertEqual(s.read(16).hex, '0203') + self.assertTrue(s.find('0x02', start=s.bitpos, bytealigned=True)) + s.read(1) + self.assertFalse(s.find('0x02', start=s.bitpos, bytealigned=True)) + + def testFindBytesAlignedCornerCases(self): + s = BitStream('0xff') + self.assertTrue(s.find(s)) + self.assertFalse(s.find(BitStream(hex='0x12'))) + self.assertFalse(s.find(BitStream(hex='0xffff'))) + + def testFindBytesBitpos(self): + s = BitStream(hex='0x1122334455') + s.pos = 2 + s.find('0x66', bytealigned=True) + self.assertEqual(s.pos, 2) + s.pos = 38 + s.find('0x66', bytealigned=True) + self.assertEqual(s.pos, 38) + + def testFindByteAligned(self): + s = BitStream(hex='0x12345678') + self.assertTrue(s.find(BitStream(hex='0x56'), bytealigned=True)) + self.assertEqual(s.bytepos, 2) + s.pos = 0 + self.assertFalse(s.find(BitStream(hex='0x45'), bytealigned=True)) + s = BitStream('0x1234') + s.find('0x1234') + self.assertTrue(s.find('0x1234')) + s += '0b111' + s.pos = 3 + s.find('0b1', start=17, bytealigned=True) + self.assertFalse(s.find('0b1', start=17, bytealigned=True)) + self.assertEqual(s.pos, 3) + + def testFindByteAlignedWithOffset(self): + s = BitStream(hex='0x112233')[4:] + self.assertTrue(s.find(BitStream(hex='0x23'))) + + def testFindByteAlignedErrors(self): + s = BitStream(hex='0xffff') + self.assertRaises(ValueError, s.find, '') + self.assertRaises(ValueError, s.find, BitStream()) + + +class Rfind(unittest.TestCase): + def testRfind(self): + a = BitStream('0b001001001') + b = a.rfind('0b001') + self.assertEqual(b, (6,)) + self.assertEqual(a.pos, 6) + big = BitStream(length=100000) + '0x12' + BitStream(length=10000) + found = big.rfind('0x12', bytealigned=True) + self.assertEqual(found, (100000,)) + self.assertEqual(big.pos, 100000) + + def testRfindByteAligned(self): + a = BitStream('0x8888') + b = a.rfind('0b1', bytealigned=True) + self.assertEqual(b, (8,)) + self.assertEqual(a.pos, 8) + + def testRfindStartbit(self): + a = BitStream('0x0000ffffff') + b = a.rfind('0x0000', start=1, bytealigned=True) + self.assertEqual(b, ()) + self.assertEqual(a.pos, 0) + b = a.rfind('0x00', start=1, bytealigned=True) + self.assertEqual(b, (8,)) + self.assertEqual(a.pos, 8) + + def testRfindEndbit(self): + a = BitStream('0x000fff') + b = a.rfind('0b011', bytealigned=False, start=0, end=14) + self.assertEqual(bool(b), True) + b = a.rfind('0b011', False, 0, 13) + self.assertEqual(b, ()) + + def testRfindErrors(self): + a = BitStream('0x43234234') + self.assertRaises(ValueError, a.rfind, '', bytealigned=True) + self.assertRaises(ValueError, a.rfind, '0b1', start=-99, bytealigned=True) + self.assertRaises(ValueError, a.rfind, '0b1', end=33, bytealigned=True) + self.assertRaises(ValueError, a.rfind, '0b1', start=10, end=9, bytealigned=True) + + +class Shift(unittest.TestCase): + def testShiftLeft(self): + s = BitStream('0b1010') + t = s << 1 + self.assertEqual(s.bin, '1010') + self.assertEqual(t.bin, '0100') + t = t << 0 + self.assertEqual(t, '0b0100') + t = t << 100 + self.assertEqual(t.bin, '0000') + + def testShiftLeftErrors(self): + s = BitStream() + self.assertRaises(ValueError, s.__lshift__, 1) + s = BitStream('0xf') + self.assertRaises(ValueError, s.__lshift__, -1) + + def testShiftRight(self): + s = BitStream('0b1010') + t = s >> 1 + self.assertEqual(s.bin, '1010') + self.assertEqual(t.bin, '0101') + q = s >> 0 + self.assertEqual(q, '0b1010') + q.replace('0b1010', '') + s = s >> 100 + self.assertEqual(s.bin, '0000') + + def testShiftRightErrors(self): + s = BitStream() + self.assertRaises(ValueError, s.__rshift__, 1) + s = BitStream('0xf') + self.assertRaises(ValueError, s.__rshift__, -1) + + def testShiftRightInPlace(self): + s = BitStream('0xffff')[4:12] + s >>= 1 + self.assertEqual(s, '0b01111111') + s = BitStream('0b11011') + s >>= 2 + self.assertEqual(s.bin, '00110') + s >>= 100000000000000 + self.assertEqual(s.bin, '00000') + s = BitStream('0xff') + s >>= 1 + self.assertEqual(s, '0x7f') + s >>= 0 + self.assertEqual(s, '0x7f') + + def testShiftRightInPlaceErrors(self): + s = BitStream() + self.assertRaises(ValueError, s.__irshift__, 1) + s += '0b11' + self.assertRaises(ValueError, s.__irshift__, -1) + + def testShiftLeftInPlace(self): + s = BitStream('0xffff') + t = s[4:12] + t <<= 2 + self.assertEqual(t, '0b11111100') + s = BitStream('0b11011') + s <<= 2 + self.assertEqual(s.bin, '01100') + s <<= 100000000000000000000 + self.assertEqual(s.bin, '00000') + s = BitStream('0xff') + s <<= 1 + self.assertEqual(s, '0xfe') + s <<= 0 + self.assertEqual(s, '0xfe') + + def testShiftLeftInPlaceErrors(self): + s = BitStream() + self.assertRaises(ValueError, s.__ilshift__, 1) + s += '0b11' + self.assertRaises(ValueError, s.__ilshift__, -1) + + +class Replace(unittest.TestCase): + def testReplace1(self): + a = BitStream('0b1') + n = a.replace('0b1', '0b0', bytealigned=True) + self.assertEqual(a.bin, '0') + self.assertEqual(n, 1) + n = a.replace('0b1', '0b0', bytealigned=True) + self.assertEqual(n, 0) + + def testReplace2(self): + a = BitStream('0b00001111111') + n = a.replace('0b1', '0b0', bytealigned=True) + self.assertEqual(a.bin, '00001111011') + self.assertEqual(n, 1) + n = a.replace('0b1', '0b0', bytealigned=False) + self.assertEqual(a.bin, '00000000000') + self.assertEqual(n, 6) + + def testReplace3(self): + a = BitStream('0b0') + n = a.replace('0b0', '0b110011111', bytealigned=True) + self.assertEqual(n, 1) + self.assertEqual(a.bin, '110011111') + n = a.replace('0b11', '', bytealigned=False) + self.assertEqual(n, 3) + self.assertEqual(a.bin, '001') + + def testReplace4(self): + a = BitStream('0x00114723ef4732344700') + n = a.replace('0x47', '0x00', bytealigned=True) + self.assertEqual(n, 3) + self.assertEqual(a.hex, '00110023ef0032340000') + a.replace('0x00', '', bytealigned=True) + self.assertEqual(a.hex, '1123ef3234') + a.replace('0x11', '', start=1, bytealigned=True) + self.assertEqual(a.hex, '1123ef3234') + a.replace('0x11', '0xfff', end=7, bytealigned=True) + self.assertEqual(a.hex, '1123ef3234') + a.replace('0x11', '0xfff', end=8, bytealigned=True) + self.assertEqual(a.hex, 'fff23ef3234') + + def testReplace5(self): + a = BitStream('0xab') + b = BitStream('0xcd') + c = BitStream('0xabef') + c.replace(a, b) + self.assertEqual(c, '0xcdef') + self.assertEqual(a, '0xab') + self.assertEqual(b, '0xcd') + a = BitStream('0x0011223344') + a.pos = 12 + a.replace('0x11', '0xfff', bytealigned=True) + self.assertEqual(a.pos, 8) + self.assertEqual(a, '0x00fff223344') + + def testReplaceWithSelf(self): + a = BitStream('0b11') + a.replace('0b1', a) + self.assertEqual(a, '0xf') + a.replace(a, a) + self.assertEqual(a, '0xf') + + def testReplaceCount(self): + a = BitStream('0x223344223344223344') + n = a.replace('0x2', '0x0', count=0, bytealigned=True) + self.assertEqual(n, 0) + self.assertEqual(a.hex, '223344223344223344') + n = a.replace('0x2', '0x0', count=1, bytealigned=True) + self.assertEqual(n, 1) + self.assertEqual(a.hex, '023344223344223344') + n = a.replace('0x33', '', count=2, bytealigned=True) + self.assertEqual(n, 2) + self.assertEqual(a.hex, '02442244223344') + n = a.replace('0x44', '0x4444', count=1435, bytealigned=True) + self.assertEqual(n, 3) + self.assertEqual(a.hex, '02444422444422334444') + + def testReplaceBitpos(self): + a = BitStream('0xff') + a.bitpos = 8 + a.replace('0xff', '', bytealigned=True) + self.assertEqual(a.bitpos, 0) + a = BitStream('0b0011110001') + a.bitpos = 4 + a.replace('0b1', '0b000') + self.assertEqual(a.bitpos, 8) + a = BitStream('0b1') + a.bitpos = 1 + a.replace('0b1', '0b11111', bytealigned=True) + self.assertEqual(a.bitpos, 5) + a.replace('0b11', '0b0', False) + self.assertEqual(a.bitpos, 3) + a.append('0b00') + a.replace('0b00', '0xffff') + self.assertEqual(a.bitpos, 17) + + def testReplaceErrors(self): + a = BitStream('0o123415') + self.assertRaises(ValueError, a.replace, '', '0o7', bytealigned=True) + self.assertRaises(ValueError, a.replace, '0b1', '0b1', start=-100, bytealigned=True) + self.assertRaises(ValueError, a.replace, '0b1', '0b1', end=19, bytealigned=True) + + +class SliceAssignment(unittest.TestCase): + + # TODO: Move this to another class + def testSetSlice(self): + a = BitStream() + a[0:0] = '0xabcdef' + self.assertEqual(a.bytepos, 3) + a[4:16] = '' + self.assertEqual(a, '0xaef') + self.assertEqual(a.bitpos, 4) + a[8:] = '0x00' + self.assertEqual(a, '0xae00') + self.assertEqual(a.bytepos, 2) + a += '0xf' + a[8:] = '0xe' + self.assertEqual(a, '0xaee') + self.assertEqual(a.bitpos, 12) + b = BitStream() + b[0:800] = '0xffee' + self.assertEqual(b, '0xffee') + b[4:48] = '0xeed123' + self.assertEqual(b, '0xfeed123') + b[-800:8] = '0x0000' + self.assertEqual(b, '0x0000ed123') + a = BitStream('0xabcde') + self.assertEqual(a[-100:-90], '') + self.assertEqual(a[-100:-16], '0xa') + a[-100:-16] = '0x0' + self.assertEqual(a, '0x0bcde') + + def testInsertingUsingSetItem(self): + a = BitStream() + a[0:0] = '0xdeadbeef' + self.assertEqual(a, '0xdeadbeef') + self.assertEqual(a.bytepos, 4) + a[16:16] = '0xfeed' + self.assertEqual(a, '0xdeadfeedbeef') + self.assertEqual(a.bytepos, 4) + a[0:0] = '0xa' + self.assertEqual(a, '0xadeadfeedbeef') + self.assertEqual(a.bitpos, 4) + a.bytepos = 6 + a[0:0] = '0xff' + self.assertEqual(a.bytepos, 1) + a[8:0] = '0x00000' + self.assertTrue(a.startswith('0xff00000adead')) + + def testSliceAssignmentBitPos(self): + a = BitStream('int:64=-1') + a.pos = 64 + a[0:8] = '' + self.assertEqual(a.pos, 0) + a.pos = 52 + a[48:56] = '0x0000' + self.assertEqual(a.pos, 64) + a[10:10] = '0x0' + self.assertEqual(a.pos, 14) + a[56:68] = '0x000' + self.assertEqual(a.pos, 14) + + +class Pack(unittest.TestCase): + def testPack1(self): + s = bitstring.pack('uint:6, bin, hex, int:6, se, ue, oct', 10, '0b110', 'ff', -1, -6, 6, '54') + t = BitStream('uint:6=10, 0b110, 0xff, int:6=-1, se=-6, ue=6, oct=54') + self.assertEqual(s, t) + self.assertRaises(bitstring.CreationError, pack, 'tomato', '0') + self.assertRaises(bitstring.CreationError, pack, 'uint', 12) + self.assertRaises(bitstring.CreationError, pack, 'hex', 'penguin') + self.assertRaises(bitstring.CreationError, pack, 'hex12', '0x12') + + def testPackWithLiterals(self): + s = bitstring.pack('0xf') + self.assertEqual(s, '0xf') + self.assertTrue(type(s), BitStream) + s = pack('0b1') + self.assertEqual(s, '0b1') + s = pack('0o7') + self.assertEqual(s, '0o7') + s = pack('int:10=-1') + self.assertEqual(s, '0b1111111111') + s = pack('uint:10=1') + self.assertEqual(s, '0b0000000001') + s = pack('ue=12') + self.assertEqual(s.ue, 12) + s = pack('se=-12') + self.assertEqual(s.se, -12) + s = pack('bin=01') + self.assertEqual(s.bin, '01') + s = pack('hex=01') + self.assertEqual(s.hex, '01') + s = pack('oct=01') + self.assertEqual(s.oct, '01') + + def testPackWithDict(self): + a = pack('uint:6=width, se=height', height=100, width=12) + w, h = a.unpack('uint:6, se') + self.assertEqual(w, 12) + self.assertEqual(h, 100) + d = {} + d['w'] = '0xf' + d['300'] = 423 + d['e'] = '0b1101' + a = pack('int:100=300, bin=e, uint:12=300', **d) + x, y, z = a.unpack('int:100, bin, uint:12') + self.assertEqual(x, 423) + self.assertEqual(y, '1101') + self.assertEqual(z, 423) + + def testPackWithDict2(self): + a = pack('int:5, bin:3=b, 0x3, bin=c, se=12', 10, b='0b111', c='0b1') + b = BitStream('int:5=10, 0b111, 0x3, 0b1, se=12') + self.assertEqual(a, b) + a = pack('bits:3=b', b=BitStream('0b101')) + self.assertEqual(a, '0b101') + a = pack('bits:24=b', b=BitStream('0x001122')) + self.assertEqual(a, '0x001122') + + def testPackWithDict3(self): + s = pack('hex:4=e, hex:4=0xe, hex:4=e', e='f') + self.assertEqual(s, '0xfef') + s = pack('sep', sep='0b00') + self.assertEqual(s, '0b00') + + def testPackWithDict4(self): + s = pack('hello', hello='0xf') + self.assertEqual(s, '0xf') + s = pack('x, y, x, y, x', x='0b10', y='uint:12=100') + t = BitStream('0b10, uint:12=100, 0b10, uint:12=100, 0b10') + self.assertEqual(s, t) + a = [1, 2, 3, 4, 5] + s = pack('int:8, div,' * 5, *a, **{'div': '0b1'}) + t = BitStream('int:8=1, 0b1, int:8=2, 0b1, int:8=3, 0b1, int:8=4, 0b1, int:8=5, 0b1') + self.assertEqual(s, t) + + def testPackWithLocals(self): + width = 352 + height = 288 + s = pack('uint:12=width, uint:12=height', **locals()) + self.assertEqual(s, '0x160120') + + def testPackWithLengthRestriction(self): + s = pack('bin:3', '0b000') + self.assertRaises(bitstring.CreationError, pack, 'bin:3', '0b0011') + self.assertRaises(bitstring.CreationError, pack, 'bin:3', '0b11') + self.assertRaises(bitstring.CreationError, pack, 'bin:3=0b0011') + self.assertRaises(bitstring.CreationError, pack, 'bin:3=0b11') + + s = pack('hex:4', '0xf') + self.assertRaises(bitstring.CreationError, pack, 'hex:4', '0b111') + self.assertRaises(bitstring.CreationError, pack, 'hex:4', '0b11111') + self.assertRaises(bitstring.CreationError, pack, 'hex:8=0xf') + + s = pack('oct:6', '0o77') + self.assertRaises(bitstring.CreationError, pack, 'oct:6', '0o1') + self.assertRaises(bitstring.CreationError, pack, 'oct:6', '0o111') + self.assertRaises(bitstring.CreationError, pack, 'oct:3', '0b1') + self.assertRaises(bitstring.CreationError, pack, 'oct:3=hello', hello='0o12') + + s = pack('bits:3', BitStream('0b111')) + self.assertRaises(bitstring.CreationError, pack, 'bits:3', BitStream('0b11')) + self.assertRaises(bitstring.CreationError, pack, 'bits:3', BitStream('0b1111')) + self.assertRaises(bitstring.CreationError, pack, 'bits:12=b', b=BitStream('0b11')) + + def testPackNull(self): + s = pack('') + self.assertFalse(s) + s = pack(',') + self.assertFalse(s) + s = pack(',,,,,0b1,,,,,,,,,,,,,0b1,,,,,,,,,,') + self.assertEqual(s, '0b11') + s = pack(',,uint:12,,bin:3,', 100, '100') + a, b = s.unpack(',,,uint:12,,,,bin:3,,,') + self.assertEqual(a, 100) + self.assertEqual(b, '100') + + def testPackDefaultUint(self): + s = pack('10, 5', 1, 2) + a, b = s.unpack('10, 5') + self.assertEqual((a, b), (1, 2)) + s = pack('10=150, 12=qee', qee=3) + self.assertEqual(s, 'uint:10=150, uint:12=3') + t = BitStream('100=5') + self.assertEqual(t, 'uint:100=5') + + def testPackDefualtUintErrors(self): + self.assertRaises(bitstring.CreationError, BitStream, '5=-1') + + def testPackingLongKeywordBitstring(self): + s = pack('bits=b', b=BitStream(128000)) + self.assertEqual(s, BitStream(128000)) + + def testPackingWithListFormat(self): + f = ['bin', 'hex', 'uint:10'] + a = pack(','.join(f), '00', '234', 100) + b = pack(f, '00', '234', 100) + self.assertEqual(a, b) + + +class Unpack(unittest.TestCase): + def testUnpack1(self): + s = BitStream('uint:13=23, hex=e, bin=010, int:41=-554, 0o44332, se=-12, ue=4') + s.pos = 11 + a, b, c, d, e, f, g = s.unpack('uint:13, hex:4, bin:3, int:41, oct:15, se, ue') + self.assertEqual(a, 23) + self.assertEqual(b, 'e') + self.assertEqual(c, '010') + self.assertEqual(d, -554) + self.assertEqual(e, '44332') + self.assertEqual(f, -12) + self.assertEqual(g, 4) + self.assertEqual(s.pos, 11) + + def testUnpack2(self): + s = BitStream('0xff, 0b000, uint:12=100') + a, b, c = s.unpack('bits:8, bits, uint:12') + self.assertEqual(type(s), BitStream) + self.assertEqual(a, '0xff') + self.assertEqual(type(s), BitStream) + self.assertEqual(b, '0b000') + self.assertEqual(c, 100) + a, b = s.unpack(['bits:11', 'uint']) + self.assertEqual(a, '0xff, 0b000') + self.assertEqual(b, 100) + + def testUnpackNull(self): + s = pack('0b1, , , 0xf,') + a, b = s.unpack('bin:1,,,hex:4,') + self.assertEqual(a, '1') + self.assertEqual(b, 'f') + + +class FromFile(unittest.TestCase): + def testCreationFromFileOperations(self): + s = BitStream(filename='smalltestfile') + s.append('0xff') + self.assertEqual(s.hex, '0123456789abcdefff') + + s = ConstBitStream(filename='smalltestfile') + t = BitStream('0xff') + s + self.assertEqual(t.hex, 'ff0123456789abcdef') + + s = BitStream(filename='smalltestfile') + del s[:1] + self.assertEqual((BitStream('0b0') + s).hex, '0123456789abcdef') + + s = BitStream(filename='smalltestfile') + del s[:7 * 8] + self.assertEqual(s.hex, 'ef') + + s = BitStream(filename='smalltestfile') + s.insert('0xc', 4) + self.assertEqual(s.hex, '0c123456789abcdef') + + s = BitStream(filename='smalltestfile') + s.prepend('0xf') + self.assertEqual(s.hex, 'f0123456789abcdef') + + s = BitStream(filename='smalltestfile') + s.overwrite('0xaaa', 12) + self.assertEqual(s.hex, '012aaa6789abcdef') + + s = BitStream(filename='smalltestfile') + s.reverse() + self.assertEqual(s.hex, 'f7b3d591e6a2c480') + + s = BitStream(filename='smalltestfile') + del s[-60:] + self.assertEqual(s.hex, '0') + + s = BitStream(filename='smalltestfile') + del s[:60] + self.assertEqual(s.hex, 'f') + + def testFileProperties(self): + s = ConstBitStream(filename='smalltestfile') + self.assertEqual(s.hex, '0123456789abcdef') + self.assertEqual(s.uint, 81985529216486895) + self.assertEqual(s.int, 81985529216486895) + self.assertEqual(s.bin, '0000000100100011010001010110011110001001101010111100110111101111') + self.assertEqual(s[:-1].oct, '002215053170465363367') + s.bitpos = 0 + self.assertEqual(s.read('se'), -72) + s.bitpos = 0 + self.assertEqual(s.read('ue'), 144) + self.assertEqual(s.bytes, b'\x01\x23\x45\x67\x89\xab\xcd\xef') + self.assertEqual(s.tobytes(), b'\x01\x23\x45\x67\x89\xab\xcd\xef') + + def testCreationFromFileWithLength(self): + s = ConstBitStream(filename='test.m1v', length=32) + self.assertEqual(s.length, 32) + self.assertEqual(s.hex, '000001b3') + s = ConstBitStream(filename='test.m1v', length=0) + self.assertFalse(s) + self.assertRaises(bitstring.CreationError, BitStream, filename='smalltestfile', length=65) + self.assertRaises(bitstring.CreationError, ConstBitStream, filename='smalltestfile', length=64, offset=1) + # self.assertRaises(bitstring.CreationError, ConstBitStream, filename='smalltestfile', offset=65) + f = open('smalltestfile', 'rb') + # self.assertRaises(bitstring.CreationError, ConstBitStream, auto=f, offset=65) + self.assertRaises(bitstring.CreationError, ConstBitStream, auto=f, length=65) + self.assertRaises(bitstring.CreationError, ConstBitStream, auto=f, offset=60, length=5) + + def testCreationFromFileWithOffset(self): + a = BitStream(filename='test.m1v', offset=4) + self.assertEqual(a.peek(4 * 8).hex, '00001b31') + b = BitStream(filename='test.m1v', offset=28) + self.assertEqual(b.peek(8).hex, '31') + + def testFileSlices(self): + s = BitStream(filename='smalltestfile') + self.assertEqual(s[-16:].hex, 'cdef') + + def testCreataionFromFileErrors(self): + self.assertRaises(IOError, BitStream, filename='Idonotexist') + + def testFindInFile(self): + s = BitStream(filename='test.m1v') + self.assertTrue(s.find('0x160120')) + self.assertEqual(s.bytepos, 4) + s3 = s.read(3 * 8) + self.assertEqual(s3.hex, '160120') + s.bytepos = 0 + self.assertTrue(s._pos == 0) + self.assertTrue(s.find('0x0001b2')) + self.assertEqual(s.bytepos, 13) + + def testHexFromFile(self): + s = BitStream(filename='test.m1v') + self.assertEqual(s[0:32].hex, '000001b3') + self.assertEqual(s[-32:].hex, '000001b7') + s.hex = '0x11' + self.assertEqual(s.hex, '11') + + def testFileOperations(self): + s1 = BitStream(filename='test.m1v') + s2 = BitStream(filename='test.m1v') + self.assertEqual(s1.read(32).hex, '000001b3') + self.assertEqual(s2.read(32).hex, '000001b3') + s1.bytepos += 4 + self.assertEqual(s1.read(8).hex, '02') + self.assertEqual(s2.read(5 * 8).hex, '1601208302') + s1.pos = s1.len + try: + s1.pos += 1 + self.assertTrue(False) + except ValueError: + pass + + def testFileBitGetting(self): + s = ConstBitStream(filename='smalltestfile', offset=16, length=8) # 0x45 + b = s[1] + self.assertTrue(b) + b = s.any(0, [-1, -2, -3]) + self.assertTrue(b) + b = s.all(0, [0, 1, 2]) + self.assertFalse(b) + + def testVeryLargeFiles(self): + # This uses an 11GB file which isn't distributed for obvious reasons + # and so this test won't work for anyone except me! + try: + s = ConstBitStream(filename='11GB.mkv') + except IOError: + return + self.assertEqual(s.len, 11743020505 * 8) + self.assertEqual(s[1000000000:1000000100].hex, 'bdef7335d4545f680d669ce24') + self.assertEqual(s[-4::8].hex, 'bbebf7a1') + + +class CreationErrors(unittest.TestCase): + def testIncorrectBinAssignment(self): + s = BitStream() + self.assertRaises(bitstring.CreationError, s._setbin_safe, '0010020') + + def testIncorrectHexAssignment(self): + s = BitStream() + self.assertRaises(bitstring.CreationError, s._sethex, '0xabcdefg') + + +class Length(unittest.TestCase): + def testLengthZero(self): + self.assertEqual(BitStream('').len, 0) + + def testLength(self): + self.assertEqual(BitStream('0x80').len, 8) + + def testLengthErrors(self): + #TODO: Lots of new checks, for various inits which now disallow length and offset + pass + #self.assertRaises(ValueError, BitStream, bin='111', length=-1) + #self.assertRaises(ValueError, BitStream, bin='111', length=4) + + def testOffsetLengthError(self): + self.assertRaises(bitstring.CreationError, BitStream, hex='0xffff', offset=-1) + + +class SimpleConversions(unittest.TestCase): + def testConvertToUint(self): + self.assertEqual(BitStream('0x10').uint, 16) + self.assertEqual(BitStream('0b000111').uint, 7) + + def testConvertToInt(self): + self.assertEqual(BitStream('0x10').int, 16) + self.assertEqual(BitStream('0b11110').int, -2) + + def testConvertToHex(self): + self.assertEqual(BitStream(bytes=b'\x00\x12\x23\xff').hex, '001223ff') + s = BitStream('0b11111') + self.assertRaises(bitstring.InterpretError, s._gethex) + + +class Empty(unittest.TestCase): + def testEmptyBitstring(self): + s = BitStream() + self.assertRaises(bitstring.ReadError, s.read, 1) + self.assertEqual(s.bin, '') + self.assertEqual(s.hex, '') + self.assertRaises(bitstring.InterpretError, s._getint) + self.assertRaises(bitstring.InterpretError, s._getuint) + self.assertFalse(s) + + def testNonEmptyBitStream(self): + s = BitStream(bin='0') + self.assertFalse(not s.len) + + +class Position(unittest.TestCase): + def testBitPosition(self): + s = BitStream(bytes=b'\x00\x00\x00') + self.assertEqual(s.bitpos, 0) + s.read(5) + self.assertEqual(s.pos, 5) + s.pos = s.len + self.assertRaises(bitstring.ReadError, s.read, 1) + + def testBytePosition(self): + s = BitStream(bytes=b'\x00\x00\x00') + self.assertEqual(s.bytepos, 0) + s.read(10) + self.assertRaises(bitstring.ByteAlignError, s._getbytepos) + s.read(6) + self.assertEqual(s.bytepos, 2) + + def testSeekToBit(self): + s = BitStream(bytes=b'\x00\x00\x00\x00\x00\x00') + s.bitpos = 0 + self.assertEqual(s.bitpos, 0) + self.assertRaises(ValueError, s._setbitpos, -1) + self.assertRaises(ValueError, s._setbitpos, 6 * 8 + 1) + s.bitpos = 6 * 8 + self.assertEqual(s.bitpos, 6 * 8) + + def testSeekToByte(self): + s = BitStream(bytes=b'\x00\x00\x00\x00\x00\xab') + s.bytepos = 5 + self.assertEqual(s.read(8).hex, 'ab') + + def testAdvanceBitsAndBytes(self): + s = BitStream(bytes=b'\x00\x00\x00\x00\x00\x00\x00\x00') + s.pos += 5 + self.assertEqual(s.pos, 5) + s.bitpos += 16 + self.assertEqual(s.pos, 2 * 8 + 5) + s.pos -= 8 + self.assertEqual(s.pos, 8 + 5) + + def testRetreatBitsAndBytes(self): + a = BitStream(length=100) + a.pos = 80 + a.bytepos -= 5 + self.assertEqual(a.bytepos, 5) + a.pos -= 5 + self.assertEqual(a.pos, 35) + + +class Offset(unittest.TestCase): + def testOffset1(self): + s = BitStream(bytes=b'\x00\x1b\x3f', offset=4) + self.assertEqual(s.read(8).bin, '00000001') + self.assertEqual(s.length, 20) + + def testOffset2(self): + s1 = BitStream(bytes=b'\xf1\x02\x04') + s2 = BitStream(bytes=b'\xf1\x02\x04', length=23) + for i in [1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0, 7, 3, 5, 1, 4]: + s1._datastore = offsetcopy(s1._datastore, i) + self.assertEqual(s1.hex, 'f10204') + s2._datastore = offsetcopy(s2._datastore, i) + self.assertEqual(s2.bin, '11110001000000100000010') + + +class Append(unittest.TestCase): + def testAppend(self): + s1 = BitStream('0b00000') + s1.append(BitStream(bool=True)) + self.assertEqual(s1.bin, '000001') + self.assertEqual((BitStream('0x0102') + BitStream('0x0304')).hex, '01020304') + + def testAppendSameBitstring(self): + s1 = BitStream('0xf0')[:6] + s1.append(s1) + self.assertEqual(s1.bin, '111100111100') + + def testAppendWithOffset(self): + s = BitStream(bytes=b'\x28\x28', offset=1) + s.append('0b0') + self.assertEqual(s.hex, '5050') + + +class ByteAlign(unittest.TestCase): + def testByteAlign(self): + s = BitStream(hex='0001ff23') + s.bytealign() + self.assertEqual(s.bytepos, 0) + s.pos += 11 + s.bytealign() + self.assertEqual(s.bytepos, 2) + s.pos -= 10 + s.bytealign() + self.assertEqual(s.bytepos, 1) + + def testByteAlignWithOffset(self): + s = BitStream(hex='0112233') + s._datastore = offsetcopy(s._datastore, 3) + bitstoalign = s.bytealign() + self.assertEqual(bitstoalign, 0) + self.assertEqual(s.read(5).bin, '00001') + + def testInsertByteAligned(self): + s = BitStream('0x0011') + s.insert(BitStream('0x22'), 8) + self.assertEqual(s.hex, '002211') + s = BitStream(0) + s.insert(BitStream(bin='101'), 0) + self.assertEqual(s.bin, '101') + + +class Truncate(unittest.TestCase): + def testTruncateStart(self): + s = BitStream('0b1') + del s[:1] + self.assertFalse(s) + s = BitStream(hex='1234') + self.assertEqual(s.hex, '1234') + del s[:4] + self.assertEqual(s.hex, '234') + del s[:9] + self.assertEqual(s.bin, '100') + del s[:2] + self.assertEqual(s.bin, '0') + self.assertEqual(s.len, 1) + del s[:1] + self.assertFalse(s) + + def testTruncateEnd(self): + s = BitStream('0b1') + del s[-1:] + self.assertFalse(s) + s = BitStream(bytes=b'\x12\x34') + self.assertEqual(s.hex, '1234') + del s[-4:] + self.assertEqual(s.hex, '123') + del s[-9:] + self.assertEqual(s.bin, '000') + del s[-3:] + self.assertFalse(s) + s = BitStream('0b001') + del s[:2] + del s[-1:] + self.assertFalse(s) + + +class Slice(unittest.TestCase): + def testByteAlignedSlice(self): + s = BitStream(hex='0x123456') + self.assertEqual(s[8:16].hex, '34') + s = s[8:24] + self.assertEqual(s.len, 16) + self.assertEqual(s.hex, '3456') + s = s[0:8] + self.assertEqual(s.hex, '34') + s.hex = '0x123456' + self.assertEqual(s[8:24][0:8].hex, '34') + + def testSlice(self): + s = BitStream(bin='000001111100000') + s1 = s[0:5] + s2 = s[5:10] + s3 = s[10:15] + self.assertEqual(s1.bin, '00000') + self.assertEqual(s2.bin, '11111') + self.assertEqual(s3.bin, '00000') + + +class Insert(unittest.TestCase): + def testInsert(self): + s1 = BitStream(hex='0x123456') + s2 = BitStream(hex='0xff') + s1.bytepos = 1 + s1.insert(s2) + self.assertEqual(s1.bytepos, 2) + self.assertEqual(s1.hex, '12ff3456') + s1.insert('0xee', 24) + self.assertEqual(s1.hex, '12ff34ee56') + self.assertEqual(s1.bitpos, 32) + self.assertRaises(ValueError, s1.insert, '0b1', -1000) + self.assertRaises(ValueError, s1.insert, '0b1', 1000) + + def testInsertNull(self): + s = BitStream(hex='0x123').insert(BitStream(), 3) + self.assertEqual(s.hex, '123') + + def testInsertBits(self): + one = BitStream(bin='1') + zero = BitStream(bin='0') + s = BitStream(bin='00') + s.insert(one, 0) + self.assertEqual(s.bin, '100') + s.insert(zero, 0) + self.assertEqual(s.bin, '0100') + s.insert(one, s.len) + self.assertEqual(s.bin, '01001') + s.insert(s, 2) + self.assertEqual(s.bin, '0101001001') + + +class Resetting(unittest.TestCase): + def testSetHex(self): + s = BitStream() + s.hex = '0' + self.assertEqual(s.hex, '0') + s.hex = '0x010203045' + self.assertEqual(s.hex, '010203045') + self.assertRaises(bitstring.CreationError, s._sethex, '0x002g') + + def testSetBin(self): + s = BitStream(bin="000101101") + self.assertEqual(s.bin, '000101101') + self.assertEqual(s.len, 9) + s.bin = '0' + self.assertEqual(s.bin, '0') + self.assertEqual(s.len, 1) + + def testSetEmptyBin(self): + s = BitStream(hex='0x000001b3') + s.bin = '' + self.assertEqual(s.len, 0) + self.assertEqual(s.bin, '') + + def testSetInvalidBin(self): + s = BitStream() + self.assertRaises(bitstring.CreationError, s._setbin_safe, '00102') + + +class Overwriting(unittest.TestCase): + def testOverwriteBit(self): + s = BitStream(bin='0') + s.overwrite(BitStream(bin='1'), 0) + self.assertEqual(s.bin, '1') + + def testOverwriteLimits(self): + s = BitStream(bin='0b11111') + s.overwrite(BitStream(bin='000'), 0) + self.assertEqual(s.bin, '00011') + s.overwrite('0b000', 2) + self.assertEqual(s.bin, '00000') + + def testOverwriteNull(self): + s = BitStream(hex='342563fedec') + s2 = BitStream(s) + s.overwrite(BitStream(bin=''), 23) + self.assertEqual(s.bin, s2.bin) + + def testOverwritePosition(self): + s1 = BitStream(hex='0123456') + s2 = BitStream(hex='ff') + s1.bytepos = 1 + s1.overwrite(s2) + self.assertEqual((s1.hex, s1.bytepos), ('01ff456', 2)) + s1.overwrite('0xff', 0) + self.assertEqual((s1.hex, s1.bytepos), ('ffff456', 1)) + + def testOverwriteWithSelf(self): + s = BitStream('0x123') + s.overwrite(s) + self.assertEqual(s, '0x123') + + +class Split(unittest.TestCase): + def testSplitByteAlignedCornerCases(self): + s = BitStream() + bsl = s.split(BitStream(hex='0xff')) + self.assertEqual(next(bsl).hex, '') + self.assertRaises(StopIteration, next, bsl) + s = BitStream(hex='aabbcceeddff') + delimiter = BitStream() + bsl = s.split(delimiter) + self.assertRaises(ValueError, next, bsl) + delimiter = BitStream(hex='11') + bsl = s.split(delimiter) + self.assertEqual(next(bsl).hex, s.hex) + + def testSplitByteAligned(self): + s = BitStream(hex='0x1234aa1234bbcc1234ffff') + delimiter = BitStream(hex='1234') + bsl = s.split(delimiter) + self.assertEqual([b.hex for b in bsl], ['', '1234aa', '1234bbcc', '1234ffff']) + self.assertEqual(s.pos, 0) + + def testSplitByteAlignedWithIntialBytes(self): + s = BitStream(hex='aa471234fedc43 47112233 47 4723 472314') + delimiter = BitStream(hex='47') + s.find(delimiter) + self.assertEqual(s.bytepos, 1) + bsl = s.split(delimiter, start=0) + self.assertEqual([b.hex for b in bsl], ['aa', '471234fedc43', '47112233', + '47', '4723', '472314']) + self.assertEqual(s.bytepos, 1) + + def testSplitByteAlignedWithOverlappingDelimiter(self): + s = BitStream(hex='aaffaaffaaffaaffaaff') + bsl = s.split(BitStream(hex='aaffaa')) + self.assertEqual([b.hex for b in bsl], ['', 'aaffaaff', 'aaffaaffaaff']) + + +class Adding(unittest.TestCase): + def testAdding(self): + s1 = BitStream(hex='0x0102') + s2 = BitStream(hex='0x0304') + s3 = s1 + s2 + self.assertEqual(s1.hex, '0102') + self.assertEqual(s2.hex, '0304') + self.assertEqual(s3.hex, '01020304') + s3 += s1 + self.assertEqual(s3.hex, '010203040102') + self.assertEqual(s2[9:16].bin, '0000100') + self.assertEqual(s1[0:9].bin, '000000010') + s4 = BitStream(bin='000000010') +\ + BitStream(bin='0000100') + self.assertEqual(s4.bin, '0000000100000100') + s2p = s2[9:16] + s1p = s1[0:9] + s5p = s1p + s2p + s5 = s1[0:9] + s2[9:16] + self.assertEqual(s5.bin, '0000000100000100') + + def testMoreAdding(self): + s = BitStream(bin='00') + BitStream(bin='') + BitStream(bin='11') + self.assertEqual(s.bin, '0011') + s = '0b01' + s += BitStream('0b11') + self.assertEqual(s.bin, '0111') + s = BitStream('0x00') + t = BitStream('0x11') + s += t + self.assertEqual(s.hex, '0011') + self.assertEqual(t.hex, '11') + s += s + self.assertEqual(s.hex, '00110011') + + def testRadd(self): + s = '0xff' + BitStream('0xee') + self.assertEqual(s.hex, 'ffee') + + + def testTruncateAsserts(self): + s = BitStream('0x001122') + s.bytepos = 2 + del s[-s.len:] + self.assertEqual(s.bytepos, 0) + s.append('0x00') + s.append('0x1122') + s.bytepos = 2 + del s[:s.len] + self.assertEqual(s.bytepos, 0) + s.append('0x00') + + def testOverwriteErrors(self): + s = BitStream(bin='11111') + self.assertRaises(ValueError, s.overwrite, BitStream(bin='1'), -10) + self.assertRaises(ValueError, s.overwrite, BitStream(bin='1'), 6) + self.assertRaises(ValueError, s.overwrite, BitStream(bin='11111'), 1) + + def testDeleteBits(self): + s = BitStream(bin='000111100000') + s.bitpos = 4 + del s[4:8] + self.assertEqual(s.bin, '00010000') + del s[4:1004] + self.assertTrue(s.bin, '0001') + + def testDeleteBitsWithPosition(self): + s = BitStream(bin='000111100000') + del s[4:8] + self.assertEqual(s.bin, '00010000') + + def testDeleteBytes(self): + s = BitStream('0x00112233') + del s[8:8] + self.assertEqual(s.hex, '00112233') + self.assertEqual(s.pos, 0) + del s[8:16] + self.assertEqual(s.hex, '002233') + self.assertEqual(s.bytepos, 0) + del s[:24] + self.assertFalse(s) + self.assertEqual(s.pos, 0) + + def testGetItemWithPositivePosition(self): + s = BitStream(bin='0b1011') + self.assertEqual(s[0], True) + self.assertEqual(s[1], False) + self.assertEqual(s[2], True) + self.assertEqual(s[3], True) + self.assertRaises(IndexError, s.__getitem__, 4) + + def testGetItemWithNegativePosition(self): + s = BitStream(bin='1011') + self.assertEqual(s[-1], True) + self.assertEqual(s[-2], True) + self.assertEqual(s[-3], False) + self.assertEqual(s[-4], True) + self.assertRaises(IndexError, s.__getitem__, -5) + + def testSlicing(self): + s = ConstBitStream(hex='0123456789') + self.assertEqual(s[0:8].hex, '01') + self.assertFalse(s[0:0]) + self.assertFalse(s[23:20]) + self.assertEqual(s[8:12].bin, '0010') + self.assertEqual(s[32:80], '0x89') + + def testNegativeSlicing(self): + s = ConstBitStream(hex='012345678') + self.assertEqual(s[:-8].hex, '0123456') + self.assertEqual(s[-16:-8].hex, '56') + self.assertEqual(s[-24:].hex, '345678') + self.assertEqual(s[-1000:-24], '0x012') + + def testLen(self): + s = BitStream() + self.assertEqual(len(s), 0) + s.append(BitStream(bin='001')) + self.assertEqual(len(s), 3) + + def testJoin(self): + s1 = BitStream(bin='0') + s2 = BitStream(bin='1') + s3 = BitStream(bin='000') + s4 = BitStream(bin='111') + strings = [s1, s2, s1, s3, s4] + s = BitStream().join(strings) + self.assertEqual(s.bin, '010000111') + + def testJoin2(self): + s1 = BitStream(hex='00112233445566778899aabbccddeeff') + s2 = BitStream(bin='0b000011') + bsl = [s1[0:32], s1[4:12], s2, s2, s2, s2] + s = ConstBitStream().join(bsl) + self.assertEqual(s.hex, '00112233010c30c3') + + bsl = [BitStream(uint=j, length=12) for j in range(10) for i in range(10)] + s = BitStream().join(bsl) + self.assertEqual(s.length, 1200) + + + def testPos(self): + s = BitStream(bin='1') + self.assertEqual(s.bitpos, 0) + s.read(1) + self.assertEqual(s.bitpos, 1) + + def testWritingData(self): + strings = [BitStream(bin=x) for x in ['0', '001', '0011010010', '010010', '1011']] + s = BitStream().join(strings) + s2 = BitStream(bytes=s.bytes) + self.assertEqual(s2.bin, '000100110100100100101011') + s2.append(BitStream(bin='1')) + s3 = BitStream(bytes=s2.tobytes()) + self.assertEqual(s3.bin, '00010011010010010010101110000000') + + def testWritingDataWithOffsets(self): + s1 = BitStream(bytes=b'\x10') + s2 = BitStream(bytes=b'\x08\x00', length=8, offset=1) + s3 = BitStream(bytes=b'\x04\x00', length=8, offset=2) + self.assertTrue(s1 == s2) + self.assertTrue(s2 == s3) + self.assertTrue(s1.bytes == s2.bytes) + self.assertTrue(s2.bytes == s3.bytes) + + def testVariousThings1(self): + hexes = ['12345678', '87654321', 'ffffffffff', 'ed', '12ec'] + bins = ['001010', '1101011', '0010000100101110110110', '11', '011'] + bsl = [] + for (hex, bin) in list(zip(hexes, bins)) * 5: + bsl.append(BitStream(hex=hex)) + bsl.append(BitStream(bin=bin)) + s = BitStream().join(bsl) + for (hex, bin) in list(zip(hexes, bins)) * 5: + h = s.read(4 * len(hex)) + b = s.read(len(bin)) + self.assertEqual(h.hex, hex) + self.assertEqual(b.bin, bin) + + def testVariousThings2(self): + s1 = BitStream(hex="0x1f08")[:13] + self.assertEqual(s1.bin, '0001111100001') + s2 = BitStream(bin='0101') + self.assertEqual(s2.bin, '0101') + s1.append(s2) + self.assertEqual(s1.length, 17) + self.assertEqual(s1.bin, '00011111000010101') + s1 = s1[3:8] + self.assertEqual(s1.bin, '11111') + + def testVariousThings3(self): + s1 = BitStream(hex='0x012480ff')[2:27] + s2 = s1 + s1 + self.assertEqual(s2.length, 50) + s3 = s2[0:25] + s4 = s2[25:50] + self.assertEqual(s3.bin, s4.bin) + + def testPeekBit(self): + s = BitStream(bin='01') + self.assertEqual(s.peek(1), [0]) + self.assertEqual(s.peek(1), [0]) + self.assertEqual(s.read(1), [0]) + self.assertEqual(s.peek(1), [1]) + self.assertEqual(s.peek(1), [1]) + + s = BitStream(bytes=b'\x1f', offset=3) + self.assertEqual(s.len, 5) + self.assertEqual(s.peek(5).bin, '11111') + self.assertEqual(s.peek(5).bin, '11111') + s.pos += 1 + self.assertRaises(bitstring.ReadError, s.peek, 5) + + s = BitStream(hex='001122334455') + self.assertEqual(s.peek(8).hex, '00') + self.assertEqual(s.read(8).hex, '00') + s.pos += 33 + self.assertRaises(bitstring.ReadError, s.peek, 8) + + s = BitStream(hex='001122334455') + self.assertEqual(s.peek(8 * 2).hex, '0011') + self.assertEqual(s.read(8 * 3).hex, '001122') + self.assertEqual(s.peek(8 * 3).hex, '334455') + self.assertRaises(bitstring.ReadError, s.peek, 25) + + def testAdvanceBit(self): + s = BitStream(hex='0xff') + s.bitpos = 6 + s.pos += 1 + self.assertEqual(s.bitpos, 7) + s.bitpos += 1 + try: + s.pos += 1 + self.assertTrue(False) + except ValueError: + pass + + def testAdvanceByte(self): + s = BitStream(hex='0x010203') + s.bytepos += 1 + self.assertEqual(s.bytepos, 1) + s.bytepos += 1 + self.assertEqual(s.bytepos, 2) + s.bytepos += 1 + try: + s.bytepos += 1 + self.assertTrue(False) + except ValueError: + pass + + def testRetreatBit(self): + s = BitStream(hex='0xff') + try: + s.pos -= 1 + self.assertTrue(False) + except ValueError: + pass + s.pos = 5 + s.pos -= 1 + self.assertEqual(s.pos, 4) + + def testRetreatByte(self): + s = BitStream(hex='0x010203') + try: + s.bytepos -= 1 + self.assertTrue(False) + except ValueError: + pass + s.bytepos = 3 + s.bytepos -= 1 + self.assertEqual(s.bytepos, 2) + self.assertEqual(s.read(8).hex, '03') + + def testCreationByAuto(self): + s = BitStream('0xff') + self.assertEqual(s.hex, 'ff') + s = BitStream('0b00011') + self.assertEqual(s.bin, '00011') + self.assertRaises(bitstring.CreationError, BitStream, 'hello') + s1 = BitStream(bytes=b'\xf5', length=3, offset=5) + s2 = BitStream(s1, length=1, offset=1) + self.assertEqual(s2, '0b0') + s = BitStream(bytes=b'\xff', offset=2) + t = BitStream(s, offset=2) + self.assertEqual(t, '0b1111') + self.assertRaises(TypeError, BitStream, auto=1.2) + + def testCreationByAuto2(self): + s = BitStream('bin=001') + self.assertEqual(s.bin, '001') + s = BitStream('oct=0o007') + self.assertEqual(s.oct, '007') + s = BitStream('hex=123abc') + self.assertEqual(s, '0x123abc') + + s = BitStream('bin:2=01') + self.assertEqual(s, '0b01') + for s in ['bin:1=01', 'bits:4=0b1', 'oct:3=000', 'hex:4=0x1234']: + self.assertRaises(bitstring.CreationError, BitStream, s) + + def testInsertUsingAuto(self): + s = BitStream('0xff') + s.insert('0x00', 4) + self.assertEqual(s.hex, 'f00f') + self.assertRaises(ValueError, s.insert, 'ff') + + def testOverwriteUsingAuto(self): + s = BitStream('0x0110') + s.overwrite('0b1') + self.assertEqual(s.hex, '8110') + s.overwrite('') + self.assertEqual(s.hex, '8110') + self.assertRaises(ValueError, s.overwrite, '0bf') + + def testFindUsingAuto(self): + s = BitStream('0b000000010100011000') + self.assertTrue(s.find('0b101')) + self.assertEqual(s.pos, 7) + + def testFindbytealignedUsingAuto(self): + s = BitStream('0x00004700') + self.assertTrue(s.find('0b01000111', bytealigned=True)) + self.assertEqual(s.bytepos, 2) + + def testAppendUsingAuto(self): + s = BitStream('0b000') + s.append('0b111') + self.assertEqual(s.bin, '000111') + s.append('0b0') + self.assertEqual(s.bin, '0001110') + + def testSplitByteAlignedUsingAuto(self): + s = BitStream('0x000143563200015533000123') + sections = s.split('0x0001') + self.assertEqual(next(sections).hex, '') + self.assertEqual(next(sections).hex, '0001435632') + self.assertEqual(next(sections).hex, '00015533') + self.assertEqual(next(sections).hex, '000123') + self.assertRaises(StopIteration, next, sections) + + def testSplitByteAlignedWithSelf(self): + s = BitStream('0x1234') + sections = s.split(s) + self.assertEqual(next(sections).hex, '') + self.assertEqual(next(sections).hex, '1234') + self.assertRaises(StopIteration, next, sections) + + def testPrepend(self): + s = BitStream('0b000') + s.prepend('0b11') + self.assertEqual(s.bin, '11000') + s.prepend(s) + self.assertEqual(s.bin, '1100011000') + s.prepend('') + self.assertEqual(s.bin, '1100011000') + + def testNullSlice(self): + s = BitStream('0x111') + t = s[1:1] + self.assertEqual(t._datastore.bytelength, 0) + + def testMultipleAutos(self): + s = BitStream('0xa') + s.prepend('0xf') + s.append('0xb') + self.assertEqual(s, '0xfab') + s.prepend(s) + s.append('0x100') + s.overwrite('0x5', 4) + self.assertEqual(s, '0xf5bfab100') + + def testReverse(self): + s = BitStream('0b0011') + s.reverse() + self.assertEqual(s.bin, '1100') + s = BitStream('0b10') + s.reverse() + self.assertEqual(s.bin, '01') + s = BitStream() + s.reverse() + self.assertEqual(s.bin, '') + + def testInitWithConcatenatedStrings(self): + s = BitStream('0xff 0Xee 0xd 0xcc') + self.assertEqual(s.hex, 'ffeedcc') + s = BitStream('0b0 0B111 0b001') + self.assertEqual(s.bin, '0111001') + s += '0b1' + '0B1' + self.assertEqual(s.bin, '011100111') + s = BitStream(hex='ff0xee') + self.assertEqual(s.hex, 'ffee') + s = BitStream(bin='000b0b11') + self.assertEqual(s.bin, '0011') + s = BitStream(' 0o123 0O 7 0 o1') + self.assertEqual(s.oct, '12371') + s += ' 0 o 332' + self.assertEqual(s.oct, '12371332') + + def testEquals(self): + s1 = BitStream('0b01010101') + s2 = BitStream('0b01010101') + self.assertTrue(s1 == s2) + s3 = BitStream() + s4 = BitStream() + self.assertTrue(s3 == s4) + self.assertFalse(s3 != s4) + s5 = BitStream(bytes=b'\xff', offset=2, length=3) + s6 = BitStream('0b111') + self.assertTrue(s5 == s6) + class A(object): + pass + self.assertFalse(s5 == A()) + + def testLargeEquals(self): + s1 = BitStream(1000000) + s2 = BitStream(1000000) + s1.set(True, [-1, 55, 53214, 534211, 999999]) + s2.set(True, [-1, 55, 53214, 534211, 999999]) + self.assertEqual(s1, s2) + s1.set(True, 800000) + self.assertNotEqual(s1, s2) + + def testNotEquals(self): + s1 = BitStream('0b0') + s2 = BitStream('0b1') + self.assertTrue(s1 != s2) + self.assertFalse(s1 != BitStream('0b0')) + + def testEqualityWithAutoInitialised(self): + a = BitStream('0b00110111') + self.assertTrue(a == '0b00110111') + self.assertTrue(a == '0x37') + self.assertTrue('0b0011 0111' == a) + self.assertTrue('0x3 0x7' == a) + self.assertFalse(a == '0b11001000') + self.assertFalse('0x3737' == a) + + def testInvertSpecialMethod(self): + s = BitStream('0b00011001') + self.assertEqual((~s).bin, '11100110') + self.assertEqual((~BitStream('0b0')).bin, '1') + self.assertEqual((~BitStream('0b1')).bin, '0') + self.assertTrue(~~s == s) + + def testInvertBitPosition(self): + s = ConstBitStream('0xefef') + s.pos = 8 + t = ~s + self.assertEqual(s.pos, 8) + self.assertEqual(t.pos, 0) + + def testInvertSpecialMethodErrors(self): + s = BitStream() + self.assertRaises(bitstring.Error, s.__invert__) + + def testJoinWithAuto(self): + s = BitStream().join(['0xf', '0b00', BitStream(bin='11')]) + self.assertEqual(s, '0b11110011') + + def testAutoBitStringCopy(self): + s = BitStream('0xabcdef') + t = BitStream(s) + self.assertEqual(t.hex, 'abcdef') + del s[-8:] + self.assertEqual(t.hex, 'abcdef') + +class Multiplication(unittest.TestCase): + + def testMultiplication(self): + a = BitStream('0xff') + b = a * 8 + self.assertEqual(b, '0xffffffffffffffff') + b = 4 * a + self.assertEqual(b, '0xffffffff') + self.assertTrue(1 * a == a * 1 == a) + c = a * 0 + self.assertFalse(c) + a *= 3 + self.assertEqual(a, '0xffffff') + a *= 0 + self.assertFalse(a) + one = BitStream('0b1') + zero = BitStream('0b0') + mix = one * 2 + 3 * zero + 2 * one * 2 + self.assertEqual(mix, '0b110001111') + q = BitStream() + q *= 143 + self.assertFalse(q) + q += [True, True, False] + q.pos += 2 + q *= 0 + self.assertFalse(q) + self.assertEqual(q.bitpos, 0) + + def testMultiplicationWithFiles(self): + a = BitStream(filename='test.m1v') + b = a.len + a *= 3 + self.assertEqual(a.len, 3 * b) + + def testMultiplicationErrors(self): + a = BitStream('0b1') + b = BitStream('0b0') + self.assertRaises(ValueError, a.__mul__, -1) + self.assertRaises(ValueError, a.__imul__, -1) + self.assertRaises(ValueError, a.__rmul__, -1) + self.assertRaises(TypeError, a.__mul__, 1.2) + self.assertRaises(TypeError, a.__rmul__, b) + self.assertRaises(TypeError, a.__imul__, b) + + def testFileAndMemEquivalence(self): + a = ConstBitStream(filename='smalltestfile') + b = BitStream(filename='smalltestfile') + self.assertTrue(isinstance(a._datastore._rawarray, bitstring.MmapByteArray)) + self.assertTrue(isinstance(b._datastore._rawarray, bytearray)) + self.assertEqual(a._datastore.getbyte(0), b._datastore.getbyte(0)) + self.assertEqual(a._datastore.getbyteslice(1, 5), bytearray(b._datastore.getbyteslice(1, 5))) + + +class BitWise(unittest.TestCase): + + def testBitwiseAnd(self): + a = BitStream('0b01101') + b = BitStream('0b00110') + self.assertEqual((a & b).bin, '00100') + self.assertEqual((a & '0b11111'), a) + self.assertRaises(ValueError, a.__and__, '0b1') + self.assertRaises(ValueError, b.__and__, '0b110111111') + c = BitStream('0b0011011') + c.pos = 4 + d = c & '0b1111000' + self.assertEqual(d.pos, 0) + self.assertEqual(d.bin, '0011000') + d = '0b1111000' & c + self.assertEqual(d.bin, '0011000') + + def testBitwiseOr(self): + a = BitStream('0b111001001') + b = BitStream('0b011100011') + self.assertEqual((a | b).bin, '111101011') + self.assertEqual((a | '0b000000000'), a) + self.assertRaises(ValueError, a.__or__, '0b0000') + self.assertRaises(ValueError, b.__or__, a + '0b1') + a = '0xff00' | BitStream('0x00f0') + self.assertEqual(a.hex, 'fff0') + + def testBitwiseXor(self): + a = BitStream('0b111001001') + b = BitStream('0b011100011') + self.assertEqual((a ^ b).bin, '100101010') + self.assertEqual((a ^ '0b111100000').bin, '000101001') + self.assertRaises(ValueError, a.__xor__, '0b0000') + self.assertRaises(ValueError, b.__xor__, a + '0b1') + a = '0o707' ^ BitStream('0o777') + self.assertEqual(a.oct, '070') + +class Split(unittest.TestCase): + + def testSplit(self): + a = BitStream('0b0 010100111 010100 0101 010') + a.pos = 20 + subs = [i.bin for i in a.split('0b010')] + self.assertEqual(subs, ['0', '010100111', '010100', '0101', '010']) + self.assertEqual(a.pos, 20) + + def testSplitCornerCases(self): + a = BitStream('0b000000') + bsl = a.split('0b1', False) + self.assertEqual(next(bsl), a) + self.assertRaises(StopIteration, next, bsl) + b = BitStream() + bsl = b.split('0b001', False) + self.assertFalse(next(bsl)) + self.assertRaises(StopIteration, next, bsl) + + def testSplitErrors(self): + a = BitStream('0b0') + b = a.split('', False) + self.assertRaises(ValueError, next, b) + + def testSliceWithOffset(self): + a = BitStream(bytes=b'\x00\xff\x00', offset=7) + b = a[7:12] + self.assertEqual(b.bin, '11000') + + def testSplitWithMaxsplit(self): + a = BitStream('0xaabbccbbccddbbccddee') + self.assertEqual(len(list(a.split('0xbb', bytealigned=True))), 4) + bsl = list(a.split('0xbb', count=1, bytealigned=True)) + self.assertEqual((len(bsl), bsl[0]), (1, '0xaa')) + bsl = list(a.split('0xbb', count=2, bytealigned=True)) + self.assertEqual(len(bsl), 2) + self.assertEqual(bsl[0], '0xaa') + self.assertEqual(bsl[1], '0xbbcc') + + def testSplitMore(self): + s = BitStream('0b1100011001110110') + for i in range(10): + a = list(s.split('0b11', False, count=i)) + b = list(s.split('0b11', False))[:i] + self.assertEqual(a, b) + b = s.split('0b11', count=-1) + self.assertRaises(ValueError, next, b) + + def testSplitStartbit(self): + a = BitStream('0b0010101001000000001111') + bsl = a.split('0b001', bytealigned=False, start=1) + self.assertEqual([x.bin for x in bsl], ['010101', '001000000', '001111']) + b = a.split('0b001', start=-100) + self.assertRaises(ValueError, next, b) + b = a.split('0b001', start=23) + self.assertRaises(ValueError, next, b) + b = a.split('0b1', start=10, end=9) + self.assertRaises(ValueError, next, b) + + def testSplitStartbitByteAligned(self): + a = BitStream('0x00ffffee') + bsl = list(a.split('0b111', start=9, bytealigned=True)) + self.assertEqual([x.bin for x in bsl], ['1111111', '11111111', '11101110']) + + def testSplitEndbit(self): + a = BitStream('0b000010001001011') + bsl = list(a.split('0b1', bytealigned=False, end=14)) + self.assertEqual([x.bin for x in bsl], ['0000', '1000', '100', '10', '1']) + self.assertEqual(list(a[4:12].split('0b0', False)), list(a.split('0b0', start=4, end=12))) + # Shouldn't raise ValueError + bsl = list(a.split('0xffee', end=15)) + # Whereas this one will when we call next() + bsl = a.split('0xffee', end=16) + self.assertRaises(ValueError, next, bsl) + + def testSplitEndbitByteAligned(self): + a = BitStream('0xff00ff')[:22] + bsl = list(a.split('0b 0000 0000 111', end=19)) + self.assertEqual([x.bin for x in bsl], ['11111111', '00000000111']) + bsl = list(a.split('0b 0000 0000 111', end=18)) + self.assertEqual([x.bin for x in bsl], ['111111110000000011']) + + def testSplitMaxSplit(self): + a = BitStream('0b1' * 20) + for i in range(10): + bsl = list(a.split('0b1', count=i)) + self.assertEqual(len(bsl), i) + + ####################### + + def testPositionInSlice(self): + a = BitStream('0x00ffff00') + a.bytepos = 2 + b = a[8:24] + self.assertEqual(b.bytepos, 0) + + def testFindByteAlignedWithBits(self): + a = BitStream('0x00112233445566778899') + a.find('0b0001', bytealigned=True) + self.assertEqual(a.bitpos, 8) + + def testFindStartbitNotByteAligned(self): + a = BitStream('0b0010000100') + found = a.find('0b1', start=4) + self.assertEqual((found, a.bitpos), ((7,), 7)) + found = a.find('0b1', start=2) + self.assertEqual((found, a.bitpos), ((2,), 2)) + found = a.find('0b1', bytealigned=False, start=8) + self.assertEqual((found, a.bitpos), ((), 2)) + + def testFindEndbitNotByteAligned(self): + a = BitStream('0b0010010000') + found = a.find('0b1', bytealigned=False, end=2) + self.assertEqual((found, a.bitpos), ((), 0)) + found = a.find('0b1', end=3) + self.assertEqual((found, a.bitpos), ((2,), 2)) + found = a.find('0b1', bytealigned=False, start=3, end=5) + self.assertEqual((found, a.bitpos), ((), 2)) + found = a.find('0b1', start=3, end=6) + self.assertEqual((found[0], a.bitpos), (5, 5)) + + def testFindStartbitByteAligned(self): + a = BitStream('0xff001122ff0011ff') + a.pos = 40 + found = a.find('0x22', start=23, bytealigned=True) + self.assertEqual((found, a.bytepos), ((24,), 3)) + a.bytepos = 4 + found = a.find('0x22', start=24, bytealigned=True) + self.assertEqual((found, a.bytepos), ((24,), 3)) + found = a.find('0x22', start=25, bytealigned=True) + self.assertEqual((found, a.pos), ((), 24)) + found = a.find('0b111', start=40, bytealigned=True) + self.assertEqual((found, a.pos), ((56,), 56)) + + def testFindEndbitByteAligned(self): + a = BitStream('0xff001122ff0011ff') + found = a.find('0x22', end=31, bytealigned=True) + self.assertFalse(found) + self.assertEqual(a.pos, 0) + found = a.find('0x22', end=32, bytealigned=True) + self.assertTrue(found) + self.assertEqual(a.pos, 24) + self.assertEqual(found[0], 24) + + def testFindStartEndbitErrors(self): + a = BitStream('0b00100') + self.assertRaises(ValueError, a.find, '0b1', bytealigned=False, start=-100) + self.assertRaises(ValueError, a.find, '0b1', end=6) + self.assertRaises(ValueError, a.find, '0b1', start=4, end=3) + b = BitStream('0x0011223344') + self.assertRaises(ValueError, a.find, '0x22', bytealigned=True, start=-100) + self.assertRaises(ValueError, a.find, '0x22', end=41, bytealigned=True) + + def testPrependAndAppendAgain(self): + c = BitStream('0x1122334455667788') + c.bitpos = 40 + c.prepend('0b1') + self.assertEqual(c.bitpos, 41) + c = BitStream() + c.prepend('0x1234') + self.assertEqual(c.bytepos, 2) + c = BitStream() + c.append('0x1234') + self.assertEqual(c.bytepos, 0) + s = BitStream(bytes=b'\xff\xff', offset=2) + self.assertEqual(s.length, 14) + t = BitStream(bytes=b'\x80', offset=1, length=2) + s.prepend(t) + self.assertEqual(s, '0x3fff') + + def testFindAll(self): + a = BitStream('0b11111') + p = a.findall('0b1') + self.assertEqual(list(p), [0, 1, 2, 3, 4]) + p = a.findall('0b11') + self.assertEqual(list(p), [0, 1, 2, 3]) + p = a.findall('0b10') + self.assertEqual(list(p), []) + a = BitStream('0x4733eeff66554747335832434547') + p = a.findall('0x47', bytealigned=True) + self.assertEqual(list(p), [0, 6 * 8, 7 * 8, 13 * 8]) + p = a.findall('0x4733', bytealigned=True) + self.assertEqual(list(p), [0, 7 * 8]) + a = BitStream('0b1001001001001001001') + p = a.findall('0b1001', bytealigned=False) + self.assertEqual(list(p), [0, 3, 6, 9, 12, 15]) + self.assertEqual(a.pos, 15) + + def testFindAllGenerator(self): + a = BitStream('0xff1234512345ff1234ff12ff') + p = a.findall('0xff', bytealigned=True) + self.assertEqual(next(p), 0) + self.assertEqual(next(p), 6 * 8) + self.assertEqual(next(p), 9 * 8) + self.assertEqual(next(p), 11 * 8) + self.assertRaises(StopIteration, next, p) + + def testFindAllCount(self): + s = BitStream('0b1') * 100 + for i in [0, 1, 23]: + self.assertEqual(len(list(s.findall('0b1', count=i))), i) + b = s.findall('0b1', bytealigned=True, count=-1) + self.assertRaises(ValueError, next, b) + + def testContains(self): + a = BitStream('0b1') + '0x0001dead0001' + self.assertTrue('0xdead' in a) + self.assertEqual(a.pos, 0) + self.assertFalse('0xfeed' in a) + + def testRepr(self): + max = bitstring.MAX_CHARS + bls = ['', '0b1', '0o5', '0x43412424f41', '0b00101001010101'] + for bs in bls: + a = BitStream(bs) + b = eval(a.__repr__()) + self.assertTrue(a == b) + for f in [ConstBitStream(filename='test.m1v'), + ConstBitStream(filename='test.m1v', length=17), + ConstBitStream(filename='test.m1v', length=23, offset=23102)]: + f2 = eval(f.__repr__()) + self.assertEqual(f._datastore._rawarray.source.name, f2._datastore._rawarray.source.name) + self.assertTrue(f2.tobytes() == f.tobytes()) + a = BitStream('0b1') + self.assertEqual(repr(a), "BitStream('0b1')") + a += '0b11' + self.assertEqual(repr(a), "BitStream('0b111')") + a += '0b1' + self.assertEqual(repr(a), "BitStream('0xf')") + a *= max + self.assertEqual(repr(a), "BitStream('0x" + "f" * max + "')") + a += '0xf' + self.assertEqual(repr(a), "BitStream('0x" + "f" * max + "...') # length=%d" % (max * 4 + 4)) + + def testPrint(self): + s = BitStream(hex='0x00') + self.assertEqual('0x' + s.hex, s.__str__()) + s = BitStream(filename='test.m1v') + self.assertEqual('0x' + s[0:bitstring.MAX_CHARS * 4].hex + '...', s.__str__()) + self.assertEqual(BitStream().__str__(), '') + s = BitStream('0b11010') + self.assertEqual('0b' + s.bin, s.__str__()) + s = BitStream('0x12345678901234567890,0b1') + self.assertEqual('0x12345678901234567890, 0b1', s.__str__()) + + def testIter(self): + a = BitStream('0b001010') + b = BitStream() + for bit in a: + b.append(ConstBitStream(bool=bit)) + self.assertEqual(a, b) + + def testDelitem(self): + a = BitStream('0xffee') + del a[0:8] + self.assertEqual(a.hex, 'ee') + del a[0:8] + self.assertFalse(a) + del a[10:12] + self.assertFalse(a) + + def testNonZeroBitsAtStart(self): + a = BitStream(bytes=b'\xff', offset=2) + b = BitStream('0b00') + b += a + self.assertTrue(b == '0b0011 1111') + #self.assertEqual(a._datastore.rawbytes, b'\xff') + self.assertEqual(a.tobytes(), b'\xfc') + + def testNonZeroBitsAtEnd(self): + a = BitStream(bytes=b'\xff', length=5) + #self.assertEqual(a._datastore.rawbytes, b'\xff') + b = BitStream('0b00') + a += b + self.assertTrue(a == '0b1111100') + self.assertEqual(a.tobytes(), b'\xf8') + self.assertRaises(ValueError, a._getbytes) + + def testNewOffsetErrors(self): + self.assertRaises(bitstring.CreationError, BitStream, hex='ff', offset=-1) + self.assertRaises(bitstring.CreationError, BitStream, '0xffffffff', offset=33) + + def testSliceStep(self): + a = BitStream('0x3') + b = a[::1] + self.assertEqual(a, b) + self.assertEqual(a[2:4:1], '0b11') + self.assertEqual(a[0:2:1], '0b00') + self.assertEqual(a[:3], '0o1') + + a = BitStream('0x0011223344556677') + self.assertEqual(a[-8:], '0x77') + self.assertEqual(a[:-24], '0x0011223344') + self.assertEqual(a[-1000:-24], '0x0011223344') + + def testInterestingSliceStep(self): + a = BitStream('0b0011000111') + self.assertEqual(a[7:3:-1], '0b1000') + self.assertEqual(a[9:2:-1], '0b1110001') + self.assertEqual(a[8:2:-2], '0b100') + self.assertEqual(a[100:-20:-3], '0b1010') + self.assertEqual(a[100:-20:-1], '0b1110001100') + self.assertEqual(a[10:2:-1], '0b1110001') + self.assertEqual(a[100:2:-1], '0b1110001') + + def testInsertionOrderAndBitpos(self): + b = BitStream() + b[0:0] = '0b0' + b[0:0] = '0b1' + self.assertEqual(b, '0b10') + self.assertEqual(b.bitpos, 1) + a = BitStream() + a.insert('0b0') + a.insert('0b1') + self.assertEqual(a, '0b01') + self.assertEqual(a.bitpos, 2) + + def testOverwriteOrderAndBitpos(self): + a = BitStream('0xff') + a.overwrite('0xa') + self.assertEqual(a, '0xaf') + self.assertEqual(a.bitpos, 4) + a.overwrite('0xb') + self.assertEqual(a, '0xab') + self.assertEqual(a.bitpos, 8) + self.assertRaises(ValueError, a.overwrite, '0b1') + a.overwrite('0xa', 4) + self.assertEqual(a, '0xaa') + self.assertEqual(a.bitpos, 8) + a.overwrite(a, 0) + self.assertEqual(a, '0xaa') + + def testInitSliceWithInt(self): + a = BitStream(length=8) + a[:] = 100 + self.assertEqual(a.uint, 100) + a[0] = 1 + self.assertEqual(a.bin, '11100100') + a[1] = 0 + self.assertEqual(a.bin, '10100100') + a[-1] = -1 + self.assertEqual(a.bin, '10100101') + a[-3:] = -2 + self.assertEqual(a.bin, '10100110') + + def testInitSliceWithIntErrors(self): + a = BitStream('0b0000') + self.assertRaises(ValueError, a.__setitem__, slice(0, 4), 16) + self.assertRaises(ValueError, a.__setitem__, slice(0, 4), -9) + self.assertRaises(ValueError, a.__setitem__, 0, 2) + self.assertRaises(ValueError, a.__setitem__, 0, -2) + + def testReverseWithSlice(self): + a = BitStream('0x0012ff') + a.reverse() + self.assertEqual(a, '0xff4800') + a.reverse(8, 16) + self.assertEqual(a, '0xff1200') + b = a[8:16] + b.reverse() + a[8:16] = b + self.assertEqual(a, '0xff4800') + + def testReverseWithSliceErrors(self): + a = BitStream('0x123') + self.assertRaises(ValueError, a.reverse, -1, 4) + self.assertRaises(ValueError, a.reverse, 10, 9) + self.assertRaises(ValueError, a.reverse, 1, 10000) + + def testInitialiseFromList(self): + a = BitStream([]) + self.assertFalse(a) + a = BitStream([True, False, [], [0], 'hello']) + self.assertEqual(a, '0b10011') + a += [] + self.assertEqual(a, '0b10011') + a += [True, False, True] + self.assertEqual(a, '0b10011101') + a.find([12, 23]) + self.assertEqual(a.pos, 3) + self.assertEqual([1, 0, False, True], BitStream('0b1001')) + a = [True] + BitStream('0b1') + self.assertEqual(a, '0b11') + + def testInitialiseFromTuple(self): + a = BitStream(()) + self.assertFalse(a) + a = BitStream((0, 1, '0', '1')) + self.assertEqual('0b0111', a) + a.replace((True, True), []) + self.assertEqual(a, (False, True)) + + def testCut(self): + a = BitStream('0x00112233445') + b = list(a.cut(8)) + self.assertEqual(b, ['0x00', '0x11', '0x22', '0x33', '0x44']) + b = list(a.cut(4, 8, 16)) + self.assertEqual(b, ['0x1', '0x1']) + b = list(a.cut(4, 0, 44, 4)) + self.assertEqual(b, ['0x0', '0x0', '0x1', '0x1']) + a = BitStream() + b = list(a.cut(10)) + self.assertTrue(not b) + + def testCutErrors(self): + a = BitStream('0b1') + b = a.cut(1, 1, 2) + self.assertRaises(ValueError, next, b) + b = a.cut(1, -2, 1) + self.assertRaises(ValueError, next, b) + b = a.cut(0) + self.assertRaises(ValueError, next, b) + b = a.cut(1, count=-1) + self.assertRaises(ValueError, next, b) + + def testCutProblem(self): + s = BitStream('0x1234') + for n in list(s.cut(4)): + s.prepend(n) + self.assertEqual(s, '0x43211234') + + def testJoinFunctions(self): + a = BitStream().join(['0xa', '0xb', '0b1111']) + self.assertEqual(a, '0xabf') + a = BitStream('0b1').join(['0b0' for i in range(10)]) + self.assertEqual(a, '0b0101010101010101010') + a = BitStream('0xff').join([]) + self.assertFalse(a) + + def testAddingBitpos(self): + a = BitStream('0xff') + b = BitStream('0x00') + a.bitpos = b.bitpos = 8 + c = a + b + self.assertEqual(c.bitpos, 0) + + def testIntelligentRead1(self): + a = BitStream(uint=123, length=23) + u = a.read('uint:23') + self.assertEqual(u, 123) + self.assertEqual(a.pos, a.len) + b = BitStream(int=-12, length=44) + i = b.read('int:44') + self.assertEqual(i, -12) + self.assertEqual(b.pos, b.len) + u2, i2 = (a + b).readlist('uint:23, int:44') + self.assertEqual((u2, i2), (123, -12)) + + def testIntelligentRead2(self): + a = BitStream(ue=822) + u = a.read('ue') + self.assertEqual(u, 822) + self.assertEqual(a.pos, a.len) + b = BitStream(se=-1001) + s = b.read('se') + self.assertEqual(s, -1001) + self.assertEqual(b.pos, b.len) + s, u1, u2 = (b + 2 * a).readlist('se, ue, ue') + self.assertEqual((s, u1, u2), (-1001, 822, 822)) + + def testIntelligentRead3(self): + a = BitStream('0x123') + '0b11101' + h = a.read('hex:12') + self.assertEqual(h, '123') + b = a.read('bin: 5') + self.assertEqual(b, '11101') + c = '0b' + b + a + b, h = c.readlist('bin:5, hex:12') + self.assertEqual((b, h), ('11101', '123')) + + def testIntelligentRead4(self): + a = BitStream('0o007') + o = a.read('oct:9') + self.assertEqual(o, '007') + self.assertEqual(a.pos, a.len) + + def testIntelligentRead5(self): + a = BitStream('0x00112233') + c0, c1, c2 = a.readlist('bits:8, bits:8, bits:16') + self.assertEqual((c0, c1, c2), (BitStream('0x00'), BitStream('0x11'), BitStream('0x2233'))) + a.pos = 0 + c = a.read('bits:16') + self.assertEqual(c, BitStream('0x0011')) + + def testIntelligentRead6(self): + a = BitStream('0b000111000') + b1, b2, b3 = a.readlist('bin :3, int: 3, int:3') + self.assertEqual(b1, '000') + self.assertEqual(b2, -1) + self.assertEqual(b3, 0) + + def testIntelligentRead7(self): + a = BitStream('0x1234') + a1, a2, a3, a4 = a.readlist('bin:0, oct:0, hex:0, bits:0') + self.assertTrue(a1 == a2 == a3 == '') + self.assertFalse(a4) + self.assertRaises(ValueError, a.read, 'int:0') + self.assertRaises(ValueError, a.read, 'uint:0') + self.assertEqual(a.pos, 0) + + def testIntelligentRead8(self): + a = BitStream('0x123456') + for t in ['hex:1', 'oct:1', 'hex4', '-5', 'fred', 'bin:-2', + 'uint:p', 'uint:-2', 'int:u', 'int:-3', 'ses', 'uee', '-14']: + self.assertRaises(ValueError, a.read, t) + + def testIntelligentRead9(self): + a = BitStream('0xff') + self.assertEqual(a.read('intle'), -1) + + def testFillerReads1(self): + s = BitStream('0x012345') + t = s.read('bits') + self.assertEqual(s, t) + s.pos = 0 + a, b = s.readlist('hex:8, hex') + self.assertEqual(a, '01') + self.assertEqual(b, '2345') + self.assertTrue(isinstance(b, str)) + s.bytepos = 0 + a, b = s.readlist('bin, hex:20') + self.assertEqual(a, '0000') + self.assertEqual(b, '12345') + self.assertTrue(isinstance(a, str)) + + def testFillerReads2(self): + s = BitStream('0xabcdef') + self.assertRaises(bitstring.Error, s.readlist, 'bits, se') + self.assertRaises(bitstring.Error, s.readlist, 'hex:4, bits, ue, bin:4') + s.pos = 0 + self.assertRaises(bitstring.Error, s.readlist, 'bin, bin') + + def testIntelligentPeek(self): + a = BitStream('0b01, 0x43, 0o4, uint:23=2, se=5, ue=3') + b, c, e = a.peeklist('bin:2, hex:8, oct:3') + self.assertEqual((b, c, e), ('01', '43', '4')) + self.assertEqual(a.pos, 0) + a.pos = 13 + f, g, h = a.peeklist('uint:23, se, ue') + self.assertEqual((f, g, h), (2, 5, 3)) + self.assertEqual(a.pos, 13) + + def testReadMultipleBits(self): + s = BitStream('0x123456789abcdef') + a, b = s.readlist([4, 4]) + self.assertEqual(a, '0x1') + self.assertEqual(b, '0x2') + c, d, e = s.readlist([8, 16, 8]) + self.assertEqual(c, '0x34') + self.assertEqual(d, '0x5678') + self.assertEqual(e, '0x9a') + + def testPeekMultipleBits(self): + s = BitStream('0b1101, 0o721, 0x2234567') + a, b, c, d = s.peeklist([2, 1, 1, 9]) + self.assertEqual(a, '0b11') + self.assertEqual(bool(b), False) + self.assertEqual(bool(c), True) + self.assertEqual(d, '0o721') + self.assertEqual(s.pos, 0) + a, b = s.peeklist([4, 9]) + self.assertEqual(a, '0b1101') + self.assertEqual(b, '0o721') + s.pos = 13 + a, b = s.peeklist([16, 8]) + self.assertEqual(a, '0x2234') + self.assertEqual(b, '0x56') + self.assertEqual(s.pos, 13) + + def testDifficultPrepends(self): + a = BitStream('0b1101011') + b = BitStream() + for i in range(10): + b.prepend(a) + self.assertEqual(b, a * 10) + + def testPackingWrongNumberOfThings(self): + self.assertRaises(bitstring.CreationError, pack, 'bin:1') + self.assertRaises(bitstring.CreationError, pack, '', 100) + + def testPackWithVariousKeys(self): + a = pack('uint10', uint10='0b1') + self.assertEqual(a, '0b1') + b = pack('0b110', **{'0b110': '0xfff'}) + self.assertEqual(b, '0xfff') + + def testPackWithVariableLength(self): + for i in range(1, 11): + a = pack('uint:n', 0, n=i) + self.assertEqual(a.bin, '0' * i) + + def testToBytes(self): + a = BitStream(bytes=b'\xab\x00') + b = a.tobytes() + self.assertEqual(a.bytes, b) + for i in range(7): + del a[-1:] + self.assertEqual(a.tobytes(), b'\xab\x00') + del a[-1:] + self.assertEqual(a.tobytes(), b'\xab') + + def testToFile(self): + a = BitStream('0x0000ff')[:17] + f = open('temp_bitstring_unit_testing_file', 'wb') + a.tofile(f) + f.close() + b = BitStream(filename='temp_bitstring_unit_testing_file') + self.assertEqual(b, '0x000080') + + a = BitStream('0x911111') + del a[:1] + self.assertEqual(a + '0b0', '0x222222') + f = open('temp_bitstring_unit_testing_file', 'wb') + a.tofile(f) + f.close() + b = BitStream(filename='temp_bitstring_unit_testing_file') + self.assertEqual(b, '0x222222') + os.remove('temp_bitstring_unit_testing_file') + + #def testToFileWithLargerFile(self): + # a = BitStream(length=16000000) + # a[1] = '0b1' + # a[-2] = '0b1' + # f = open('temp_bitstring_unit_testing_file' ,'wb') + # a.tofile(f) + # f.close() + # b = BitStream(filename='temp_bitstring_unit_testing_file') + # self.assertEqual(b.len, 16000000) + # self.assertEqual(b[1], True) + # + # f = open('temp_bitstring_unit_testing_file' ,'wb') + # a[1:].tofile(f) + # f.close() + # b = BitStream(filename='temp_bitstring_unit_testing_file') + # self.assertEqual(b.len, 16000000) + # self.assertEqual(b[0], True) + # os.remove('temp_bitstring_unit_testing_file') + + def testTokenParser(self): + tp = bitstring.tokenparser + self.assertEqual(tp('hex'), (True, [('hex', None, None)])) + self.assertEqual(tp('hex=14'), (True, [('hex', None, '14')])) + self.assertEqual(tp('se'), (False, [('se', None, None)])) + self.assertEqual(tp('ue=12'), (False, [('ue', None, '12')])) + self.assertEqual(tp('0xef'), (False, [('0x', None, 'ef')])) + self.assertEqual(tp('uint:12'), (False, [('uint', 12, None)])) + self.assertEqual(tp('int:30=-1'), (False, [('int', 30, '-1')])) + self.assertEqual(tp('bits:10'), (False, [('bits', 10, None)])) + self.assertEqual(tp('bits:10'), (False, [('bits', 10, None)])) + self.assertEqual(tp('123'), (False, [('uint', 123, None)])) + self.assertEqual(tp('123'), (False, [('uint', 123, None)])) + self.assertRaises(ValueError, tp, 'hex12') + self.assertEqual(tp('hex12', ('hex12',)), (False, [('hex12', None, None)])) + self.assertEqual(tp('2*bits:6'), (False, [('bits', 6, None), ('bits', 6, None)])) + + def testAutoFromFileObject(self): + with open('test.m1v', 'rb') as f: + s = ConstBitStream(f, offset=32, length=12) + self.assertEqual(s.uint, 352) + t = ConstBitStream('0xf') + f + self.assertTrue(t.startswith('0xf000001b3160')) + s2 = ConstBitStream(f) + t2 = BitStream('0xc') + t2.prepend(s2) + self.assertTrue(t2.startswith('0x000001b3')) + self.assertTrue(t2.endswith('0xc')) + with open('test.m1v', 'rb') as b: + u = BitStream(bytes=b.read()) + # TODO: u == s2 is much slower than u.bytes == s2.bytes + self.assertEqual(u.bytes, s2.bytes) + + def testFileBasedCopy(self): + with open('smalltestfile', 'rb') as f: + s = BitStream(f) + t = BitStream(s) + s.prepend('0b1') + self.assertEqual(s[1:], t) + s = BitStream(f) + t = copy.copy(s) + t.append('0b1') + self.assertEqual(s, t[:-1]) + + def testBigEndianSynonyms(self): + s = BitStream('0x12318276ef') + self.assertEqual(s.int, s.intbe) + self.assertEqual(s.uint, s.uintbe) + s = BitStream(intbe=-100, length=16) + self.assertEqual(s, 'int:16=-100') + s = BitStream(uintbe=13, length=24) + self.assertEqual(s, 'int:24=13') + s = BitStream('uintbe:32=1000') + self.assertEqual(s, 'uint:32=1000') + s = BitStream('intbe:8=2') + self.assertEqual(s, 'int:8=2') + self.assertEqual(s.read('intbe'), 2) + s.pos = 0 + self.assertEqual(s.read('uintbe'), 2) + + def testBigEndianSynonymErrors(self): + self.assertRaises(bitstring.CreationError, BitStream, uintbe=100, length=15) + self.assertRaises(bitstring.CreationError, BitStream, intbe=100, length=15) + self.assertRaises(bitstring.CreationError, BitStream, 'uintbe:17=100') + self.assertRaises(bitstring.CreationError, BitStream, 'intbe:7=2') + s = BitStream('0b1') + self.assertRaises(bitstring.InterpretError, s._getintbe) + self.assertRaises(bitstring.InterpretError, s._getuintbe) + self.assertRaises(ValueError, s.read, 'uintbe') + self.assertRaises(ValueError, s.read, 'intbe') + + def testLittleEndianUint(self): + s = BitStream(uint=100, length=16) + self.assertEqual(s.uintle, 25600) + s = BitStream(uintle=100, length=16) + self.assertEqual(s.uint, 25600) + self.assertEqual(s.uintle, 100) + s.uintle += 5 + self.assertEqual(s.uintle, 105) + s = BitStream('uintle:32=999') + self.assertEqual(s.uintle, 999) + s.byteswap() + self.assertEqual(s.uint, 999) + s = pack('uintle:24', 1001) + self.assertEqual(s.uintle, 1001) + self.assertEqual(s.length, 24) + self.assertEqual(s.read('uintle'), 1001) + + def testLittleEndianInt(self): + s = BitStream(int=100, length=16) + self.assertEqual(s.intle, 25600) + s = BitStream(intle=100, length=16) + self.assertEqual(s.int, 25600) + self.assertEqual(s.intle, 100) + s.intle += 5 + self.assertEqual(s.intle, 105) + s = BitStream('intle:32=999') + self.assertEqual(s.intle, 999) + s.byteswap() + self.assertEqual(s.int, 999) + s = pack('intle:24', 1001) + self.assertEqual(s.intle, 1001) + self.assertEqual(s.length, 24) + self.assertEqual(s.read('intle'), 1001) + + def testLittleEndianErrors(self): + self.assertRaises(bitstring.CreationError, BitStream, 'uintle:15=10') + self.assertRaises(bitstring.CreationError, BitStream, 'intle:31=-999') + self.assertRaises(bitstring.CreationError, BitStream, uintle=100, length=15) + self.assertRaises(bitstring.CreationError, BitStream, intle=100, length=15) + s = BitStream('0xfff') + self.assertRaises(bitstring.InterpretError, s._getintle) + self.assertRaises(bitstring.InterpretError, s._getuintle) + self.assertRaises(ValueError, s.read, 'uintle') + self.assertRaises(ValueError, s.read, 'intle') + + def testStructTokens1(self): + self.assertEqual(pack('<b', 23), BitStream('intle:8=23')) + self.assertEqual(pack('<B', 23), BitStream('uintle:8=23')) + self.assertEqual(pack('<h', 23), BitStream('intle:16=23')) + self.assertEqual(pack('<H', 23), BitStream('uintle:16=23')) + self.assertEqual(pack('<l', 23), BitStream('intle:32=23')) + self.assertEqual(pack('<L', 23), BitStream('uintle:32=23')) + self.assertEqual(pack('<q', 23), BitStream('intle:64=23')) + self.assertEqual(pack('<Q', 23), BitStream('uintle:64=23')) + self.assertEqual(pack('>b', 23), BitStream('intbe:8=23')) + self.assertEqual(pack('>B', 23), BitStream('uintbe:8=23')) + self.assertEqual(pack('>h', 23), BitStream('intbe:16=23')) + self.assertEqual(pack('>H', 23), BitStream('uintbe:16=23')) + self.assertEqual(pack('>l', 23), BitStream('intbe:32=23')) + self.assertEqual(pack('>L', 23), BitStream('uintbe:32=23')) + self.assertEqual(pack('>q', 23), BitStream('intbe:64=23')) + self.assertEqual(pack('>Q', 23), BitStream('uintbe:64=23')) + self.assertRaises(bitstring.CreationError, pack, '<B', -1) + self.assertRaises(bitstring.CreationError, pack, '<H', -1) + self.assertRaises(bitstring.CreationError, pack, '<L', -1) + self.assertRaises(bitstring.CreationError, pack, '<Q', -1) + + def testStructTokens2(self): + endianness = sys.byteorder + sys.byteorder = 'little' + self.assertEqual(pack('@b', 23), BitStream('intle:8=23')) + self.assertEqual(pack('@B', 23), BitStream('uintle:8=23')) + self.assertEqual(pack('@h', 23), BitStream('intle:16=23')) + self.assertEqual(pack('@H', 23), BitStream('uintle:16=23')) + self.assertEqual(pack('@l', 23), BitStream('intle:32=23')) + self.assertEqual(pack('@L', 23), BitStream('uintle:32=23')) + self.assertEqual(pack('@q', 23), BitStream('intle:64=23')) + self.assertEqual(pack('@Q', 23), BitStream('uintle:64=23')) + sys.byteorder = 'big' + self.assertEqual(pack('@b', 23), BitStream('intbe:8=23')) + self.assertEqual(pack('@B', 23), BitStream('uintbe:8=23')) + self.assertEqual(pack('@h', 23), BitStream('intbe:16=23')) + self.assertEqual(pack('@H', 23), BitStream('uintbe:16=23')) + self.assertEqual(pack('@l', 23), BitStream('intbe:32=23')) + self.assertEqual(pack('@L', 23), BitStream('uintbe:32=23')) + self.assertEqual(pack('@q', 23), BitStream('intbe:64=23')) + self.assertEqual(pack('@Q', 23), BitStream('uintbe:64=23')) + sys.byteorder = endianness + + def testNativeEndianness(self): + s = pack('@2L', 40, 40) + if sys.byteorder == 'little': + self.assertEqual(s, pack('<2L', 40, 40)) + else: + self.assertEqual(sys.byteorder, 'big') + self.assertEqual(s, pack('>2L', 40, 40)) + + def testStructTokens2(self): + s = pack('>hhl', 1, 2, 3) + a, b, c = s.unpack('>hhl') + self.assertEqual((a, b, c), (1, 2, 3)) + s = pack('<QL, >Q \tL', 1001, 43, 21, 9999) + self.assertEqual(s.unpack('<QL, >QL'), [1001, 43, 21, 9999]) + + def testStructTokensMultiplicativeFactors(self): + s = pack('<2h', 1, 2) + a, b = s.unpack('<2h') + self.assertEqual((a, b), (1, 2)) + s = pack('<100q', *range(100)) + self.assertEqual(s.len, 100 * 64) + self.assertEqual(s[44*64:45*64].uintle, 44) + s = pack('@L0B2h', 5, 5, 5) + self.assertEqual(s.unpack('@Lhh'), [5, 5, 5]) + + def testStructTokensErrors(self): + for f in ['>>q', '<>q', 'q>', '2q', 'q', '>-2q', '@a', '>int:8', '>q2']: + self.assertRaises(bitstring.CreationError, pack, f, 100) + + def testImmutableBitStreams(self): + a = ConstBitStream('0x012345') + self.assertEqual(a, '0x012345') + b = BitStream('0xf') + a + self.assertEqual(b, '0xf012345') + try: + a.append(b) + self.assertTrue(False) + except AttributeError: + pass + try: + a.prepend(b) + self.assertTrue(False) + except AttributeError: + pass + try: + a[0] = '0b1' + self.assertTrue(False) + except TypeError: + pass + try: + del a[5] + self.assertTrue(False) + except TypeError: + pass + try: + a.replace('0b1', '0b0') + self.assertTrue(False) + except AttributeError: + pass + try: + a.insert('0b11', 4) + self.assertTrue(False) + except AttributeError: + pass + try: + a.reverse() + self.assertTrue(False) + except AttributeError: + pass + try: + a.reversebytes() + self.assertTrue(False) + except AttributeError: + pass + self.assertEqual(a, '0x012345') + self.assertTrue(isinstance(a, ConstBitStream)) + + def testReverseBytes(self): + a = BitStream('0x123456') + a.byteswap() + self.assertEqual(a, '0x563412') + b = a + '0b1' + b.byteswap() + self.assertEqual('0x123456, 0b1', b) + a = BitStream('0x54') + a.byteswap() + self.assertEqual(a, '0x54') + a = BitStream() + a.byteswap() + self.assertFalse(a) + + def testReverseBytes2(self): + a = BitStream() + a.byteswap() + self.assertFalse(a) + a = BitStream('0x00112233') + a.byteswap(0, 0, 16) + self.assertEqual(a, '0x11002233') + a.byteswap(0, 4, 28) + self.assertEqual(a, '0x12302103') + a.byteswap(start=0, end=18) + self.assertEqual(a, '0x30122103') + self.assertRaises(ValueError, a.byteswap, 0, 10, 2) + self.assertRaises(ValueError, a.byteswap, 0, -4, 4) + self.assertRaises(ValueError, a.byteswap, 0, 24, 48) + a.byteswap(0, 24) + self.assertEqual(a, '0x30122103') + a.byteswap(0, 11, 11) + self.assertEqual(a, '0x30122103') + + def testCapitalsInPack(self): + a = pack('A', A='0b1') + self.assertEqual(a, '0b1') + format = 'bits:4=BL_OFFT, uint:12=width, uint:12=height' + d = {'BL_OFFT': '0b1011', 'width': 352, 'height': 288} + s = bitstring.pack(format, **d) + self.assertEqual(s, '0b1011, uint:12=352, uint:12=288') + a = pack('0X0, uint:8, hex', 45, '0XABcD') + self.assertEqual(a, '0x0, uint:8=45, 0xabCD') + + def testOtherCapitals(self): + a = ConstBitStream('0XABC, 0O0, 0B11') + self.assertEqual(a, 'hex=0Xabc, oct=0, bin=0B11') + + def testEfficientOverwrite(self): + a = BitStream(1000000000) + a.overwrite([1], 123456) + self.assertEqual(a[123456], True) + a.overwrite('0xff', 1) + self.assertEqual(a[0:32:1], '0x7f800000') + b = BitStream('0xffff') + b.overwrite('0x0000') + self.assertEqual(b, '0x0000') + self.assertEqual(b.pos, 16) + c = BitStream(length=1000) + c.overwrite('0xaaaaaaaaaaaa', 81) + self.assertEqual(c[81:81 + 6 * 8], '0xaaaaaaaaaaaa') + self.assertEqual(len(list(c.findall('0b1'))), 24) + s = BitStream(length=1000) + s = s[5:] + s.overwrite('0xffffff', 500) + s.pos = 500 + self.assertEqual(s.read(4 * 8), '0xffffff00') + s.overwrite('0xff', 502) + self.assertEqual(s[502:518], '0xffff') + + def testPeekAndReadListErrors(self): + a = BitStream('0x123456') + self.assertRaises(ValueError, a.read, 'hex:8, hex:8') + self.assertRaises(ValueError, a.peek, 'hex:8, hex:8') + self.assertRaises(TypeError, a.read, 10, 12) + self.assertRaises(TypeError, a.peek, 12, 14) + self.assertRaises(TypeError, a.read, 8, 8) + self.assertRaises(TypeError, a.peek, 80, 80) + + def testStartswith(self): + a = BitStream() + self.assertTrue(a.startswith(BitStream())) + self.assertFalse(a.startswith('0b0')) + a = BitStream('0x12ff') + self.assertTrue(a.startswith('0x1')) + self.assertTrue(a.startswith('0b0001001')) + self.assertTrue(a.startswith('0x12ff')) + self.assertFalse(a.startswith('0x12ff, 0b1')) + self.assertFalse(a.startswith('0x2')) + + def testStartswithStartEnd(self): + s = BitStream('0x123456') + self.assertTrue(s.startswith('0x234', 4)) + self.assertFalse(s.startswith('0x123', end=11)) + self.assertTrue(s.startswith('0x123', end=12)) + self.assertTrue(s.startswith('0x34', 8, 16)) + self.assertFalse(s.startswith('0x34', 7, 16)) + self.assertFalse(s.startswith('0x34', 9, 16)) + self.assertFalse(s.startswith('0x34', 8, 15)) + + def testEndswith(self): + a = BitStream() + self.assertTrue(a.endswith('')) + self.assertFalse(a.endswith(BitStream('0b1'))) + a = BitStream('0xf2341') + self.assertTrue(a.endswith('0x41')) + self.assertTrue(a.endswith('0b001')) + self.assertTrue(a.endswith('0xf2341')) + self.assertFalse(a.endswith('0x1f2341')) + self.assertFalse(a.endswith('0o34')) + + def testEndswithStartEnd(self): + s = BitStream('0x123456') + self.assertTrue(s.endswith('0x234', end=16)) + self.assertFalse(s.endswith('0x456', start=13)) + self.assertTrue(s.endswith('0x456', start=12)) + self.assertTrue(s.endswith('0x34', 8, 16)) + self.assertTrue(s.endswith('0x34', 7, 16)) + self.assertFalse(s.endswith('0x34', 9, 16)) + self.assertFalse(s.endswith('0x34', 8, 15)) + + def testUnhashability(self): + s = BitStream('0xf') + self.assertRaises(TypeError, set, [s]) + self.assertRaises(TypeError, hash, [s]) + + def testConstBitStreamSetCreation(self): + sl = [ConstBitStream(uint=i, length=7) for i in range(15)] + s = set(sl) + self.assertEqual(len(s), 15) + s.add(ConstBitStream('0b0000011')) + self.assertEqual(len(s), 15) + self.assertRaises(TypeError, s.add, BitStream('0b0000011')) + + def testConstBitStreamFunctions(self): + s = ConstBitStream('0xf, 0b1') + self.assertEqual(type(s), ConstBitStream) + t = copy.copy(s) + self.assertEqual(type(t), ConstBitStream) + a = s + '0o3' + self.assertEqual(type(a), ConstBitStream) + b = a[0:4] + self.assertEqual(type(b), ConstBitStream) + b = a[4:3] + self.assertEqual(type(b), ConstBitStream) + b = a[5:2:-1] + self.assertEqual(type(b), ConstBitStream) + b = ~a + self.assertEqual(type(b), ConstBitStream) + b = a << 2 + self.assertEqual(type(b), ConstBitStream) + b = a >> 2 + self.assertEqual(type(b), ConstBitStream) + b = a * 2 + self.assertEqual(type(b), ConstBitStream) + b = a * 0 + self.assertEqual(type(b), ConstBitStream) + b = a & ~a + self.assertEqual(type(b), ConstBitStream) + b = a | ~a + self.assertEqual(type(b), ConstBitStream) + b = a ^ ~a + self.assertEqual(type(b), ConstBitStream) + b = a._slice(4, 4) + self.assertEqual(type(b), ConstBitStream) + b = a.read(4) + self.assertEqual(type(b), ConstBitStream) + + def testConstBitStreamProperties(self): + a = ConstBitStream('0x123123') + try: + a.hex = '0x234' + self.assertTrue(False) + except AttributeError: + pass + try: + a.oct = '0o234' + self.assertTrue(False) + except AttributeError: + pass + try: + a.bin = '0b101' + self.assertTrue(False) + except AttributeError: + pass + try: + a.ue = 3453 + self.assertTrue(False) + except AttributeError: + pass + try: + a.se = -123 + self.assertTrue(False) + except AttributeError: + pass + try: + a.int = 432 + self.assertTrue(False) + except AttributeError: + pass + try: + a.uint = 4412 + self.assertTrue(False) + except AttributeError: + pass + try: + a.intle = 123 + self.assertTrue(False) + except AttributeError: + pass + try: + a.uintle = 4412 + self.assertTrue(False) + except AttributeError: + pass + try: + a.intbe = 123 + self.assertTrue(False) + except AttributeError: + pass + try: + a.uintbe = 4412 + self.assertTrue(False) + except AttributeError: + pass + try: + a.intne = 123 + self.assertTrue(False) + except AttributeError: + pass + try: + a.uintne = 4412 + self.assertTrue(False) + except AttributeError: + pass + try: + a.bytes = b'hello' + self.assertTrue(False) + except AttributeError: + pass + + def testConstBitStreamMisc(self): + a = ConstBitStream('0xf') + b = a + a += '0xe' + self.assertEqual(b, '0xf') + self.assertEqual(a, '0xfe') + c = BitStream(a) + self.assertEqual(a, c) + a = ConstBitStream('0b1') + a._append(a) + self.assertEqual(a, '0b11') + self.assertEqual(type(a), ConstBitStream) + a._prepend(a) + self.assertEqual(a, '0b1111') + self.assertEqual(type(a), ConstBitStream) + + def testConstBitStreamHashibility(self): + a = ConstBitStream('0x1') + b = ConstBitStream('0x2') + c = ConstBitStream('0x1') + c.pos = 3 + s = set((a, b, c)) + self.assertEqual(len(s), 2) + self.assertEqual(hash(a), hash(c)) + + def testConstBitStreamCopy(self): + a = ConstBitStream('0xabc') + a.pos = 11 + b = copy.copy(a) + b.pos = 4 + self.assertEqual(id(a._datastore), id(b._datastore)) + self.assertEqual(a.pos, 11) + self.assertEqual(b.pos, 4) + + def testPython26stuff(self): + s = BitStream('0xff') + self.assertTrue(isinstance(s.tobytes(), bytes)) + self.assertTrue(isinstance(s.bytes, bytes)) + + def testReadFromBits(self): + a = ConstBitStream('0xaabbccdd') + b = a.read(8) + self.assertEqual(b, '0xaa') + self.assertEqual(a[0:8], '0xaa') + self.assertEqual(a[-1], True) + a.pos = 0 + self.assertEqual(a.read(4).uint, 10) + + +class Set(unittest.TestCase): + def testSet(self): + a = BitStream(length=16) + a.set(True, 0) + self.assertEqual(a, '0b10000000 00000000') + a.set(1, 15) + self.assertEqual(a, '0b10000000 00000001') + b = a[4:12] + b.set(True, 1) + self.assertEqual(b, '0b01000000') + b.set(True, -1) + self.assertEqual(b, '0b01000001') + b.set(1, -8) + self.assertEqual(b, '0b11000001') + self.assertRaises(IndexError, b.set, True, -9) + self.assertRaises(IndexError, b.set, True, 8) + + def testSetNegativeIndex(self): + a = BitStream(10) + a.set(1, -1) + self.assertEqual(a.bin, '0000000001') + a.set(1, [-1, -10]) + self.assertEqual(a.bin, '1000000001') + self.assertRaises(IndexError, a.set, 1, [-11]) + + def testFileBasedSetUnset(self): + a = BitStream(filename='test.m1v') + a.set(True, (0, 1, 2, 3, 4)) + self.assertEqual(a[0:32], '0xf80001b3') + a = BitStream(filename='test.m1v') + a.set(False, (28, 29, 30, 31)) + self.assertTrue(a.startswith('0x000001b0')) + + def testSetList(self): + a = BitStream(length=18) + a.set(True, range(18)) + self.assertEqual(a.int, -1) + a.set(False, range(18)) + self.assertEqual(a.int, 0) + + def testUnset(self): + a = BitStream(length=16, int=-1) + a.set(False, 0) + self.assertEqual(~a, '0b10000000 00000000') + a.set(0, 15) + self.assertEqual(~a, '0b10000000 00000001') + b = a[4:12] + b.set(False, 1) + self.assertEqual(~b, '0b01000000') + b.set(False, -1) + self.assertEqual(~b, '0b01000001') + b.set(False, -8) + self.assertEqual(~b, '0b11000001') + self.assertRaises(IndexError, b.set, False, -9) + self.assertRaises(IndexError, b.set, False, 8) + + def testSetWholeBitStream(self): + a = BitStream(14) + a.set(1) + self.assertTrue(a.all(1)) + a.set(0) + self.assertTrue(a.all(0)) + + +class Invert(unittest.TestCase): + def testInvertBits(self): + a = BitStream('0b111000') + a.invert(range(a.len)) + self.assertEqual(a, '0b000111') + a.invert([0, 1, -1]) + self.assertEqual(a, '0b110110') + + def testInvertWholeBitStream(self): + a = BitStream('0b11011') + a.invert() + self.assertEqual(a, '0b00100') + + def testInvertSingleBit(self): + a = BitStream('0b000001') + a.invert(0) + self.assertEqual(a.bin, '100001') + a.invert(-1) + self.assertEqual(a.bin, '100000') + + def testInvertErrors(self): + a = BitStream(10) + self.assertRaises(IndexError, a.invert, 10) + self.assertRaises(IndexError, a.invert, -11) + self.assertRaises(IndexError, a.invert, [1, 2, 10]) + + + ####################### + + def testIor(self): + a = BitStream('0b1101001') + a |= '0b1110000' + self.assertEqual(a, '0b1111001') + b = a[2:] + c = a[1:-1] + b |= c + self.assertEqual(c, '0b11100') + self.assertEqual(b, '0b11101') + + def testIand(self): + a = BitStream('0b0101010101000') + a &= '0b1111110000000' + self.assertEqual(a, '0b0101010000000') + s = BitStream(filename='test.m1v', offset=26, length=24) + s &= '0xff00ff' + self.assertEqual(s, '0xcc0004') + + def testIxor(self): + a = BitStream('0b11001100110011') + a ^= '0b11111100000010' + self.assertEqual(a, '0b00110000110001') + + def testLogicalInplaceErrors(self): + a = BitStream(4) + self.assertRaises(ValueError, a.__ior__, '0b111') + self.assertRaises(ValueError, a.__iand__, '0b111') + self.assertRaises(ValueError, a.__ixor__, '0b111') + + +class AllAndAny(unittest.TestCase): + def testAll(self): + a = BitStream('0b0111') + self.assertTrue(a.all(True, (1, 3))) + self.assertFalse(a.all(True, (0, 1, 2))) + self.assertTrue(a.all(True, [-1])) + self.assertFalse(a.all(True, [0])) + + def testFileBasedAll(self): + a = BitStream(filename='test.m1v') + self.assertTrue(a.all(True, [31])) + a = BitStream(filename='test.m1v') + self.assertTrue(a.all(False, (0, 1, 2, 3, 4))) + + def testFileBasedAny(self): + a = BitStream(filename='test.m1v') + self.assertTrue(a.any(True, (31, 12))) + a = BitStream(filename='test.m1v') + self.assertTrue(a.any(False, (0, 1, 2, 3, 4))) + + def testAny(self): + a = BitStream('0b10011011') + self.assertTrue(a.any(True, (1, 2, 3, 5))) + self.assertFalse(a.any(True, (1, 2, 5))) + self.assertTrue(a.any(True, (-1,))) + self.assertFalse(a.any(True, (1,))) + + def testAllFalse(self): + a = BitStream('0b0010011101') + self.assertTrue(a.all(False, (0, 1, 3, 4))) + self.assertFalse(a.all(False, (0, 1, 2, 3, 4))) + + def testAnyFalse(self): + a = BitStream('0b01001110110111111111111111111') + self.assertTrue(a.any(False, (4, 5, 6, 2))) + self.assertFalse(a.any(False, (1, 15, 20))) + + def testAnyEmptyBitstring(self): + a = ConstBitStream() + self.assertFalse(a.any(True)) + self.assertFalse(a.any(False)) + + def testAllEmptyBitStream(self): + a = ConstBitStream() + self.assertTrue(a.all(True)) + self.assertTrue(a.all(False)) + + def testAnyWholeBitstring(self): + a = ConstBitStream('0xfff') + self.assertTrue(a.any(True)) + self.assertFalse(a.any(False)) + + def testAllWholeBitstring(self): + a = ConstBitStream('0xfff') + self.assertTrue(a.all(True)) + self.assertFalse(a.all(False)) + + def testErrors(self): + a = BitStream('0xf') + self.assertRaises(IndexError, a.all, True, [5]) + self.assertRaises(IndexError, a.all, True, [-5]) + self.assertRaises(IndexError, a.any, True, [5]) + self.assertRaises(IndexError, a.any, True, [-5]) + + ################### + + def testFloatInitialisation(self): + for f in (0.0000001, -1.0, 1.0, 0.2, -3.1415265, 1.331e32): + a = BitStream(float=f, length=64) + a.pos = 6 + self.assertEqual(a.float, f) + a = BitStream('float:64=%s' % str(f)) + a.pos = 6 + self.assertEqual(a.float, f) + a = BitStream('floatbe:64=%s' % str(f)) + a.pos = 6 + self.assertEqual(a.floatbe, f) + a = BitStream('floatle:64=%s' % str(f)) + a.pos = 6 + self.assertEqual(a.floatle, f) + a = BitStream('floatne:64=%s' % str(f)) + a.pos = 6 + self.assertEqual(a.floatne, f) + b = BitStream(float=f, length=32) + b.pos = 6 + self.assertAlmostEqual(b.float / f, 1.0) + b = BitStream('float:32=%s' % str(f)) + b.pos = 6 + self.assertAlmostEqual(b.float / f, 1.0) + b = BitStream('floatbe:32=%s' % str(f)) + b.pos = 6 + self.assertAlmostEqual(b.floatbe / f, 1.0) + b = BitStream('floatle:32=%s' % str(f)) + b.pos = 6 + self.assertAlmostEqual(b.floatle / f, 1.0) + b = BitStream('floatne:32=%s' % str(f)) + b.pos = 6 + self.assertAlmostEqual(b.floatne / f, 1.0) + a = BitStream('0x12345678') + a.pos = 6 + a.float = 23 + self.assertEqual(a.float, 23.0) + + def testFloatInitStrings(self): + for s in ('5', '+0.0001', '-1e101', '4.', '.2', '-.65', '43.21E+32'): + a = BitStream('float:64=%s' % s) + self.assertEqual(a.float, float(s)) + + def testFloatPacking(self): + a = pack('>d', 0.01) + self.assertEqual(a.float, 0.01) + self.assertEqual(a.floatbe, 0.01) + a.byteswap() + self.assertEqual(a.floatle, 0.01) + b = pack('>f', 1e10) + self.assertAlmostEqual(b.float / 1e10, 1.0) + c = pack('<f', 10.3) + self.assertAlmostEqual(c.floatle / 10.3, 1.0) + d = pack('>5d', 10.0, 5.0, 2.5, 1.25, 0.1) + self.assertEqual(d.unpack('>5d'), [10.0, 5.0, 2.5, 1.25, 0.1]) + + def testFloatReading(self): + a = BitStream('floatle:64=12, floatbe:64=-0.01, floatne:64=3e33') + x, y, z = a.readlist('floatle:64, floatbe:64, floatne:64') + self.assertEqual(x, 12.0) + self.assertEqual(y, -0.01) + self.assertEqual(z, 3e33) + a = BitStream('floatle:32=12, floatbe:32=-0.01, floatne:32=3e33') + x, y, z = a.readlist('floatle:32, floatbe:32, floatne:32') + self.assertAlmostEqual(x / 12.0, 1.0) + self.assertAlmostEqual(y / -0.01, 1.0) + self.assertAlmostEqual(z / 3e33, 1.0) + a = BitStream('0b11, floatle:64=12, 0xfffff') + a.pos = 2 + self.assertEqual(a.read('floatle:64'), 12.0) + b = BitStream(floatle=20, length=32) + b.floatle = 10.0 + b = [0] + b + self.assertEqual(b[1:].floatle, 10.0) + + def testNonAlignedFloatReading(self): + s = BitStream('0b1, float:32 = 10.0') + x, y = s.readlist('1, float:32') + self.assertEqual(y, 10.0) + s[1:] = 'floatle:32=20.0' + x, y = s.unpack('1, floatle:32') + self.assertEqual(y, 20.0) + + def testFloatErrors(self): + a = BitStream('0x3') + self.assertRaises(bitstring.InterpretError, a._getfloat) + self.assertRaises(bitstring.CreationError, a._setfloat, -0.2) + for l in (8, 10, 12, 16, 30, 128, 200): + self.assertRaises(ValueError, BitStream, float=1.0, length=l) + self.assertRaises(bitstring.CreationError, BitStream, floatle=0.3, length=0) + self.assertRaises(bitstring.CreationError, BitStream, floatle=0.3, length=1) + self.assertRaises(bitstring.CreationError, BitStream, float=2) + self.assertRaises(bitstring.InterpretError, a.read, 'floatle:2') + + def testReadErrorChangesPos(self): + a = BitStream('0x123123') + try: + a.read('10, 5') + except ValueError: + pass + self.assertEqual(a.pos, 0) + + def testRor(self): + a = BitStream('0b11001') + a.ror(0) + self.assertEqual(a, '0b11001') + a.ror(1) + self.assertEqual(a, '0b11100') + a.ror(5) + self.assertEqual(a, '0b11100') + a.ror(101) + self.assertEqual(a, '0b01110') + a = BitStream('0b1') + a.ror(1000000) + self.assertEqual(a, '0b1') + + def testRorErrors(self): + a = BitStream() + self.assertRaises(bitstring.Error, a.ror, 0) + a += '0b001' + self.assertRaises(ValueError, a.ror, -1) + + def testRol(self): + a = BitStream('0b11001') + a.rol(0) + self.assertEqual(a, '0b11001') + a.rol(1) + self.assertEqual(a, '0b10011') + a.rol(5) + self.assertEqual(a, '0b10011') + a.rol(101) + self.assertEqual(a, '0b00111') + a = BitStream('0b1') + a.rol(1000000) + self.assertEqual(a, '0b1') + + def testRolFromFile(self): + a = BitStream(filename='test.m1v') + l = a.len + a.rol(1) + self.assertTrue(a.startswith('0x000003')) + self.assertEqual(a.len, l) + self.assertTrue(a.endswith('0x0036e')) + + def testRorFromFile(self): + a = BitStream(filename='test.m1v') + l = a.len + a.ror(1) + self.assertTrue(a.startswith('0x800000')) + self.assertEqual(a.len, l) + self.assertTrue(a.endswith('0x000db')) + + def testRolErrors(self): + a = BitStream() + self.assertRaises(bitstring.Error, a.rol, 0) + a += '0b001' + self.assertRaises(ValueError, a.rol, -1) + + def testBytesToken(self): + a = BitStream('0x010203') + b = a.read('bytes:1') + self.assertTrue(isinstance(b, bytes)) + self.assertEqual(b, b'\x01') + x, y, z = a.unpack('4, bytes:2, uint') + self.assertEqual(x, 0) + self.assertEqual(y, b'\x10\x20') + self.assertEqual(z, 3) + s = pack('bytes:4', b'abcd') + self.assertEqual(s.bytes, b'abcd') + + def testBytesTokenMoreThoroughly(self): + a = BitStream('0x0123456789abcdef') + a.pos += 16 + self.assertEqual(a.read('bytes:1'), b'\x45') + self.assertEqual(a.read('bytes:3'), b'\x67\x89\xab') + x, y, z = a.unpack('bits:28, bytes, bits:12') + self.assertEqual(y, b'\x78\x9a\xbc') + + def testDedicatedReadFunctions(self): + a = BitStream('0b11, uint:43=98798798172, 0b11111') + x = a._readuint(43, 2) + self.assertEqual(x, 98798798172) + self.assertEqual(a.pos, 0) + x = a._readint(43, 2) + self.assertEqual(x, 98798798172) + self.assertEqual(a.pos, 0) + + a = BitStream('0b11, uintbe:48=98798798172, 0b11111') + x = a._readuintbe(48, 2) + self.assertEqual(x, 98798798172) + self.assertEqual(a.pos, 0) + x = a._readintbe(48, 2) + self.assertEqual(x, 98798798172) + self.assertEqual(a.pos, 0) + + a = BitStream('0b111, uintle:40=123516, 0b111') + self.assertEqual(a._readuintle(40, 3), 123516) + b = BitStream('0xff, uintle:800=999, 0xffff') + self.assertEqual(b._readuintle(800, 8), 999) + + a = BitStream('0b111, intle:48=999999999, 0b111111111111') + self.assertEqual(a._readintle(48, 3), 999999999) + b = BitStream('0xff, intle:200=918019283740918263512351235, 0xfffffff') + self.assertEqual(b._readintle(200, 8), 918019283740918263512351235) + + a = BitStream('0b111, floatbe:64=-5.32, 0xffffffff') + self.assertEqual(a._readfloat(64, 3), -5.32) + + a = BitStream('0b111, floatle:64=9.9998, 0b111') + self.assertEqual(a._readfloatle(64, 3), 9.9998) + + def testAutoInitWithInt(self): + a = BitStream(0) + self.assertFalse(a) + a = BitStream(1) + self.assertEqual(a, '0b0') + a = BitStream(1007) + self.assertEqual(a, BitStream(length=1007)) + self.assertRaises(bitstring.CreationError, BitStream, -1) + + a = 6 + ConstBitStream('0b1') + 3 + self.assertEqual(a, '0b0000001000') + a += 1 + self.assertEqual(a, '0b00000010000') + self.assertEqual(ConstBitStream(13), 13) + + def testReadingProblems(self): + a = BitStream('0x000001') + b = a.read('uint:24') + self.assertEqual(b, 1) + a.pos = 0 + self.assertRaises(bitstring.ReadError, a.read, 'bytes:4') + + def testAddVersesInPlaceAdd(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 += '0xdef' + self.assertEqual(a1, '0xabcdef') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 + '0x0' + a2 += '0xdef' + self.assertEqual(a2, '0xabcdef') + self.assertEqual(b2, '0xabcdef') + self.assertEqual(c2, '0xabc0') + + def testAndVersesInPlaceAnd(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 &= '0xf0f' + self.assertEqual(a1, '0xa0c') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 & '0x00f' + a2 &= '0xf0f' + self.assertEqual(a2, '0xa0c') + self.assertEqual(b2, '0xa0c') + self.assertEqual(c2, '0x00c') + + def testOrVersesInPlaceOr(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 |= '0xf0f' + self.assertEqual(a1, '0xfbf') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 | '0x00f' + a2 |= '0xf0f' + self.assertEqual(a2, '0xfbf') + self.assertEqual(b2, '0xfbf') + self.assertEqual(c2, '0xabf') + + def testXorVersesInPlaceXor(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 ^= '0xf0f' + self.assertEqual(a1, '0x5b3') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 ^ '0x00f' + a2 ^= '0xf0f' + self.assertEqual(a2, '0x5b3') + self.assertEqual(b2, '0x5b3') + self.assertEqual(c2, '0xab3') + + def testMulVersesInPlaceMul(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 *= 3 + self.assertEqual(a1, '0xabcabcabc') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 * 2 + a2 *= 3 + self.assertEqual(a2, '0xabcabcabc') + self.assertEqual(b2, '0xabcabcabc') + self.assertEqual(c2, '0xabcabc') + + def testLshiftVersesInPlaceLshift(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 <<= 4 + self.assertEqual(a1, '0xbc0') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 << 8 + a2 <<= 4 + self.assertEqual(a2, '0xbc0') + self.assertEqual(b2, '0xbc0') + self.assertEqual(c2, '0xc00') + + def testRshiftVersesInPlaceRshift(self): + a1 = ConstBitStream('0xabc') + b1 = a1 + a1 >>= 4 + self.assertEqual(a1, '0x0ab') + self.assertEqual(b1, '0xabc') + + a2 = BitStream('0xabc') + b2 = a2 + c2 = a2 >> 8 + a2 >>= 4 + self.assertEqual(a2, '0x0ab') + self.assertEqual(b2, '0x0ab') + self.assertEqual(c2, '0x00a') + + def testAutoFromBool(self): + a = ConstBitStream() + True + False + True + self.assertEqual(a, '0b00') + # self.assertEqual(a, '0b101') + # b = ConstBitStream(False) + # self.assertEqual(b, '0b0') + # c = ConstBitStream(True) + # self.assertEqual(c, '0b1') + # self.assertEqual(b, False) + # self.assertEqual(c, True) + # self.assertEqual(b & True, False) + + +class Bugs(unittest.TestCase): + def testBugInReplace(self): + s = BitStream('0x00112233') + l = list(s.split('0x22', start=8, bytealigned=True)) + self.assertEqual(l, ['0x11', '0x2233']) + s = BitStream('0x00112233') + s.replace('0x22', '0xffff', start=8, bytealigned=True) + self.assertEqual(s, '0x0011ffff33') + s = BitStream('0x0123412341234') + s.replace('0x23', '0xf', start=9, bytealigned=True) + self.assertEqual(s, '0x012341f41f4') + + def testTruncateStartBug(self): + a = BitStream('0b000000111')[2:] + a._truncatestart(6) + self.assertEqual(a, '0b1') + + def testNullBits(self): + s = ConstBitStream(bin='') + t = ConstBitStream(oct='') + u = ConstBitStream(hex='') + v = ConstBitStream(bytes=b'') + self.assertFalse(s) + self.assertFalse(t) + self.assertFalse(u) + self.assertFalse(v) + + def testMultiplicativeFactorsCreation(self): + s = BitStream('1*0b1') + self.assertEqual(s, '0b1') + s = BitStream('4*0xc') + self.assertEqual(s, '0xcccc') + s = BitStream('0b1, 0*0b0') + self.assertEqual(s, '0b1') + s = BitStream('0b1, 3*uint:8=34, 2*0o755') + self.assertEqual(s, '0b1, uint:8=34, uint:8=34, uint:8=34, 0o755755') + s = BitStream('0*0b1001010') + self.assertFalse(s) + + def testMultiplicativeFactorsReading(self): + s = BitStream('0xc') * 5 + a, b, c, d, e = s.readlist('5*4') + self.assertTrue(a == b == c == d == e == 12) + s = ConstBitStream('2*0b101, 4*uint:7=3') + a, b, c, d, e = s.readlist('2*bin:3, 3*uint:7') + self.assertTrue(a == b == '101') + self.assertTrue(c == d == e == 3) + + def testMultiplicativeFactorsPacking(self): + s = pack('3*bin', '1', '001', '101') + self.assertEqual(s, '0b1001101') + s = pack('hex, 2*se=-56, 3*uint:37', '34', 1, 2, 3) + a, b, c, d, e, f = s.unpack('hex:8, 2*se, 3*uint:37') + self.assertEqual(a, '34') + self.assertEqual(b, -56) + self.assertEqual(c, -56) + self.assertEqual((d, e, f), (1, 2, 3)) + # This isn't allowed yet. See comment in tokenparser. + #s = pack('fluffy*uint:8', *range(3), fluffy=3) + #a, b, c = s.readlist('2*uint:8, 1*uint:8, 0*uint:8') + #self.assertEqual((a, b, c), (0, 1, 2)) + + def testMultiplicativeFactorsUnpacking(self): + s = ConstBitStream('0b10111') + a, b, c, d = s.unpack('3*bool, bin') + self.assertEqual((a, b, c), (True, False, True)) + self.assertEqual(d, '11') + + + def testPackingDefaultIntWithKeyword(self): + s = pack('12', 100) + self.assertEqual(s.unpack('12')[0], 100) + s = pack('oh_no_not_the_eyes=33', oh_no_not_the_eyes=17) + self.assertEqual(s.uint, 33) + self.assertEqual(s.len, 17) + + def testInitFromIterable(self): + self.assertTrue(isinstance(range(10), collections.Iterable)) + s = ConstBitStream(range(12)) + self.assertEqual(s, '0x7ff') + + def testFunctionNegativeIndices(self): + # insert + s = BitStream('0b0111') + s.insert('0b0', -1) + self.assertEqual(s, '0b01101') + self.assertRaises(ValueError, s.insert, '0b0', -1000) + + # reverse + s.reverse(-2) + self.assertEqual(s, '0b01110') + t = BitStream('0x778899abcdef') + t.reverse(-12, -4) + self.assertEqual(t, '0x778899abc7bf') + + # reversebytes + t.byteswap(0, -40, -16) + self.assertEqual(t, '0x77ab9988c7bf') + + # overwrite + t.overwrite('0x666', -20) + self.assertEqual(t, '0x77ab998666bf') + + # find + found = t.find('0x998', bytealigned=True, start=-31) + self.assertFalse(found) + found = t.find('0x998', bytealigned=True, start=-32) + self.assertTrue(found) + self.assertEqual(t.pos, 16) + t.pos = 0 + found = t.find('0x988', bytealigned=True, end=-21) + self.assertFalse(found) + found = t.find('0x998', bytealigned=True, end=-20) + self.assertTrue(found) + self.assertEqual(t.pos, 16) + + #findall + s = BitStream('0x1234151f') + l = list(s.findall('0x1', bytealigned=True, start=-15)) + self.assertEqual(l, [24]) + l = list(s.findall('0x1', bytealigned=True, start=-16)) + self.assertEqual(l, [16, 24]) + l = list(s.findall('0x1', bytealigned=True, end=-5)) + self.assertEqual(l, [0, 16]) + l = list(s.findall('0x1', bytealigned=True, end=-4)) + self.assertEqual(l, [0, 16, 24]) + + # rfind + found = s.rfind('0x1f', end=-1) + self.assertFalse(found) + found = s.rfind('0x12', start=-31) + self.assertFalse(found) + + # cut + s = BitStream('0x12345') + l = list(s.cut(4, start=-12, end=-4)) + self.assertEqual(l, ['0x3', '0x4']) + + # split + s = BitStream('0xfe0012fe1200fe') + l = list(s.split('0xfe', bytealigned=True, end=-1)) + self.assertEqual(l, ['', '0xfe0012', '0xfe1200f, 0b111']) + l = list(s.split('0xfe', bytealigned=True, start=-8)) + self.assertEqual(l, ['', '0xfe']) + + # startswith + self.assertTrue(s.startswith('0x00f', start=-16)) + self.assertTrue(s.startswith('0xfe00', end=-40)) + self.assertFalse(s.startswith('0xfe00', end=-41)) + + # endswith + self.assertTrue(s.endswith('0x00fe', start=-16)) + self.assertFalse(s.endswith('0x00fe', start=-15)) + self.assertFalse(s.endswith('0x00fe', end=-1)) + self.assertTrue(s.endswith('0x00f', end=-4)) + + # replace + s.replace('0xfe', '', end=-1) + self.assertEqual(s, '0x00121200fe') + s.replace('0x00', '', start=-24) + self.assertEqual(s, '0x001212fe') + + def testRotateStartAndEnd(self): + a = BitStream('0b110100001') + a.rol(1, 3, 6) + self.assertEqual(a, '0b110001001') + a.ror(1, start=-4) + self.assertEqual(a, '0b110001100') + a.rol(202, end=-5) + self.assertEqual(a, '0b001101100') + a.ror(3, end=4) + self.assertEqual(a, '0b011001100') + self.assertRaises(ValueError, a.rol, 5, start=-4, end=-6) + + def testByteSwapInt(self): + s = pack('5*uintle:16', *range(10, 15)) + self.assertEqual(list(range(10, 15)), s.unpack('5*uintle:16')) + swaps = s.byteswap(2) + self.assertEqual(list(range(10, 15)), s.unpack('5*uintbe:16')) + self.assertEqual(swaps, 5) + s = BitStream('0xf234567f') + swaps = s.byteswap(1, start=4) + self.assertEqual(swaps, 3) + self.assertEqual(s, '0xf234567f') + s.byteswap(2, start=4) + self.assertEqual(s, '0xf452367f') + s.byteswap(2, start=4, end=-4) + self.assertEqual(s, '0xf234567f') + s.byteswap(3) + self.assertEqual(s, '0x5634f27f') + s.byteswap(2, repeat=False) + self.assertEqual(s, '0x3456f27f') + swaps = s.byteswap(5) + self.assertEqual(swaps, 0) + swaps = s.byteswap(4, repeat=False) + self.assertEqual(swaps, 1) + self.assertEqual(s, '0x7ff25634') + + def testByteSwapPackCode(self): + s = BitStream('0x0011223344556677') + swaps = s.byteswap('b') + self.assertEqual(s, '0x0011223344556677') + self.assertEqual(swaps, 8) + swaps = s.byteswap('>3h', repeat=False) + self.assertEqual(s, '0x1100332255446677') + self.assertEqual(swaps, 1) + + def testByteSwapIterable(self): + s = BitStream('0x0011223344556677') + swaps = s.byteswap(range(1, 4), repeat=False) + self.assertEqual(swaps, 1) + self.assertEqual(s, '0x0022115544336677') + swaps = s.byteswap([2], start=8) + self.assertEqual(s, '0x0011224455663377') + self.assertEqual(3, swaps) + swaps = s.byteswap([2, 3], start=4) + self.assertEqual(swaps, 1) + self.assertEqual(s, '0x0120156452463377') + + def testByteSwapErrors(self): + s = BitStream('0x0011223344556677') + self.assertRaises(ValueError, s.byteswap, 'z') + self.assertRaises(ValueError, s.byteswap, -1) + self.assertRaises(ValueError, s.byteswap, [-1]) + self.assertRaises(ValueError, s.byteswap, [1, 'e']) + self.assertRaises(ValueError, s.byteswap, '!h') + self.assertRaises(ValueError, s.byteswap, 2, start=-1000) + self.assertRaises(TypeError, s.byteswap, 5.4) + + def testByteSwapFromFile(self): + s = BitStream(filename='smalltestfile') + swaps = s.byteswap('2bh') + self.assertEqual(s, '0x0123674589abefcd') + self.assertEqual(swaps, 2) + + def testBracketExpander(self): + be = bitstring.expand_brackets + self.assertEqual(be('hello'), 'hello') + self.assertEqual(be('(hello)'), 'hello') + self.assertEqual(be('1*(hello)'), 'hello') + self.assertEqual(be('2*(hello)'), 'hello,hello') + self.assertEqual(be('1*(a, b)'), 'a,b') + self.assertEqual(be('2*(a, b)'), 'a,b,a,b') + self.assertEqual(be('2*(a), 3*(b)'), 'a,a,b,b,b') + self.assertEqual(be('2*(a, b, 3*(c, d), e)'), 'a,b,c,d,c,d,c,d,e,a,b,c,d,c,d,c,d,e') + + def testBracketTokens(self): + s = BitStream('3*(0x0, 0b1)') + self.assertEqual(s, '0x0, 0b1, 0x0, 0b1, 0x0, 0b1') + s = pack('2*(uint:12, 3*(7, 6))', *range(3, 17)) + a = s.unpack('12, 7, 6, 7, 6, 7, 6, 12, 7, 6, 7, 6, 7, 6') + self.assertEqual(a, list(range(3, 17))) + b = s.unpack('2*(12,3*(7,6))') + self.assertEqual(a, b) + + def testPackCodeDicts(self): + self.assertEqual(sorted(bitstring.REPLACEMENTS_BE.keys()), + sorted(bitstring.REPLACEMENTS_LE.keys())) + self.assertEqual(sorted(bitstring.REPLACEMENTS_BE.keys()), + sorted(bitstring.PACK_CODE_SIZE.keys())) + for key in bitstring.PACK_CODE_SIZE: + be = pack(bitstring.REPLACEMENTS_BE[key], 0) + le = pack(bitstring.REPLACEMENTS_LE[key], 0) + self.assertEqual(be.len, bitstring.PACK_CODE_SIZE[key] * 8) + self.assertEqual(le.len, be.len) + + # These tests don't compile for Python 3, so they're commented out to save me stress. + #def testUnicode(self): + #a = ConstBitStream(u'uint:12=34') + #self.assertEqual(a.uint, 34) + #a += u'0xfe' + #self.assertEqual(a[12:], '0xfe') + #a = BitStream('0x1122') + #c = a.byteswap(u'h') + #self.assertEqual(c, 1) + #self.assertEqual(a, u'0x2211') + + #def testLongInt(self): + #a = BitStream(4L) + #self.assertEqual(a, '0b0000') + #a[1:3] = -1L + #self.assertEqual(a, '0b0110') + #a[0] = 1L + #self.assertEqual(a, '0b1110') + #a *= 4L + #self.assertEqual(a, '0xeeee') + #c = a.byteswap(2L) + #self.assertEqual(c, 1) + #a = BitStream('0x11223344') + #a.byteswap([1, 2L]) + #self.assertEqual(a, '0x11332244') + #b = a*2L + #self.assertEqual(b, '0x1133224411332244') + #s = pack('uint:12', 46L) + #self.assertEqual(s.uint, 46) + + +class UnpackWithDict(unittest.TestCase): + def testLengthKeywords(self): + a = ConstBitStream('2*13=100, 0b111') + x, y, z = a.unpack('n, uint:m, bin:q', n=13, m=13, q=3) + self.assertEqual(x, 100) + self.assertEqual(y, 100) + self.assertEqual(z, '111') + + def testLengthKeywordsWithStretch(self): + a = ConstBitStream('0xff, 0b000, 0xf') + x, y, z = a.unpack('hex:a, bin, hex:b', a=8, b=4) + self.assertEqual(y, '000') + + def testUnusedKeyword(self): + a = ConstBitStream('0b110') + x, = a.unpack('bin:3', notused=33) + self.assertEqual(x, '110') + + def testLengthKeywordErrors(self): + a = pack('uint:p=33', p=12) + self.assertRaises(ValueError, a.unpack, 'uint:p') + self.assertRaises(ValueError, a.unpack, 'uint:p', p='a_string') + + +class ReadWithDict(unittest.TestCase): + def testLengthKeywords(self): + s = BitStream('0x0102') + x, y = s.readlist('a, hex:b', a=8, b=4) + self.assertEqual((x, y), (1, '0')) + self.assertEqual(s.pos, 12) + + def testBytesKeywordProblem(self): + s = BitStream('0x01') + x, = s.unpack('bytes:a', a=1) + self.assertEqual(x, b'\x01') + + s = BitStream('0x000ff00a') + x, y, z = s.unpack('12, bytes:x, bits', x=2) + self.assertEqual((x, y, z), (0, b'\xff\x00', '0xa')) + + + +class PeekWithDict(unittest.TestCase): + def testLengthKeywords(self): + s = BitStream('0x0102') + x, y = s.peeklist('a, hex:b', a=8, b=4) + self.assertEqual((x, y), (1, '0')) + self.assertEqual(s.pos, 0) + +##class Miscellany(unittest.TestCase): +## +## def testNumpyInt(self): +## try: +## import numpy +## a = ConstBitStream(uint=numpy.uint8(5), length=3) +## self.assertEqual(a.uint, 5) +## except ImportError: +## # Not to worry +## pass + +class BoolToken(unittest.TestCase): + def testInterpretation(self): + a = ConstBitStream('0b1') + self.assertEqual(a.bool, True) + self.assertEqual(a.read('bool'), True) + self.assertEqual(a.unpack('bool')[0], True) + b = ConstBitStream('0b0') + self.assertEqual(b.bool, False) + self.assertEqual(b.peek('bool'), False) + self.assertEqual(b.unpack('bool')[0], False) + + def testPack(self): + a = pack('bool=True') + b = pack('bool=False') + self.assertEqual(a.bool, True) + self.assertEqual(b.bool, False) + c = pack('4*bool', False, True, 'False', 'True') + self.assertEqual(c, '0b0101') + + def testAssignment(self): + a = BitStream() + a.bool = True + self.assertEqual(a.bool, True) + a.hex = 'ee' + a.bool = False + self.assertEqual(a.bool, False) + a.bool = 'False' + self.assertEqual(a.bool, False) + a.bool = 'True' + self.assertEqual(a.bool, True) + a.bool = 0 + self.assertEqual(a.bool, False) + a.bool = 1 + self.assertEqual(a.bool, True) + + def testErrors(self): + self.assertRaises(bitstring.CreationError, pack, 'bool', 'hello') + self.assertRaises(bitstring.CreationError, pack, 'bool=true') + self.assertRaises(bitstring.CreationError, pack, 'True') + self.assertRaises(bitstring.CreationError, pack, 'bool', 2) + a = BitStream('0b11') + self.assertRaises(bitstring.InterpretError, a._getbool) + b = BitStream() + self.assertRaises(bitstring.InterpretError, a._getbool) + self.assertRaises(bitstring.CreationError, a._setbool, 'false') + + def testLengthWithBoolRead(self): + a = ConstBitStream('0xf') + self.assertRaises(ValueError, a.read, 'bool:0') + self.assertRaises(ValueError, a.read, 'bool:1') + self.assertRaises(ValueError, a.read, 'bool:2') + + +class ReadWithIntegers(unittest.TestCase): + def testReadInt(self): + a = ConstBitStream('0xffeedd') + b = a.read(8) + self.assertEqual(b.hex, 'ff') + self.assertEqual(a.pos, 8) + b = a.peek(8) + self.assertEqual(b.hex, 'ee') + self.assertEqual(a.pos, 8) + b = a.peek(1) + self.assertEqual(b, '0b1') + b = a.read(1) + self.assertEqual(b, '0b1') + + def testReadIntList(self): + a = ConstBitStream('0xab, 0b110') + b, c = a.readlist([8, 3]) + self.assertEqual(b.hex, 'ab') + self.assertEqual(c.bin, '110') + + +class FileReadingStrategy(unittest.TestCase): + def testBitStreamIsAlwaysRead(self): + a = BitStream(filename='smalltestfile') + self.assertTrue(isinstance(a._datastore, bitstring.ByteStore)) + f = open('smalltestfile', 'rb') + b = BitStream(f) + self.assertTrue(isinstance(b._datastore, bitstring.ByteStore)) + + def testBitsIsNeverRead(self): + a = ConstBitStream(filename='smalltestfile') + self.assertTrue(isinstance(a._datastore._rawarray, bitstring.MmapByteArray)) + f = open('smalltestfile', 'rb') + b = ConstBitStream(f) + self.assertTrue(isinstance(b._datastore._rawarray, bitstring.MmapByteArray)) + + +class Count(unittest.TestCase): + def testCount(self): + a = ConstBitStream('0xf0f') + self.assertEqual(a.count(True), 8) + self.assertEqual(a.count(False), 4) + + b = BitStream() + self.assertEqual(b.count(True), 0) + self.assertEqual(b.count(False), 0) + + def testCountWithOffsetData(self): + a = ConstBitStream('0xff0120ff') + b = a[1:-1] + self.assertEqual(b.count(1), 16) + self.assertEqual(b.count(0), 14) + + +class ZeroBitReads(unittest.TestCase): + def testInteger(self): + a = ConstBitStream('0x123456') + self.assertRaises(bitstring.InterpretError, a.read, 'uint:0') + self.assertRaises(bitstring.InterpretError, a.read, 'float:0') + +#class EfficientBitsCopies(unittest.TestCase): +# +# def testBitsCopy(self): +# a = ConstBitStream('0xff') +# b = ConstBitStream(a) +# c = a[:] +# d = copy.copy(a) +# self.assertTrue(a._datastore is b._datastore) +# self.assertTrue(a._datastore is c._datastore) +# self.assertTrue(a._datastore is d._datastore) + +class InitialiseFromBytes(unittest.TestCase): + def testBytesBehaviour(self): + a = ConstBitStream(b'uint:5=2') + b = ConstBitStream(b'') + c = ConstBitStream(bytes=b'uint:5=2') + if b'' == '': + # Python 2 + self.assertEqual(a, 'uint:5=2') + self.assertFalse(b) + self.assertEqual(c.bytes, b'uint:5=2') + else: + self.assertEqual(a.bytes, b'uint:5=2') + self.assertFalse(b) + self.assertEqual(c, b'uint:5=2') + + def testBytearrayBehaviour(self): + a = ConstBitStream(bytearray(b'uint:5=2')) + b = ConstBitStream(bytearray(4)) + c = ConstBitStream(bytes=bytearray(b'uint:5=2')) + self.assertEqual(a.bytes, b'uint:5=2') + self.assertEqual(b, '0x00000000') + self.assertEqual(c.bytes, b'uint:5=2') + + +class CoverageCompletionTests(unittest.TestCase): + def testUeReadError(self): + s = ConstBitStream('0b000000001') + self.assertRaises(bitstring.ReadError, s.read, 'ue') + + def testOverwriteWithSelf(self): + s = BitStream('0b1101') + s.overwrite(s) + self.assertEqual(s, '0b1101') + + +class Subclassing(unittest.TestCase): + + def testIsInstance(self): + class SubBits(BitStream): pass + a = SubBits() + self.assertTrue(isinstance(a, SubBits)) + + def testClassType(self): + class SubBits(BitStream): pass + self.assertEqual(SubBits().__class__, SubBits) + + +class BytesProblems(unittest.TestCase): + + def testOffsetButNoLength(self): + b = BitStream(bytes=b'\x00\xaa', offset=8) + self.assertEqual(b.hex, 'aa') + b = BitStream(bytes=b'\x00\xaa', offset=4) + self.assertEqual(b.hex, '0aa') + + def testInvert(self): + b = BitStream(bytes=b'\x00\xaa', offset=8, length=8) + self.assertEqual(b.hex, 'aa') + b.invert() + self.assertEqual(b.hex, '55') + + def testPrepend(self): + b = BitStream(bytes=b'\xaa\xbb', offset=8, length=4) + self.assertEqual(b.hex, 'b') + b.prepend('0xe') + self.assertEqual(b.hex, 'eb') + b = BitStream(bytes=b'\x00\xaa', offset=8, length=8) + b.prepend('0xee') + self.assertEqual(b.hex, 'eeaa') + + def testByteSwap(self): + b = BitStream(bytes=b'\x01\x02\x03\x04', offset=8) + b.byteswap() + self.assertEqual(b, '0x040302') + + def testBinProperty(self): + b = BitStream(bytes=b'\x00\xaa', offset=8, length=4) + self.assertEqual(b.bin, '1010')
\ No newline at end of file diff --git a/python/bitstring/test/test_bitstring.py b/python/bitstring/test/test_bitstring.py new file mode 100644 index 000000000..1b52b7b80 --- /dev/null +++ b/python/bitstring/test/test_bitstring.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +""" +Module-level unit tests. +""" + +import unittest +import sys +sys.path.insert(0, '..') +import bitstring +import copy + + +class ModuleData(unittest.TestCase): + def testVersion(self): + self.assertEqual(bitstring.__version__, '3.1.3') + + def testAll(self): + exported = ['ConstBitArray', 'ConstBitStream', 'BitStream', 'BitArray', + 'Bits', 'BitString', 'pack', 'Error', 'ReadError', + 'InterpretError', 'ByteAlignError', 'CreationError', 'bytealigned'] + self.assertEqual(set(bitstring.__all__), set(exported)) + + def testReverseDict(self): + d = bitstring.BYTE_REVERSAL_DICT + for i in range(256): + a = bitstring.Bits(uint=i, length=8) + b = d[i] + self.assertEqual(a.bin[::-1], bitstring.Bits(bytes=b).bin) + + def testAliases(self): + self.assertTrue(bitstring.Bits is bitstring.ConstBitArray) + self.assertTrue(bitstring.BitStream is bitstring.BitString) + + +class MemoryUsage(unittest.TestCase): + def testBaselineMemory(self): + try: + import pympler.asizeof.asizeof as size + except ImportError: + return + # These values might be platform dependent, so don't fret too much. + self.assertEqual(size(bitstring.ConstBitStream([0])), 64) + self.assertEqual(size(bitstring.Bits([0])), 64) + self.assertEqual(size(bitstring.BitStream([0])), 64) + self.assertEqual(size(bitstring.BitArray([0])), 64) + from bitstring.bitstore import ByteStore + self.assertEqual(size(ByteStore(bytearray())), 100) + + +class Copy(unittest.TestCase): + def testConstBitArrayCopy(self): + import copy + cba = bitstring.Bits(100) + cba_copy = copy.copy(cba) + self.assertTrue(cba is cba_copy) + + def testBitArrayCopy(self): + ba = bitstring.BitArray(100) + ba_copy = copy.copy(ba) + self.assertFalse(ba is ba_copy) + self.assertFalse(ba._datastore is ba_copy._datastore) + self.assertTrue(ba == ba_copy) + + def testConstBitStreamCopy(self): + cbs = bitstring.ConstBitStream(100) + cbs.pos = 50 + cbs_copy = copy.copy(cbs) + self.assertEqual(cbs_copy.pos, 0) + self.assertTrue(cbs._datastore is cbs_copy._datastore) + self.assertTrue(cbs == cbs_copy) + + def testBitStreamCopy(self): + bs = bitstring.BitStream(100) + bs.pos = 50 + bs_copy = copy.copy(bs) + self.assertEqual(bs_copy.pos, 0) + self.assertFalse(bs._datastore is bs_copy._datastore) + self.assertTrue(bs == bs_copy) + + +class Interning(unittest.TestCase): + def testBits(self): + a = bitstring.Bits('0xf') + b = bitstring.Bits('0xf') + self.assertTrue(a is b) + c = bitstring.Bits('0b1111') + self.assertFalse(a is c) + + def testCBS(self): + a = bitstring.ConstBitStream('0b11000') + b = bitstring.ConstBitStream('0b11000') + self.assertFalse(a is b) + # self.assertTrue(a._datastore is b._datastore) + + + +
\ No newline at end of file diff --git a/python/bitstring/test/test_constbitstream.py b/python/bitstring/test/test_constbitstream.py new file mode 100644 index 000000000..a1bef743f --- /dev/null +++ b/python/bitstring/test/test_constbitstream.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +import unittest +import sys +sys.path.insert(0, '..') +import bitstring +from bitstring import ConstBitStream as CBS + +class All(unittest.TestCase): + def testFromFile(self): + s = CBS(filename='test.m1v') + self.assertEqual(s[0:32].hex, '000001b3') + self.assertEqual(s.read(8 * 4).hex, '000001b3') + width = s.read(12).uint + height = s.read(12).uint + self.assertEqual((width, height), (352, 288)) + + +class InterleavedExpGolomb(unittest.TestCase): + def testReading(self): + s = CBS(uie=333) + a = s.read('uie') + self.assertEqual(a, 333) + s = CBS('uie=12, sie=-9, sie=9, uie=1000000') + u = s.unpack('uie, 2*sie, uie') + self.assertEqual(u, [12, -9, 9, 1000000]) + + def testReadingErrors(self): + s = CBS(10) + self.assertRaises(bitstring.ReadError, s.read, 'uie') + self.assertEqual(s.pos, 0) + self.assertRaises(bitstring.ReadError, s.read, 'sie') + self.assertEqual(s.pos, 0) + + +class ReadTo(unittest.TestCase): + def testByteAligned(self): + a = CBS('0xaabb00aa00bb') + b = a.readto('0x00', bytealigned=True) + self.assertEqual(b, '0xaabb00') + self.assertEqual(a.bytepos, 3) + b = a.readto('0xaa', bytealigned=True) + self.assertEqual(b, '0xaa') + self.assertRaises(bitstring.ReadError, a.readto, '0xcc', bytealigned=True) + + def testNotAligned(self): + a = CBS('0b00111001001010011011') + a.pos = 1 + self.assertEqual(a.readto('0b00'), '0b011100') + self.assertEqual(a.readto('0b110'), '0b10010100110') + self.assertRaises(ValueError, a.readto, '') + + def testDisallowIntegers(self): + a = CBS('0x0f') + self.assertRaises(ValueError, a.readto, 4) + + def testReadingLines(self): + s = b"This is a test\nof reading lines\nof text\n" + b = CBS(bytes=s) + n = bitstring.Bits(bytes=b'\n') + self.assertEqual(b.readto(n).bytes, b'This is a test\n') + self.assertEqual(b.readto(n).bytes, b'of reading lines\n') + self.assertEqual(b.readto(n).bytes, b'of text\n') + + +class Subclassing(unittest.TestCase): + + def testIsInstance(self): + class SubBits(CBS): pass + a = SubBits() + self.assertTrue(isinstance(a, SubBits)) + + def testClassType(self): + class SubBits(CBS): pass + self.assertEqual(SubBits().__class__, SubBits) + + +class PadToken(unittest.TestCase): + + def testRead(self): + s = CBS('0b100011110001') + a = s.read('pad:1') + self.assertEqual(a, None) + self.assertEqual(s.pos, 1) + a = s.read(3) + self.assertEqual(a, CBS('0b000')) + a = s.read('pad:0') + self.assertEqual(a, None) + self.assertEqual(s.pos, 4) + + def testReadList(self): + s = CBS('0b10001111001') + t = s.readlist('pad:1, uint:3, pad:4, uint:3') + self.assertEqual(t, [0, 1]) + s.pos = 0 + t = s.readlist('pad:1, pad:5') + self.assertEqual(t, []) + self.assertEqual(s.pos, 6) + s.pos = 0 + t = s.readlist('pad:1, bin, pad:4, uint:3') + self.assertEqual(t, ['000', 1]) + s.pos = 0 + t = s.readlist('pad, bin:3, pad:4, uint:3') + self.assertEqual(t, ['000', 1]) + +class ReadingBytes(unittest.TestCase): + + def testUnpackingBytes(self): + s = CBS(80) + t = s.unpack('bytes:1') + self.assertEqual(t[0], b'\x00') + a, b, c = s.unpack('bytes:1, bytes, bytes:2') + self.assertEqual(a, b'\x00') + self.assertEqual(b, b'\x00'*7) + self.assertEqual(c, b'\x00'*2) + + def testUnpackingBytesWithKeywords(self): + s = CBS('0x55'*10) + t = s.unpack('pad:a, bytes:b, bytes, pad:a', a=4, b=6) + self.assertEqual(t, [b'\x55'*6, b'\x55'*3]) + |