2.3.5. Actions#
Actions are a powerful feature that allow you to perform custom operations on the struct’s state during parsing. Instead of directly parsing or storing a field’s value, you can define actions to modify or interact with the data processing at different stages.
There are two types of actions you can define:
Pack Actions: These actions are executed before packing data into the struct. They are typically used for operations such as checksum calculation, logging, or any other operation that must occur before serializing the data.
Unpack Actions: These actions are executed before unpacking the data from the struct. They are commonly used for validation, verification, or any operation that needs to happen before deserialization.
An action can be as simple as executing a function during packing or unpacking. For example:
@struct
class Format:
_: Action(pack=lambda ctx: print("Hello, World!"))
a: uint8
In this case, when the struct is packed, it will print "Hello, World!" to the
console. Actions like this can be used for logging, validation, or other side
effects that are not tied to the direct data of the struct.
2.3.5.1. Advanced Usage: Message Digests#
Warning
This feature has a different syntax in Python 3.14+.
Actions can also be used to perform more complex operations, such as calculating
checksums or cryptographic hash values before or after processing fields. For
example, you can use an action to automatically wrap a sequence of fields with a
specialized message digest like SHA256 (see Digest):
@struct
class Format:
key: b"..."
with Sha2_256("hash", verify=True):
user_data: Bytes(50)
# the 'hash' attribute will be set automatically
@struct
class Format:
key: b"..."
_hash_begin: DigestField.begin("hash", Sha2_256_Algo)
user_data: Bytes(50)
# the 'hash' attribute must be set manually
hash: DigestField("hash", Bytes(32), verify=True) = None
# or
hash: Sha2_256_Field("hash", verify=True) = None
In this example, the user_data field is wrapped with the Sha2_256 digest action.
When the struct is packed, a SHA256 hash is computed for the user_data and stored
in the hash attribute. If the struct is unpacked and the data has been tampered
with, the verification step will raise an error.
The resulting struct includes the following fields:
>>> Format.__struct__.fields
[
Field('key', struct=<ConstBytes>, ...),
(Action(Digest.begin), None),
Field('user_data', struct=<Bytes>, ...),
(Action(Digest.end_pack, Digest.end_unpack), None),
Field('hash', struct=<Bytes>, ...),
(UnpackAction(Digest.verfiy), None)
]
>>> Format.__struct__.fields
[
Field('key', struct=<ConstBytes>, ...),
(<DigestFieldAction>, None),
Field('user_data', struct=<Bytes>, ...),
<DigestField>,
]
Here, you can see that:
The
Digest.beginandDigest.[end,begin]_packactions are executed around theuser_datafield during packing.The
hashfield is automatically calculated and added to the struct.During unpacking, the
Digest.verifyaction ensures that the hash matches the expected value.