How to model enums and bit fields¶
Use these techniques when a wire integer really represents a closed set of named values, or when several small fields share a single byte-aligned run:
Expose an integer as an enum¶
Wire scalars decode to integers. Use rustruct.convert() when the public value should
be an enum:
import enum
from rustruct import Struct, U8, convert
class Status(enum.IntEnum):
OK = 0
ERROR = 1
class Reply(Struct):
status: object = convert(U8, decode=Status, encode=int)
A closed IntEnum rejects unknown values while decoding. For extensible
protocols, use an enum implementation that preserves unknown integers or
leave the field as a plain scalar.
Pack adjacent bit fields¶
Consecutive rustruct.bits() fields are packed most-significant bit first:
from rustruct import Struct, bits, slice
class Fragment(Struct):
reserved: int = bits(1, default=0)
df: int = bits(1, default=0)
mf: int = bits(1, default=0)
offset: int = bits(13, default=0)
fragment = Fragment(df=True)
assert fragment.pack() == b"\x40\x00"
assert Fragment.unpack(b"\x40\x00").df == 1
Those 16 bits (0x40 0x00 = 0100 0000 0000 0000), most significant bit
first:
Bit(s) |
Field |
Value in |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
|
3-15 |
|
|
Each consecutive bit run must end on a byte boundary. signed=True selects a
signed field, and const= declares a fixed bit value. Values that do not fit
their width raise a pack error.
Bit fields are flat fields on the containing object. This makes their names available to later length and condition expressions:
from rustruct import Struct, bits, slice
class IPv4Options(Struct):
version: int = bits(4, default=4)
ihl: int = bits(4, default=5)
options: bytes = slice(len=lambda f: (f.ihl - 5) * 4, default=b"")
Return a mapping of flags¶
The mapping-based schema API also has a flags kind. It decodes one integer
into named booleans or bit ranges and can keep, ignore, or reject unnamed
bits. See Low-level schema language when a mapping is more useful than
flat typed fields.