2.3.2. Dynamic Byte Order#
In addition to traditional byte order types, caterpillar supports a dynamic byte order based on the current pack or unpack context.
Added in version 2.6.4: This feature is available in starting from version 2.6.4
There are various use-cases that require a struct to handle both big-endian and
little-endian. In order to reduce the amount of code for these structs, caterpillar
introduces a special byte order type: DynByteOrder. It
supports two different configuration levels:
struct-wide: endianess is configured when calling
pack()orunpack().field-level: byte order is applied per field
Each of these configuration levels support various methods of selecting the target endian:
global configuration using an additional keyword argument in
pack()orunpack().context-key: configuration based on a value within the current context
custom function: endian is derived from a custom function
2.3.2.1. Example: struct-wide dynamic byte order#
Let’s consider the following struct definition. The dynamic endian configuration will be applied to all fields that haven’t got an endian already set.
1from caterpillar.context import CTX_ORDER
2
3@struct(order=Dynamic)
4class Format:
5 a: uint16 # litte endian or big endian is decided using
6 b: uint32 # a global context variable
7
8config = {CTX_ORDER: BigEndian}
9obj = Format(a=0x1234, b=0x56789ABC)
10
11# pack the object using BigEndian
12pack(obj, **config)
13
14# now pack with little endian
15config[CTX_ORDER] = LittleEndian
16pack(obj, **config)
Here we pass an additional global context variable named CTX_ORDER ("_order")
to the packing and unpacking process. The dynamic endian will automatically infer the order based on this
global variable.
2.3.2.2. Example: field-level dynamic byte order#
The same concept as shown above can be applied to single fields too. By default, the endian to use must be given as a global context variable as described before.
1from caterpillar.context import CTX_ORDER
2
3@struct
4class Format:
5 a: uint16
6 b: Dynamic + uint32 # only this field will be affected
7
8# packing and unpacking is the same as in the previous example
2.3.2.3. Example: context key reference#
Sometimes format specifications use a special field indicating whether all following fields are using big or little endian. To implement this kind of endian selection, a so called context key can be specified, which can take one of the following forms:
direct reference: just a string reference
# ... spec: uint8 number: Dynamic(key="spec") # ...
context-lambda: a function that takes the current context as its first parameter and returns the target endian configuration value.
# ... spec: uint8 number: Dynamic(key=this.spec) # ...
The target endianess is decided based on the context value:
strwill be applied directly as the format characterany object storing a
chstring valueany other object is converted to a
booland the following mapping is applied:True:LittleEndianFalse:BigEndian
As an example, consider the following definition:
1@struct(order=BigEndian)
2class Format:
3 spec: uint8 = 0
4 a: DynByteOrder(key=this.spec) + uint16
5 # alternatively
6 # a: Dynamic(this.spec) + uint16
7 b: uint32
8
9# packing and unpacking does not require the extra endian value
10obj = Format(spec=0, a=0x1234, b=0x56789ABC)
11# 0 -> False, results in BigEndian
12data_be = pack(obj)
13
14# 1 -> True, results in LittleEndian
15obj.spec = 1
16data_le = pack(obj)