dissect.cstruct 5.0.dev0__py3-none-any.whl → 5.0.dev7__py3-none-any.whl
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/__init__.py +2 -2
- dissect/cstruct/bitbuffer.py +3 -3
- dissect/cstruct/compiler.py +7 -7
- dissect/cstruct/cstruct.py +192 -65
- dissect/cstruct/expression.py +8 -9
- dissect/cstruct/lexer.py +17 -7
- dissect/cstruct/parser.py +31 -13
- dissect/cstruct/tools/stubgen.py +18 -22
- dissect/cstruct/types/base.py +78 -33
- dissect/cstruct/types/char.py +21 -11
- dissect/cstruct/types/enum.py +33 -13
- dissect/cstruct/types/int.py +9 -7
- dissect/cstruct/types/leb128.py +6 -4
- dissect/cstruct/types/packed.py +19 -10
- dissect/cstruct/types/pointer.py +74 -56
- dissect/cstruct/types/structure.py +130 -30
- dissect/cstruct/types/void.py +6 -4
- dissect/cstruct/types/wchar.py +26 -14
- dissect/cstruct/{utils.py → util.py} +36 -25
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev7.dist-info/RECORD +32 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/scm_file_list.json +2 -2
- dissect_cstruct-5.0.dev7.dist-info/scm_version.json +8 -0
- dissect_cstruct-5.0.dev0.dist-info/RECORD +0 -32
- dissect_cstruct-5.0.dev0.dist-info/scm_version.json +0 -8
- /dissect/cstruct/{exceptions.py → exception.py} +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/top_level.txt +0 -0
dissect/cstruct/parser.py
CHANGED
|
@@ -5,13 +5,13 @@ import re
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from dissect.cstruct import compiler
|
|
8
|
-
from dissect.cstruct.
|
|
8
|
+
from dissect.cstruct.exception import (
|
|
9
9
|
ExpressionParserError,
|
|
10
10
|
LexerError,
|
|
11
11
|
ParserError,
|
|
12
12
|
)
|
|
13
13
|
from dissect.cstruct.expression import Expression
|
|
14
|
-
from dissect.cstruct.lexer import IDENTIFIER_TYPES, TokenCursor, TokenType, tokenize
|
|
14
|
+
from dissect.cstruct.lexer import IDENTIFIER_TYPES, TokenCursor, TokenType, format_error_context, tokenize
|
|
15
15
|
from dissect.cstruct.types import BaseArray, BaseType, Field, Structure
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
@@ -58,6 +58,7 @@ class CStyleParser(Parser):
|
|
|
58
58
|
super().__init__(cs)
|
|
59
59
|
self.compiled = compiled
|
|
60
60
|
self.align = align
|
|
61
|
+
self._data = None
|
|
61
62
|
|
|
62
63
|
self._flags: list[str] = []
|
|
63
64
|
self._conditional_stack: list[tuple[Token, bool]] = []
|
|
@@ -74,6 +75,8 @@ class CStyleParser(Parser):
|
|
|
74
75
|
|
|
75
76
|
data = _join_line_continuations(data)
|
|
76
77
|
|
|
78
|
+
# Keep a reference for error messages
|
|
79
|
+
self._data = data
|
|
77
80
|
self._reset_tokens(tokenize(data))
|
|
78
81
|
self._parse()
|
|
79
82
|
|
|
@@ -88,7 +91,12 @@ class CStyleParser(Parser):
|
|
|
88
91
|
return self._tokens[self._pos].type in types
|
|
89
92
|
|
|
90
93
|
def _error(self, msg: str, *, token: Token | None = None) -> ParserError:
|
|
91
|
-
|
|
94
|
+
lineno = (token if token is not None else self._tokens[self._pos]).line
|
|
95
|
+
if self._data is None:
|
|
96
|
+
return ParserError(f"line {lineno}: {msg}")
|
|
97
|
+
|
|
98
|
+
content = format_error_context(self._data, lineno)
|
|
99
|
+
return ParserError(f"line {lineno}: {msg}\n{content}")
|
|
92
100
|
|
|
93
101
|
def _in_false_branch(self) -> bool:
|
|
94
102
|
"""Return whether we're currently in a false conditional branch."""
|
|
@@ -142,7 +150,8 @@ class CStyleParser(Parser):
|
|
|
142
150
|
|
|
143
151
|
# If it's an anonymous enum/flag, add its members to the constants for convenience
|
|
144
152
|
if not type_.__name__:
|
|
145
|
-
|
|
153
|
+
for k, v in type_.__members__.items():
|
|
154
|
+
self.cs.add_const(k, v)
|
|
146
155
|
|
|
147
156
|
self._expect(TokenType.SEMICOLON)
|
|
148
157
|
else:
|
|
@@ -177,8 +186,9 @@ class CStyleParser(Parser):
|
|
|
177
186
|
if value[-1] != quote:
|
|
178
187
|
raise self._error("unterminated bytes literal", token=token)
|
|
179
188
|
|
|
180
|
-
# Remove the leading b and surrounding quotes
|
|
181
|
-
value =
|
|
189
|
+
# Remove the leading b and surrounding quotes and flatten escape sequences
|
|
190
|
+
value = value[2:-1].encode().decode("unicode_escape")
|
|
191
|
+
value = ast.literal_eval(f"b{value!r}")
|
|
182
192
|
else:
|
|
183
193
|
try:
|
|
184
194
|
# Lazy mode, try to evaluate as a Python literal first (for simple constants)
|
|
@@ -192,17 +202,17 @@ class CStyleParser(Parser):
|
|
|
192
202
|
# If evaluation fails, just keep it as a string (e.g. for macro-like constants)
|
|
193
203
|
pass
|
|
194
204
|
|
|
195
|
-
self.cs.
|
|
205
|
+
self.cs.add_const(name_token.value, value)
|
|
196
206
|
|
|
197
207
|
def _parse_undef(self) -> None:
|
|
198
208
|
"""Parse an undef directive and remove the constant."""
|
|
199
209
|
self._expect(TokenType.PP_UNDEF)
|
|
200
210
|
|
|
201
211
|
name_token = self._expect(TokenType.IDENTIFIER)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
raise self._error(f"constant {name_token.value!r} not defined", token=name_token)
|
|
212
|
+
try:
|
|
213
|
+
self.cs.del_const(name_token.value)
|
|
214
|
+
except KeyError:
|
|
215
|
+
raise self._error(f"constant {name_token.value!r} not defined", token=name_token) from None
|
|
206
216
|
|
|
207
217
|
def _parse_include(self) -> None:
|
|
208
218
|
"""Parse an include directive and add the included file to the includes list."""
|
|
@@ -385,7 +395,15 @@ class CStyleParser(Parser):
|
|
|
385
395
|
continue
|
|
386
396
|
self._assert_not_eof()
|
|
387
397
|
|
|
388
|
-
|
|
398
|
+
# For historical reasons, we allow enum/flag member names to start with a digit
|
|
399
|
+
# E.g. `32BIT`
|
|
400
|
+
member_name = ""
|
|
401
|
+
if token := self._match(TokenType.NUMBER):
|
|
402
|
+
member_name += token.value
|
|
403
|
+
if token := self._match(TokenType.IDENTIFIER):
|
|
404
|
+
member_name += token.value
|
|
405
|
+
else:
|
|
406
|
+
member_name = self._expect(TokenType.IDENTIFIER).value
|
|
389
407
|
|
|
390
408
|
if self._match(TokenType.EQUALS):
|
|
391
409
|
expression = self._collect_until(TokenType.COMMA, TokenType.RBRACE)
|
|
@@ -527,7 +545,7 @@ class CStyleParser(Parser):
|
|
|
527
545
|
):
|
|
528
546
|
# This identifier is followed by a field delimiter, it should be the field name,
|
|
529
547
|
# UNLESS the current parts don't form a valid type yet.
|
|
530
|
-
if " ".join(parts) in self.cs.
|
|
548
|
+
if " ".join(parts) in self.cs.types:
|
|
531
549
|
break
|
|
532
550
|
|
|
533
551
|
# Current parts don't resolve, consume and hope this completes the type name
|
dissect/cstruct/tools/stubgen.py
CHANGED
|
@@ -79,31 +79,29 @@ def generate_cstruct_stub(cs: cstruct, module_prefix: str = "", cls_name: str =
|
|
|
79
79
|
|
|
80
80
|
defined_names = set()
|
|
81
81
|
|
|
82
|
-
# Then
|
|
83
|
-
for name,
|
|
84
|
-
if name in empty_cs.
|
|
82
|
+
# Then types
|
|
83
|
+
for name, type_ in cs.types.items():
|
|
84
|
+
if name in empty_cs.types:
|
|
85
85
|
continue
|
|
86
86
|
|
|
87
|
-
if
|
|
88
|
-
stub = f"{name}: TypeAlias = {cs_prefix}{
|
|
89
|
-
elif
|
|
87
|
+
if type_.__name__ in empty_cs.types:
|
|
88
|
+
stub = f"{name}: TypeAlias = {cs_prefix}{type_.__name__}"
|
|
89
|
+
elif type_.__name__ in defined_names:
|
|
90
90
|
# Create an alias to the type if we have already seen it before.
|
|
91
|
-
stub = f"{name}: TypeAlias = {
|
|
92
|
-
elif issubclass(
|
|
93
|
-
stub = generate_enum_stub(
|
|
94
|
-
elif issubclass(
|
|
95
|
-
typehint = generate_typehint(
|
|
91
|
+
stub = f"{name}: TypeAlias = {type_.__name__}"
|
|
92
|
+
elif issubclass(type_, (types.Enum, types.Flag)):
|
|
93
|
+
stub = generate_enum_stub(type_, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
94
|
+
elif issubclass(type_, types.Pointer):
|
|
95
|
+
typehint = generate_typehint(type_, prefix=cs_prefix, module_prefix=module_prefix)
|
|
96
96
|
stub = f"{name}: TypeAlias = {typehint}"
|
|
97
|
-
elif issubclass(
|
|
98
|
-
stub = generate_structure_stub(
|
|
99
|
-
elif issubclass(
|
|
100
|
-
stub = generate_generic_stub(
|
|
101
|
-
elif isinstance(typedef, str):
|
|
102
|
-
stub = f"{name}: TypeAlias = {typedef}"
|
|
97
|
+
elif issubclass(type_, types.Structure):
|
|
98
|
+
stub = generate_structure_stub(type_, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
99
|
+
elif issubclass(type_, types.BaseType):
|
|
100
|
+
stub = generate_generic_stub(type_, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
103
101
|
else:
|
|
104
|
-
raise TypeError(f"Unknown
|
|
102
|
+
raise TypeError(f"Unknown type: {type_}")
|
|
105
103
|
|
|
106
|
-
defined_names.add(
|
|
104
|
+
defined_names.add(type_.__name__)
|
|
107
105
|
|
|
108
106
|
body.append(textwrap.indent(stub, prefix=indent))
|
|
109
107
|
|
|
@@ -158,12 +156,10 @@ def generate_structure_stub(
|
|
|
158
156
|
module_prefix: str = "",
|
|
159
157
|
) -> str:
|
|
160
158
|
result = [f"class {name_prefix}{structure.__name__}({module_prefix}{structure.__base__.__name__}):"]
|
|
161
|
-
|
|
162
159
|
indent = " " * 4
|
|
163
160
|
|
|
164
161
|
args = ["self"]
|
|
165
162
|
for field_name, field in structure.fields.items():
|
|
166
|
-
type_name = field.type.__name__
|
|
167
163
|
inlined = False
|
|
168
164
|
|
|
169
165
|
# If it's a structure and not globally defined, add an inline stub for it
|
|
@@ -171,7 +167,7 @@ def generate_structure_stub(
|
|
|
171
167
|
while issubclass(nested_type, types.BaseArray):
|
|
172
168
|
nested_type = nested_type.type
|
|
173
169
|
|
|
174
|
-
if issubclass(nested_type, types.Structure) and
|
|
170
|
+
if issubclass(nested_type, types.Structure) and nested_type.__name__ not in structure.cs.types:
|
|
175
171
|
inlined = True
|
|
176
172
|
inline_stub = generate_structure_stub(nested_type, cs_prefix=cs_prefix, module_prefix=module_prefix)
|
|
177
173
|
|
dissect/cstruct/types/base.py
CHANGED
|
@@ -4,7 +4,7 @@ import functools
|
|
|
4
4
|
from io import BytesIO
|
|
5
5
|
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar
|
|
6
6
|
|
|
7
|
-
from dissect.cstruct.
|
|
7
|
+
from dissect.cstruct.exception import ArraySizeError
|
|
8
8
|
from dissect.cstruct.expression import Expression
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
from typing_extensions import Self
|
|
14
14
|
|
|
15
|
-
from dissect.cstruct.cstruct import cstruct
|
|
15
|
+
from dissect.cstruct.cstruct import AllowedEndianness, Endianness, cstruct
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
EOF = -0xE0F # Negative counts are illegal anyway, so abuse that for our EOF sentinel
|
|
@@ -41,14 +41,15 @@ class MetaType(type):
|
|
|
41
41
|
stream = args[0]
|
|
42
42
|
|
|
43
43
|
if _is_readable_type(stream):
|
|
44
|
-
|
|
44
|
+
endian = normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.cs.endian
|
|
45
|
+
return cls._read(stream, endian=endian)
|
|
45
46
|
|
|
46
47
|
if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.size:
|
|
47
48
|
# Shortcut for char/bytes type
|
|
48
49
|
return type.__call__(cls, *args, **kwargs)
|
|
49
50
|
|
|
50
51
|
if _is_buffer_type(stream):
|
|
51
|
-
return cls.reads(stream)
|
|
52
|
+
return cls.reads(stream, endian=kwargs.get("endian"))
|
|
52
53
|
|
|
53
54
|
return type.__call__(cls, *args, **kwargs)
|
|
54
55
|
|
|
@@ -71,60 +72,73 @@ class MetaType(type):
|
|
|
71
72
|
"""Return the default value of this type."""
|
|
72
73
|
return cls()
|
|
73
74
|
|
|
74
|
-
def reads(cls, data: bytes | memoryview | bytearray) -> Self: # type: ignore
|
|
75
|
+
def reads(cls, data: bytes | memoryview | bytearray, *, endian: AllowedEndianness | None = None) -> Self: # type: ignore
|
|
75
76
|
"""Parse the given data from a bytes-like object.
|
|
76
77
|
|
|
77
78
|
Args:
|
|
78
79
|
data: Bytes-like object to parse.
|
|
80
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
81
|
+
When omitted, the cstruct instance's current endianness is used.
|
|
79
82
|
|
|
80
83
|
Returns:
|
|
81
84
|
The parsed value of this type.
|
|
82
85
|
"""
|
|
83
|
-
|
|
86
|
+
endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
|
|
87
|
+
return cls._read(BytesIO(data), endian=endian)
|
|
84
88
|
|
|
85
|
-
def read(cls, obj: BinaryIO | bytes | memoryview | bytearray) -> Self: # type: ignore
|
|
89
|
+
def read(cls, obj: BinaryIO | bytes | memoryview | bytearray, *, endian: AllowedEndianness | None = None) -> Self: # type: ignore
|
|
86
90
|
"""Parse the given data.
|
|
87
91
|
|
|
88
92
|
Args:
|
|
89
93
|
obj: Data to parse. Can be a bytes-like object or a file-like object.
|
|
94
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
95
|
+
When omitted, the cstruct instance's current endianness is used.
|
|
90
96
|
|
|
91
97
|
Returns:
|
|
92
98
|
The parsed value of this type.
|
|
93
99
|
"""
|
|
94
100
|
if _is_buffer_type(obj):
|
|
95
|
-
return cls.reads(obj)
|
|
101
|
+
return cls.reads(obj, endian=endian)
|
|
96
102
|
|
|
97
103
|
if not _is_readable_type(obj):
|
|
98
104
|
raise TypeError("Invalid object type")
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
|
|
107
|
+
return cls._read(obj, endian=endian)
|
|
101
108
|
|
|
102
|
-
def write(cls, stream: BinaryIO, value: Any) -> int:
|
|
109
|
+
def write(cls, stream: BinaryIO, value: Any, *, endian: AllowedEndianness | None = None) -> int:
|
|
103
110
|
"""Write a value to a writable file-like object.
|
|
104
111
|
|
|
105
112
|
Args:
|
|
106
113
|
stream: File-like objects that supports writing.
|
|
107
114
|
value: Value to write.
|
|
115
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
116
|
+
When omitted, the cstruct instance's current endianness is used.
|
|
108
117
|
|
|
109
118
|
Returns:
|
|
110
119
|
The amount of bytes written.
|
|
111
120
|
"""
|
|
112
|
-
|
|
121
|
+
endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
|
|
122
|
+
return cls._write(stream, value, endian=endian)
|
|
113
123
|
|
|
114
|
-
def dumps(cls, value: Any) -> bytes:
|
|
124
|
+
def dumps(cls, value: Any, *, endian: AllowedEndianness | None = None) -> bytes:
|
|
115
125
|
"""Dump a value to a byte string.
|
|
116
126
|
|
|
117
127
|
Args:
|
|
118
128
|
value: Value to dump.
|
|
129
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
130
|
+
When omitted, the cstruct instance's current endianness is used.
|
|
119
131
|
|
|
120
132
|
Returns:
|
|
121
133
|
The raw bytes of this type.
|
|
122
134
|
"""
|
|
135
|
+
endian = normalize_endianness(endian) if endian is not None else cls.cs.endian
|
|
136
|
+
|
|
123
137
|
out = BytesIO()
|
|
124
|
-
cls._write(out, value)
|
|
138
|
+
cls._write(out, value, endian=endian)
|
|
125
139
|
return out.getvalue()
|
|
126
140
|
|
|
127
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self: # type: ignore
|
|
141
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: # type: ignore
|
|
128
142
|
"""Internal function for reading value.
|
|
129
143
|
|
|
130
144
|
Must be implemented per type.
|
|
@@ -132,10 +146,13 @@ class MetaType(type):
|
|
|
132
146
|
Args:
|
|
133
147
|
stream: The stream to read from.
|
|
134
148
|
context: Optional reading context.
|
|
149
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
135
150
|
"""
|
|
136
151
|
raise NotImplementedError
|
|
137
152
|
|
|
138
|
-
def _read_array(
|
|
153
|
+
def _read_array(
|
|
154
|
+
cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
155
|
+
) -> list[Self]: # type: ignore
|
|
139
156
|
"""Internal function for reading array values.
|
|
140
157
|
|
|
141
158
|
Allows type implementations to do optimized reading for their type.
|
|
@@ -144,16 +161,17 @@ class MetaType(type):
|
|
|
144
161
|
stream: The stream to read from.
|
|
145
162
|
count: The amount of values to read.
|
|
146
163
|
context: Optional reading context.
|
|
164
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
147
165
|
"""
|
|
148
166
|
if count == EOF:
|
|
149
167
|
result = []
|
|
150
168
|
while not _is_eof(stream):
|
|
151
|
-
result.append(cls._read(stream, context))
|
|
169
|
+
result.append(cls._read(stream, context=context, endian=endian))
|
|
152
170
|
return result
|
|
153
171
|
|
|
154
|
-
return [cls._read(stream, context) for _ in range(count)]
|
|
172
|
+
return [cls._read(stream, context=context, endian=endian) for _ in range(count)]
|
|
155
173
|
|
|
156
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]:
|
|
174
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]:
|
|
157
175
|
"""Internal function for reading null-terminated data.
|
|
158
176
|
|
|
159
177
|
"Null" is type specific, so must be implemented per type.
|
|
@@ -161,13 +179,14 @@ class MetaType(type):
|
|
|
161
179
|
Args:
|
|
162
180
|
stream: The stream to read from.
|
|
163
181
|
context: Optional reading context.
|
|
182
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
164
183
|
"""
|
|
165
184
|
raise NotImplementedError
|
|
166
185
|
|
|
167
|
-
def _write(cls, stream: BinaryIO, data: Any) -> int:
|
|
186
|
+
def _write(cls, stream: BinaryIO, data: Any, *, endian: Endianness) -> int:
|
|
168
187
|
raise NotImplementedError
|
|
169
188
|
|
|
170
|
-
def _write_array(cls, stream: BinaryIO, array: list[Self]) -> int: # type: ignore
|
|
189
|
+
def _write_array(cls, stream: BinaryIO, array: list[Self], *, endian: Endianness) -> int: # type: ignore
|
|
171
190
|
"""Internal function for writing arrays.
|
|
172
191
|
|
|
173
192
|
Allows type implementations to do optimized writing for their type.
|
|
@@ -175,10 +194,11 @@ class MetaType(type):
|
|
|
175
194
|
Args:
|
|
176
195
|
stream: The stream to read from.
|
|
177
196
|
array: The array to write.
|
|
197
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
178
198
|
"""
|
|
179
|
-
return sum(cls._write(stream, entry) for entry in array)
|
|
199
|
+
return sum(cls._write(stream, entry, endian=endian) for entry in array)
|
|
180
200
|
|
|
181
|
-
def _write_0(cls, stream: BinaryIO, array: list[Self]) -> int: # type: ignore
|
|
201
|
+
def _write_0(cls, stream: BinaryIO, array: list[Self], *, endian: Endianness) -> int: # type: ignore
|
|
182
202
|
"""Internal function for writing null-terminated arrays.
|
|
183
203
|
|
|
184
204
|
Allows type implementations to do optimized writing for their type.
|
|
@@ -186,8 +206,9 @@ class MetaType(type):
|
|
|
186
206
|
Args:
|
|
187
207
|
stream: The stream to read from.
|
|
188
208
|
array: The array to write.
|
|
209
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
189
210
|
"""
|
|
190
|
-
return cls._write_array(stream, [*array, cls.__default__()])
|
|
211
|
+
return cls._write_array(stream, [*array, cls.__default__()], endian=endian)
|
|
191
212
|
|
|
192
213
|
|
|
193
214
|
class _overload:
|
|
@@ -250,9 +271,9 @@ class BaseArray(BaseType):
|
|
|
250
271
|
)
|
|
251
272
|
|
|
252
273
|
@classmethod
|
|
253
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[BaseType]:
|
|
274
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[BaseType]:
|
|
254
275
|
if cls.null_terminated:
|
|
255
|
-
return cls.type._read_0(stream, context)
|
|
276
|
+
return cls.type._read_0(stream, context=context, endian=endian)
|
|
256
277
|
|
|
257
278
|
if isinstance(cls.num_entries, int):
|
|
258
279
|
num = max(0, cls.num_entries)
|
|
@@ -266,23 +287,25 @@ class BaseArray(BaseType):
|
|
|
266
287
|
raise
|
|
267
288
|
num = EOF
|
|
268
289
|
|
|
269
|
-
return cls.type._read_array(stream, num, context)
|
|
290
|
+
return cls.type._read_array(stream, num, context=context, endian=endian)
|
|
270
291
|
|
|
271
292
|
@classmethod
|
|
272
|
-
def _write(cls, stream: BinaryIO, data: list[Any]) -> int:
|
|
293
|
+
def _write(cls, stream: BinaryIO, data: list[Any], *, endian: Endianness) -> int:
|
|
273
294
|
if cls.null_terminated:
|
|
274
|
-
return cls.type._write_0(stream, data)
|
|
295
|
+
return cls.type._write_0(stream, data, endian=endian)
|
|
275
296
|
|
|
276
|
-
if not cls.dynamic and cls.num_entries
|
|
277
|
-
|
|
297
|
+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
|
|
298
|
+
if remaining < 0:
|
|
299
|
+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
|
|
300
|
+
data = data + [cls.type.__default__()] * remaining
|
|
278
301
|
|
|
279
|
-
return cls.type._write_array(stream, data)
|
|
302
|
+
return cls.type._write_array(stream, data, endian=endian)
|
|
280
303
|
|
|
281
304
|
|
|
282
305
|
class Array(list[T], BaseArray):
|
|
283
306
|
@classmethod
|
|
284
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[T]:
|
|
285
|
-
return cls(super()._read(stream, context))
|
|
307
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[T]:
|
|
308
|
+
return cls(super()._read(stream, context=context, endian=endian))
|
|
286
309
|
|
|
287
310
|
|
|
288
311
|
def _is_readable_type(value: object) -> bool:
|
|
@@ -305,5 +328,27 @@ def _is_eof(stream: BinaryIO) -> bool:
|
|
|
305
328
|
return False
|
|
306
329
|
|
|
307
330
|
|
|
331
|
+
ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
|
|
332
|
+
"<": "<",
|
|
333
|
+
">": ">",
|
|
334
|
+
"!": "!",
|
|
335
|
+
"@": "@",
|
|
336
|
+
"=": "=",
|
|
337
|
+
"network": "!",
|
|
338
|
+
"little": "<",
|
|
339
|
+
"big": ">",
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def normalize_endianness(endian: AllowedEndianness) -> Endianness:
|
|
344
|
+
"""Normalize an endianness string to one of the standard format characters."""
|
|
345
|
+
try:
|
|
346
|
+
return ENDIANNESS_MAP[endian]
|
|
347
|
+
except KeyError:
|
|
348
|
+
raise ValueError(
|
|
349
|
+
f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
|
|
350
|
+
) from None
|
|
351
|
+
|
|
352
|
+
|
|
308
353
|
# As mentioned in the BaseType class, we correctly set the type here
|
|
309
354
|
MetaType.ArrayType = Array
|
dissect/cstruct/types/char.py
CHANGED
|
@@ -2,11 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
4
4
|
|
|
5
|
+
from dissect.cstruct.exception import ArraySizeError
|
|
5
6
|
from dissect.cstruct.types.base import EOF, BaseArray, BaseType
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from typing_extensions import Self
|
|
9
10
|
|
|
11
|
+
from dissect.cstruct.cstruct import Endianness
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
class CharArray(bytes, BaseArray):
|
|
12
15
|
"""Character array type for reading and writing byte strings."""
|
|
@@ -16,19 +19,24 @@ class CharArray(bytes, BaseArray):
|
|
|
16
19
|
return type.__call__(cls, b"\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
|
|
17
20
|
|
|
18
21
|
@classmethod
|
|
19
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
20
|
-
return type.__call__(cls, super()._read(stream, context))
|
|
22
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
23
|
+
return type.__call__(cls, super()._read(stream, context=context, endian=endian))
|
|
21
24
|
|
|
22
25
|
@classmethod
|
|
23
|
-
def _write(cls, stream: BinaryIO, data: bytes) -> int:
|
|
24
|
-
if isinstance(data, list)
|
|
26
|
+
def _write(cls, stream: BinaryIO, data: bytes | str | list[int], *, endian: Endianness) -> int:
|
|
27
|
+
if isinstance(data, list):
|
|
25
28
|
data = bytes(data)
|
|
26
|
-
|
|
27
29
|
elif isinstance(data, str):
|
|
28
30
|
data = data.encode("latin-1")
|
|
29
31
|
|
|
30
32
|
if cls.null_terminated:
|
|
31
|
-
|
|
33
|
+
data += b"\x00"
|
|
34
|
+
|
|
35
|
+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
|
|
36
|
+
if remaining < 0:
|
|
37
|
+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
|
|
38
|
+
data += b"\x00" * remaining
|
|
39
|
+
|
|
32
40
|
return stream.write(data)
|
|
33
41
|
|
|
34
42
|
|
|
@@ -42,11 +50,13 @@ class Char(bytes, BaseType):
|
|
|
42
50
|
return type.__call__(cls, b"\x00")
|
|
43
51
|
|
|
44
52
|
@classmethod
|
|
45
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
46
|
-
return cls._read_array(stream, 1, context)
|
|
53
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
54
|
+
return cls._read_array(stream, 1, context=context, endian=endian)
|
|
47
55
|
|
|
48
56
|
@classmethod
|
|
49
|
-
def _read_array(
|
|
57
|
+
def _read_array(
|
|
58
|
+
cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
59
|
+
) -> Self:
|
|
50
60
|
if count == 0:
|
|
51
61
|
return type.__call__(cls, b"")
|
|
52
62
|
|
|
@@ -57,7 +67,7 @@ class Char(bytes, BaseType):
|
|
|
57
67
|
return type.__call__(cls, data)
|
|
58
68
|
|
|
59
69
|
@classmethod
|
|
60
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
70
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
61
71
|
buf = []
|
|
62
72
|
while True:
|
|
63
73
|
byte = stream.read(1)
|
|
@@ -72,7 +82,7 @@ class Char(bytes, BaseType):
|
|
|
72
82
|
return type.__call__(cls, b"".join(buf))
|
|
73
83
|
|
|
74
84
|
@classmethod
|
|
75
|
-
def _write(cls, stream: BinaryIO, data: bytes | int | str) -> int:
|
|
85
|
+
def _write(cls, stream: BinaryIO, data: bytes | int | str, *, endian: Endianness) -> int:
|
|
76
86
|
if isinstance(data, int):
|
|
77
87
|
data = chr(data)
|
|
78
88
|
|
dissect/cstruct/types/enum.py
CHANGED
|
@@ -10,7 +10,7 @@ from dissect.cstruct.types.base import BaseType, MetaType
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from typing_extensions import Self
|
|
12
12
|
|
|
13
|
-
from dissect.cstruct.cstruct import cstruct
|
|
13
|
+
from dissect.cstruct.cstruct import Endianness, cstruct
|
|
14
14
|
from dissect.cstruct.types.base import Array
|
|
15
15
|
|
|
16
16
|
|
|
@@ -84,25 +84,45 @@ class EnumMetaType(EnumMeta, MetaType):
|
|
|
84
84
|
return True
|
|
85
85
|
return value in cls._value2member_map_
|
|
86
86
|
|
|
87
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
88
|
-
return cls(cls.type._read(stream, context))
|
|
87
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
88
|
+
return cls(cls.type._read(stream, context=context, endian=endian))
|
|
89
89
|
|
|
90
|
-
def _read_array(
|
|
91
|
-
|
|
90
|
+
def _read_array(
|
|
91
|
+
cls, stream: BinaryIO, count: int, *, context: dict[str, Any] | None = None, endian: Endianness
|
|
92
|
+
) -> list[Self]:
|
|
93
|
+
return list(map(cls, cls.type._read_array(stream, count, context=context, endian=endian)))
|
|
92
94
|
|
|
93
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> list[Self]:
|
|
94
|
-
return list(map(cls, cls.type._read_0(stream, context)))
|
|
95
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> list[Self]:
|
|
96
|
+
return list(map(cls, cls.type._read_0(stream, context=context, endian=endian)))
|
|
95
97
|
|
|
96
|
-
def _write(cls, stream: BinaryIO, data: Enum) -> int:
|
|
97
|
-
return cls.type._write(stream, data.value)
|
|
98
|
+
def _write(cls, stream: BinaryIO, data: Enum, *, endian: Endianness) -> int:
|
|
99
|
+
return cls.type._write(stream, data.value, endian=endian)
|
|
98
100
|
|
|
99
|
-
def _write_array(cls, stream: BinaryIO, array: list[BaseType | int]) -> int:
|
|
101
|
+
def _write_array(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
|
|
100
102
|
data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
|
|
101
|
-
return cls.type._write_array(stream, data)
|
|
103
|
+
return cls.type._write_array(stream, data, endian=endian)
|
|
102
104
|
|
|
103
|
-
def _write_0(cls, stream: BinaryIO, array: list[BaseType | int]) -> int:
|
|
105
|
+
def _write_0(cls, stream: BinaryIO, array: list[BaseType | int], *, endian: Endianness) -> int:
|
|
104
106
|
data = [entry.value if isinstance(entry, _Enum) else entry for entry in array]
|
|
105
|
-
return cls._write_array(stream, [*data, cls.type.__default__()])
|
|
107
|
+
return cls._write_array(stream, [*data, cls.type.__default__()], endian=endian)
|
|
108
|
+
|
|
109
|
+
def cdef(cls) -> str:
|
|
110
|
+
"""Render this enum (or flag) back to its C-style definition.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
The C-style definition as a string.
|
|
114
|
+
"""
|
|
115
|
+
keyword = "flag" if issubclass(cls, IntFlag) else "enum"
|
|
116
|
+
underlying = cls.type.__name__
|
|
117
|
+
title = f"{keyword} {cls.__name__} : {underlying}" if cls.__name__ else f"{keyword} : {underlying}"
|
|
118
|
+
|
|
119
|
+
lines = [f"{title} {{"]
|
|
120
|
+
for member_name, member in cls.__members__.items():
|
|
121
|
+
value = hex(member.value) if keyword == "flag" else member.value
|
|
122
|
+
lines.append(f" {member_name} = {value},")
|
|
123
|
+
lines.append("};")
|
|
124
|
+
|
|
125
|
+
return "\n".join(lines)
|
|
106
126
|
|
|
107
127
|
|
|
108
128
|
def _fix_alias_members(cls: type[Enum]) -> None:
|
dissect/cstruct/types/int.py
CHANGED
|
@@ -3,11 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, BinaryIO
|
|
4
4
|
|
|
5
5
|
from dissect.cstruct.types.base import BaseType
|
|
6
|
-
from dissect.cstruct.
|
|
6
|
+
from dissect.cstruct.util import ENDIANNESS_TO_BYTEORDER_MAP
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from typing_extensions import Self
|
|
10
10
|
|
|
11
|
+
from dissect.cstruct.cstruct import Endianness
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
class Int(int, BaseType):
|
|
13
15
|
"""Integer type that can span an arbitrary amount of bytes."""
|
|
@@ -15,20 +17,20 @@ class Int(int, BaseType):
|
|
|
15
17
|
signed: bool
|
|
16
18
|
|
|
17
19
|
@classmethod
|
|
18
|
-
def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
20
|
+
def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
19
21
|
data = stream.read(cls.size)
|
|
20
22
|
|
|
21
23
|
if len(data) != cls.size:
|
|
22
24
|
raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}")
|
|
23
25
|
|
|
24
|
-
return cls.from_bytes(data,
|
|
26
|
+
return cls.from_bytes(data, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed)
|
|
25
27
|
|
|
26
28
|
@classmethod
|
|
27
|
-
def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
|
|
29
|
+
def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self:
|
|
28
30
|
result = []
|
|
29
31
|
|
|
30
32
|
while True:
|
|
31
|
-
if (value := cls._read(stream, context)) == 0:
|
|
33
|
+
if (value := cls._read(stream, context=context, endian=endian)) == 0:
|
|
32
34
|
break
|
|
33
35
|
|
|
34
36
|
result.append(value)
|
|
@@ -36,5 +38,5 @@ class Int(int, BaseType):
|
|
|
36
38
|
return result
|
|
37
39
|
|
|
38
40
|
@classmethod
|
|
39
|
-
def _write(cls, stream: BinaryIO, data: int) -> int:
|
|
40
|
-
return stream.write(data.to_bytes(cls.size,
|
|
41
|
+
def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int:
|
|
42
|
+
return stream.write(data.to_bytes(cls.size, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed))
|