dissect.cstruct 4.5.dev5__tar.gz → 4.6.dev1__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.
- dissect_cstruct-4.6.dev1/CHANGELOG.md +53 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/compiler.py +5 -7
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/expression.py +5 -6
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/parser.py +7 -7
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/base.py +1 -1
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/structure.py +64 -14
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/utils.py +4 -5
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/pyproject.toml +1 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_align.py +1 -1
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_compiler.py +3 -23
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_expression.py +3 -3
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_structure.py +35 -1
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/.gitattributes +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/COPYRIGHT +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/LICENSE +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/MANIFEST.in +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/README.md +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/cstruct.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/tools/__init__.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/tools/stubgen.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/__init__.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/enum.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/leb128.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/pointer.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/void.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/wchar.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/entry_points.txt +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/requires.txt +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/examples/disk.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/examples/mirai.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/examples/pe.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/setup.cfg +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/__init__.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/_docs/__init__.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/conftest.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_annotations.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_basic.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_ctypes.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_parser.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_tools_stubgen.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_base.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_char.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_custom.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_int.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_union.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_void.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/test_utils.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tests/utils.py +0 -0
- {dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/tox.ini +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Optimize storage of field sizes.
|
|
12
|
+
- Rename `_sizes` property of `Structure` to `__sizes__`.
|
|
13
|
+
- Rename `_values` property of `Structure` to `__values__`.
|
|
14
|
+
|
|
15
|
+
## [4.5] - 20-05-2025
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Introduce experimental tool `cstruct-stubgen` to generate type stubs for cstruct definitions.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Generated classes are now hashable.
|
|
24
|
+
- Suppress spurious `TypeError: Dynamic size` errors when using cstruct interactively.
|
|
25
|
+
|
|
26
|
+
## [4.4] - 03-10-2025
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Resolve documentation warnings.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Use the Ruff linter.
|
|
35
|
+
|
|
36
|
+
## [4.3] - 11-18-2024
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- All cstruct types are now correctly default-initialized using the `__default__` member.
|
|
41
|
+
|
|
42
|
+
## [4.2] - 10-10-2024
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- The string representation of enums now outputs the name of the constants.
|
|
47
|
+
|
|
48
|
+
## [4.1] - 10-09-2024
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- Declaring an array of a nested struct type now works as intended.
|
|
53
|
+
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.dev1
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Apache License 2.0
|
|
7
7
|
Project-URL: homepage, https://dissect.tools
|
|
8
8
|
Project-URL: documentation, https://docs.dissect.tools/en/latest/projects/dissect.cstruct
|
|
9
9
|
Project-URL: repository, https://github.com/fox-it/dissect.cstruct
|
|
10
|
+
Project-URL: changelog, https://github.com/fox-it/dissect.cstruct/blob/main/CHANGELOG.md
|
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
12
|
Classifier: Environment :: Console
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -123,8 +123,7 @@ class _ReadSourceGenerator:
|
|
|
123
123
|
|
|
124
124
|
outro = """
|
|
125
125
|
obj = type.__call__(cls, **r)
|
|
126
|
-
obj.
|
|
127
|
-
obj._values = r
|
|
126
|
+
obj.__dynamic_sizes__ = s
|
|
128
127
|
|
|
129
128
|
return obj
|
|
130
129
|
"""
|
|
@@ -227,18 +226,18 @@ class _ReadSourceGenerator:
|
|
|
227
226
|
|
|
228
227
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
229
228
|
template = f"""
|
|
230
|
-
_s = stream.tell()
|
|
229
|
+
{'_s = stream.tell()' if field.type.dynamic else ''}
|
|
231
230
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
232
|
-
s["{field._name}"] = stream.tell() - _s
|
|
231
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ''}
|
|
233
232
|
"""
|
|
234
233
|
|
|
235
234
|
yield dedent(template)
|
|
236
235
|
|
|
237
236
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
238
237
|
template = f"""
|
|
239
|
-
_s = stream.tell()
|
|
238
|
+
{'_s = stream.tell()' if field.type.dynamic else ''}
|
|
240
239
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
241
|
-
s["{field._name}"] = stream.tell() - _s
|
|
240
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ''}
|
|
242
241
|
"""
|
|
243
242
|
|
|
244
243
|
yield dedent(template)
|
|
@@ -326,7 +325,6 @@ class _ReadSourceGenerator:
|
|
|
326
325
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
327
326
|
|
|
328
327
|
reads.append(f'r["{field._name}"] = {parser}')
|
|
329
|
-
reads.append(f's["{field._name}"] = {field_type.size}')
|
|
330
328
|
reads.append("") # Generates a newline in the resulting code
|
|
331
329
|
|
|
332
330
|
size += field_type.size
|
|
@@ -187,8 +187,7 @@ class Expression:
|
|
|
187
187
|
"sizeof": 6,
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
def __init__(self,
|
|
191
|
-
self.cstruct = cstruct
|
|
190
|
+
def __init__(self, expression: str):
|
|
192
191
|
self.expression = expression
|
|
193
192
|
self.tokens = ExpressionTokenizer(expression).tokenize()
|
|
194
193
|
self.stack = []
|
|
@@ -222,7 +221,7 @@ class Expression:
|
|
|
222
221
|
def is_number(self, token: str) -> bool:
|
|
223
222
|
return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
|
|
224
223
|
|
|
225
|
-
def evaluate(self, context: dict[str, int] | None = None) -> int:
|
|
224
|
+
def evaluate(self, cs: cstruct, context: dict[str, int] | None = None) -> int:
|
|
226
225
|
"""Evaluates an expression using a Shunting-Yard implementation."""
|
|
227
226
|
|
|
228
227
|
self.stack = []
|
|
@@ -249,14 +248,14 @@ class Expression:
|
|
|
249
248
|
self.queue.append(int(current_token, 0))
|
|
250
249
|
elif current_token in context:
|
|
251
250
|
self.queue.append(int(context[current_token]))
|
|
252
|
-
elif current_token in
|
|
253
|
-
self.queue.append(int(
|
|
251
|
+
elif current_token in cs.consts:
|
|
252
|
+
self.queue.append(int(cs.consts[current_token]))
|
|
254
253
|
elif current_token in self.unary_operators:
|
|
255
254
|
self.stack.append(current_token)
|
|
256
255
|
elif current_token == "sizeof":
|
|
257
256
|
if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
|
|
258
257
|
raise ExpressionParserError("Invalid sizeof operation")
|
|
259
|
-
self.queue.append(len(
|
|
258
|
+
self.queue.append(len(cs.resolve(tmp_expression[i + 2])))
|
|
260
259
|
i += 3
|
|
261
260
|
elif current_token in operators:
|
|
262
261
|
while (
|
|
@@ -92,7 +92,7 @@ class TokenParser(Parser):
|
|
|
92
92
|
|
|
93
93
|
if isinstance(value, str):
|
|
94
94
|
try:
|
|
95
|
-
value = Expression(
|
|
95
|
+
value = Expression(value).evaluate(self.cstruct)
|
|
96
96
|
except (ExpressionParserError, ExpressionTokenizerError):
|
|
97
97
|
pass
|
|
98
98
|
|
|
@@ -120,7 +120,7 @@ class TokenParser(Parser):
|
|
|
120
120
|
if not key:
|
|
121
121
|
continue
|
|
122
122
|
|
|
123
|
-
val = nextval if not val else Expression(self.cstruct,
|
|
123
|
+
val = nextval if not val else Expression(val).evaluate(self.cstruct, values)
|
|
124
124
|
|
|
125
125
|
if enumtype == "flag":
|
|
126
126
|
high_bit = val.bit_length() - 1
|
|
@@ -293,9 +293,9 @@ class TokenParser(Parser):
|
|
|
293
293
|
if count == "":
|
|
294
294
|
count = None
|
|
295
295
|
else:
|
|
296
|
-
count = Expression(
|
|
296
|
+
count = Expression(count)
|
|
297
297
|
try:
|
|
298
|
-
count = count.evaluate()
|
|
298
|
+
count = count.evaluate(self.cstruct)
|
|
299
299
|
except Exception:
|
|
300
300
|
pass
|
|
301
301
|
|
|
@@ -434,7 +434,7 @@ class CStyleParser(Parser):
|
|
|
434
434
|
if not key:
|
|
435
435
|
continue
|
|
436
436
|
|
|
437
|
-
val = nextval if not val else Expression(
|
|
437
|
+
val = nextval if not val else Expression(val).evaluate(self.cstruct)
|
|
438
438
|
|
|
439
439
|
if enumtype == "flag":
|
|
440
440
|
high_bit = val.bit_length() - 1
|
|
@@ -515,9 +515,9 @@ class CStyleParser(Parser):
|
|
|
515
515
|
if d["count"] == "":
|
|
516
516
|
count = None
|
|
517
517
|
else:
|
|
518
|
-
count = Expression(
|
|
518
|
+
count = Expression(d["count"])
|
|
519
519
|
try:
|
|
520
|
-
count = count.evaluate()
|
|
520
|
+
count = count.evaluate(self.cstruct)
|
|
521
521
|
except Exception:
|
|
522
522
|
pass
|
|
523
523
|
|
|
@@ -262,7 +262,7 @@ class BaseArray(BaseType):
|
|
|
262
262
|
num = EOF
|
|
263
263
|
elif isinstance(cls.num_entries, Expression):
|
|
264
264
|
try:
|
|
265
|
-
num = max(0, cls.num_entries.evaluate(context))
|
|
265
|
+
num = max(0, cls.num_entries.evaluate(cls.cs, context))
|
|
266
266
|
except Exception:
|
|
267
267
|
if cls.num_entries.expression != "EOF":
|
|
268
268
|
raise
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
+
from collections import ChainMap
|
|
5
|
+
from collections.abc import MutableMapping
|
|
4
6
|
from contextlib import contextmanager
|
|
5
7
|
from enum import Enum
|
|
6
8
|
from functools import lru_cache
|
|
@@ -21,7 +23,7 @@ from dissect.cstruct.types.enum import EnumMetaType
|
|
|
21
23
|
from dissect.cstruct.types.pointer import Pointer
|
|
22
24
|
|
|
23
25
|
if TYPE_CHECKING:
|
|
24
|
-
from collections.abc import Iterator
|
|
26
|
+
from collections.abc import Iterator, Mapping
|
|
25
27
|
from types import FunctionType
|
|
26
28
|
|
|
27
29
|
from typing_extensions import Self
|
|
@@ -60,6 +62,7 @@ class StructureMetaType(MetaType):
|
|
|
60
62
|
__anonymous__: bool
|
|
61
63
|
__updating__ = False
|
|
62
64
|
__compiled__ = False
|
|
65
|
+
__static_sizes__: dict[str, int] # Cache of static sizes by field name
|
|
63
66
|
|
|
64
67
|
def __new__(metacls, name: str, bases: tuple[type, ...], classdict: dict[str, Any]) -> Self: # type: ignore
|
|
65
68
|
if (fields := classdict.pop("fields", None)) is not None:
|
|
@@ -79,8 +82,7 @@ class StructureMetaType(MetaType):
|
|
|
79
82
|
return type.__call__(cls, *args, **kwargs)
|
|
80
83
|
if not args and not kwargs:
|
|
81
84
|
obj = type.__call__(cls)
|
|
82
|
-
object.__setattr__(obj, "
|
|
83
|
-
object.__setattr__(obj, "_sizes", {})
|
|
85
|
+
object.__setattr__(obj, "__dynamic_sizes__", {})
|
|
84
86
|
return obj
|
|
85
87
|
|
|
86
88
|
return super().__call__(*args, **kwargs)
|
|
@@ -93,10 +95,14 @@ class StructureMetaType(MetaType):
|
|
|
93
95
|
lookup = {}
|
|
94
96
|
raw_lookup = {}
|
|
95
97
|
field_names = []
|
|
98
|
+
static_sizes = {}
|
|
96
99
|
for field in fields:
|
|
97
100
|
if field._name in lookup and field._name != "_":
|
|
98
101
|
raise ValueError(f"Duplicate field name: {field._name}")
|
|
99
102
|
|
|
103
|
+
if not field.type.dynamic:
|
|
104
|
+
static_sizes[field._name] = field.type.size
|
|
105
|
+
|
|
100
106
|
if isinstance(field.type, StructureMetaType) and field.name is None:
|
|
101
107
|
for anon_field in field.type.fields.values():
|
|
102
108
|
attr = f"{field._name}.{anon_field.name}"
|
|
@@ -112,6 +118,7 @@ class StructureMetaType(MetaType):
|
|
|
112
118
|
classdict["fields"] = lookup
|
|
113
119
|
classdict["lookup"] = raw_lookup
|
|
114
120
|
classdict["__fields__"] = fields
|
|
121
|
+
classdict["__static_sizes__"] = static_sizes
|
|
115
122
|
classdict["__bool__"] = _generate__bool__(field_names)
|
|
116
123
|
|
|
117
124
|
if issubclass(cls, UnionMetaType) or isinstance(cls, UnionMetaType):
|
|
@@ -271,8 +278,9 @@ class StructureMetaType(MetaType):
|
|
|
271
278
|
|
|
272
279
|
value = field.type._read(stream, result)
|
|
273
280
|
|
|
274
|
-
sizes[field._name] = stream.tell() - offset
|
|
275
281
|
result[field._name] = value
|
|
282
|
+
if field.type.dynamic:
|
|
283
|
+
sizes[field._name] = stream.tell() - offset
|
|
276
284
|
|
|
277
285
|
if cls.__align__:
|
|
278
286
|
# Align the stream
|
|
@@ -281,8 +289,7 @@ class StructureMetaType(MetaType):
|
|
|
281
289
|
# Using type.__call__ directly calls the __init__ method of the class
|
|
282
290
|
# This is faster than calling cls() and bypasses the metaclass __call__ method
|
|
283
291
|
obj = type.__call__(cls, **result)
|
|
284
|
-
obj.
|
|
285
|
-
obj._values = result
|
|
292
|
+
obj.__dynamic_sizes__ = sizes
|
|
286
293
|
return obj
|
|
287
294
|
|
|
288
295
|
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]: # type: ignore
|
|
@@ -375,10 +382,13 @@ class StructureMetaType(MetaType):
|
|
|
375
382
|
|
|
376
383
|
|
|
377
384
|
class Structure(BaseType, metaclass=StructureMetaType):
|
|
378
|
-
"""Base class for cstruct structure type classes.
|
|
385
|
+
"""Base class for cstruct structure type classes.
|
|
386
|
+
|
|
387
|
+
Note that setting attributes which do not correspond to a field in the structure results in undefined behavior.
|
|
388
|
+
For performance reasons, the structure does not check if the field exists when writing to an attribute.
|
|
389
|
+
"""
|
|
379
390
|
|
|
380
|
-
|
|
381
|
-
_sizes: dict[str, int]
|
|
391
|
+
__dynamic_sizes__: dict[str, int]
|
|
382
392
|
|
|
383
393
|
def __len__(self) -> int:
|
|
384
394
|
return len(self.dumps())
|
|
@@ -401,6 +411,47 @@ class Structure(BaseType, metaclass=StructureMetaType):
|
|
|
401
411
|
|
|
402
412
|
return f"<{self.__class__.__name__} {' '.join(values)}>"
|
|
403
413
|
|
|
414
|
+
@property
|
|
415
|
+
def __values__(self) -> MutableMapping[str, Any]:
|
|
416
|
+
return StructureValuesProxy(self)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def __sizes__(self) -> Mapping[str, int | None]:
|
|
420
|
+
return ChainMap(self.__class__.__static_sizes__, self.__dynamic_sizes__)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class StructureValuesProxy(MutableMapping):
|
|
424
|
+
"""A proxy for the values of fields of a Structure."""
|
|
425
|
+
|
|
426
|
+
def __init__(self, struct: Structure):
|
|
427
|
+
self._struct: Structure = struct
|
|
428
|
+
|
|
429
|
+
def __getitem__(self, key: str) -> Any:
|
|
430
|
+
if key in self:
|
|
431
|
+
return getattr(self._struct, key)
|
|
432
|
+
raise KeyError(key)
|
|
433
|
+
|
|
434
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
435
|
+
if key in self:
|
|
436
|
+
return setattr(self._struct, key, value)
|
|
437
|
+
raise KeyError(key)
|
|
438
|
+
|
|
439
|
+
def __contains__(self, key: str) -> bool:
|
|
440
|
+
return key in self._struct.__class__.fields
|
|
441
|
+
|
|
442
|
+
def __iter__(self) -> Iterator[str]:
|
|
443
|
+
return iter(self._struct.__class__.fields)
|
|
444
|
+
|
|
445
|
+
def __len__(self) -> int:
|
|
446
|
+
return len(self._struct.__class__.fields)
|
|
447
|
+
|
|
448
|
+
def __repr__(self) -> str:
|
|
449
|
+
return repr(dict(self))
|
|
450
|
+
|
|
451
|
+
def __delitem__(self, _: str):
|
|
452
|
+
# Is abstract in base, but deleting is not supported.
|
|
453
|
+
raise NotImplementedError("Cannot delete fields from a Structure")
|
|
454
|
+
|
|
404
455
|
|
|
405
456
|
class UnionMetaType(StructureMetaType):
|
|
406
457
|
"""Base metaclass for cstruct union type classes."""
|
|
@@ -473,8 +524,9 @@ class UnionMetaType(StructureMetaType):
|
|
|
473
524
|
buf.seek(offset + start)
|
|
474
525
|
value = field_type._read(buf, result)
|
|
475
526
|
|
|
476
|
-
sizes[field._name] = buf.tell() - start
|
|
477
527
|
result[field._name] = value
|
|
528
|
+
if field.type.dynamic:
|
|
529
|
+
sizes[field._name] = buf.tell() - start
|
|
478
530
|
|
|
479
531
|
return result, sizes
|
|
480
532
|
|
|
@@ -496,8 +548,7 @@ class UnionMetaType(StructureMetaType):
|
|
|
496
548
|
# It also makes it easier to differentiate between user-initialization of the class
|
|
497
549
|
# and initialization from a stream read
|
|
498
550
|
obj: Union = type.__call__(cls, **result)
|
|
499
|
-
object.__setattr__(obj, "
|
|
500
|
-
object.__setattr__(obj, "_sizes", sizes)
|
|
551
|
+
object.__setattr__(obj, "__dynamic_sizes__", sizes)
|
|
501
552
|
object.__setattr__(obj, "_buf", buf)
|
|
502
553
|
|
|
503
554
|
if cls.size is not None:
|
|
@@ -579,8 +630,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
579
630
|
def _update(self) -> None:
|
|
580
631
|
result, sizes = self.__class__._read_fields(io.BytesIO(self._buf))
|
|
581
632
|
self.__dict__.update(result)
|
|
582
|
-
object.__setattr__(self, "
|
|
583
|
-
object.__setattr__(self, "_sizes", sizes)
|
|
633
|
+
object.__setattr__(self, "__dynamic_sizes__", sizes)
|
|
584
634
|
|
|
585
635
|
def _proxify(self) -> None:
|
|
586
636
|
def _proxy_structure(value: Structure) -> None:
|
|
@@ -157,11 +157,6 @@ def _dumpstruct(
|
|
|
157
157
|
if getattr(field.type, "anonymous", False):
|
|
158
158
|
continue
|
|
159
159
|
|
|
160
|
-
if color:
|
|
161
|
-
foreground, background = colors[ci % len(colors)]
|
|
162
|
-
palette.append((structure._sizes[field._name], background))
|
|
163
|
-
ci += 1
|
|
164
|
-
|
|
165
160
|
value = getattr(structure, field._name)
|
|
166
161
|
if isinstance(value, (str, Pointer, Enum)):
|
|
167
162
|
value = repr(value)
|
|
@@ -173,6 +168,10 @@ def _dumpstruct(
|
|
|
173
168
|
value = value.replace("\n", f"\n{' ' * (len(field._name) + 4)}")
|
|
174
169
|
|
|
175
170
|
if color:
|
|
171
|
+
foreground, background = colors[ci % len(colors)]
|
|
172
|
+
size = structure.__sizes__[field._name]
|
|
173
|
+
palette.append((size, background))
|
|
174
|
+
ci += 1
|
|
176
175
|
out.append(f"- {foreground}{field._name}{COLOR_NORMAL}: {value}")
|
|
177
176
|
else:
|
|
178
177
|
out.append(f"- {field._name}: {value}")
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.6.dev1
|
|
4
4
|
Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
|
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
|
6
6
|
License: Apache License 2.0
|
|
7
7
|
Project-URL: homepage, https://dissect.tools
|
|
8
8
|
Project-URL: documentation, https://docs.dissect.tools/en/latest/projects/dissect.cstruct
|
|
9
9
|
Project-URL: repository, https://github.com/fox-it/dissect.cstruct
|
|
10
|
+
Project-URL: changelog, https://github.com/fox-it/dissect.cstruct/blob/main/CHANGELOG.md
|
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
12
|
Classifier: Environment :: Console
|
|
12
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -30,6 +30,7 @@ dynamic = ["version"]
|
|
|
30
30
|
homepage = "https://dissect.tools"
|
|
31
31
|
documentation = "https://docs.dissect.tools/en/latest/projects/dissect.cstruct"
|
|
32
32
|
repository = "https://github.com/fox-it/dissect.cstruct"
|
|
33
|
+
changelog = "https://github.com/fox-it/dissect.cstruct/blob/main/CHANGELOG.md"
|
|
33
34
|
|
|
34
35
|
[project.optional-dependencies]
|
|
35
36
|
dev = [
|
|
@@ -45,7 +45,7 @@ def test_align_struct(cs: cstruct, compiled: bool) -> None:
|
|
|
45
45
|
obj = cs.test(fh)
|
|
46
46
|
assert fh.tell() == 32
|
|
47
47
|
|
|
48
|
-
for name, value in obj.
|
|
48
|
+
for name, value in obj.__values__.items():
|
|
49
49
|
assert cs.test.fields[name].offset == value
|
|
50
50
|
|
|
51
51
|
assert obj.dumps() == buf
|
|
@@ -8,6 +8,7 @@ from unittest.mock import Mock
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
10
|
from dissect.cstruct import compiler
|
|
11
|
+
from dissect.cstruct.expression import Expression
|
|
11
12
|
from dissect.cstruct.types.structure import Field
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
@@ -111,16 +112,12 @@ def test_generate_packed_read(cs: cstruct) -> None:
|
|
|
111
112
|
data = _struct(cls.cs.endian, "BhIq").unpack(buf)
|
|
112
113
|
|
|
113
114
|
r["a"] = type.__call__(_0, data[0])
|
|
114
|
-
s["a"] = 1
|
|
115
115
|
|
|
116
116
|
r["b"] = type.__call__(_1, data[1])
|
|
117
|
-
s["b"] = 2
|
|
118
117
|
|
|
119
118
|
r["c"] = type.__call__(_2, data[2])
|
|
120
|
-
s["c"] = 4
|
|
121
119
|
|
|
122
120
|
r["d"] = type.__call__(_3, data[3])
|
|
123
|
-
s["d"] = 8
|
|
124
121
|
"""
|
|
125
122
|
|
|
126
123
|
assert code == dedent(expected)
|
|
@@ -143,22 +140,18 @@ def test_generate_packed_read_array(cs: cstruct) -> None:
|
|
|
143
140
|
_t = _0
|
|
144
141
|
_et = _t.type
|
|
145
142
|
r["a"] = type.__call__(_t, [type.__call__(_et, e) for e in data[0:2]])
|
|
146
|
-
s["a"] = 2
|
|
147
143
|
|
|
148
144
|
_t = _1
|
|
149
145
|
_et = _t.type
|
|
150
146
|
r["b"] = type.__call__(_t, [type.__call__(_et, e) for e in data[2:5]])
|
|
151
|
-
s["b"] = 6
|
|
152
147
|
|
|
153
148
|
_t = _2
|
|
154
149
|
_et = _t.type
|
|
155
150
|
r["c"] = type.__call__(_t, [type.__call__(_et, e) for e in data[5:9]])
|
|
156
|
-
s["c"] = 16
|
|
157
151
|
|
|
158
152
|
_t = _3
|
|
159
153
|
_et = _t.type
|
|
160
154
|
r["d"] = type.__call__(_t, [type.__call__(_et, e) for e in data[9:14]])
|
|
161
|
-
s["d"] = 40
|
|
162
155
|
"""
|
|
163
156
|
|
|
164
157
|
assert code == dedent(expected)
|
|
@@ -181,25 +174,19 @@ def test_generate_packed_read_byte_types(cs: cstruct) -> None:
|
|
|
181
174
|
data = _struct(cls.cs.endian, "18x").unpack(buf)
|
|
182
175
|
|
|
183
176
|
r["a"] = type.__call__(_0, buf[0:1])
|
|
184
|
-
s["a"] = 1
|
|
185
177
|
|
|
186
178
|
r["b"] = type.__call__(_1, buf[1:3])
|
|
187
|
-
s["b"] = 2
|
|
188
179
|
|
|
189
180
|
r["c"] = _2(buf[3:5])
|
|
190
|
-
s["c"] = 2
|
|
191
181
|
|
|
192
182
|
r["d"] = _3(buf[5:9])
|
|
193
|
-
s["d"] = 4
|
|
194
183
|
|
|
195
184
|
r["e"] = _4(buf[9:12])
|
|
196
|
-
s["e"] = 3
|
|
197
185
|
|
|
198
186
|
_t = _5
|
|
199
187
|
_et = _t.type
|
|
200
188
|
_b = buf[12:18]
|
|
201
189
|
r["f"] = type.__call__(_t, [_et(_b[i:i + 3]) for i in range(0, 6, 3)])
|
|
202
|
-
s["f"] = 6
|
|
203
190
|
"""
|
|
204
191
|
|
|
205
192
|
assert code == dedent(expected)
|
|
@@ -223,16 +210,13 @@ def test_generate_packed_read_composite_types(cs: cstruct, TestEnum: type[Enum])
|
|
|
223
210
|
data = _struct(cls.cs.endian, "BQ2B").unpack(buf)
|
|
224
211
|
|
|
225
212
|
r["a"] = type.__call__(_0, data[0])
|
|
226
|
-
s["a"] = 1
|
|
227
213
|
|
|
228
214
|
_pt = _1
|
|
229
215
|
r["b"] = _pt.__new__(_pt, data[1], stream, r)
|
|
230
|
-
s["b"] = 8
|
|
231
216
|
|
|
232
217
|
_t = _2
|
|
233
218
|
_et = _t.type
|
|
234
219
|
r["c"] = type.__call__(_t, [type.__call__(_et, e) for e in data[2:4]])
|
|
235
|
-
s["c"] = 2
|
|
236
220
|
"""
|
|
237
221
|
|
|
238
222
|
assert code == dedent(expected)
|
|
@@ -251,10 +235,8 @@ def test_generate_packed_read_offsets(cs: cstruct) -> None:
|
|
|
251
235
|
data = _struct(cls.cs.endian, "B7xB").unpack(buf)
|
|
252
236
|
|
|
253
237
|
r["a"] = type.__call__(_0, data[0])
|
|
254
|
-
s["a"] = 1
|
|
255
238
|
|
|
256
239
|
r["b"] = type.__call__(_1, data[1])
|
|
257
|
-
s["b"] = 1
|
|
258
240
|
"""
|
|
259
241
|
|
|
260
242
|
assert code == dedent(expected)
|
|
@@ -335,7 +317,7 @@ def test_generate_fields_dynamic_after_bitfield(cs: cstruct, TestEnum: Enum, oth
|
|
|
335
317
|
Field("size", cs.uint16, offset=0),
|
|
336
318
|
Field("a", TestEnum, 4, offset=2),
|
|
337
319
|
Field("b", _type, 4),
|
|
338
|
-
Field("c", cs.char["size"], offset=3),
|
|
320
|
+
Field("c", cs.char[Expression("size")], offset=3),
|
|
339
321
|
]
|
|
340
322
|
|
|
341
323
|
output = "\n".join(compiler._ReadSourceGenerator(cs, fields)._generate_fields())
|
|
@@ -346,7 +328,6 @@ def test_generate_fields_dynamic_after_bitfield(cs: cstruct, TestEnum: Enum, oth
|
|
|
346
328
|
data = _struct(cls.cs.endian, "H").unpack(buf)
|
|
347
329
|
|
|
348
330
|
r["size"] = type.__call__(_0, data[0])
|
|
349
|
-
s["size"] = 2
|
|
350
331
|
|
|
351
332
|
|
|
352
333
|
_t = _1
|
|
@@ -375,7 +356,7 @@ def test_generate_fields_dynamic_before_bitfield(cs: cstruct, TestEnum: Enum, ot
|
|
|
375
356
|
Field("size", cs.uint16, offset=0),
|
|
376
357
|
Field("a", _type, 4, offset=2),
|
|
377
358
|
Field("b", TestEnum, 4),
|
|
378
|
-
Field("c", cs.char["size"], offset=3),
|
|
359
|
+
Field("c", cs.char[Expression("size")], offset=3),
|
|
379
360
|
]
|
|
380
361
|
|
|
381
362
|
output = "\n".join(compiler._ReadSourceGenerator(cs, fields)._generate_fields())
|
|
@@ -386,7 +367,6 @@ def test_generate_fields_dynamic_before_bitfield(cs: cstruct, TestEnum: Enum, ot
|
|
|
386
367
|
data = _struct(cls.cs.endian, "H").unpack(buf)
|
|
387
368
|
|
|
388
369
|
r["size"] = type.__call__(_0, data[0])
|
|
389
|
-
s["size"] = 2
|
|
390
370
|
|
|
391
371
|
|
|
392
372
|
_t = _1
|
|
@@ -88,8 +88,8 @@ def id_fn(val: Any) -> str | None:
|
|
|
88
88
|
|
|
89
89
|
@pytest.mark.parametrize(("expression", "answer"), testdata, ids=id_fn)
|
|
90
90
|
def test_expression(expression: str, answer: int) -> None:
|
|
91
|
-
parser = Expression(
|
|
92
|
-
assert parser.evaluate() == answer
|
|
91
|
+
parser = Expression(expression)
|
|
92
|
+
assert parser.evaluate(Consts()) == answer
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
@pytest.mark.parametrize(
|
|
@@ -110,7 +110,7 @@ def test_expression(expression: str, answer: int) -> None:
|
|
|
110
110
|
)
|
|
111
111
|
def test_expression_failure(expression: str, exception: type, message: str) -> None:
|
|
112
112
|
with pytest.raises(exception, match=message):
|
|
113
|
-
Expression(
|
|
113
|
+
Expression(expression).evaluate(Consts())
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def test_sizeof(cs: cstruct) -> None:
|
|
@@ -265,7 +265,8 @@ def test_structure_definition_simple(cs: cstruct, compiled: bool) -> None:
|
|
|
265
265
|
with pytest.raises(AttributeError):
|
|
266
266
|
obj.nope # noqa: B018
|
|
267
267
|
|
|
268
|
-
assert obj.
|
|
268
|
+
assert obj.__dynamic_sizes__ == { "string": 7, "wstring": 10 }
|
|
269
|
+
assert obj.__sizes__ == { "magic": 4, "wmagic": 8, "a": 1, "b": 2, "c": 4, "string": 7, "wstring": 10 }
|
|
269
270
|
assert len(obj) == len(buf)
|
|
270
271
|
assert obj.dumps() == buf
|
|
271
272
|
|
|
@@ -275,6 +276,39 @@ def test_structure_definition_simple(cs: cstruct, compiled: bool) -> None:
|
|
|
275
276
|
obj.write(fh)
|
|
276
277
|
assert fh.getvalue() == buf
|
|
277
278
|
|
|
279
|
+
def test_structure_values_dict(cs: cstruct, compiled: bool) -> None:
|
|
280
|
+
cdef = """
|
|
281
|
+
struct test {
|
|
282
|
+
char magic[4];
|
|
283
|
+
wchar wmagic[4];
|
|
284
|
+
uint8 a;
|
|
285
|
+
uint16 b;
|
|
286
|
+
uint32 c;
|
|
287
|
+
char string[];
|
|
288
|
+
wchar wstring[];
|
|
289
|
+
};
|
|
290
|
+
"""
|
|
291
|
+
cs.load(cdef, compiled=compiled)
|
|
292
|
+
buf = b"testt\x00e\x00s\x00t\x00\x01\x02\x03\x04\x05\x06\x07lalala\x00t\x00e\x00s\x00t\x00\x00\x00"
|
|
293
|
+
obj = cs.test(buf)
|
|
294
|
+
|
|
295
|
+
# Test reading all values
|
|
296
|
+
values = obj.__values__
|
|
297
|
+
assert values["magic"] == b"test"
|
|
298
|
+
assert values["wmagic"] == "test"
|
|
299
|
+
assert values["a"] == 0x01
|
|
300
|
+
assert values["b"] == 0x0302
|
|
301
|
+
assert values["c"] == 0x07060504
|
|
302
|
+
assert values["string"] == b"lalala"
|
|
303
|
+
assert values["wstring"] == "test"
|
|
304
|
+
|
|
305
|
+
# Test writing a single field through the proxy
|
|
306
|
+
values["a"] = 0xFF
|
|
307
|
+
assert obj.a == 0xFF
|
|
308
|
+
|
|
309
|
+
# Test dictionary methods
|
|
310
|
+
assert values.keys() == {"magic", "wmagic", "a", "b", "c", "string", "wstring"}
|
|
311
|
+
|
|
278
312
|
|
|
279
313
|
def test_structure_definition_simple_be(cs: cstruct, compiled: bool) -> None:
|
|
280
314
|
cdef = """
|
|
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
|
|
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
|
{dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cstruct-4.5.dev5 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/top_level.txt
RENAMED
|
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
|
|
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
|
|
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
|