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:

  1. struct-wide: endianess is configured when calling pack() or unpack().

  2. 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() or unpack().

  • 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:

  • str will be applied directly as the format character

  • any object storing a ch string value

  • any other object is converted to a bool and the following mapping is applied:

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)