Skip to content

Commit

Permalink
Copy from archive.
Browse files Browse the repository at this point in the history
  • Loading branch information
eerimoq committed Apr 26, 2015
1 parent 6e42f28 commit 09cc0fb
Show file tree
Hide file tree
Showing 10 changed files with 1,155 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Makefile
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/*
87 changes: 87 additions & 0 deletions README.rst
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
170 changes: 170 additions & 0 deletions bitstruct.py
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

0 comments on commit 09cc0fb

Please sign in to comment.