[plum] String Tutorial: Sized String Structure MembersΒΆ

This tutorial shows how to use the SizeMember and VariableSizeMember() member definitions within a structure definition to associate a size member and another variably sized string 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 string member. During string pack operations or during string instantiations, the association allows the size member to be left unspecified and then derived from the size of the string member.

To demonstrate, start by examining the example below. The AsciiStr type acts greedy (it varies in size and consumes all bytes given to it). If left to unpack as normal, the string member consumes every byte including the byte intended for the bookmark. The use of SizeMember and VariableSizeMember() member definitions associates the string member with the size member. This causes the unpacking operation to only allow the string member to consume the number of bytes specified by the size member.

>>> from plum import pack, unpack
>>> from plum.int.little import UInt8
>>> from plum.structure import Member, SizeMember, Structure, VariableSizeMember
>>> from plum.str import AsciiStr
>>>
>>> class Struct1(Structure):
...     size: int = SizeMember(cls=UInt8)
...     string: str = VariableSizeMember(size_member=size, cls=AsciiStr)
...     bookend: int = Member(cls=UInt8)
...
>>> unpack(Struct1, b'\x0cHello World!\x99').dump()
+--------+----------------+----------------+-------------------------------------+----------+
| Offset | Access         | Value          | Bytes                               | Type     |
+--------+----------------+----------------+-------------------------------------+----------+
|        |                |                |                                     | Struct1  |
|  0     | [0] (.size)    | 12             | 0c                                  | UInt8    |
|        | [1] (.string)  |                |                                     | AsciiStr |
|  1     |   [0:12]       | 'Hello World!' | 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |          |
| 13     | [2] (.bookend) | 153            | 99                                  | UInt8    |
+--------+----------------+----------------+-------------------------------------+----------+

The association also allows the size to be left unspecified when instantiating the structure or packing a dictionary of member values:

>>> Struct1(string='Hello World!', bookend=0x99).dump()
+--------+----------------+----------------+-------------------------------------+----------+
| Offset | Access         | Value          | Bytes                               | Type     |
+--------+----------------+----------------+-------------------------------------+----------+
|        |                |                |                                     | Struct1  |
|  0     | [0] (.size)    | 12             | 0c                                  | UInt8    |
|        | [1] (.string)  |                |                                     | AsciiStr |
|  1     |   [0:12]       | 'Hello World!' | 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |          |
| 13     | [2] (.bookend) | 153            | 99                                  | UInt8    |
+--------+----------------+----------------+-------------------------------------+----------+
>>>
>>> pack(Struct1, {'string': 'Hello World!', 'bookend': 0x99})
bytearray(b'\x0cHello World!\x99')

Note

The SizeMember accepts a ratio argument to facilitate converting the size member units to a byte count when it is not a 1:1 ratio.