.. _tutorial-advanced-actions: 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: .. code-block:: python @struct class Format: _: Action(pack=lambda ctx: print("Hello, World!")) a: uint8 In this case, when the struct is packed, it will print :code:`"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. Advanced Usage: Message Digests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 :class:`~caterpillar.py.Digest`): .. code-block:: python @struct class Format: a: uint8 with Sha2_256("hash", verify=True): user_data: Bytes(50) # the 'hash' attribute will be set automatically In this example, the :code:`user_data` field is wrapped with the :code:`Sha2_256` digest action. When the struct is packed, a SHA256 hash is computed for the :code:`user_data` and stored in the :code:`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: .. code-block:: python >>> 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) ] Here, you can see that: - The :code:`Digest.begin` and :code:`Digest.[end,begin]_pack` actions are executed around the :code:`user_data` field during packing. - The :code:`hash` field is automatically calculated and added to the struct. - During unpacking, the :code:`Digest.verify` action ensures that the hash matches the expected value.