BitTypes¶
BitTypes is a package that exposes classes for working with C-style binary data in Python. BitTypes classes (e.g. UInt8, Int16, etc.) have two main attributes: value and bits.
The value attribute is the pythonic value of what the binary data represents. For example, if the binary data is 0b00000001, the value attribute of a UInt8 object would be 1.
The bits attribute is the binary representation of the value. For example, if the value of a UInt8 object is 1, the bits attribute would be 0b00000001.
Setting either the value or bits attribute will update the other attribute, as both are properties rather than fields.
[6]:
from bytemaker.bittypes import (
Buffer32, Buffer8, SInt8, UInt8, UInt16, UInt32, Float16, Str32,
SInt, UInt, Float)
Construction¶
BitTypes can be constructed in one of two ways: by bits, or by value.
All BitTypes share a common constructor signature; that is,
def __init__(
self,
source: Optional[(T | BitVector | BitType)] = None,
value: Optional[T] = None,
bits: Optional[BitVector] = None,
endianness: Literal["big", "little", "source_else_big"] = "source_else_big",
)
Where T is the pythonic value type that the BitType represents (e.g., int for SInt8/UInt8). If source is provided rather than value or bits, __init__ will determine which of value or bits it corresponds to.
If a BitType is provided as input, the constructed type will use value to determine what the appropriate type is. As such, Str8(UInt8(5)) will be equivalent to Str8("5"), not Str8(bits=BitVector("00000101")). To achieve the latter, you can do Str8(bits=UInt8(5).bits).
BitTypes do support endianness. This is set at object creation time— to adjust it further, create a new BitType from the previous one, but with different endianness. The underlying BitVector is agnostic to what endianness is set to; it only becomes relevant when converting the BitType to a bytestream via bytes() or bytearray()
[16]:
sint8_32 = SInt8(32)
print("SInt8(32):", sint8_32)
uint8_32 = UInt8(SInt8(32))
print("UInt8(SInt8(32)):", uint8_32)
print("------------------------------")
print("Constructing one bittype from another's bits:")
uint8_other = UInt8(bits=SInt8(-32).bits)
print("UInt8(bits=SInt8(-32).bits):", uint8_other)
print("------------------------------")
print("Showing endianness relevancy.")
print("Note the first two lines being the same and the next two being different")
a_str32 = Str32("Hiya")
print("Str32('Hiya'):", a_str32)
a_str32_little = Str32("Hiya", endianness="little")
print("Str32('Hiya', endianness='little'):", a_str32_little)
a_str32_bytes = bytes(a_str32)
print("bytes(Str32('Hiya')):", a_str32_bytes)
a_str32_little_bytes = bytes(a_str32_little)
print("bytes(Str32('Hiya', endianness='little')):", a_str32_little_bytes)
SInt8(32): SInt8[big](32 = 00100000)
UInt8(SInt8(32)): UInt8[big](32 = 00100000)
------------------------------
Constructing one bittype from another's bits:
UInt8(bits=SInt8(-32).bits): UInt8[big](224 = 11100000)
------------------------------
Showing endianness relevancy.
Note the first two lines being the same and the next two being different
Str32('Hiya'): Str32[big](Hiya = 01001000...01100001)
Str32('Hiya', endianness='little'): Str32[little](Hiya = 01001000...01100001)
bytes(Str32('Hiya')): b'Hiya'
bytes(Str32('Hiya', endianness='little')): b'ayiH'
Magic Methods¶
All bittypes support typical bit-manipulation magic methods (__lshift__, __rshift__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __invert__). Additionally, the numeric types support all operations you’d expect for int and float types.
[19]:
a_uint32 = UInt32(0x12345678)
print("UInt32(0x12345678):", a_uint32)
a_uint32_shifted = a_uint32 << 1
print("UInt32(0x12345678) <<1 (≈ **2):", a_uint32_shifted)
print('Buffer32("0xFEFEFEFE") & Buffer32("0x88888888"):', Buffer32("0xFEFEFEFE") & Buffer32("0x88888888"))
print("------------------------------")
print("XOR swap algorithm using Bytemaker:")
a_uint8_1 = UInt8(0x12)
a_uint8_2 = UInt8(0x34)
print("a = UInt8(0x12):", a_uint8_1)
print("b = UInt8(0x34):", a_uint8_2)
a_uint8_1 ^= a_uint8_2
print("UInt8(0x12) ^= UInt8(0x34):", ("a=" + str(a_uint8_1), "b=" + str(a_uint8_2)))
a_uint8_2 ^= a_uint8_1
print("UInt8(0x34) ^= UInt8(0x12):", ("a=" + str(a_uint8_1), "b=" + str(a_uint8_2)))
a_uint8_1 ^= a_uint8_2
print("UInt8(0x12) ^= UInt8(0x34):", ("a=" + str(a_uint8_1), "b=" + str(a_uint8_2)))
print("a == UInt8(0x34):", a_uint8_1)
print("b == UInt8(0x12):", a_uint8_2)
print("------------------------------")
UInt32(0x12345678): UInt32[big](305419896 = 00010010...01111000)
UInt32(0x12345678) <<1 (≈ **2): UInt32[big](610839792 = 00100100...11110000)
Buffer32("0xFEFEFEFE") & Buffer32("0x88888888"): Buffer32[big](BitVector('10001000 10001000 10001000 10001000') = 10001000...10001000)
------------------------------
XOR swap algorithm using Bytemaker:
a = UInt8(0x12): UInt8[big](18 = 00010010)
b = UInt8(0x34): UInt8[big](52 = 00110100)
UInt8(0x12) ^= UInt8(0x34): ('a=UInt8[big](38 = 00100110)', 'b=UInt8[big](52 = 00110100)')
UInt8(0x34) ^= UInt8(0x12): ('a=UInt8[big](38 = 00100110)', 'b=UInt8[big](18 = 00010010)')
UInt8(0x12) ^= UInt8(0x34): ('a=UInt8[big](52 = 00110100)', 'b=UInt8[big](18 = 00010010)')
a == UInt8(0x34): UInt8[big](52 = 00110100)
b == UInt8(0x12): UInt8[big](18 = 00010010)
------------------------------
[ ]:
a_float16 = Float16(3.14159)
a_uint16 = UInt16(0x12)
print()