dissect.cstruct 4.5.dev4__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.dev4 → dissect_cstruct-4.6.dev1}/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/compiler.py +8 -8
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/expression.py +5 -6
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/parser.py +7 -7
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/__init__.py +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/base.py +5 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/structure.py +64 -14
- dissect_cstruct-4.6.dev1/dissect/cstruct/types/void.py +44 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/utils.py +4 -5
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/PKG-INFO +2 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/pyproject.toml +1 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_align.py +1 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_compiler.py +3 -23
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_expression.py +3 -3
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_base.py +9 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_structure.py +35 -1
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_void.py +9 -7
- dissect_cstruct-4.5.dev4/dissect/cstruct/types/void.py +0 -27
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/.gitattributes +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/COPYRIGHT +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/LICENSE +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/MANIFEST.in +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/README.md +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/cstruct.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/tools/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/tools/stubgen.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/enum.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/leb128.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/pointer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect/cstruct/types/wchar.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/entry_points.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/requires.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/examples/disk.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/examples/mirai.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/examples/pe.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/setup.cfg +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/_docs/__init__.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/conftest.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_annotations.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_basic.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_ctypes.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_parser.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_tools_stubgen.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_char.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_custom.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_int.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_union.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/test_utils.py +0 -0
- {dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/tests/utils.py +0 -0
- {dissect_cstruct-4.5.dev4 → 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
|
|
@@ -21,6 +21,7 @@ from dissect.cstruct.types import (
|
|
|
21
21
|
Structure,
|
|
22
22
|
Union,
|
|
23
23
|
Void,
|
|
24
|
+
VoidArray,
|
|
24
25
|
Wchar,
|
|
25
26
|
WcharArray,
|
|
26
27
|
)
|
|
@@ -48,6 +49,7 @@ SUPPORTED_TYPES = (
|
|
|
48
49
|
Void,
|
|
49
50
|
Wchar,
|
|
50
51
|
WcharArray,
|
|
52
|
+
VoidArray,
|
|
51
53
|
)
|
|
52
54
|
|
|
53
55
|
log = logging.getLogger(__name__)
|
|
@@ -121,8 +123,7 @@ class _ReadSourceGenerator:
|
|
|
121
123
|
|
|
122
124
|
outro = """
|
|
123
125
|
obj = type.__call__(cls, **r)
|
|
124
|
-
obj.
|
|
125
|
-
obj._values = r
|
|
126
|
+
obj.__dynamic_sizes__ = s
|
|
126
127
|
|
|
127
128
|
return obj
|
|
128
129
|
"""
|
|
@@ -225,18 +226,18 @@ class _ReadSourceGenerator:
|
|
|
225
226
|
|
|
226
227
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
227
228
|
template = f"""
|
|
228
|
-
_s = stream.tell()
|
|
229
|
+
{'_s = stream.tell()' if field.type.dynamic else ''}
|
|
229
230
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
230
|
-
s["{field._name}"] = stream.tell() - _s
|
|
231
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ''}
|
|
231
232
|
"""
|
|
232
233
|
|
|
233
234
|
yield dedent(template)
|
|
234
235
|
|
|
235
236
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
236
237
|
template = f"""
|
|
237
|
-
_s = stream.tell()
|
|
238
|
+
{'_s = stream.tell()' if field.type.dynamic else ''}
|
|
238
239
|
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
239
|
-
s["{field._name}"] = stream.tell() - _s
|
|
240
|
+
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ''}
|
|
240
241
|
"""
|
|
241
242
|
|
|
242
243
|
yield dedent(template)
|
|
@@ -324,7 +325,6 @@ class _ReadSourceGenerator:
|
|
|
324
325
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
325
326
|
|
|
326
327
|
reads.append(f'r["{field._name}"] = {parser}')
|
|
327
|
-
reads.append(f's["{field._name}"] = {field_type.size}')
|
|
328
328
|
reads.append("") # Generates a newline in the resulting code
|
|
329
329
|
|
|
330
330
|
size += field_type.size
|
|
@@ -365,7 +365,7 @@ def _generate_struct_info(cs: cstruct, fields: list[Field], align: bool = False)
|
|
|
365
365
|
read_type = _get_read_type(cs, field.type)
|
|
366
366
|
|
|
367
367
|
# Drop voids
|
|
368
|
-
if issubclass(read_type, Void):
|
|
368
|
+
if issubclass(read_type, (Void, VoidArray)):
|
|
369
369
|
continue
|
|
370
370
|
|
|
371
371
|
# Array of more complex types are handled elsewhere
|
|
@@ -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
|
|
|
@@ -7,7 +7,7 @@ from dissect.cstruct.types.leb128 import LEB128
|
|
|
7
7
|
from dissect.cstruct.types.packed import Packed
|
|
8
8
|
from dissect.cstruct.types.pointer import Pointer
|
|
9
9
|
from dissect.cstruct.types.structure import Field, Structure, Union
|
|
10
|
-
from dissect.cstruct.types.void import Void
|
|
10
|
+
from dissect.cstruct.types.void import Void, VoidArray
|
|
11
11
|
from dissect.cstruct.types.wchar import Wchar, WcharArray
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
@@ -27,6 +27,7 @@ __all__ = [
|
|
|
27
27
|
"Structure",
|
|
28
28
|
"Union",
|
|
29
29
|
"Void",
|
|
30
|
+
"VoidArray",
|
|
30
31
|
"Wchar",
|
|
31
32
|
"WcharArray",
|
|
32
33
|
]
|
|
@@ -54,6 +54,10 @@ class MetaType(type):
|
|
|
54
54
|
"""Create a new array with the given number of entries."""
|
|
55
55
|
return cls.cs._make_array(cls, num_entries)
|
|
56
56
|
|
|
57
|
+
def __bool__(cls) -> bool:
|
|
58
|
+
"""Type class is always truthy."""
|
|
59
|
+
return True
|
|
60
|
+
|
|
57
61
|
def __len__(cls) -> int:
|
|
58
62
|
"""Return the byte size of the type."""
|
|
59
63
|
# Python 3.9 compat thing for bound type vars
|
|
@@ -258,7 +262,7 @@ class BaseArray(BaseType):
|
|
|
258
262
|
num = EOF
|
|
259
263
|
elif isinstance(cls.num_entries, Expression):
|
|
260
264
|
try:
|
|
261
|
-
num = max(0, cls.num_entries.evaluate(context))
|
|
265
|
+
num = max(0, cls.num_entries.evaluate(cls.cs, context))
|
|
262
266
|
except Exception:
|
|
263
267
|
if cls.num_entries.expression != "EOF":
|
|
264
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:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
4
|
+
|
|
5
|
+
from dissect.cstruct.types.base import BaseArray, BaseType
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VoidArray(list, BaseArray):
|
|
12
|
+
"""Array type representing void elements, primarily used for no-op reading and writing operations."""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def __default__(cls) -> Self:
|
|
16
|
+
return cls()
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
20
|
+
return cls()
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _write(cls, stream: BinaryIO, data: bytes) -> int:
|
|
24
|
+
return 0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Void(BaseType):
|
|
28
|
+
"""Void type."""
|
|
29
|
+
|
|
30
|
+
ArrayType = VoidArray
|
|
31
|
+
|
|
32
|
+
def __bool__(self) -> bool:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
def __eq__(self, value: object) -> bool:
|
|
36
|
+
return isinstance(value, Void)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
40
|
+
return cls.__new__(cls)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def _write(cls, stream: BinaryIO, data: Void) -> int:
|
|
44
|
+
return 0
|
|
@@ -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:
|
|
@@ -137,3 +137,12 @@ def test_custom_array_type(cs: cstruct, compiled: bool) -> None:
|
|
|
137
137
|
assert isinstance(result.b, CustomType)
|
|
138
138
|
assert result.a.value == b"ASDF"
|
|
139
139
|
assert result.b.value == b"asdf"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_truthy_type(cs: cstruct) -> None:
|
|
143
|
+
static_type = cs.uint32
|
|
144
|
+
dynamic_type = cs.uint32[None]
|
|
145
|
+
|
|
146
|
+
assert static_type
|
|
147
|
+
# Should not raise a TypeError: Dynamic size
|
|
148
|
+
assert dynamic_type
|
|
@@ -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 = """
|
|
@@ -10,7 +10,9 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def test_void_read(cs: cstruct) -> None:
|
|
13
|
-
|
|
13
|
+
# The type itself is truthy, but an instance is not
|
|
14
|
+
assert cs.void
|
|
15
|
+
assert not cs.void()
|
|
14
16
|
|
|
15
17
|
stream = io.BytesIO(b"AAAA")
|
|
16
18
|
assert not cs.void(stream)
|
|
@@ -23,11 +25,11 @@ def test_void_write(cs: cstruct) -> None:
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
def test_void_array_read(cs: cstruct) -> None:
|
|
26
|
-
assert not cs.void[4]
|
|
28
|
+
assert not cs.void[4]()
|
|
27
29
|
|
|
28
30
|
stream = io.BytesIO(b"AAAA")
|
|
29
|
-
assert not
|
|
30
|
-
assert not
|
|
31
|
+
assert not cs.void[4](stream)
|
|
32
|
+
assert not cs.void[None](stream)
|
|
31
33
|
assert stream.tell() == 0
|
|
32
34
|
|
|
33
35
|
|
|
@@ -41,7 +43,7 @@ def test_void_default(cs: cstruct) -> None:
|
|
|
41
43
|
assert not cs.void()
|
|
42
44
|
assert not cs.void.__default__()
|
|
43
45
|
|
|
44
|
-
assert cs.void[1].__default__() == [
|
|
46
|
+
assert cs.void[1].__default__() == []
|
|
45
47
|
assert cs.void[None].__default__() == []
|
|
46
48
|
|
|
47
49
|
|
|
@@ -61,8 +63,8 @@ def test_void_struct(cs: cstruct, compiled: bool) -> None:
|
|
|
61
63
|
|
|
62
64
|
obj = cs.test(stream)
|
|
63
65
|
assert not obj.a
|
|
64
|
-
assert not
|
|
65
|
-
assert not
|
|
66
|
+
assert not obj.b
|
|
67
|
+
assert not obj.c
|
|
66
68
|
|
|
67
69
|
assert stream.tell() == 0
|
|
68
70
|
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any, BinaryIO
|
|
4
|
-
|
|
5
|
-
from dissect.cstruct.types.base import BaseType
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Void(BaseType):
|
|
9
|
-
"""Void type."""
|
|
10
|
-
|
|
11
|
-
def __bool__(self) -> bool:
|
|
12
|
-
return False
|
|
13
|
-
|
|
14
|
-
def __eq__(self, value: object) -> bool:
|
|
15
|
-
return isinstance(value, Void)
|
|
16
|
-
|
|
17
|
-
@classmethod
|
|
18
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Void:
|
|
19
|
-
return cls.__new__(cls)
|
|
20
|
-
|
|
21
|
-
@classmethod
|
|
22
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Void:
|
|
23
|
-
return [cls.__new__(cls)]
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def _write(cls, stream: BinaryIO, data: Void) -> int:
|
|
27
|
-
return 0
|
|
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.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{dissect_cstruct-4.5.dev4 → dissect_cstruct-4.6.dev1}/dissect.cstruct.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dissect_cstruct-4.5.dev4 → 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
|