-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
1,155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# create release and upload to pypi | ||
|
||
all: | ||
python setup.py sdist | ||
python setup.py bdist_wheel --universal | ||
twine upload dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
|buildstatus|_ | ||
|
||
About | ||
===== | ||
|
||
This module is intended to have a similar interface as the python struct module, but | ||
working on bits instead of normal datatypes (char, int, ...). | ||
|
||
Homepage: https://github.com/eerimoq/archive/blob/master/python/bitstruct | ||
|
||
|
||
Installation | ||
============ | ||
|
||
.. code-block:: python | ||
pip install bitstruct | ||
Example usage | ||
============= | ||
|
||
See the test suite: https://github.com/eerimoq/archive/blob/master/python/bitstruct/tests/test_bitstruct.py | ||
|
||
A basic example of packing/unpacking four integers: | ||
|
||
.. code-block:: python | ||
>>> from bitstruct import * | ||
>>> pack('u1u3u4s16', 1, 2, 3, -4) | ||
bytearray(b'\xa3\xff\xfc') | ||
>>> unpack('u1u3u4s16', bytearray(b'\xa3\xff\xfc')) | ||
(1, 2, 3, -4) | ||
>>> calcsize('u1u3u4s16') | ||
24 | ||
Unpacked fields can be named by assigning them to variables or by wrapping the result in a named tuple: | ||
|
||
.. code-block:: python | ||
>>> from bitstruct import * | ||
>>> from collections import namedtuple | ||
>>> MyName = namedtuple('myname', [ 'a', 'b', 'c', 'd' ]) | ||
>>> unpacked = unpack('u1u3u4s16', bytearray(b'\xa3\xff\xfc')) | ||
>>> myname = MyName(*unpacked) | ||
>>> myname | ||
myname(a=1, b=2, c=3, d=-4) | ||
>>> myname.c | ||
3 | ||
An example of packing/unpacking a unsinged integer, a signed integer, a float and a bytearray: | ||
|
||
.. code-block:: python | ||
>>> from bitstruct import * | ||
>>> pack('u5s5f32b13', 1, -1, 3.75, bytearray(b'\xff\xff')) | ||
bytearray(b'\x0f\xd0\x1c\x00\x00?\xfe') | ||
>>> unpack('u5s5f32b13', bytearray(b'\x0f\xd0\x1c\x00\x00?\xfe')) | ||
(1, -1, 3.75, bytearray(b'\xff\xf8')) | ||
>>> calcsize('u5s5f32b13') | ||
55 | ||
An example of unpacking from a hexstring and a binary file: | ||
|
||
.. code-block:: python | ||
>>> from bitstruct import * | ||
>>> from binascii import * | ||
>>> unpack('s17s13b24', bytearray(unhexlify('0123456789abcdef'))) | ||
(582, -3751, bytearray(b'\xe2j\xf3')) | ||
>>> with open("test.bin", "rb") as fin: | ||
... unpack('s17s13b24', bytearray(fin.read(8))) | ||
... | ||
... | ||
(582, -3751, bytearray(b'\xe2j\xf3')) | ||
Change endianess of data and then unpack it: | ||
|
||
.. code-block:: python | ||
>>> from bitstruct import * | ||
>>> packed = pack('u1u3u4s16', 1, 2, 3, 1) | ||
>>> unpack('u1u3u4s16', byteswap('12', packed)) | ||
(1, 2, 3, 256) | ||
.. |buildstatus| image:: https://travis-ci.org/eerimoq/archive.svg | ||
.. _buildstatus: https://travis-ci.org/eerimoq/archive |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import re | ||
import struct | ||
|
||
|
||
def _parse_format(fmt): | ||
types = re.findall(r'[a-zA-Z]+', fmt) | ||
sizes = map(lambda size: int(size), | ||
re.findall(r'\d+', fmt)) | ||
return zip(types, sizes) | ||
|
||
|
||
def _pack_integer(size, arg): | ||
if arg < 0: | ||
arg = ((1 << size) + arg) | ||
return '{{:0{}b}}'.format(size).format(arg) | ||
|
||
|
||
def _pack_float(size, arg): | ||
if size == 32: | ||
value = struct.pack('>f', arg) | ||
elif size == 64: | ||
value = struct.pack('>d', arg) | ||
else: | ||
raise ValueError('Bad float size {}. Must be 32 or 64.'.format(size)) | ||
return ''.join('{:08b}'.format(b) | ||
for b in bytearray(value)) | ||
|
||
|
||
def _pack_bytearray(size, arg): | ||
bits = ''.join('{:08b}'.format(b) | ||
for b in arg) | ||
return bits[0:size] | ||
|
||
|
||
def _unpack_integer(type, bits): | ||
value = int(bits, 2) | ||
if type == 's': | ||
if bits[0] == '1': | ||
value -= (1 << len(bits)) | ||
return value | ||
|
||
|
||
def _unpack_float(size, bits): | ||
packed = _unpack_bytearray(size, bits) | ||
if size == 32: | ||
value = struct.unpack('>f', packed)[0] | ||
elif size == 64: | ||
value = struct.unpack('>d', packed)[0] | ||
else: | ||
raise ValueError('Bad float size {}. Must be 32 or 64.'.format(size)) | ||
return value | ||
|
||
def _unpack_bytearray(size, bits): | ||
value = bytearray() | ||
for i in range(size // 8): | ||
value.append(int(bits[8*i:8*i+8], 2)) | ||
rest = size % 8 | ||
if rest > 0: | ||
value.append(int(bits[size-rest:], 2) << (8-rest)) | ||
return value | ||
|
||
|
||
def pack(fmt, *args): | ||
''' | ||
Return a bytearray containing the values v1, v2, ... packed according | ||
to the given format. The arguments must match the values required by | ||
the format exactly. If the total number of bits are not a multiple | ||
of 8, padding will be added at the end of the last byte. | ||
:param fmt: Bitstruct format string. | ||
:param args: Variable argument list of values to pack. | ||
:returns: Bytearray of packed values. | ||
`fmt` is a string of type-length pairs. There are three | ||
types; 'u', 's' and 'p'. Length is the number of bits to pack | ||
the value into. | ||
- 'u' -- unsigned integer | ||
- 's' -- signed integer | ||
- 'f' -- floating point number of 32 or 64 bits | ||
- 'b' -- bytearray | ||
- 'p' -- padding, ignore | ||
Example format string: 'u1u3p7s16' | ||
''' | ||
bits = '' | ||
infos = _parse_format(fmt) | ||
i = 0 | ||
for type, size in infos: | ||
if type == 'p': | ||
bits += size * '0' | ||
else: | ||
if type in 'us': | ||
bits += _pack_integer(size, args[i]) | ||
elif type == 'f': | ||
bits += _pack_float(size, args[i]) | ||
elif type == 'b': | ||
bits += _pack_bytearray(size, args[i]) | ||
else: | ||
raise ValueError("bad type '{}' in format".format(type)) | ||
i += 1 | ||
|
||
# padding of last byte | ||
tail = len(bits) % 8 | ||
if tail != 0: | ||
bits += (8 - tail) * '0' | ||
|
||
return bytearray([int(''.join(bits[i:i+8]), 2) | ||
for i in range(0, len(bits), 8)]) | ||
|
||
|
||
def unpack(fmt, data): | ||
''' | ||
Unpack the bytearray (presumably packed by pack(fmt, ...)) according | ||
to the given format. The result is a tuple even if it contains exactly | ||
one item. | ||
:param fmt: Bitstruct format string. | ||
:param data: Bytearray of values to unpack. | ||
:returns: Tuple of unpacked values. | ||
''' | ||
bits = ''.join(['{:08b}'.format(b) for b in data]) | ||
infos = _parse_format(fmt) | ||
res = [] | ||
i = 0 | ||
for type, size in infos: | ||
if type == 'p': | ||
pass | ||
else: | ||
if type in 'us': | ||
value = _unpack_integer(type, bits[i:i+size]) | ||
elif type == 'f': | ||
value = _unpack_float(size, bits[i:i+size]) | ||
elif type == 'b': | ||
value = _unpack_bytearray(size, bits[i:i+size]) | ||
res.append(value) | ||
i += size | ||
return tuple(res) | ||
|
||
|
||
def calcsize(fmt): | ||
''' | ||
Return the size of the bitstruct (and hence of the bytearray) corresponding | ||
to the given format. | ||
:param fmt: Bitstruct format string. | ||
:returns: Number of bits in format string. | ||
''' | ||
return sum([size for _, size in _parse_format(fmt)]) | ||
|
||
|
||
def byteswap(fmt, data, offset = 0): | ||
''' | ||
In place swap bytes in `data` according to `fmt`, starting at | ||
byte `offset`. `fmt` must be an iterable, iterating over | ||
number of bytes to swap. | ||
:param fmt: Swap format string. | ||
:param data: Bytearray of data to swap. | ||
:param offset: Start offset into `data`. | ||
:returns: Bytearray of swapped bytes. | ||
''' | ||
i = offset | ||
for f in fmt: | ||
length = int(f) | ||
value = data[i:i+length] | ||
value.reverse() | ||
data[i:i+length] = value | ||
i += length | ||
return data |
Oops, something went wrong.