Previous topic

Next topic

Quick search

Another method of creating BitStream objects is to use the pack function. This takes a format specifier which is a string with comma separated tokens, and a number of items to pack according to it. It’s signature is bitstring.pack(format,*values,**kwargs).

For example using just the *values arguments we can say:

s=bitstring.pack('hex:32, uint:12, uint:12','0x000001b3',352,288)

which is equivalent to initialising as:

s=BitStream('0x0000001b3, uint:12=352, uint:12=288')

The advantage of the pack function is if you want to write more general code for creation.

Note how you can use some tokens without sizes (such as bin and bits in the above example), and use values of any length to fill them. If the size had been specified then a ValueError would be raised if the parameter given was the wrong length. Note also how bitstring literals can be used (the 0b110 in the bitstring returned by foo) and these don’t consume any of the items in *values.

You can also include keyword, value pairs (or an equivalent dictionary) as the final parameter(s). The values are then packed according to the positions of the keywords in the format string. This is most easily explained with some examples. Firstly the format string needs to contain parameter names:

format='hex:32=start_code, uint:12=width, uint:12=height'

Then we can make a dictionary with these parameters as keys and pass it to pack:

Another option when using pack, as well as other methods such as read and byteswap, is to use a format specifier similar to those used in the struct and array modules. These consist of a character to give the endianness, followed by more single characters to give the format.

The endianness character must start the format string and unlike in the struct module it is not optional (except when used with byteswap):

>

Big-endian

<

Little-endian

@

Native-endian

For ‘network’ endianness use > as network and big-endian are equivalent. This is followed by at least one of these format characters:

b

8 bit signed integer

B

8 bit unsigned integer

h

16 bit signed integer

H

16 bit unsigned integer

l

32 bit signed integer

L

32 bit unsigned integer

q

64 bit signed integer

Q

64 bit unsigned integer

f

32 bit floating point number

d

64 bit floating point number

The exact type is determined by combining the endianness character with the format character, but rather than give an exhaustive list a single example should explain:

>h

Big-endian 16 bit signed integer

intbe:16

<h

Little-endian 16 bit signed integer

intle:16

@h

Native-endian 16 bit signed integer

intne:16

As you can see all three are signed integers in 16 bits, the only difference is the endianness. The native-endian @h will equal the big-endian >h on big-endian systems, and equal the little-endian <h on little-endian systems. For the single byte codes b and B the endianness doesn’t make any difference, but you still need to specify one so that the format string can be parsed correctly.

Just as in the struct module you can also give a multiplicative factor before the format character, so the previous example could be written even more concisely as

s=bitstring.pack('>4q',10,11,12,13)

You can of course combine these format strings with other initialisers, even mixing endiannesses (although I’m not sure why you’d want to):

s=bitstring.pack('>6h3b, 0b1, <9L',*range(18))

This rather contrived example takes the numbers 0 to 17 and packs the first 6 as signed big-endian 2-byte integers, the next 3 as single bytes, then inserts a single 1 bit, before packing the remaining 9 as little-endian 4-byte unsigned integers.