Skip to content

Commit

Permalink
Byte order per item.
Browse files Browse the repository at this point in the history
  • Loading branch information
eerimoq committed Jun 8, 2017
1 parent 24a5d3a commit 1a9e3b6
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 43 deletions.
56 changes: 27 additions & 29 deletions bitstruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,28 @@
import re
import struct

__version__ = "3.4.0"
__version__ = "4.0.0"


def _parse_format(fmt):
if fmt[-1] in [">", "<"]:
byte_order = fmt[-1]
fmt = fmt[:-1]
else:
byte_order = ">"

parsed_infos = re.findall(r'([<>]?)([a-zA-Z])(\d+)', fmt)
parsed_infos = re.findall(r'([<>]?)([<>]?)([a-zA-Z])(\d+)', fmt)

# Use big endian as default and use the endianness of the previous
# value if none is given for the current value.
infos = []
endianness = ">"
byte_order = ">"

for info in parsed_infos:
if info[0] != "":
endianness = info[0]

infos.append((info[1], int(info[2]), endianness))
if info[1] != "":
byte_order = info[1]

infos.append((info[2], int(info[3]), endianness, byte_order))

return infos, byte_order
return infos


def _pack_integer(size, arg):
Expand Down Expand Up @@ -127,9 +125,9 @@ def pack(fmt, *args):
:param args: Variable argument list of values to pack.
:returns: A byte string of the packed values.
`fmt` is a string of bitorder-type-length groups, and optionally a
byteorder identifier afer the groups. Bitorder and byteorder may
be omitted.
`fmt` is a string of [bitorder-[byteorder-]]type-length
groups. Bitorder and byteorder may be omitted. Bitorder must be
given if byteorder is given.
Bitorder is either ">" or "<", where ">" means MSB first and "<"
means LSB first. If bitorder is omitted, the previous values'
Expand All @@ -138,7 +136,9 @@ def pack(fmt, *args):
Byteorder is either ">" or "<", where ">" means most significant
byte first and "<" means least significant byte first. If
byteorder is omitted, most significant byte first is used.
byteorder is omitted, the previous values' byteorder is used for
the current value. By default, most significant byte first is
used.
There are seven types; 'u', 's', 'f', 'b', 't', 'r' and 'p'.
Expand All @@ -155,15 +155,12 @@ def pack(fmt, *args):
Example format string with default bit and byte ordering: 'u1u3p7s16'
Same format string, but with least significant byte first:
'u1u3p7s16<'
Same format string, but with LSB first ('<' prefix) and least
significant byte first ('<' suffix): '<u1u3p7s16<'
'><u1u3p7s16'
"""

bits = ''
infos, byte_order = _parse_format(fmt)
infos = _parse_format(fmt)
i = 0

# Sanity check of the number of arguments.
Expand All @@ -177,7 +174,7 @@ def pack(fmt, *args):
raise ValueError("pack expected {} item(s) for packing "
"(got {})".format(number_of_arguments, len(args)))

for type_, size, endianness in infos:
for type_, size, endianness, byte_order in infos:
if type_ == 'p':
bits += size * '0'
else:
Expand All @@ -194,11 +191,11 @@ def pack(fmt, *args):
else:
raise ValueError("bad type '{}' in format".format(type_))

# reverse the bit order in little endian values
# Reverse the bit order in little endian values.
if endianness == "<":
value_bits = value_bits[::-1]

# reverse bytes order for least significant byte first
# Reverse bytes order for least significant byte first.
if byte_order == ">":
bits += value_bits
else:
Expand All @@ -213,8 +210,9 @@ def pack(fmt, *args):

i += 1

# padding of last byte
# Padding of last byte.
tail = len(bits) % 8

if tail != 0:
bits += (8 - tail) * '0'

Expand All @@ -234,10 +232,10 @@ def unpack(fmt, data):
"""

bits = ''.join(['{:08b}'.format(b) for b in bytearray(data)])
infos, byte_order = _parse_format(fmt)
infos = _parse_format(fmt)

# Sanity check.
number_of_bits_to_unpack = sum([size for _, size, _ in infos])
number_of_bits_to_unpack = sum([size for _, size, _, _ in infos])

