sltcodec 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sltcodec-1.0.0 → sltcodec-1.1.0}/PKG-INFO +1 -1
- {sltcodec-1.0.0 → sltcodec-1.1.0}/pyproject.toml +1 -1
- {sltcodec-1.0.0 → sltcodec-1.1.0}/src/sltcodec/codec.py +18 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/src/sltcodec/types.py +19 -1
- {sltcodec-1.0.0 → sltcodec-1.1.0}/tests/test_codec.py +71 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/uv.lock +1 -1
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.github/workflows/publish_to_pypi.yml +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.github/workflows/publish_to_testpypi.yml +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.gitignore +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.python-version +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.vscode/launch.json +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/.vscode/settings.json +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/LICENSE +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/README.md +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/bump_major.ps1 +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/bump_minor.ps1 +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/bump_patch.ps1 +0 -0
- {sltcodec-1.0.0 → sltcodec-1.1.0}/src/sltcodec/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sltcodec
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Decode and encode bytearrays according to struct layout definitions using sltcore.
|
|
5
5
|
Project-URL: Homepage, https://github.com/fangface-hub/StructLayoutToolkitCodec
|
|
6
6
|
Project-URL: Documentation, https://readthedocs.org
|
|
@@ -105,6 +105,18 @@ def _resolve_size(field_def: FieldDef, env: dict[str, Any]) -> InfoSize:
|
|
|
105
105
|
return _resolve_info_size(field_def.size, env)
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def _resolve_byte_swap(field_def: FieldDef, env: dict[str, Any]) -> bool:
|
|
109
|
+
"""Resolve a byte-swap flag that can be static or expression-based."""
|
|
110
|
+
if isinstance(field_def.byte_swap, str):
|
|
111
|
+
return bool(SltEval(env).eval(field_def.byte_swap))
|
|
112
|
+
return field_def.byte_swap
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _byte_swap_info(info: Info, size: InfoSize) -> Info:
|
|
116
|
+
"""Reverse the byte representation of an Info payload."""
|
|
117
|
+
return Info.from_bytes(info.to_bytes[::-1], size, scale=info.scale)
|
|
118
|
+
|
|
119
|
+
|
|
108
120
|
def _is_padding_field_def(field_def: FieldDef) -> bool:
|
|
109
121
|
"""Check whether a field definition represents padding."""
|
|
110
122
|
return (field_def.name.startswith("padding[")
|
|
@@ -235,6 +247,8 @@ def encode_field(
|
|
|
235
247
|
return
|
|
236
248
|
|
|
237
249
|
offset, size, info = prepared
|
|
250
|
+
if _resolve_byte_swap(field_def, env):
|
|
251
|
+
info = _byte_swap_info(info, size)
|
|
238
252
|
required_bytes = (offset + size).bytes
|
|
239
253
|
if len(buf) < required_bytes:
|
|
240
254
|
buf.extend(b"\x00" * (required_bytes - len(buf)))
|
|
@@ -341,6 +355,9 @@ def decode_field(
|
|
|
341
355
|
if size.byte == 0 and size.bit == 0:
|
|
342
356
|
return None
|
|
343
357
|
info = bits_get(data, offset, size, scale=field_def.scale)
|
|
358
|
+
byte_swap = _resolve_byte_swap(field_def, env)
|
|
359
|
+
if byte_swap:
|
|
360
|
+
info = _byte_swap_info(info, size)
|
|
344
361
|
resolved_type = _resolve_field_type(field_def.type, struct_def_dict, env)
|
|
345
362
|
resolved_field_def = FieldDef(
|
|
346
363
|
name=field_def.name,
|
|
@@ -352,6 +369,7 @@ def decode_field(
|
|
|
352
369
|
description=field_def.description,
|
|
353
370
|
range_expression=field_def.range_expression,
|
|
354
371
|
enum_def=field_def.enum_def,
|
|
372
|
+
byte_swap=byte_swap,
|
|
355
373
|
)
|
|
356
374
|
if isinstance(resolved_type, StructDef):
|
|
357
375
|
return FieldInstance(
|
|
@@ -106,6 +106,9 @@ class FieldDef:
|
|
|
106
106
|
default=None, metadata={"desc": "The value range expression"})
|
|
107
107
|
enum_def: EnumDef | None = field(
|
|
108
108
|
default=None, metadata={"desc": "The enum definition for the field"})
|
|
109
|
+
byte_swap: bool | str = field(
|
|
110
|
+
default=False,
|
|
111
|
+
metadata={"desc": "Whether to reverse the field byte order"})
|
|
109
112
|
|
|
110
113
|
def split_repeat(self,
|
|
111
114
|
index: int,
|
|
@@ -123,6 +126,8 @@ class FieldDef:
|
|
|
123
126
|
split_name)
|
|
124
127
|
split_range_expression = self._replace_name_in_expression(
|
|
125
128
|
self.range_expression, self.name, split_name)
|
|
129
|
+
split_byte_swap = self._replace_name_in_expression(
|
|
130
|
+
self.byte_swap, self.name, split_name)
|
|
126
131
|
|
|
127
132
|
return FieldDef(name=split_name,
|
|
128
133
|
offset=split_offset,
|
|
@@ -132,7 +137,8 @@ class FieldDef:
|
|
|
132
137
|
repeat=None,
|
|
133
138
|
description=self.description,
|
|
134
139
|
range_expression=split_range_expression,
|
|
135
|
-
enum_def=self.enum_def
|
|
140
|
+
enum_def=self.enum_def,
|
|
141
|
+
byte_swap=split_byte_swap)
|
|
136
142
|
|
|
137
143
|
@staticmethod
|
|
138
144
|
def _replace_name_in_expression(value: Any, old_name: str,
|
|
@@ -180,6 +186,8 @@ class FieldDef:
|
|
|
180
186
|
self.range_expression,
|
|
181
187
|
"enum_def":
|
|
182
188
|
None if self.enum_def is None else self.enum_def.serialize(),
|
|
189
|
+
"byte_swap":
|
|
190
|
+
self.byte_swap,
|
|
183
191
|
}
|
|
184
192
|
|
|
185
193
|
def to_json(self) -> str:
|
|
@@ -198,6 +206,7 @@ class FieldDef:
|
|
|
198
206
|
description = data.get("description")
|
|
199
207
|
range_expression = data.get("range_expression")
|
|
200
208
|
enum_def_data = data.get("enum_def")
|
|
209
|
+
byte_swap = data.get("byte_swap", False)
|
|
201
210
|
|
|
202
211
|
def _deserialize_info_like(value: Any) -> Any:
|
|
203
212
|
if isinstance(value, dict):
|
|
@@ -251,6 +260,7 @@ class FieldDef:
|
|
|
251
260
|
description=description,
|
|
252
261
|
range_expression=range_expression,
|
|
253
262
|
enum_def=enum_def,
|
|
263
|
+
byte_swap=byte_swap,
|
|
254
264
|
)
|
|
255
265
|
|
|
256
266
|
@classmethod
|
|
@@ -270,6 +280,7 @@ class FieldDef:
|
|
|
270
280
|
"" if self.description is None else self.description,
|
|
271
281
|
"" if self.range_expression is None else self.range_expression,
|
|
272
282
|
self._sortable_enum_type(self.enum_def),
|
|
283
|
+
self._sortable_byte_swap(self.byte_swap),
|
|
273
284
|
)
|
|
274
285
|
|
|
275
286
|
@staticmethod
|
|
@@ -299,6 +310,13 @@ class FieldDef:
|
|
|
299
310
|
},
|
|
300
311
|
sort_keys=True)
|
|
301
312
|
|
|
313
|
+
@staticmethod
|
|
314
|
+
def _sortable_byte_swap(value: bool | str) -> tuple[int, str]:
|
|
315
|
+
"""Build a comparable key for a byte-swap flag or expression."""
|
|
316
|
+
if isinstance(value, bool):
|
|
317
|
+
return 0, str(value)
|
|
318
|
+
return 1, value
|
|
319
|
+
|
|
302
320
|
|
|
303
321
|
@total_ordering
|
|
304
322
|
@dataclass(frozen=True)
|
|
@@ -261,6 +261,77 @@ def test_field_def_to_json_from_json_round_trip():
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
def test_field_def_byte_swap_json_round_trip():
|
|
265
|
+
"""Test that expression-based byte_swap metadata is preserved."""
|
|
266
|
+
field_def = FieldDef(name="value",
|
|
267
|
+
offset=InfoSize(0, 0),
|
|
268
|
+
size=InfoSize(2, 0),
|
|
269
|
+
type="unsigned int",
|
|
270
|
+
byte_swap="kind == 1")
|
|
271
|
+
|
|
272
|
+
restored = FieldDef.from_json(field_def.to_json())
|
|
273
|
+
|
|
274
|
+
assert restored == field_def
|
|
275
|
+
assert restored.byte_swap == "kind == 1"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_byte_swap_reverses_bytearray_input_and_output():
|
|
279
|
+
"""Test byte_swap reverses bytearray values symmetrically."""
|
|
280
|
+
field_def = FieldDef(name="payload",
|
|
281
|
+
offset=InfoSize(0, 0),
|
|
282
|
+
size=InfoSize(3, 0),
|
|
283
|
+
type="bytearray",
|
|
284
|
+
byte_swap=True)
|
|
285
|
+
|
|
286
|
+
encoded = encode(
|
|
287
|
+
StructInstance(struct_def=StructDef(fields=[field_def]),
|
|
288
|
+
field_instances=[
|
|
289
|
+
FieldInstance(field_def, bytearray(b"\x01\x02\x03"))
|
|
290
|
+
]),
|
|
291
|
+
bytearray(),
|
|
292
|
+
)
|
|
293
|
+
decoded = decode([field_def], encoded)
|
|
294
|
+
|
|
295
|
+
assert encoded == bytearray(b"\x03\x02\x01")
|
|
296
|
+
assert decoded.field_instances == [
|
|
297
|
+
FieldInstance(field_def, bytearray(b"\x01\x02\x03"))
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_byte_swap_expression_uses_previous_field_value():
|
|
302
|
+
"""Test byte_swap expressions use values decoded or encoded before it."""
|
|
303
|
+
kind_field = FieldDef(name="kind",
|
|
304
|
+
offset=InfoSize(0, 0),
|
|
305
|
+
size=InfoSize(1, 0),
|
|
306
|
+
type="unsigned int")
|
|
307
|
+
value_field = FieldDef(name="value",
|
|
308
|
+
offset=InfoSize(1, 0),
|
|
309
|
+
size=InfoSize(2, 0),
|
|
310
|
+
type="unsigned int",
|
|
311
|
+
byte_swap="kind == 1")
|
|
312
|
+
|
|
313
|
+
encoded = encode(
|
|
314
|
+
StructInstance(struct_def=StructDef(fields=[kind_field, value_field]),
|
|
315
|
+
field_instances=[
|
|
316
|
+
FieldInstance(kind_field, 1),
|
|
317
|
+
FieldInstance(value_field, 0x1234),
|
|
318
|
+
]),
|
|
319
|
+
bytearray(),
|
|
320
|
+
)
|
|
321
|
+
decoded = decode([kind_field, value_field], encoded)
|
|
322
|
+
|
|
323
|
+
assert encoded == bytearray(b"\x01\x34\x12")
|
|
324
|
+
assert decoded.field_instances == [
|
|
325
|
+
FieldInstance(kind_field, 1),
|
|
326
|
+
FieldInstance(
|
|
327
|
+
FieldDef(name="value",
|
|
328
|
+
offset=InfoSize(1, 0),
|
|
329
|
+
size=InfoSize(2, 0),
|
|
330
|
+
type="unsigned int",
|
|
331
|
+
byte_swap=True), 0x1234),
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
|
|
264
335
|
def test_enum_def_to_json_from_json_round_trip():
|
|
265
336
|
"""Test EnumDef.to_json/from_json round-trip conversion."""
|
|
266
337
|
enum_def = EnumDef(name="Status",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|