dissect.cstruct 4.4.dev3__tar.gz → 4.5.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.4.dev3 → dissect_cstruct-4.5.dev1}/PKG-INFO +5 -2
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/bitbuffer.py +15 -4
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/compiler.py +16 -15
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/cstruct.py +202 -40
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/parser.py +25 -15
- dissect_cstruct-4.5.dev1/dissect/cstruct/tools/stubgen.py +220 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/__init__.py +2 -2
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/base.py +50 -38
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/char.py +19 -16
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/enum.py +33 -14
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/int.py +6 -3
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/leb128.py +6 -3
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/packed.py +13 -7
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/pointer.py +25 -20
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/structure.py +46 -39
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/wchar.py +11 -11
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/utils.py +10 -9
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/PKG-INFO +5 -2
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/SOURCES.txt +6 -0
- dissect_cstruct-4.5.dev1/dissect.cstruct.egg-info/entry_points.txt +2 -0
- dissect_cstruct-4.5.dev1/dissect.cstruct.egg-info/requires.txt +3 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/pyproject.toml +8 -0
- dissect_cstruct-4.5.dev1/tests/_docs/__init__.py +0 -0
- dissect_cstruct-4.5.dev1/tests/test_annotations.py +29 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_basic.py +11 -4
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_compiler.py +2 -2
- dissect_cstruct-4.5.dev1/tests/test_ctypes.py +49 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_parser.py +40 -5
- dissect_cstruct-4.5.dev1/tests/test_tools_stubgen.py +332 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_base.py +2 -2
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_custom.py +2 -2
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_structure.py +1 -1
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_union.py +1 -1
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/utils.py +5 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tox.ini +1 -0
- dissect_cstruct-4.4.dev3/tests/test_ctypes.py +0 -32
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/.git-blame-ignore-revs +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/.gitattributes +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/COPYRIGHT +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/LICENSE +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/MANIFEST.in +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/README.md +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/__init__.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/expression.py +0 -0
- {dissect_cstruct-4.4.dev3/tests → dissect_cstruct-4.5.dev1/dissect/cstruct/tools}/__init__.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect/cstruct/types/void.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/disk.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/mirai.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/pe.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/setup.cfg +0 -0
- {dissect_cstruct-4.4.dev3/tests/_docs → dissect_cstruct-4.5.dev1/tests}/__init__.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_data/testdef.txt +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/Makefile +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/conf.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/_docs/index.rst +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/conftest.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_align.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_bitbuffer.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_bitfield.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_expression.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_char.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_enum.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_flag.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_int.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_leb128.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_packed.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_pointer.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_void.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_types_wchar.py +0 -0
- {dissect_cstruct-4.4.dev3 → dissect_cstruct-4.5.dev1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.5.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
|
|
@@ -22,6 +22,9 @@ Requires-Python: ~=3.9
|
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
License-File: COPYRIGHT
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: typing_extensions; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
25
28
|
|
|
26
29
|
# dissect.cstruct
|
|
27
30
|
|
|
@@ -13,12 +13,15 @@ class BitBuffer:
|
|
|
13
13
|
self.stream = stream
|
|
14
14
|
self.endian = endian
|
|
15
15
|
|
|
16
|
-
self._type = None
|
|
16
|
+
self._type: type[BaseType] | None = None
|
|
17
17
|
self._buffer = 0
|
|
18
18
|
self._remaining = 0
|
|
19
19
|
|
|
20
|
-
def read(self, field_type: BaseType, bits: int) -> int:
|
|
20
|
+
def read(self, field_type: type[BaseType], bits: int) -> int:
|
|
21
21
|
if self._remaining == 0 or self._type != field_type:
|
|
22
|
+
if field_type.size is None:
|
|
23
|
+
raise ValueError("Reading variable-length fields is unsupported")
|
|
24
|
+
|
|
22
25
|
self._type = field_type
|
|
23
26
|
self._remaining = field_type.size * 8
|
|
24
27
|
self._buffer = field_type._read(self.stream)
|
|
@@ -43,13 +46,20 @@ class BitBuffer:
|
|
|
43
46
|
|
|
44
47
|
return v
|
|
45
48
|
|
|
46
|
-
def write(self, field_type: BaseType, data: int, bits: int) -> None:
|
|
49
|
+
def write(self, field_type: type[BaseType], data: int, bits: int) -> None:
|
|
47
50
|
if self._remaining == 0 or self._type != field_type:
|
|
48
51
|
if self._type:
|
|
49
52
|
self.flush()
|
|
53
|
+
|
|
54
|
+
if field_type.size is None:
|
|
55
|
+
raise ValueError("Writing variable-length fields is unsupported")
|
|
56
|
+
|
|
50
57
|
self._remaining = field_type.size * 8
|
|
51
58
|
self._type = field_type
|
|
52
59
|
|
|
60
|
+
if self._type is None or self._type.size is None:
|
|
61
|
+
raise ValueError("Invalid state")
|
|
62
|
+
|
|
53
63
|
if self.endian == "<":
|
|
54
64
|
self._buffer |= data << (self._type.size * 8 - self._remaining)
|
|
55
65
|
else:
|
|
@@ -60,7 +70,8 @@ class BitBuffer:
|
|
|
60
70
|
self.flush()
|
|
61
71
|
|
|
62
72
|
def flush(self) -> None:
|
|
63
|
-
self._type
|
|
73
|
+
if self._type is not None:
|
|
74
|
+
self._type._write(self.stream, self._buffer)
|
|
64
75
|
self._type = None
|
|
65
76
|
self._remaining = 0
|
|
66
77
|
self._buffer = 0
|
|
@@ -11,12 +11,11 @@ from typing import TYPE_CHECKING
|
|
|
11
11
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
12
12
|
from dissect.cstruct.types import (
|
|
13
13
|
Array,
|
|
14
|
-
|
|
14
|
+
BaseType,
|
|
15
15
|
Char,
|
|
16
16
|
CharArray,
|
|
17
17
|
Flag,
|
|
18
18
|
Int,
|
|
19
|
-
MetaType,
|
|
20
19
|
Packed,
|
|
21
20
|
Pointer,
|
|
22
21
|
Structure,
|
|
@@ -25,6 +24,7 @@ from dissect.cstruct.types import (
|
|
|
25
24
|
Wchar,
|
|
26
25
|
WcharArray,
|
|
27
26
|
)
|
|
27
|
+
from dissect.cstruct.types.base import BaseArray
|
|
28
28
|
from dissect.cstruct.types.enum import EnumMetaType
|
|
29
29
|
from dissect.cstruct.types.packed import _struct
|
|
30
30
|
|
|
@@ -159,7 +159,7 @@ class _ReadSourceGenerator:
|
|
|
159
159
|
yield f"stream.seek(-stream.tell() & ({field.alignment} - 1), {io.SEEK_CUR})"
|
|
160
160
|
|
|
161
161
|
for field in self.fields:
|
|
162
|
-
field_type =
|
|
162
|
+
field_type = field.type
|
|
163
163
|
|
|
164
164
|
if isinstance(field_type, EnumMetaType):
|
|
165
165
|
field_type = field_type.type
|
|
@@ -187,7 +187,7 @@ class _ReadSourceGenerator:
|
|
|
187
187
|
|
|
188
188
|
# Array of structures and multi-dimensional arrays
|
|
189
189
|
elif issubclass(field_type, (Array, CharArray, WcharArray)) and (
|
|
190
|
-
issubclass(field_type.type, Structure) or
|
|
190
|
+
issubclass(field_type.type, Structure) or issubclass(field_type.type, BaseArray) or is_dynamic
|
|
191
191
|
):
|
|
192
192
|
yield from flush()
|
|
193
193
|
yield from align_to_field(field)
|
|
@@ -195,6 +195,9 @@ class _ReadSourceGenerator:
|
|
|
195
195
|
|
|
196
196
|
# Bit fields
|
|
197
197
|
elif field.bits:
|
|
198
|
+
if size is None:
|
|
199
|
+
raise TypeError(f"Unsupported type for bit field: {field_type}")
|
|
200
|
+
|
|
198
201
|
if not prev_was_bits:
|
|
199
202
|
prev_bits_type = field_type
|
|
200
203
|
prev_was_bits = True
|
|
@@ -223,8 +226,8 @@ class _ReadSourceGenerator:
|
|
|
223
226
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
224
227
|
template = f"""
|
|
225
228
|
_s = stream.tell()
|
|
226
|
-
r["{field.
|
|
227
|
-
s["{field.
|
|
229
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
230
|
+
s["{field._name}"] = stream.tell() - _s
|
|
228
231
|
"""
|
|
229
232
|
|
|
230
233
|
yield dedent(template)
|
|
@@ -232,8 +235,8 @@ class _ReadSourceGenerator:
|
|
|
232
235
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
233
236
|
template = f"""
|
|
234
237
|
_s = stream.tell()
|
|
235
|
-
r["{field.
|
|
236
|
-
s["{field.
|
|
238
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
239
|
+
s["{field._name}"] = stream.tell() - _s
|
|
237
240
|
"""
|
|
238
241
|
|
|
239
242
|
yield dedent(template)
|
|
@@ -252,7 +255,7 @@ class _ReadSourceGenerator:
|
|
|
252
255
|
|
|
253
256
|
template = f"""
|
|
254
257
|
_t = {lookup}
|
|
255
|
-
r["{field.
|
|
258
|
+
r["{field._name}"] = type.__call__(_t, bit_reader.read({read_type}, {field.bits}))
|
|
256
259
|
"""
|
|
257
260
|
|
|
258
261
|
yield dedent(template)
|
|
@@ -269,7 +272,7 @@ class _ReadSourceGenerator:
|
|
|
269
272
|
size += count
|
|
270
273
|
continue
|
|
271
274
|
|
|
272
|
-
field_type =
|
|
275
|
+
field_type = field.type
|
|
273
276
|
read_type = _get_read_type(self.cs, field_type)
|
|
274
277
|
|
|
275
278
|
if issubclass(field_type, (Array, CharArray, WcharArray)):
|
|
@@ -320,8 +323,8 @@ class _ReadSourceGenerator:
|
|
|
320
323
|
else:
|
|
321
324
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
322
325
|
|
|
323
|
-
reads.append(f'r["{field.
|
|
324
|
-
reads.append(f's["{field.
|
|
326
|
+
reads.append(f'r["{field._name}"] = {parser}')
|
|
327
|
+
reads.append(f's["{field._name}"] = {field_type.size}')
|
|
325
328
|
reads.append("") # Generates a newline in the resulting code
|
|
326
329
|
|
|
327
330
|
size += field_type.size
|
|
@@ -411,9 +414,7 @@ def _optimize_struct_fmt(info: Iterator[tuple[Field, int, str]]) -> str:
|
|
|
411
414
|
return "".join(f"{count if count > 1 else ''}{char}" for count, char in chars)
|
|
412
415
|
|
|
413
416
|
|
|
414
|
-
def _get_read_type(cs: cstruct, type_:
|
|
415
|
-
type_ = cs.resolve(type_)
|
|
416
|
-
|
|
417
|
+
def _get_read_type(cs: cstruct, type_: type[BaseType]) -> type[BaseType]:
|
|
417
418
|
if issubclass(type_, (Enum, Flag)):
|
|
418
419
|
type_ = type_.type
|
|
419
420
|
|
|
@@ -5,21 +5,21 @@ import struct
|
|
|
5
5
|
import sys
|
|
6
6
|
import types
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
8
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
|
|
9
9
|
|
|
10
10
|
from dissect.cstruct.exceptions import ResolveError
|
|
11
11
|
from dissect.cstruct.expression import Expression
|
|
12
12
|
from dissect.cstruct.parser import CStyleParser, TokenParser
|
|
13
13
|
from dissect.cstruct.types import (
|
|
14
14
|
LEB128,
|
|
15
|
-
|
|
15
|
+
Array,
|
|
16
|
+
BaseArray,
|
|
16
17
|
BaseType,
|
|
17
18
|
Char,
|
|
18
19
|
Enum,
|
|
19
20
|
Field,
|
|
20
21
|
Flag,
|
|
21
22
|
Int,
|
|
22
|
-
MetaType,
|
|
23
23
|
Packed,
|
|
24
24
|
Pointer,
|
|
25
25
|
Structure,
|
|
@@ -29,7 +29,12 @@ from dissect.cstruct.types import (
|
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import
|
|
32
|
+
from collections.abc import Iterable
|
|
33
|
+
|
|
34
|
+
from typing_extensions import TypeAlias
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
T = TypeVar("T", bound=BaseType)
|
|
33
38
|
|
|
34
39
|
|
|
35
40
|
class cstruct:
|
|
@@ -37,7 +42,7 @@ class cstruct:
|
|
|
37
42
|
|
|
38
43
|
Args:
|
|
39
44
|
endian: The endianness to use when parsing.
|
|
40
|
-
pointer: The pointer type to use for
|
|
45
|
+
pointer: The pointer type to use for pointers.
|
|
41
46
|
"""
|
|
42
47
|
|
|
43
48
|
DEF_CSTYLE = 1
|
|
@@ -81,16 +86,16 @@ class cstruct:
|
|
|
81
86
|
"signed char": "int8",
|
|
82
87
|
"unsigned char": "char",
|
|
83
88
|
"short": "int16",
|
|
84
|
-
"signed short": "
|
|
89
|
+
"signed short": "int16",
|
|
85
90
|
"unsigned short": "uint16",
|
|
86
91
|
"int": "int32",
|
|
87
|
-
"signed int": "
|
|
92
|
+
"signed int": "int32",
|
|
88
93
|
"unsigned int": "uint32",
|
|
89
94
|
"long": "int32",
|
|
90
|
-
"signed long": "
|
|
95
|
+
"signed long": "int32",
|
|
91
96
|
"unsigned long": "uint32",
|
|
92
97
|
"long long": "int64",
|
|
93
|
-
"signed long long": "
|
|
98
|
+
"signed long long": "int64",
|
|
94
99
|
"unsigned long long": "uint64",
|
|
95
100
|
|
|
96
101
|
# Windows types
|
|
@@ -113,14 +118,14 @@ class cstruct:
|
|
|
113
118
|
"ULONG64": "uint64",
|
|
114
119
|
"ULONGLONG": "uint64",
|
|
115
120
|
|
|
116
|
-
"INT": "
|
|
121
|
+
"INT": "int32",
|
|
117
122
|
"INT8": "int8",
|
|
118
123
|
"INT16": "int16",
|
|
119
124
|
"INT32": "int32",
|
|
120
125
|
"INT64": "int64",
|
|
121
126
|
"INT128": "int128",
|
|
122
127
|
|
|
123
|
-
"UINT": "
|
|
128
|
+
"UINT": "uint32",
|
|
124
129
|
"UINT8": "uint8",
|
|
125
130
|
"UINT16": "uint16",
|
|
126
131
|
"UINT32": "uint32",
|
|
@@ -172,14 +177,14 @@ class cstruct:
|
|
|
172
177
|
"__u32": "uint32",
|
|
173
178
|
"__u64": "uint64",
|
|
174
179
|
"uchar": "uint8",
|
|
175
|
-
"ushort": "
|
|
176
|
-
"uint": "
|
|
177
|
-
"ulong": "
|
|
180
|
+
"ushort": "uint16",
|
|
181
|
+
"uint": "uint32",
|
|
182
|
+
"ulong": "uint32",
|
|
178
183
|
}
|
|
179
184
|
# fmt: on
|
|
180
185
|
|
|
181
186
|
pointer = pointer or ("uint64" if sys.maxsize > 2**32 else "uint32")
|
|
182
|
-
self.pointer = self.resolve(pointer)
|
|
187
|
+
self.pointer: type[BaseType] = self.resolve(pointer)
|
|
183
188
|
self._anonymous_count = 0
|
|
184
189
|
|
|
185
190
|
def __getattr__(self, attr: str) -> Any:
|
|
@@ -200,7 +205,7 @@ class cstruct:
|
|
|
200
205
|
self._anonymous_count += 1
|
|
201
206
|
return name
|
|
202
207
|
|
|
203
|
-
def add_type(self, name: str, type_:
|
|
208
|
+
def add_type(self, name: str, type_: type[BaseType] | str, replace: bool = False) -> None:
|
|
204
209
|
"""Add a type or type reference.
|
|
205
210
|
|
|
206
211
|
Only use this method when creating type aliases or adding already bound types.
|
|
@@ -220,7 +225,7 @@ class cstruct:
|
|
|
220
225
|
addtype = add_type
|
|
221
226
|
|
|
222
227
|
def add_custom_type(
|
|
223
|
-
self, name: str, type_:
|
|
228
|
+
self, name: str, type_: type[BaseType], size: int | None = None, alignment: int | None = None, **kwargs
|
|
224
229
|
) -> None:
|
|
225
230
|
"""Add a custom type.
|
|
226
231
|
|
|
@@ -265,7 +270,7 @@ class cstruct:
|
|
|
265
270
|
def loadfile(self, path: str, deftype: int | None = None, **kwargs) -> None:
|
|
266
271
|
"""Load structure definitions from a file.
|
|
267
272
|
|
|
268
|
-
The given path will be read and parsed using the .load
|
|
273
|
+
The given path will be read and parsed using the :meth:`~cstruct.load` function.
|
|
269
274
|
|
|
270
275
|
Args:
|
|
271
276
|
path: The path to load definitions from.
|
|
@@ -287,7 +292,7 @@ class cstruct:
|
|
|
287
292
|
"""
|
|
288
293
|
return self.resolve(name).read(stream)
|
|
289
294
|
|
|
290
|
-
def resolve(self, name: str) ->
|
|
295
|
+
def resolve(self, name: type[BaseType] | str) -> type[BaseType]:
|
|
291
296
|
"""Resolve a type name to get the actual type object.
|
|
292
297
|
|
|
293
298
|
Types can be referenced using different names. When we want
|
|
@@ -320,7 +325,7 @@ class cstruct:
|
|
|
320
325
|
def _make_type(
|
|
321
326
|
self,
|
|
322
327
|
name: str,
|
|
323
|
-
bases:
|
|
328
|
+
bases: Iterable[object],
|
|
324
329
|
size: int | None,
|
|
325
330
|
*,
|
|
326
331
|
alignment: int | None = None,
|
|
@@ -341,10 +346,18 @@ class cstruct:
|
|
|
341
346
|
)
|
|
342
347
|
return types.new_class(name, bases, {}, lambda ns: ns.update(attrs))
|
|
343
348
|
|
|
344
|
-
def _make_array(self, type_:
|
|
345
|
-
null_terminated =
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
def _make_array(self, type_: T, num_entries: int | Expression | None) -> type[Array[T]]:
|
|
350
|
+
null_terminated = False
|
|
351
|
+
if num_entries is None:
|
|
352
|
+
null_terminated = True
|
|
353
|
+
size = None
|
|
354
|
+
elif isinstance(num_entries, Expression) or type_.dynamic:
|
|
355
|
+
size = None
|
|
356
|
+
else:
|
|
357
|
+
if type_.size is None:
|
|
358
|
+
raise ValueError(f"Cannot create array of dynamic type: {type_.__name__}")
|
|
359
|
+
size = num_entries * type_.size
|
|
360
|
+
|
|
348
361
|
name = f"{type_.__name__}[]" if null_terminated else f"{type_.__name__}[{num_entries}]"
|
|
349
362
|
|
|
350
363
|
bases = (type_.ArrayType,)
|
|
@@ -355,27 +368,30 @@ class cstruct:
|
|
|
355
368
|
"null_terminated": null_terminated,
|
|
356
369
|
}
|
|
357
370
|
|
|
358
|
-
return self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)
|
|
371
|
+
return cast(type[Array], self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs))
|
|
359
372
|
|
|
360
373
|
def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
|
|
361
|
-
return self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})
|
|
374
|
+
return cast(type[Int], self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed}))
|
|
362
375
|
|
|
363
376
|
def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
|
|
364
|
-
return
|
|
365
|
-
|
|
366
|
-
(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
377
|
+
return cast(
|
|
378
|
+
type[Packed],
|
|
379
|
+
self._make_type(
|
|
380
|
+
name,
|
|
381
|
+
(base, Packed),
|
|
382
|
+
struct.calcsize(packchar),
|
|
383
|
+
alignment=alignment,
|
|
384
|
+
attrs={"packchar": packchar},
|
|
385
|
+
),
|
|
370
386
|
)
|
|
371
387
|
|
|
372
|
-
def _make_enum(self, name: str, type_:
|
|
388
|
+
def _make_enum(self, name: str, type_: type[BaseType], values: dict[str, int]) -> type[Enum]:
|
|
373
389
|
return Enum(self, name, type_, values)
|
|
374
390
|
|
|
375
|
-
def _make_flag(self, name: str, type_:
|
|
391
|
+
def _make_flag(self, name: str, type_: type[BaseType], values: dict[str, int]) -> type[Flag]:
|
|
376
392
|
return Flag(self, name, type_, values)
|
|
377
393
|
|
|
378
|
-
def _make_pointer(self, target:
|
|
394
|
+
def _make_pointer(self, target: type[BaseType]) -> type[Pointer]:
|
|
379
395
|
return self._make_type(
|
|
380
396
|
f"{target.__name__}*",
|
|
381
397
|
(Pointer,),
|
|
@@ -409,18 +425,164 @@ class cstruct:
|
|
|
409
425
|
) -> type[Structure]:
|
|
410
426
|
return self._make_struct(name, fields, align=align, anonymous=anonymous, base=Union)
|
|
411
427
|
|
|
428
|
+
Z = TYPE_CHECKING
|
|
429
|
+
|
|
430
|
+
if TYPE_CHECKING:
|
|
431
|
+
A = 1
|
|
432
|
+
# ruff: noqa: PYI042
|
|
433
|
+
_int = int
|
|
434
|
+
_float = float
|
|
435
|
+
|
|
436
|
+
class int8(_int, Packed[_int]): ...
|
|
437
|
+
|
|
438
|
+
class uint8(_int, Packed[_int]): ...
|
|
439
|
+
|
|
440
|
+
class int16(_int, Packed[_int]): ...
|
|
441
|
+
|
|
442
|
+
class uint16(_int, Packed[_int]): ...
|
|
443
|
+
|
|
444
|
+
class int32(_int, Packed[_int]): ...
|
|
445
|
+
|
|
446
|
+
class uint32(_int, Packed[_int]): ...
|
|
447
|
+
|
|
448
|
+
class int64(_int, Packed[_int]): ...
|
|
449
|
+
|
|
450
|
+
class uint64(_int, Packed[_int]): ...
|
|
451
|
+
|
|
452
|
+
class float16(_float, Packed[_float]): ...
|
|
453
|
+
|
|
454
|
+
class float(_float, Packed[_float]): ...
|
|
455
|
+
|
|
456
|
+
class double(_float, Packed[_float]): ...
|
|
457
|
+
|
|
458
|
+
class char(Char): ...
|
|
459
|
+
|
|
460
|
+
class wchar(Wchar): ...
|
|
461
|
+
|
|
462
|
+
class int24(Int): ...
|
|
463
|
+
|
|
464
|
+
class uint24(Int): ...
|
|
465
|
+
|
|
466
|
+
class int48(Int): ...
|
|
467
|
+
|
|
468
|
+
class uint48(Int): ...
|
|
469
|
+
|
|
470
|
+
class int128(Int): ...
|
|
471
|
+
|
|
472
|
+
class uint128(Int): ...
|
|
473
|
+
|
|
474
|
+
class uleb128(LEB128): ...
|
|
475
|
+
|
|
476
|
+
class ileb128(LEB128): ...
|
|
477
|
+
|
|
478
|
+
class void(Void): ...
|
|
479
|
+
|
|
480
|
+
# signed char: TypeAlias = int8
|
|
481
|
+
# signed char: TypeAlias = char
|
|
482
|
+
short: TypeAlias = int16
|
|
483
|
+
# signed short: TypeAlias = int16
|
|
484
|
+
# unsigned short: TypeAlias = uint16
|
|
485
|
+
int: TypeAlias = int32
|
|
486
|
+
# signed int: TypeAlias = int32
|
|
487
|
+
# unsigned int: TypeAlias = uint32
|
|
488
|
+
long: TypeAlias = int32
|
|
489
|
+
# signed long: TypeAlias = int32
|
|
490
|
+
# unsigned long: TypeAlias = uint32
|
|
491
|
+
# long long: TypeAlias = int64
|
|
492
|
+
# signed long long: TypeAlias = int64
|
|
493
|
+
# unsigned long long: TypeAlias = uint64
|
|
494
|
+
|
|
495
|
+
BYTE: TypeAlias = uint8
|
|
496
|
+
CHAR: TypeAlias = char
|
|
497
|
+
SHORT: TypeAlias = int16
|
|
498
|
+
WORD: TypeAlias = uint16
|
|
499
|
+
DWORD: TypeAlias = uint32
|
|
500
|
+
LONG: TypeAlias = int32
|
|
501
|
+
LONG32: TypeAlias = int32
|
|
502
|
+
LONG64: TypeAlias = int64
|
|
503
|
+
LONGLONG: TypeAlias = int64
|
|
504
|
+
QWORD: TypeAlias = uint64
|
|
505
|
+
OWORD: TypeAlias = uint128
|
|
506
|
+
WCHAR: TypeAlias = wchar
|
|
507
|
+
|
|
508
|
+
UCHAR: TypeAlias = uint8
|
|
509
|
+
USHORT: TypeAlias = uint16
|
|
510
|
+
ULONG: TypeAlias = uint32
|
|
511
|
+
ULONG64: TypeAlias = uint64
|
|
512
|
+
ULONGLONG: TypeAlias = uint64
|
|
513
|
+
|
|
514
|
+
INT: TypeAlias = int32
|
|
515
|
+
INT8: TypeAlias = int8
|
|
516
|
+
INT16: TypeAlias = int16
|
|
517
|
+
INT32: TypeAlias = int32
|
|
518
|
+
INT64: TypeAlias = int64
|
|
519
|
+
INT128: TypeAlias = int128
|
|
520
|
+
|
|
521
|
+
UINT: TypeAlias = uint32
|
|
522
|
+
UINT8: TypeAlias = uint8
|
|
523
|
+
UINT16: TypeAlias = uint16
|
|
524
|
+
UINT32: TypeAlias = uint32
|
|
525
|
+
UINT64: TypeAlias = uint64
|
|
526
|
+
UINT128: TypeAlias = uint128
|
|
527
|
+
|
|
528
|
+
__int8: TypeAlias = int8
|
|
529
|
+
__int16: TypeAlias = int16
|
|
530
|
+
__int32: TypeAlias = int32
|
|
531
|
+
__int64: TypeAlias = int64
|
|
532
|
+
__int128: TypeAlias = int128
|
|
533
|
+
|
|
534
|
+
# unsigned __int8: TypeAlias = uint8
|
|
535
|
+
# unsigned __int16: TypeAlias = uint16
|
|
536
|
+
# unsigned __int32: TypeAlias = uint32
|
|
537
|
+
# unsigned __int64: TypeAlias = uint64
|
|
538
|
+
# unsigned __int128: TypeAlias = uint128
|
|
539
|
+
|
|
540
|
+
wchar_t: TypeAlias = wchar
|
|
412
541
|
|
|
413
|
-
|
|
542
|
+
int8_t: TypeAlias = int8
|
|
543
|
+
int16_t: TypeAlias = int16
|
|
544
|
+
int32_t: TypeAlias = int32
|
|
545
|
+
int64_t: TypeAlias = int64
|
|
546
|
+
int128_t: TypeAlias = int128
|
|
547
|
+
|
|
548
|
+
uint8_t: TypeAlias = uint8
|
|
549
|
+
uint16_t: TypeAlias = uint16
|
|
550
|
+
uint32_t: TypeAlias = uint32
|
|
551
|
+
uint64_t: TypeAlias = uint64
|
|
552
|
+
uint128_t: TypeAlias = uint128
|
|
553
|
+
|
|
554
|
+
_BYTE: TypeAlias = uint8
|
|
555
|
+
_WORD: TypeAlias = uint16
|
|
556
|
+
_DWORD: TypeAlias = uint32
|
|
557
|
+
_QWORD: TypeAlias = uint64
|
|
558
|
+
_OWORD: TypeAlias = uint128
|
|
559
|
+
|
|
560
|
+
u1: TypeAlias = uint8
|
|
561
|
+
u2: TypeAlias = uint16
|
|
562
|
+
u4: TypeAlias = uint32
|
|
563
|
+
u8: TypeAlias = uint64
|
|
564
|
+
u16: TypeAlias = uint128
|
|
565
|
+
__u8: TypeAlias = uint8
|
|
566
|
+
__u16: TypeAlias = uint16
|
|
567
|
+
__u32: TypeAlias = uint32
|
|
568
|
+
__u64: TypeAlias = uint64
|
|
569
|
+
uchar: TypeAlias = uint8
|
|
570
|
+
ushort: TypeAlias = uint16
|
|
571
|
+
uint: TypeAlias = uint32
|
|
572
|
+
ulong: TypeAlias = uint32
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
|
|
414
576
|
"""Create ctypes structures from cstruct structures."""
|
|
415
577
|
fields = []
|
|
416
578
|
for field in structure.__fields__:
|
|
417
579
|
t = ctypes_type(field.type)
|
|
418
|
-
fields.append((field.
|
|
580
|
+
fields.append((field._name, t))
|
|
419
581
|
|
|
420
|
-
return type(structure.
|
|
582
|
+
return type(structure.__name__, (_ctypes.Structure,), {"_fields_": fields})
|
|
421
583
|
|
|
422
584
|
|
|
423
|
-
def ctypes_type(type_:
|
|
585
|
+
def ctypes_type(type_: type[BaseType]) -> Any:
|
|
424
586
|
mapping = {
|
|
425
587
|
"b": _ctypes.c_int8,
|
|
426
588
|
"B": _ctypes.c_uint8,
|
|
@@ -443,7 +605,7 @@ def ctypes_type(type_: MetaType) -> Any:
|
|
|
443
605
|
if issubclass(type_, Wchar):
|
|
444
606
|
return _ctypes.c_wchar
|
|
445
607
|
|
|
446
|
-
if
|
|
608
|
+
if issubclass(type_, BaseArray):
|
|
447
609
|
subtype = ctypes_type(type_.type)
|
|
448
610
|
return subtype * type_.num_entries
|
|
449
611
|
|
|
@@ -11,7 +11,7 @@ from dissect.cstruct.exceptions import (
|
|
|
11
11
|
ParserError,
|
|
12
12
|
)
|
|
13
13
|
from dissect.cstruct.expression import Expression
|
|
14
|
-
from dissect.cstruct.types import
|
|
14
|
+
from dissect.cstruct.types import BaseArray, BaseType, Field, Structure
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
from dissect.cstruct import cstruct
|
|
@@ -147,22 +147,28 @@ class TokenParser(Parser):
|
|
|
147
147
|
tokens.consume()
|
|
148
148
|
type_ = None
|
|
149
149
|
|
|
150
|
+
names = []
|
|
151
|
+
|
|
150
152
|
if tokens.next == self.TOK.IDENTIFIER:
|
|
151
153
|
type_ = self.cstruct.resolve(self._identifier(tokens))
|
|
152
154
|
elif tokens.next == self.TOK.STRUCT:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
type_ = self._struct(tokens, register=True)
|
|
155
|
+
type_ = self._struct(tokens)
|
|
156
|
+
if not type_.__anonymous__:
|
|
157
|
+
names.append(type_.__name__)
|
|
157
158
|
|
|
158
|
-
names
|
|
159
|
+
names.extend(self._names(tokens))
|
|
159
160
|
for name in names:
|
|
161
|
+
if issubclass(type_, Structure) and type_.__anonymous__:
|
|
162
|
+
type_.__anonymous__ = False
|
|
163
|
+
type_.__name__ = name
|
|
164
|
+
type_.__qualname__ = name
|
|
165
|
+
|
|
160
166
|
type_, name, bits = self._parse_field_type(type_, name)
|
|
161
167
|
if bits is not None:
|
|
162
168
|
raise ParserError(f"line {self._lineno(tokens.previous)}: typedefs cannot have bitfields")
|
|
163
169
|
self.cstruct.add_type(name, type_)
|
|
164
170
|
|
|
165
|
-
def _struct(self, tokens: TokenConsumer, register: bool = False) ->
|
|
171
|
+
def _struct(self, tokens: TokenConsumer, register: bool = False) -> type[Structure]:
|
|
166
172
|
stype = tokens.consume()
|
|
167
173
|
|
|
168
174
|
factory = self.cstruct._make_union if stype.value.startswith("union") else self.cstruct._make_struct
|
|
@@ -204,9 +210,14 @@ class TokenParser(Parser):
|
|
|
204
210
|
field = self._parse_field(tokens)
|
|
205
211
|
fields.append(field)
|
|
206
212
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
213
|
+
if register:
|
|
214
|
+
names.extend(self._names(tokens))
|
|
215
|
+
|
|
216
|
+
# If the next token is EOL, consume it
|
|
217
|
+
# Otherwise we're part of a typedef or field definition
|
|
218
|
+
if tokens.next == self.TOK.EOL:
|
|
219
|
+
tokens.eol()
|
|
220
|
+
|
|
210
221
|
name = names[0] if names else None
|
|
211
222
|
|
|
212
223
|
if st is None:
|
|
@@ -251,8 +262,7 @@ class TokenParser(Parser):
|
|
|
251
262
|
type_ = self._struct(tokens)
|
|
252
263
|
|
|
253
264
|
if tokens.next != self.TOK.NAME:
|
|
254
|
-
|
|
255
|
-
return Field(name.strip(), type_, bits)
|
|
265
|
+
return Field(None, type_, None)
|
|
256
266
|
|
|
257
267
|
if tokens.next != self.TOK.NAME:
|
|
258
268
|
raise ParserError(f"line {self._lineno(tokens.next)}: expected name")
|
|
@@ -263,7 +273,7 @@ class TokenParser(Parser):
|
|
|
263
273
|
tokens.eol()
|
|
264
274
|
return Field(name.strip(), type_, bits)
|
|
265
275
|
|
|
266
|
-
def _parse_field_type(self, type_:
|
|
276
|
+
def _parse_field_type(self, type_: type[BaseType], name: str) -> tuple[type[BaseType], str, int | None]:
|
|
267
277
|
pattern = self.TOK.patterns[self.TOK.NAME]
|
|
268
278
|
# Dirty trick because the regex expects a ; but we don't want it to be part of the value
|
|
269
279
|
d = pattern.match(name + ";").groupdict()
|
|
@@ -289,7 +299,7 @@ class TokenParser(Parser):
|
|
|
289
299
|
except Exception:
|
|
290
300
|
pass
|
|
291
301
|
|
|
292
|
-
if
|
|
302
|
+
if issubclass(type_, BaseArray) and count is None:
|
|
293
303
|
raise ParserError("Depth required for multi-dimensional array")
|
|
294
304
|
|
|
295
305
|
type_ = self.cstruct._make_array(type_, count)
|
|
@@ -571,7 +581,7 @@ class TokenCollection:
|
|
|
571
581
|
|
|
572
582
|
return object.__getattribute__(self, attr)
|
|
573
583
|
|
|
574
|
-
def add(self, regex: str, name: str) -> None:
|
|
584
|
+
def add(self, regex: str, name: str | None) -> None:
|
|
575
585
|
if name is None:
|
|
576
586
|
self.tokens.append((regex, None))
|
|
577
587
|
else:
|