if number_of_bits_to_unpack > len(bits):
raise ValueError("unpack requires at least {} bits to unpack "
Expand All @@ -247,11 +245,11 @@ def unpack(fmt, data):
res = []
offset = 0

for type_, size, endianness in infos:
for type_, size, endianness, byte_order in infos:
if type_ == 'p':
pass
else:
# reverse bytes order for least significant byte first
# Reverse bytes order for least significant byte first.
if byte_order == ">":
value_bits = bits[offset:offset+size]
else:
Expand All @@ -266,7 +264,7 @@ def unpack(fmt, data):

value_bits += value_bits_tmp

# reverse the bit order in little endian values
# Reverse the bit order in little endian values.
if endianness == "<":
value_bits = value_bits[::-1]

Expand Down Expand Up @@ -298,7 +296,7 @@ def calcsize(fmt):
"""

return sum([size for _, size, _ in _parse_format(fmt)[0]])
return sum([size for _, size, _, _ in _parse_format(fmt)])


def byteswap(fmt, data, offset = 0):
Expand Down
28 changes: 14 additions & 14 deletions tests/test_bitstruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,51 +271,51 @@ def test_byte_order(self):

# most significant byte first (default)
ref = b'\x02\x46\x9a\xfe\x00\x00\x00'
packed = pack('u19s3f32>', 0x1234, -2, -1.0)
packed = pack('>>u19s3f32', 0x1234, -2, -1.0)
self.assertEqual(packed, ref)
unpacked = unpack('u19s3f32>', packed)
unpacked = unpack('>>u19s3f32', packed)
self.assertEqual(unpacked, (0x1234, -2, -1.0))

# least significant byte first
ref = b'\x34\x12\x18\x00\x00\xe0\xbc'
packed = pack('u19s3f32<', 0x1234, -2, -1.0)
packed = pack('><u19s3f32', 0x1234, -2, -1.0)
self.assertEqual(packed, ref)
unpacked = unpack('u19s3f32<', packed)
unpacked = unpack('><u19s3f32', packed)
self.assertEqual(unpacked, (0x1234, -2, -1.0))

# least significant byte first
ref = b'\x34\x12'
packed = pack('u8s8<', 0x34, 0x12)
packed = pack('><u8s8', 0x34, 0x12)
self.assertEqual(packed, ref)
unpacked = unpack('u8s8<', packed)
unpacked = unpack('><u8s8', packed)
self.assertEqual(unpacked, (0x34, 0x12))

# least significant byte first
ref = b'\x34\x22'
packed = pack('u3u12<', 1, 0x234)
packed = pack('><u3u12', 1, 0x234)
self.assertEqual(packed, ref)
unpacked = unpack('u3s12<', packed)
unpacked = unpack('><u3s12', packed)
self.assertEqual(unpacked, (1, 0x234))

# least significant byte first
ref = b'\x34\x11\x00'
packed = pack('u3u17<', 1, 0x234)
packed = pack('><u3u17', 1, 0x234)
self.assertEqual(packed, ref)
unpacked = unpack('u3s17<', packed)
unpacked = unpack('><u3s17', packed)
self.assertEqual(unpacked, (1, 0x234))

# least significant byte first
ref = b'\x80'
packed = pack('u1<', 1)
packed = pack('><u1', 1)
self.assertEqual(packed, ref)
unpacked = unpack('u1<', packed)
unpacked = unpack('><u1', packed)
self.assertEqual(unpacked, (1, ))

# least significant byte first
ref = b'\x45\x23\x25\x82'
packed = pack('u19u5u1u7<', 0x12345, 5, 1, 2)
packed = pack('><u19u5u1u7', 0x12345, 5, 1, 2)
self.assertEqual(packed, ref)
unpacked = unpack('u19u5u1u7<', packed)
unpacked = unpack('><u19u5u1u7', packed)
self.assertEqual(unpacked, (0x12345, 5, 1, 2))

def test_performance(self):
Expand Down

0 comments on commit 1a9e3b6

Please sign in to comment.