Skip to content

Commit 94f1a42

Browse files
authored
[2.7.0] Dynamic Endian
Merge pull request #52 from MatrixEditor/dev/dyn-endian
2 parents 1d5b795 + 1ec4ed0 commit 94f1a42

27 files changed

Lines changed: 648 additions & 36 deletions

.github/workflows/python-publish.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,15 @@ jobs:
3131
pip install build
3232
3333
- name: Build Package
34+
# This is a small hach since the 'caterpillar' project is already
35+
# published on PyPi and we can't use that name.
3436
run: |
35-
python -m build .
37+
python3 -m build pypi || true
38+
sed -i 's/\.\.\/src/\.\/src/g' pypi/pyproject.toml
39+
python3 -m build pypi
3640
3741
- name: Publish package to Prod PyPi
38-
# if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
39-
uses: pypa/gh-action-pypi-publish@release/v1
42+
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') && !github.event.release.prerelease
43+
uses: pypa/gh-action-pypi-publish@release/v1
44+
with:
45+
package-dir: pypi/dist

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## [2.7.0] - Dynamic Byteorder
4+
5+
### Added
6+
7+
- New concept: *dynamic* byte order (explained in documentation "Dynamic Byte Order")
8+
9+
### Fixed
10+
11+
**caterpillar.field.net**
12+
13+
- Fixed parsing of :class:`~caterpillar.fields.net.MACAddress`
14+
- Fixed mixed endian in a struct, sequence or bit-field
15+
16+
17+
318
## [2.6.3] - Patch Release
419

520
### Fixed

CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
cmake_minimum_required(VERSION 3.15...3.26)
22
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)
33

4+
# TODO: document this change
5+
if (DEFINED ENV{CP_ENABLE_NATIVE} OR DEFINED CP_ENABLE_NATIVE)
6+
message(STATUS "Building Caterpillar C extension")
7+
48
find_package(
59
Python
610
COMPONENTS Interpreter Development.Module
711
REQUIRED)
812

913
add_compile_definitions(_CPMODULE)
1014

