Source code for bitstruct

import re
import struct

__version__ = "3.3.1"


def _parse_format(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 = ">"
    for info in parsed_infos:
        if info[0] != "":
            endianness = info[0]
        infos.append((info[1], int(info[2]), endianness))

    return infos


def _pack_integer(size, arg):
    value = int(arg)

    if value < 0:
        value = ((1 << size) + value)

    return '{{:0{}b}}'.format(size).format(value)


def _pack_boolean(size, arg):
    value = bool(arg)

    return _pack_integer(size, int(value))


def _pack_float(size, arg):
    value = float(arg)

    if size == 32:
        value = struct.pack('>f', value)
    elif size == 64:
        value = struct.pack('>d', value)
    else:
        raise ValueError('expected float size of 32 of 64 bits (got {})'.format(
            size))

    return ''.join('{:08b}'.format(b)
                   for b in bytearray(value))


def _pack_bytearray(size, arg):
    value = bytearray(arg)
    bits = ''.join('{:08b}'.format(b)
                   for b in value)

    return bits[0:size]


def _pack_text(size, arg):
    value = arg.encode('utf-8')

    return _pack_bytearray(size, bytearray(value))


def _unpack_integer(type_, bits):
    value = int(bits, 2)

    if type_ == 's':
        if bits[0] == '1':
            value -= (1 << len(bits))

    return value


def _unpack_boolean(bits):
    value = _unpack_integer('u', bits)

    return bool(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('expected float size of 32 of 64 bits (got {})'.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 _unpack_text(size, bits):
    return _unpack_bytearray(size, bits).decode('utf-8')


[docs]def pack(fmt, *args): """Return a byte string containing the values v1, v2, ... packed according to the given format. 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. See format description below. :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. Bitorder may be omitted. Bitorder is either ">" or "<", where ">" means MSB first and "<" means LSB first. If bitorder is omitted, the previous values' bitorder is used for the current value. For example, in the format string "u1<u2u3" u1 is MSB first and both u2 and u3 are LSB first. There are seven types; 'u', 's', 'f', 'b', 't', 'r' and 'p'. - 'u' -- unsigned integer - 's' -- signed integer - 'f' -- floating point number of 32 or 64 bits - 'b' -- boolean - 't' -- text (ascii or utf-8) - 'r' -- raw, bytes - 'p' -- padding, ignore Length is the number of bits to pack the value into. Example format string: 'u1u3p7s16' """ bits = '' infos = _parse_format(fmt) i = 0 # Sanity check of the number of arguments. number_of_arguments = 0 for info in infos: if info[0] != 'p': number_of_arguments += 1 if number_of_arguments > len(args): raise ValueError("pack expected {} item(s) for packing " "(got {})".format(number_of_arguments, len(args))) for type_, size, endianness in infos: if type_ == 'p': bits += size * '0' else: if type_ in 'us': value_bits = _pack_integer(size, args[i]) elif type_ == 'f': value_bits = _pack_float(size, args[i]) elif type_ == 'b': value_bits = _pack_boolean(size, args[i]) elif type_ == 't': value_bits = _pack_text(size, args[i]) elif type_ == 'r': value_bits = _pack_bytearray(size, bytearray(args[i])) else: raise ValueError("bad type '{}' in format".format(type_)) # reverse the bit order in little endian values if endianness == "<": value_bits = value_bits[::-1] bits += value_bits i += 1 # padding of last byte tail = len(bits) % 8 if tail != 0: bits += (8 - tail) * '0' return bytes(bytearray([int(''.join(bits[i:i+8]), 2) for i in range(0, len(bits), 8)]))
[docs]def unpack(fmt, data): """Unpack `data` (byte string, bytearray or list of integers) according to the given format. The result is a tuple even if it contains exactly one item. :param fmt: Bitstruct format string. See pack() for details. :param data: Byte string of values to unpack. :returns: A tuple of the unpacked values. """ bits = ''.join(['{:08b}'.format(b) for b in bytearray(data)]) infos = _parse_format(fmt) # Sanity check. 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 " "(got {})".format(number_of_bits_to_unpack, len(bits))) res = [] i = 0 for type_, size, endianness in infos: if type_ == 'p': pass else: value_bits = bits[i:i+size] # reverse the bit order in little endian values if endianness == "<": value_bits = value_bits[::-1] if type_ in 'us': value = _unpack_integer(type_, value_bits) elif type_ == 'f': value = _unpack_float(size, value_bits) elif type_ == 'b': value = _unpack_boolean(value_bits) elif type_ == 't': value = _unpack_text(size, value_bits) elif type_ == 'r': value = bytes(_unpack_bytearray(size, value_bits)) else: raise ValueError("bad type '{}' in format".format(type_)) res.append(value) i += size return tuple(res)
[docs]def calcsize(fmt): """Calculate the number of bits in given format. :param fmt: Bitstruct format string. :returns: Number of bits in format string. """ return sum([size for _, size, _ in _parse_format(fmt)])
[docs]def byteswap(fmt, data, offset = 0): """Swap bytes in `data` according to `fmt`, starting at byte `offset`. `fmt` must be an iterable, iterating over number of bytes to swap. For example, the format string "24" applied to the byte string b'\x00\x11\x22\x33\x44\x55' will produce the result b'\x11\x00\x55\x44\x33\x22'. :param fmt: Swap format string. :param data: Byte string of data to swap. :param offset: Start offset into `data`. :returns: Byte string of swapped bytes. """ i = offset data_swapped = b'' for f in fmt: length = int(f) value = data[i:i+length] data_swapped += value[::-1] i += length return data_swapped