[plum] Tutorial: Sized Structure Member¶
This tutorial shows how to use the sized_member()
function within a structure
definition to associate a size member and another variably sized member to one another.
During unpack operations, the association causes the the unpacked size member to
control the number of bytes that are available during the unpacking of the associated
member. The association allows the size member to be left unspecified and the value of
it derived from the other member.
The examples shown on this page require the following setup:
>>> from plum.array import ArrayX
>>> from plum.bigendian import uint8
>>> from plum.structure import member, sized_member, Structure
Basics¶
To demonstrate, examine the example below. The example uses an undimensioned
array transform since it acts greedy (it varies in size and consumes all bytes
given to it). If left to unpack as normal, the array member consumes every byte
including the byte intended for the bookmark
. The use of the sized_member()
function associates the array
member with the size
member. This
causes the unpacking operation to only allow the array
member to consume the
number of bytes specified by the size
member.
>>> array = ArrayX(fmt=uint8) # greedy array
>>>
>>> class Struct1(Structure):
... size: int = member(fmt=uint8, compute=True)
... array: list = sized_member(size=size, fmt=array)
... bookend: int = member(fmt=uint8)
...
>>> Struct1.unpack(b'\x02\x01\x02\x99').dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct1 (Structure) |
| 0 | size | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
This association also the size
to be left unspecified
when instantiating the structure with member values:
>>> Struct1(array=[1, 2], bookend=0x99).dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct1 (Structure) |
| 0 | size | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
>>>
>>> Struct1(array=[1, 2], bookend=0x99).ipack()
b'\x02\x01\x02\x99'
The association causes the size
member to be updated when setting
a new array
value using the array
attribute:
>>> struct1 = Struct1(array=[1, 2], bookend=123)
>>> struct1.size
2
>>>
>>> struct1.array = [1, 2, 3, 4, 5]
>>> struct1.size
5
Using Ratios¶
The sized_member()
function supports accepting a ratio
parameter to facilitate
converting the size
member units to a byte count when it is not a 1:1 ratio.
In the example below, a ratio of 2 means that for every size increment of
1, the array holds 2 extra bytes:
>>> class Struct2(Structure):
... size: int = member(fmt=uint8, compute=True)
... array: list = sized_member(size=size, fmt=array, ratio=2)
... bookend: int = member(fmt=uint8)
...
>>> Struct2.unpack(b'\x01\x01\x02\x99').dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct2 (Structure) |
| 0 | size | 1 | 01 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
>>>
>>> Struct2(array=[1, 2], bookend=0x99).ipack()
b'\x01\x01\x02\x99'
Ignoring Members¶
member()
and sized_member()
member definition functions
support marking the members to be ignored during comparisons and may
be used at the same time for that purpose:
>>> class Struct4(Structure):
... size: int = member(fmt=uint8, compute=True, ignore=True)
... array: list = sized_member(size=size, fmt=array, ignore=True)
... bookend: int = member(fmt=uint8)
...
>>> struct4 = Struct4.unpack(b'\x02\x01\x02\x99')
>>> struct4.dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct4 (Structure) |
| 0 | size | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 1 | 01 | uint8 |
| 2 | [1] | 2 | 02 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
>>>
>>> struct4 == Struct4(array=[], bookend=0x99)
True
Defaulting Sized Member¶
Specifying a default value for the array in the member definition allows the structure to be instantiated or packed without a value provided for the array:
>>> class Struct5(Structure):
... size: int = member(fmt=uint8, compute=True)
... array: list = sized_member(size=size, fmt=array, default=(0, 1))
... bookend: int = member(fmt=uint8)
...
>>> Struct5(bookend=0x99).dump()
+--------+---------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+---------+-------+-------+---------------------+
| | | | | Struct5 (Structure) |
| 0 | size | 2 | 02 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 0 | 00 | uint8 |
| 2 | [1] | 1 | 01 | uint8 |
| 3 | bookend | 153 | 99 | uint8 |
+--------+---------+-------+-------+---------------------+
>>>
>>> Struct5(bookend=0x99).ipack()
b'\x02\x00\x01\x99'
Accommodating Fixed Offset¶
The offset
argument of the sized_member()
function compensates for
the size member reflecting both the size of the variable size member
and overhead bytes such as the size member itself. For example,
the size member in the following structure reflects the total size
of the structure including the size
member:
>>> from plum.array import ArrayX
>>> from plum.structure import Structure, member, sized_member
>>>
>>> array = ArrayX(fmt=uint8) # greedy array
>>>
>>> class Struct6(Structure):
... size: int = member(fmt=uint8, compute=True)
... array: list = sized_member(fmt=array, size=size, offset=1)
...
>>> Struct6(array=[0, 1]).dump()
+--------+--------+-------+-------+---------------------+
| Offset | Access | Value | Bytes | Format |
+--------+--------+-------+-------+---------------------+
| | | | | Struct6 (Structure) |
| 0 | size | 3 | 03 | uint8 |
| | array | | | List[int] |
| 1 | [0] | 0 | 00 | uint8 |
| 2 | [1] | 1 | 01 | uint8 |
+--------+--------+-------+-------+---------------------+