11-
# TODO: document this change
12-
if (DEFINED ENV{CP_ENABLE_NATIVE} OR DEFINED CP_ENABLE_NATIVE)
13-
message(STATUS "Building Caterpillar C extension")
14-
1515
python_add_library(
1616
_C
1717
MODULE

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ options will be added in the future. Documentation is [here >](https://matrixedi
2222
* allowing you to place conditional statements into class definitions,
2323
* insert proper types into the class definition to support documentation and
2424
* it helps you to create cleaner and more compact code.
25+
* There is also a feature that lets you dynamically change the endian within a struct!
2526
* You can even extend Caterpillar and write your parsing logic in C or C++!!
2627

2728
> [!NOTE]
@@ -34,6 +35,8 @@ options will be added in the future. Documentation is [here >](https://matrixedi
3435
## Give me some code!
3536

3637
```python
38+
from caterpillar.py import *
39+
3740
@bitfield(order=LittleEndian)
3841
class Header:
3942
version : 4 # 4bit integer
@@ -46,7 +49,7 @@ class Format:
4649
magic : b"ITS MAGIC" # Supports string and byte constants directly
4750
header : Header
4851
a : uint8 # Primitive data types
49-
b : int32
52+
b : Dynamic + int32 # dynamic endian based on global config
5053
length : uint8 # String fields with computed lengths
5154
name : String(this.length) # -> you can also use Prefixed(uint8)
5255

@@ -70,6 +73,9 @@ blob = pack(obj, Format)
7073

7174
# Unpacking the binary data, reads as 'UNPACK Format FROM blob'
7275
obj2 = unpack(Format, blob)
76+
77+
# to pack with a different endian for field 'b', use _order
78+
data = pack(obj, Format, _order=BigEndian)
7379
```
7480

7581
This library offers extensive functionality beyond basic struct handling. For further details

docs/sphinx/source/development/changelog.rst

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ Changelog
66

77
*More entries will be added in the future.*
88

9+
.. _changelog_2.7.0:
10+
11+
[2.7.0] - Dynamic Byteorder
12+
===========================
13+
14+
Added
15+
-----
16+
17+
- New concept: *dynamic* byte order (explained in :ref:`tutorial-dyn_byteorder`)
18+
19+
Fixed
20+
-----
21+
22+
*caterpillar.field.net*
23+
^^^^^^^^^^^^^^^^^^^^^^^
24+
25+
- Fixed parsing of :class:`~caterpillar.fields.net.MACAddress`
26+
- Fixed mixed endian in a struct, sequence or bit-field
27+
28+
929
.. _changelog_2.6.3:
1030

1131
[2.6.3] - Patch
@@ -60,8 +80,8 @@ Fixed
6080

6181
.. _changelog_2.6.0:
6282

63-
[2.6.0] - Minor Release
64-
=======================
83+
[2.6.0] - CAPI Compatibility
84+
============================
6585

6686
Added
6787
-----
@@ -175,8 +195,8 @@ Fixed
175195

176196
.. _changelog_2.5.0:
177197

178-
[2.5.0] - Minor Release
179-
=======================
198+
[2.5.0] - Stub Files
199+
====================
180200

181201
This version introduces massive changes due to the addition of stub files. Most of the type hints in the Python
182202
file are ported into several stub files. Additionally, the bitfield concept was completely renewed to be more

docs/sphinx/source/library/byteorder.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ Byteorder
1212
.. autoclass:: ByteOrder
1313
:members:
1414

15+
.. autoclass:: DynByteOrder
16+
:members:
17+
1518
.. autofunction:: byteorder(obj, default: Optional[ByteOrder] = None) -> ByteOrder
19+
.. autofunction:: byteorder_is_little
1620

1721
Standard Byteorder Instances
1822
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -27,6 +31,7 @@ Standard Byteorder Instances
2731

2832
.. autoattribute:: caterpillar.byteorder.SysNative
2933

34+
.. autoattribute:: caterpillar.byteorder.Dynamic
3035

3136

3237

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
.. _tutorial-dyn_byteorder:
2+
3+
Dynamic Byte Order
4+
==================
5+
6+
In addition to traditional byte order types, *caterpillar* supports a dynamic byte order
7+
based on the current pack or unpack context.
8+
9+
.. versionadded:: 2.6.4
10+
This feature is available in starting from version ``2.6.4``
11+
12+
13+
There are various use-cases that require a struct to handle both big-endian and
14+
little-endian. In order to reduce the amount of code for these structs, *caterpillar*
15+
introduces a special byte order type: :class:`~caterpillar.byteorder.DynByteOrder`. It
16+
supports two different configuration levels:
17+
18+
1. struct-wide: endianess is configured when calling :func:`~caterpillar.model.pack` or
19+
:func:`~caterpillar.model.unpack`.
20+
2. field-level: byte order is applied per field
21+
22+
Each of these configuration levels support various methods of selecting the target endian:
23+
24+
* global configuration using an additional keyword argument in :func:`~caterpillar.model.pack` or
25+
:func:`~caterpillar.model.unpack`.
26+
* context-key: configuration based on a value within the current context
27+
* custom function: endian is derived from a custom function
28+
29+
Example: struct-wide dynamic byte order
30+
---------------------------------------
31+
32+
Let's consider the following struct definition. The dynamic endian configuration will be applied
33+
to all fields that haven't got an endian already set.
34+
35+
.. code-block:: python
36+
:linenos:
37+
38+
from caterpillar.context import CTX_ORDER
39+
40+
@struct(order=Dynamic)
41+
class Format:
42+
a: uint16 # litte endian or big endian is decided using
43+
b: uint32 # a global context variable
44+
45+
config = {CTX_ORDER: BigEndian}
46+
obj = Format(a=0x1234, b=0x56789ABC)
47+
48+
# pack the object using BigEndian
49+
pack(obj, **config)
50+
51+
# now pack with little endian
52+
config[CTX_ORDER] = LittleEndian
53+
pack(obj, **config)
54+
55+
Here we pass an additional global context variable named :attr:`~caterpillar.context.CTX_ORDER` (``"_order"``)
56+
to the packing and unpacking process. The dynamic endian will automatically infer the order based on this
57+
global variable.
58+
59+
Example: field-level dynamic byte order
60+
---------------------------------------
61+
62+
The same concept as shown above can be applied to single fields too. By default, the endian to use must be
63+
given as a global context variable as described before.
64+
65+
.. code-block:: python
66+
:linenos:
67+
68+
from caterpillar.context import CTX_ORDER
69+
70+
@struct
71+
class Format:
72+
a: uint16
73+
b: Dynamic + uint32 # only this field will be affected
74+
75+
# packing and unpacking is the same as in the previous example
76+
77+
78+
Example: context key reference
79+
------------------------------
80+
81+
Sometimes format specifications use a special field indicating whether all following
82+
fields are using big or little endian. To implement this kind of endian selection, a
83+
so called *context key* can be specified, which can take one of the following forms:
84+
85+
* direct reference: just a string reference
86+
87+
.. code-block:: python
88+
89+
# ...
90+
spec: uint8
91+
number: Dynamic(key="spec")
92+
# ...
93+
94+
* context-lambda: a function that takes the current context as its first parameter and
95+
returns the target endian configuration value.
96+
97+
.. code-block:: python
98+
99+
# ...
100+
spec: uint8
101+
number: Dynamic(key=this.spec)
102+
# ...
103+
104+
The target endianess is decided based on the context value:
105+
106+
* :code:`str` will be applied directly as the format character
107+
* any object storing a :code:`ch` string value
108+
* any other object is converted to a :code:`bool` and the following mapping is applied:
109+
110+
* :code:`True`: :attr:`~caterpillar.byteorder.LittleEndian`
111+
* :code:`False`: :attr:`~caterpillar.byteorder.BigEndian`
112+
113+
As an example, consider the following definition:
114+
115+
.. code-block:: python
116+
:linenos:
117+
118+
@struct(order=BigEndian)
119+
class Format:
120+
spec: uint8 = 0
121+
a: DynByteOrder(key=this.spec) + uint16
122+
# alternatively
123+
# a: Dynamic(this.spec) + uint16
124+
b: uint32
125+
126+
# packing and unpacking does not require the extra endian value
127+
obj = Format(spec=0, a=0x1234, b=0x56789ABC)
128+
# 0 -> False, results in BigEndian
129+
data_be = pack(obj)
130+
131+
# 1 -> True, results in LittleEndian
132+
obj.spec = 1
133+
data_le = pack(obj)
134+

docs/sphinx/source/tutorial/advanced/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ create and manipulate your own custom struct classes in Python.
2121
:maxdepth: 2
2222

2323
operators
24+
byteorder
2425
pointers
2526
chaining
2627
conditional

docs/sphinx/source/tutorial/first_steps/configuration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ In both cases, the :code:`pixels_per_unit_x` and :code:`pixels_per_unit_y` field
3232
so they will be interpreted using big-endian encoding. The :code:`unit` field is only 1 byte, so
3333
endianess doesn't affect it.
3434

35+
.. tip::
36+
It is also possible to mixup various byte orders within the same struct by applying the
37+
endianess directly to the field types. Additionally, there is a special byte order type
38+
that allows dynamic selection of the endian during runtime. See the
39+
:ref:`advanced configuration <tutorial-dyn_byteorder>` section for more details.
40+
3541
In addition to configuring the endianess, you can also specify the **architecture** associated
3642
with the struct using the :class:`~caterpillar.byteorder.Arch` class with the :code:`arch` keyword.
3743

examples/comparison/comparison_1_caterpillar.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
# The __slots__ options does not affect the packing or
66
# unpacking process.
7+
# from caterpillar import options as opt
78
# opt.set_struct_flags(opt.S_SLOTS, opt.S_REPLACE_TYPES)
89

910
# The following lines will reduce the time around 10% for

0 commit comments

Comments
 (0)