dissect.cstruct 4.3.dev2__tar.gz → 4.4.dev2__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.dev2/.git-blame-ignore-revs +6 -0
- {dissect_cstruct-4.3.dev2/dissect.cstruct.egg-info → dissect_cstruct-4.4.dev2}/PKG-INFO +2 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/compiler.py +8 -8
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/cstruct.py +11 -9
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/expression.py +6 -7
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/parser.py +17 -25
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/base.py +19 -11
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/enum.py +1 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/leb128.py +3 -4
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/pointer.py +2 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/structure.py +9 -7
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/wchar.py +5 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/utils.py +16 -14
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2/dissect.cstruct.egg-info}/PKG-INFO +2 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/pyproject.toml +48 -5
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/conftest.py +1 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_align.py +6 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_basic.py +32 -38
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_bitbuffer.py +7 -3
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_bitfield.py +8 -6
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_compiler.py +9 -5
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_ctypes.py +3 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_expression.py +13 -7
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_parser.py +7 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_base.py +4 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_char.py +5 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_custom.py +4 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_enum.py +24 -12
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_flag.py +23 -10
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_int.py +7 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_leb128.py +5 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_packed.py +8 -3
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_pointer.py +3 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_structure.py +19 -15
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_union.py +8 -3
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_void.py +6 -2
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_wchar.py +5 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_utils.py +17 -7
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/utils.py +4 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tox.ini +4 -17
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/COPYRIGHT +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/LICENSE +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/MANIFEST.in +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/README.md +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/__init__.py +10 -10
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/exceptions.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/__init__.py +1 -1
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/char.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/flag.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/int.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/packed.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/void.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/disk.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/mirai.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/pe.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/protobuf.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/secdesc.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/setup.cfg +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/__init__.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/data/testdef.txt +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/Makefile +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/conf.py +0 -0
- {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/index.rst +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.dev2
|
|
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
|
|
@@ -6,8 +6,7 @@ import io
|
|
|
6
6
|
import logging
|
|
7
7
|
from enum import Enum
|
|
8
8
|
from textwrap import dedent, indent
|
|
9
|
-
from
|
|
10
|
-
from typing import TYPE_CHECKING, Iterator
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
11
10
|
|
|
12
11
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
13
12
|
from dissect.cstruct.types import (
|
|
@@ -30,6 +29,9 @@ from dissect.cstruct.types.enum import EnumMetaType
|
|
|
30
29
|
from dissect.cstruct.types.packed import _struct
|
|
31
30
|
|
|
32
31
|
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Iterator
|
|
33
|
+
from types import MethodType
|
|
34
|
+
|
|
33
35
|
from dissect.cstruct.cstruct import cstruct
|
|
34
36
|
from dissect.cstruct.types.structure import Field
|
|
35
37
|
|
|
@@ -127,8 +129,7 @@ class _ReadSourceGenerator:
|
|
|
127
129
|
|
|
128
130
|
code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
|
|
129
131
|
|
|
130
|
-
|
|
131
|
-
return template
|
|
132
|
+
return f"def _read(cls, stream, context=None):\n{code}"
|
|
132
133
|
|
|
133
134
|
def _generate_fields(self) -> Iterator[str]:
|
|
134
135
|
current_offset = 0
|
|
@@ -210,10 +211,9 @@ class _ReadSourceGenerator:
|
|
|
210
211
|
else:
|
|
211
212
|
current_block.append(field)
|
|
212
213
|
|
|
213
|
-
if current_offset is not None and size is not None:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
bits_rollover = False
|
|
214
|
+
if current_offset is not None and size is not None and (not field.bits or bits_rollover):
|
|
215
|
+
current_offset += size
|
|
216
|
+
bits_rollover = False
|
|
217
217
|
|
|
218
218
|
yield from flush()
|
|
219
219
|
|
|
@@ -4,7 +4,8 @@ import ctypes as _ctypes
|
|
|
4
4
|
import struct
|
|
5
5
|
import sys
|
|
6
6
|
import types
|
|
7
|
-
from
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
8
9
|
|
|
9
10
|
from dissect.cstruct.exceptions import ResolveError
|
|
10
11
|
from dissect.cstruct.expression import Expression
|
|
@@ -27,6 +28,9 @@ from dissect.cstruct.types import (
|
|
|
27
28
|
Wchar,
|
|
28
29
|
)
|
|
29
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Iterator
|
|
33
|
+
|
|
30
34
|
|
|
31
35
|
class cstruct:
|
|
32
36
|
"""Main class of cstruct. All types are registered in here.
|
|
@@ -134,7 +138,6 @@ class cstruct:
|
|
|
134
138
|
"unsigned __int32": "uint32",
|
|
135
139
|
"unsigned __int64": "uint64",
|
|
136
140
|
"unsigned __int128": "uint128",
|
|
137
|
-
"unsigned __int128": "uint128",
|
|
138
141
|
|
|
139
142
|
"wchar_t": "wchar",
|
|
140
143
|
|
|
@@ -233,7 +236,7 @@ class cstruct:
|
|
|
233
236
|
"""
|
|
234
237
|
self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
|
|
235
238
|
|
|
236
|
-
def load(self, definition: str, deftype: int = None, **kwargs) -> cstruct:
|
|
239
|
+
def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
|
|
237
240
|
"""Parse structures from the given definitions using the given definition type.
|
|
238
241
|
|
|
239
242
|
Definitions can be parsed using different parsers. Currently, there's
|
|
@@ -259,7 +262,7 @@ class cstruct:
|
|
|
259
262
|
|
|
260
263
|
return self
|
|
261
264
|
|
|
262
|
-
def loadfile(self, path: str, deftype: int = None, **kwargs) -> None:
|
|
265
|
+
def loadfile(self, path: str, deftype: int | None = None, **kwargs) -> None:
|
|
263
266
|
"""Load structure definitions from a file.
|
|
264
267
|
|
|
265
268
|
The given path will be read and parsed using the .load() function.
|
|
@@ -269,7 +272,7 @@ class cstruct:
|
|
|
269
272
|
deftype: The definition type to parse the definitions with.
|
|
270
273
|
**kwargs: Keyword arguments for parsers.
|
|
271
274
|
"""
|
|
272
|
-
with
|
|
275
|
+
with Path(path).open() as fh:
|
|
273
276
|
self.load(fh.read(), deftype, **kwargs)
|
|
274
277
|
|
|
275
278
|
def read(self, name: str, stream: BinaryIO) -> Any:
|
|
@@ -354,10 +357,10 @@ class cstruct:
|
|
|
354
357
|
|
|
355
358
|
return self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)
|
|
356
359
|
|
|
357
|
-
def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int = None) -> type[Int]:
|
|
360
|
+
def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
|
|
358
361
|
return self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})
|
|
359
362
|
|
|
360
|
-
def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int = None) -> type[Packed]:
|
|
363
|
+
def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
|
|
361
364
|
return self._make_type(
|
|
362
365
|
name,
|
|
363
366
|
(base, Packed),
|
|
@@ -414,8 +417,7 @@ def ctypes(structure: Structure) -> _ctypes.Structure:
|
|
|
414
417
|
t = ctypes_type(field.type)
|
|
415
418
|
fields.append((field.name, t))
|
|
416
419
|
|
|
417
|
-
|
|
418
|
-
return tt
|
|
420
|
+
return type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
|
|
419
421
|
|
|
420
422
|
|
|
421
423
|
def ctypes_type(type_: MetaType) -> Any:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import string
|
|
4
|
-
from typing import TYPE_CHECKING, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Callable, ClassVar
|
|
5
5
|
|
|
6
6
|
from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
|
|
7
7
|
|
|
@@ -21,8 +21,7 @@ class ExpressionTokenizer:
|
|
|
21
21
|
def equal(self, token: str, expected: str | set[str]) -> bool:
|
|
22
22
|
if isinstance(expected, set):
|
|
23
23
|
return token in expected
|
|
24
|
-
|
|
25
|
-
return token == expected
|
|
24
|
+
return token == expected
|
|
26
25
|
|
|
27
26
|
def alnum(self, token: str) -> bool:
|
|
28
27
|
return token.isalnum()
|
|
@@ -88,7 +87,7 @@ class ExpressionTokenizer:
|
|
|
88
87
|
continue
|
|
89
88
|
|
|
90
89
|
# If token is a single digit, keep looping over expression and build the number
|
|
91
|
-
|
|
90
|
+
if self.match(self.digit, consume=False, append=False):
|
|
92
91
|
token += self.get_token()
|
|
93
92
|
self.consume()
|
|
94
93
|
|
|
@@ -154,7 +153,7 @@ class ExpressionTokenizer:
|
|
|
154
153
|
class Expression:
|
|
155
154
|
"""Expression parser for calculations in definitions."""
|
|
156
155
|
|
|
157
|
-
binary_operators = {
|
|
156
|
+
binary_operators: ClassVar[dict[str, Callable[[int, int], int]]] = {
|
|
158
157
|
"|": lambda a, b: a | b,
|
|
159
158
|
"^": lambda a, b: a ^ b,
|
|
160
159
|
"&": lambda a, b: a & b,
|
|
@@ -167,12 +166,12 @@ class Expression:
|
|
|
167
166
|
"%": lambda a, b: a % b,
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
unary_operators = {
|
|
169
|
+
unary_operators: ClassVar[dict[str, Callable[[int], int]]] = {
|
|
171
170
|
"u": lambda a: -a,
|
|
172
171
|
"~": lambda a: ~a,
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
precedence_levels = {
|
|
174
|
+
precedence_levels: ClassVar[dict[str, int]] = {
|
|
176
175
|
"|": 0,
|
|
177
176
|
"^": 1,
|
|
178
177
|
"&": 2,
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import re
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
from dissect.cstruct import compiler
|
|
8
8
|
from dissect.cstruct.exceptions import (
|
|
@@ -33,7 +33,7 @@ class Parser:
|
|
|
33
33
|
Args:
|
|
34
34
|
data: Data to parse definitions from, usually a string.
|
|
35
35
|
"""
|
|
36
|
-
raise NotImplementedError
|
|
36
|
+
raise NotImplementedError
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class TokenParser(Parser):
|
|
@@ -119,10 +119,8 @@ class TokenParser(Parser):
|
|
|
119
119
|
val = val.strip()
|
|
120
120
|
if not key:
|
|
121
121
|
continue
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else:
|
|
125
|
-
val = Expression(self.cstruct, val).evaluate(values)
|
|
122
|
+
|
|
123
|
+
val = nextval if not val else Expression(self.cstruct, val).evaluate(values)
|
|
126
124
|
|
|
127
125
|
if enumtype == "flag":
|
|
128
126
|
high_bit = val.bit_length() - 1
|
|
@@ -243,7 +241,7 @@ class TokenParser(Parser):
|
|
|
243
241
|
# Dirty trick because the regex expects a ; but we don't want it to be part of the value
|
|
244
242
|
m = pattern.match(ltok.value + ";")
|
|
245
243
|
d = ast.literal_eval(m.group(2))
|
|
246
|
-
self.cstruct.lookups[m.group(1)] =
|
|
244
|
+
self.cstruct.lookups[m.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
|
|
247
245
|
|
|
248
246
|
def _parse_field(self, tokens: TokenConsumer) -> Field:
|
|
249
247
|
type_ = None
|
|
@@ -279,10 +277,7 @@ class TokenParser(Parser):
|
|
|
279
277
|
|
|
280
278
|
if count_expression is not None:
|
|
281
279
|
# Poor mans multi-dimensional array by abusing the eager regex match of count
|
|
282
|
-
if "][" in count_expression
|
|
283
|
-
counts = count_expression.split("][")
|
|
284
|
-
else:
|
|
285
|
-
counts = [count_expression]
|
|
280
|
+
counts = count_expression.split("][") if "][" in count_expression else [count_expression]
|
|
286
281
|
|
|
287
282
|
for count in reversed(counts):
|
|
288
283
|
if count == "":
|
|
@@ -315,8 +310,7 @@ class TokenParser(Parser):
|
|
|
315
310
|
if ntoken == self.TOK.NAME:
|
|
316
311
|
names.append(ntoken.value.strip())
|
|
317
312
|
elif ntoken == self.TOK.DEFS:
|
|
318
|
-
for name in ntoken.value.strip().split(",")
|
|
319
|
-
names.append(name.strip())
|
|
313
|
+
names.extend([name.strip() for name in ntoken.value.strip().split(",")])
|
|
320
314
|
|
|
321
315
|
return names
|
|
322
316
|
|
|
@@ -333,8 +327,8 @@ class TokenParser(Parser):
|
|
|
333
327
|
# it means we have captured a non-quoted (real) comment string.
|
|
334
328
|
if comment := match.group(2):
|
|
335
329
|
return "\n" * comment.count("\n") # so we will return empty to remove the comment
|
|
336
|
-
|
|
337
|
-
|
|
330
|
+
# otherwise, we will return the 1st group
|
|
331
|
+
return match.group(1) # captured quoted-string
|
|
338
332
|
|
|
339
333
|
return regex.sub(_replacer, string)
|
|
340
334
|
|
|
@@ -429,10 +423,8 @@ class CStyleParser(Parser):
|
|
|
429
423
|
val = val.strip()
|
|
430
424
|
if not key:
|
|
431
425
|
continue
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
else:
|
|
435
|
-
val = Expression(self.cstruct, val).evaluate()
|
|
426
|
+
|
|
427
|
+
val = nextval if not val else Expression(self.cstruct, val).evaluate()
|
|
436
428
|
|
|
437
429
|
if enumtype == "flag":
|
|
438
430
|
high_bit = val.bit_length() - 1
|
|
@@ -535,7 +527,7 @@ class CStyleParser(Parser):
|
|
|
535
527
|
|
|
536
528
|
for t in r:
|
|
537
529
|
d = ast.literal_eval(t.group(2))
|
|
538
|
-
self.cstruct.lookups[t.group(1)] =
|
|
530
|
+
self.cstruct.lookups[t.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
|
|
539
531
|
|
|
540
532
|
def parse(self, data: str) -> None:
|
|
541
533
|
self._constants(data)
|
|
@@ -545,23 +537,23 @@ class CStyleParser(Parser):
|
|
|
545
537
|
|
|
546
538
|
|
|
547
539
|
class Token:
|
|
548
|
-
__slots__ = ("
|
|
540
|
+
__slots__ = ("match", "token", "value")
|
|
549
541
|
|
|
550
542
|
def __init__(self, token: str, value: str, match: re.Match):
|
|
551
543
|
self.token = token
|
|
552
544
|
self.value = value
|
|
553
545
|
self.match = match
|
|
554
546
|
|
|
555
|
-
def __eq__(self, other):
|
|
547
|
+
def __eq__(self, other: object) -> bool:
|
|
556
548
|
if isinstance(other, Token):
|
|
557
549
|
other = other.token
|
|
558
550
|
|
|
559
551
|
return self.token == other
|
|
560
552
|
|
|
561
|
-
def __ne__(self, other):
|
|
553
|
+
def __ne__(self, other: object) -> bool:
|
|
562
554
|
return not self == other
|
|
563
555
|
|
|
564
|
-
def __repr__(self):
|
|
556
|
+
def __repr__(self) -> str:
|
|
565
557
|
return f"<Token.{self.token} value={self.value!r}>"
|
|
566
558
|
|
|
567
559
|
|
|
@@ -571,7 +563,7 @@ class TokenCollection:
|
|
|
571
563
|
self.lookup: dict[str, str] = {}
|
|
572
564
|
self.patterns: dict[str, re.Pattern] = {}
|
|
573
565
|
|
|
574
|
-
def __getattr__(self, attr: str):
|
|
566
|
+
def __getattr__(self, attr: str) -> str | Any:
|
|
575
567
|
try:
|
|
576
568
|
return self.lookup[attr]
|
|
577
569
|
except AttributeError:
|
|
@@ -122,7 +122,7 @@ class MetaType(type):
|
|
|
122
122
|
stream: The stream to read from.
|
|
123
123
|
context: Optional reading context.
|
|
124
124
|
"""
|
|
125
|
-
raise NotImplementedError
|
|
125
|
+
raise NotImplementedError
|
|
126
126
|
|
|
127
127
|
def _read_array(cls, stream: BinaryIO, count: int, context: dict[str, Any] | None = None) -> list[BaseType]:
|
|
128
128
|
"""Internal function for reading array values.
|
|
@@ -136,11 +136,8 @@ class MetaType(type):
|
|
|
136
136
|
"""
|
|
137
137
|
if count == EOF:
|
|
138
138
|
result = []
|
|
139
|
-
while
|
|
140
|
-
|
|
141
|
-
result.append(cls._read(stream, context))
|
|
142
|
-
except EOFError:
|
|
143
|
-
break
|
|
139
|
+
while not _is_eof(stream):
|
|
140
|
+
result.append(cls._read(stream, context))
|
|
144
141
|
return result
|
|
145
142
|
|
|
146
143
|
return [cls._read(stream, context) for _ in range(count)]
|
|
@@ -154,10 +151,10 @@ class MetaType(type):
|
|
|
154
151
|
stream: The stream to read from.
|
|
155
152
|
context: Optional reading context.
|
|
156
153
|
"""
|
|
157
|
-
raise NotImplementedError
|
|
154
|
+
raise NotImplementedError
|
|
158
155
|
|
|
159
156
|
def _write(cls, stream: BinaryIO, data: Any) -> int:
|
|
160
|
-
raise NotImplementedError
|
|
157
|
+
raise NotImplementedError
|
|
161
158
|
|
|
162
159
|
def _write_array(cls, stream: BinaryIO, array: list[BaseType]) -> int:
|
|
163
160
|
"""Internal function for writing arrays.
|
|
@@ -179,7 +176,7 @@ class MetaType(type):
|
|
|
179
176
|
stream: The stream to read from.
|
|
180
177
|
array: The array to write.
|
|
181
178
|
"""
|
|
182
|
-
return cls._write_array(stream, array
|
|
179
|
+
return cls._write_array(stream, [*array, cls.__default__()])
|
|
183
180
|
|
|
184
181
|
|
|
185
182
|
class _overload:
|
|
@@ -200,8 +197,7 @@ class _overload:
|
|
|
200
197
|
def __get__(self, instance: BaseType | None, owner: MetaType) -> Callable[[Any], bytes]:
|
|
201
198
|
if instance is None:
|
|
202
199
|
return functools.partial(self.func, owner)
|
|
203
|
-
|
|
204
|
-
return functools.partial(self.func, instance.__class__, value=instance)
|
|
200
|
+
return functools.partial(self.func, instance.__class__, value=instance)
|
|
205
201
|
|
|
206
202
|
|
|
207
203
|
class BaseType(metaclass=MetaType):
|
|
@@ -283,5 +279,17 @@ def _is_buffer_type(value: Any) -> bool:
|
|
|
283
279
|
return isinstance(value, (bytes, memoryview, bytearray))
|
|
284
280
|
|
|
285
281
|
|
|
282
|
+
def _is_eof(stream: BinaryIO) -> bool:
|
|
283
|
+
"""Check if the stream has reached EOF."""
|
|
284
|
+
pos = stream.tell()
|
|
285
|
+
stream.read(1)
|
|
286
|
+
|
|
287
|
+
if stream.tell() == pos:
|
|
288
|
+
return True
|
|
289
|
+
|
|
290
|
+
stream.seek(pos)
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
|
|
286
294
|
# As mentioned in the BaseType class, we correctly set the type here
|
|
287
295
|
MetaType.ArrayType = Array
|
|
@@ -82,7 +82,7 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
82
82
|
|
|
83
83
|
def _write_0(cls, stream: BinaryIO, array: list[BaseType]) -> int:
|
|
84
84
|
data = [entry.value if isinstance(entry, Enum) else entry for entry in array]
|
|
85
|
-
return cls._write_array(stream, data
|
|
85
|
+
return cls._write_array(stream, [*data, cls.type.__default__()])
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
def _fix_alias_members(cls: type[Enum]) -> None:
|
|
@@ -28,9 +28,8 @@ class LEB128(int, BaseType):
|
|
|
28
28
|
if (b & 0x80) == 0:
|
|
29
29
|
break
|
|
30
30
|
|
|
31
|
-
if cls.signed:
|
|
32
|
-
|
|
33
|
-
result |= ~0 << shift
|
|
31
|
+
if cls.signed and b & 0x40 != 0:
|
|
32
|
+
result |= ~0 << shift
|
|
34
33
|
|
|
35
34
|
return cls.__new__(cls, result)
|
|
36
35
|
|
|
@@ -60,7 +59,7 @@ class LEB128(int, BaseType):
|
|
|
60
59
|
|
|
61
60
|
# function works similar for signed- and unsigned integers, except for the check when to stop
|
|
62
61
|
# the encoding process.
|
|
63
|
-
if (cls.signed and (data == 0 and byte & 0x40 == 0) or (data == -1 and byte & 0x40 != 0)) or (
|
|
62
|
+
if ((cls.signed and (data == 0 and byte & 0x40 == 0)) or (data == -1 and byte & 0x40 != 0)) or (
|
|
64
63
|
not cls.signed and data == 0
|
|
65
64
|
):
|
|
66
65
|
result.append(byte)
|
|
@@ -16,7 +16,7 @@ class Pointer(int, BaseType):
|
|
|
16
16
|
_context: dict[str, Any] | None
|
|
17
17
|
_value: BaseType
|
|
18
18
|
|
|
19
|
-
def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer:
|
|
19
|
+
def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer: # noqa: PYI034
|
|
20
20
|
obj = super().__new__(cls, value)
|
|
21
21
|
obj._stream = stream
|
|
22
22
|
obj._context = context
|
|
@@ -79,7 +79,7 @@ class Pointer(int, BaseType):
|
|
|
79
79
|
|
|
80
80
|
def dereference(self) -> Any:
|
|
81
81
|
if self == 0 or self._stream is None:
|
|
82
|
-
raise NullPointerDereference
|
|
82
|
+
raise NullPointerDereference
|
|
83
83
|
|
|
84
84
|
if self._value is None and not issubclass(self.type, Void):
|
|
85
85
|
# Read current position of file read/write pointer
|
|
@@ -7,8 +7,7 @@ from functools import lru_cache
|
|
|
7
7
|
from itertools import chain
|
|
8
8
|
from operator import attrgetter
|
|
9
9
|
from textwrap import dedent
|
|
10
|
-
from
|
|
11
|
-
from typing import Any, BinaryIO, Callable, Iterator
|
|
10
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Callable
|
|
12
11
|
|
|
13
12
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
14
13
|
from dissect.cstruct.types.base import (
|
|
@@ -20,11 +19,15 @@ from dissect.cstruct.types.base import (
|
|
|
20
19
|
from dissect.cstruct.types.enum import EnumMetaType
|
|
21
20
|
from dissect.cstruct.types.pointer import Pointer
|
|
22
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Iterator
|
|
24
|
+
from types import FunctionType
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
class Field:
|
|
25
28
|
"""Structure field."""
|
|
26
29
|
|
|
27
|
-
def __init__(self, name: str, type_: MetaType, bits: int = None, offset: int = None):
|
|
30
|
+
def __init__(self, name: str, type_: MetaType, bits: int | None = None, offset: int | None = None):
|
|
28
31
|
self.name = name
|
|
29
32
|
self.type = type_
|
|
30
33
|
self.bits = bits
|
|
@@ -70,7 +73,7 @@ class StructureMetaType(MetaType):
|
|
|
70
73
|
):
|
|
71
74
|
# Shortcut for single char/bytes type
|
|
72
75
|
return type.__call__(cls, *args, **kwargs)
|
|
73
|
-
|
|
76
|
+
if not args and not kwargs:
|
|
74
77
|
obj = type.__call__(cls)
|
|
75
78
|
object.__setattr__(obj, "_values", {})
|
|
76
79
|
object.__setattr__(obj, "_sizes", {})
|
|
@@ -259,8 +262,7 @@ class StructureMetaType(MetaType):
|
|
|
259
262
|
if field.name:
|
|
260
263
|
result[field.name] = value
|
|
261
264
|
continue
|
|
262
|
-
|
|
263
|
-
bit_buffer.reset()
|
|
265
|
+
bit_buffer.reset()
|
|
264
266
|
|
|
265
267
|
value = field_type._read(stream, result)
|
|
266
268
|
|
|
@@ -540,7 +542,7 @@ class Union(Structure, metaclass=UnionMetaType):
|
|
|
540
542
|
|
|
541
543
|
_buf: bytes
|
|
542
544
|
|
|
543
|
-
def __eq__(self, other:
|
|
545
|
+
def __eq__(self, other: object) -> bool:
|
|
544
546
|
return self.__class__ is other.__class__ and bytes(self) == bytes(other)
|
|
545
547
|
|
|
546
548
|
def __setattr__(self, attr: str, value: Any) -> None:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
-
from typing import Any, BinaryIO
|
|
4
|
+
from typing import Any, BinaryIO, ClassVar
|
|
5
5
|
|
|
6
6
|
from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
|
|
7
7
|
|
|
@@ -9,6 +9,8 @@ from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
|
|
|
9
9
|
class WcharArray(str, BaseType, metaclass=ArrayMetaType):
|
|
10
10
|
"""Wide-character array type for reading and writing UTF-16 strings."""
|
|
11
11
|
|
|
12
|
+
__slots__ = ()
|
|
13
|
+
|
|
12
14
|
@classmethod
|
|
13
15
|
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> WcharArray:
|
|
14
16
|
return type.__call__(cls, ArrayMetaType._read(cls, stream, context))
|
|
@@ -29,7 +31,8 @@ class Wchar(str, BaseType):
|
|
|
29
31
|
|
|
30
32
|
ArrayType = WcharArray
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
__slots__ = ()
|
|
35
|
+
__encoding_map__: ClassVar[dict[str, str]] = {
|
|
33
36
|
"@": f"utf-16-{sys.byteorder[0]}e",
|
|
34
37
|
"=": f"utf-16-{sys.byteorder[0]}e",
|
|
35
38
|
"<": "utf-16-le",
|
|
@@ -4,11 +4,14 @@ import pprint
|
|
|
4
4
|
import string
|
|
5
5
|
import sys
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
9
9
|
from dissect.cstruct.types.pointer import Pointer
|
|
10
10
|
from dissect.cstruct.types.structure import Structure
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
|
|
12
15
|
COLOR_RED = "\033[1;31m"
|
|
13
16
|
COLOR_GREEN = "\033[1;32m"
|
|
14
17
|
COLOR_YELLOW = "\033[1;33m"
|
|
@@ -95,9 +98,8 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
|
|
|
95
98
|
if palette is not None:
|
|
96
99
|
values += COLOR_NORMAL
|
|
97
100
|
|
|
98
|
-
if j == 15:
|
|
99
|
-
|
|
100
|
-
values += COLOR_NORMAL
|
|
101
|
+
if j == 15 and palette is not None:
|
|
102
|
+
values += COLOR_NORMAL
|
|
101
103
|
|
|
102
104
|
values += " "
|
|
103
105
|
if j == 7:
|
|
@@ -122,12 +124,12 @@ def hexdump(
|
|
|
122
124
|
generator = _hexdump(data, palette, offset, prefix)
|
|
123
125
|
if output == "print":
|
|
124
126
|
print("\n".join(generator))
|
|
125
|
-
|
|
127
|
+
return None
|
|
128
|
+
if output == "generator":
|
|
126
129
|
return generator
|
|
127
|
-
|
|
130
|
+
if output == "string":
|
|
128
131
|
return "\n".join(list(generator))
|
|
129
|
-
|
|
130
|
-
raise ValueError(f"Invalid output argument: {output!r} (should be 'print', 'generator' or 'string').")
|
|
132
|
+
raise ValueError(f"Invalid output argument: {output!r} (should be 'print', 'generator' or 'string').")
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
def _dumpstruct(
|
|
@@ -183,6 +185,7 @@ def _dumpstruct(
|
|
|
183
185
|
print(out)
|
|
184
186
|
elif output == "string":
|
|
185
187
|
return "\n".join(["", hexdump(data, palette, offset=offset, output="string"), "", out])
|
|
188
|
+
return None
|
|
186
189
|
|
|
187
190
|
|
|
188
191
|
def dumpstruct(
|
|
@@ -207,13 +210,12 @@ def dumpstruct(
|
|
|
207
210
|
|
|
208
211
|
if isinstance(obj, Structure):
|
|
209
212
|
return _dumpstruct(obj, obj.dumps(), offset, color, output)
|
|
210
|
-
|
|
213
|
+
if issubclass(obj, Structure) and data:
|
|
211
214
|
return _dumpstruct(obj(data), data, offset, color, output)
|
|
212
|
-
|
|
213
|
-
raise ValueError("Invalid arguments")
|
|
215
|
+
raise ValueError("Invalid arguments")
|
|
214
216
|
|
|
215
217
|
|
|
216
|
-
def pack(value: int, size: int = None, endian: str = "little") -> bytes:
|
|
218
|
+
def pack(value: int, size: int | None = None, endian: str = "little") -> bytes:
|
|
217
219
|
"""Pack an integer value to a given bit size, endianness.
|
|
218
220
|
|
|
219
221
|
Arguments:
|
|
@@ -225,7 +227,7 @@ def pack(value: int, size: int = None, endian: str = "little") -> bytes:
|
|
|
225
227
|
return value.to_bytes(size, ENDIANNESS_MAP.get(endian, endian), signed=value < 0)
|
|
226
228
|
|
|
227
229
|
|
|
228
|
-
def unpack(value: bytes, size: int = None, endian: str = "little", sign: bool = False) -> int:
|
|
230
|
+
def unpack(value: bytes, size: int | None = None, endian: str = "little", sign: bool = False) -> int:
|
|
229
231
|
"""Unpack an integer value from a given bit size, endianness and sign.
|
|
230
232
|
|
|
231
233
|
Arguments:
|
|
@@ -323,7 +325,7 @@ def u64(value: bytes, endian: str = "little", sign: bool = False) -> int:
|
|
|
323
325
|
return unpack(value, 64, endian, sign)
|
|
324
326
|
|
|
325
327
|
|
|
326
|
-
def swap(value: int, size: int):
|
|
328
|
+
def swap(value: int, size: int) -> int:
|
|
327
329
|
"""Swap the endianness of an integer with a given bit size.
|
|
328
330
|
|
|
329
331
|
Arguments:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.dev2
|
|
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
|