dissect.cstruct 3.10.dev2__tar.gz → 3.11__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-3.10.dev2/dissect.cstruct.egg-info → dissect.cstruct-3.11}/PKG-INFO +1 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/cstruct.py +7 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/exceptions.py +8 -0
- dissect.cstruct-3.11/dissect/cstruct/expression.py +301 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/utils.py +4 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11/dissect.cstruct.egg-info}/PKG-INFO +1 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/pyproject.toml +0 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_expression.py +36 -3
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_struct.py +1 -1
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_util.py +36 -0
- dissect.cstruct-3.10.dev2/dissect/cstruct/expression.py +0 -84
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/COPYRIGHT +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/LICENSE +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/MANIFEST.in +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/README.md +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/__init__.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/bitbuffer.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/compiler.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/parser.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/__init__.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/base.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/bytesinteger.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/chartype.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/enum.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/flag.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/instance.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/packedtype.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/pointer.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/structure.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/voidtype.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect/cstruct/types/wchartype.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/top_level.txt +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/disk.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/mirai.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/pe.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/examples/secdesc.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/setup.cfg +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/__init__.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/conftest.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/data/testdef.txt +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/Makefile +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/conf.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/docs/index.rst +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_align.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_basic.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bitbuffer.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bitfield.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_bytesinteger.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_ctypes_type.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_enum.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_flag.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_packedtype.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_parser.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_pointer.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_union.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/test_utils.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tests/utils.py +0 -0
- {dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.11
|
|
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
|
|
@@ -139,6 +139,10 @@ class cstruct:
|
|
|
139
139
|
"u4": "uint32",
|
|
140
140
|
"u8": "uint64",
|
|
141
141
|
"u16": "uint128",
|
|
142
|
+
"__u8": "uint8",
|
|
143
|
+
"__u16": "uint16",
|
|
144
|
+
"__u32": "uint32",
|
|
145
|
+
"__u64": "uint64",
|
|
142
146
|
"uchar": "uint8",
|
|
143
147
|
"ushort": "unsigned short",
|
|
144
148
|
"uint": "unsigned int",
|
|
@@ -184,7 +188,7 @@ class cstruct:
|
|
|
184
188
|
|
|
185
189
|
self.typedefs[name] = type_
|
|
186
190
|
|
|
187
|
-
def load(self, definition: str, deftype: int = None, **kwargs) ->
|
|
191
|
+
def load(self, definition: str, deftype: int = None, **kwargs) -> "cstruct":
|
|
188
192
|
"""Parse structures from the given definitions using the given definition type.
|
|
189
193
|
|
|
190
194
|
Definitions can be parsed using different parsers. Currently, there's
|
|
@@ -208,6 +212,8 @@ class cstruct:
|
|
|
208
212
|
elif deftype == cstruct.DEF_LEGACY:
|
|
209
213
|
CStyleParser(self, **kwargs).parse(definition)
|
|
210
214
|
|
|
215
|
+
return self
|
|
216
|
+
|
|
211
217
|
def loadfile(self, path: str, deftype: int = None, **kwargs) -> None:
|
|
212
218
|
"""Load structure definitions from a file.
|
|
213
219
|
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import string
|
|
4
|
+
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
5
|
+
|
|
6
|
+
from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from dissect.cstruct import cstruct
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
HEXBIN_SUFFIX = {"x", "X", "b", "B"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExpressionTokenizer:
|
|
16
|
+
def __init__(self, expression: str):
|
|
17
|
+
self.expression = expression
|
|
18
|
+
self.pos = 0
|
|
19
|
+
self.tokens = []
|
|
20
|
+
|
|
21
|
+
def equal(self, token: str, expected: Union[str, str[str]]) -> bool:
|
|
22
|
+
if isinstance(expected, set):
|
|
23
|
+
return token in expected
|
|
24
|
+
else:
|
|
25
|
+
return token == expected
|
|
26
|
+
|
|
27
|
+
def alnum(self, token: str) -> bool:
|
|
28
|
+
return token.isalnum()
|
|
29
|
+
|
|
30
|
+
def alpha(self, token: str) -> bool:
|
|
31
|
+
return token.isalpha()
|
|
32
|
+
|
|
33
|
+
def digit(self, token: str) -> bool:
|
|
34
|
+
return token.isdigit()
|
|
35
|
+
|
|
36
|
+
def hexdigit(self, token: str) -> bool:
|
|
37
|
+
return token in string.hexdigits
|
|
38
|
+
|
|
39
|
+
def operator(self, token: str) -> bool:
|
|
40
|
+
return token in {"*", "/", "+", "-", "%", "&", "^", "|", "(", ")", "~"}
|
|
41
|
+
|
|
42
|
+
def match(
|
|
43
|
+
self,
|
|
44
|
+
func: Optional[Callable[[str], bool]] = None,
|
|
45
|
+
expected: Optional[str] = None,
|
|
46
|
+
consume: bool = True,
|
|
47
|
+
append: bool = True,
|
|
48
|
+
) -> bool:
|
|
49
|
+
if self.eol():
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
token = self.get_token()
|
|
53
|
+
|
|
54
|
+
if expected and self.equal(token, expected):
|
|
55
|
+
if append:
|
|
56
|
+
self.tokens.append(token)
|
|
57
|
+
if consume:
|
|
58
|
+
self.consume()
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
if func and func(token):
|
|
62
|
+
if append:
|
|
63
|
+
self.tokens.append(token)
|
|
64
|
+
if consume:
|
|
65
|
+
self.consume()
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def consume(self) -> None:
|
|
71
|
+
self.pos += 1
|
|
72
|
+
|
|
73
|
+
def eol(self) -> bool:
|
|
74
|
+
return self.pos >= len(self.expression)
|
|
75
|
+
|
|
76
|
+
def get_token(self) -> str:
|
|
77
|
+
if self.eol():
|
|
78
|
+
raise ExpressionTokenizerError(f"Out of bounds index: {self.pos}, length: {len(self.expression)}")
|
|
79
|
+
return self.expression[self.pos]
|
|
80
|
+
|
|
81
|
+
def tokenize(self) -> list[str]:
|
|
82
|
+
token = ""
|
|
83
|
+
|
|
84
|
+
# Loop over expression runs in linear time
|
|
85
|
+
while not self.eol():
|
|
86
|
+
# If token is a single character operand add it to tokens
|
|
87
|
+
if self.match(self.operator):
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# If token is a single digit, keep looping over expression and build the number
|
|
91
|
+
elif self.match(self.digit, consume=False, append=False):
|
|
92
|
+
token += self.get_token()
|
|
93
|
+
self.consume()
|
|
94
|
+
|
|
95
|
+
# Support for binary and hexadecimal notation
|
|
96
|
+
if self.match(expected=HEXBIN_SUFFIX, consume=False, append=False):
|
|
97
|
+
token += self.get_token()
|
|
98
|
+
self.consume()
|
|
99
|
+
|
|
100
|
+
while self.match(self.hexdigit, consume=False, append=False):
|
|
101
|
+
token += self.get_token()
|
|
102
|
+
self.consume()
|
|
103
|
+
if self.eol():
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
# Checks for suffixes in numbers
|
|
107
|
+
if self.match(expected={"u", "U"}, consume=False, append=False):
|
|
108
|
+
self.consume()
|
|
109
|
+
self.match(expected={"l", "L"}, append=False)
|
|
110
|
+
self.match(expected={"l", "L"}, append=False)
|
|
111
|
+
|
|
112
|
+
elif self.match(expected={"l", "L"}, append=False):
|
|
113
|
+
self.match(expected={"l", "L"}, append=False)
|
|
114
|
+
self.match(expected={"u", "U"}, append=False)
|
|
115
|
+
else:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
# Number cannot end on x or b in the case of binary or hexadecimal notation
|
|
119
|
+
if len(token) == 2 and token[-1] in HEXBIN_SUFFIX:
|
|
120
|
+
raise ExpressionTokenizerError("Invalid binary or hex notation")
|
|
121
|
+
|
|
122
|
+
if len(token) > 1 and token[0] == "0" and token[1] not in HEXBIN_SUFFIX:
|
|
123
|
+
token = token[:1] + "o" + token[1:]
|
|
124
|
+
self.tokens.append(token)
|
|
125
|
+
token = ""
|
|
126
|
+
|
|
127
|
+
# If token is alpha or underscore we need to build the identifier
|
|
128
|
+
elif self.match(self.alpha, consume=False, append=False) or self.match(
|
|
129
|
+
expected="_", consume=False, append=False
|
|
130
|
+
):
|
|
131
|
+
while self.match(self.alnum, consume=False, append=False) or self.match(
|
|
132
|
+
expected="_", consume=False, append=False
|
|
133
|
+
):
|
|
134
|
+
token += self.get_token()
|
|
135
|
+
self.consume()
|
|
136
|
+
if self.eol():
|
|
137
|
+
break
|
|
138
|
+
self.tokens.append(token)
|
|
139
|
+
token = ""
|
|
140
|
+
# If token is length 2 operand make sure next character is part of length 2 operand append to tokens
|
|
141
|
+
elif self.match(expected=">", append=False) and self.match(expected=">", append=False):
|
|
142
|
+
self.tokens.append(">>")
|
|
143
|
+
elif self.match(expected="<", append=False) and self.match(expected="<", append=False):
|
|
144
|
+
self.tokens.append("<<")
|
|
145
|
+
elif self.match(expected=" ", append=False):
|
|
146
|
+
continue
|
|
147
|
+
else:
|
|
148
|
+
raise ExpressionTokenizerError(
|
|
149
|
+
f"Tokenizer does not recognize following token '{self.expression[self.pos]}'"
|
|
150
|
+
)
|
|
151
|
+
return self.tokens
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Expression:
|
|
155
|
+
"""Expression parser for calculations in definitions."""
|
|
156
|
+
|
|
157
|
+
operators = {
|
|
158
|
+
"|": lambda a, b: a | b,
|
|
159
|
+
"^": lambda a, b: a ^ b,
|
|
160
|
+
"&": lambda a, b: a & b,
|
|
161
|
+
"<<": lambda a, b: a << b,
|
|
162
|
+
">>": lambda a, b: a >> b,
|
|
163
|
+
"+": lambda a, b: a + b,
|
|
164
|
+
"-": lambda a, b: a - b,
|
|
165
|
+
"*": lambda a, b: a * b,
|
|
166
|
+
"/": lambda a, b: a // b,
|
|
167
|
+
"%": lambda a, b: a % b,
|
|
168
|
+
"u": lambda a: -a,
|
|
169
|
+
"~": lambda a: ~a,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
precedence_levels = {
|
|
173
|
+
"|": 0,
|
|
174
|
+
"^": 1,
|
|
175
|
+
"&": 2,
|
|
176
|
+
"<<": 3,
|
|
177
|
+
">>": 3,
|
|
178
|
+
"+": 4,
|
|
179
|
+
"-": 4,
|
|
180
|
+
"*": 5,
|
|
181
|
+
"/": 5,
|
|
182
|
+
"%": 5,
|
|
183
|
+
"u": 6,
|
|
184
|
+
"~": 6,
|
|
185
|
+
"sizeof": 6,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def __init__(self, cstruct: cstruct, expression: str):
|
|
189
|
+
self.cstruct = cstruct
|
|
190
|
+
self.expression = expression
|
|
191
|
+
self.tokens = ExpressionTokenizer(expression).tokenize()
|
|
192
|
+
self.stack = []
|
|
193
|
+
self.queue = []
|
|
194
|
+
|
|
195
|
+
def __repr__(self) -> str:
|
|
196
|
+
return self.expression
|
|
197
|
+
|
|
198
|
+
def precedence(self, o1: str, o2: str) -> bool:
|
|
199
|
+
return self.precedence_levels[o1] >= self.precedence_levels[o2]
|
|
200
|
+
|
|
201
|
+
def evaluate_exp(self) -> None:
|
|
202
|
+
operator = self.stack.pop(-1)
|
|
203
|
+
res = 0
|
|
204
|
+
|
|
205
|
+
if len(self.queue) < 1:
|
|
206
|
+
raise ExpressionParserError("Invalid expression: not enough operands")
|
|
207
|
+
|
|
208
|
+
right = self.queue.pop(-1)
|
|
209
|
+
if operator in ("u", "~"):
|
|
210
|
+
res = self.operators[operator](right)
|
|
211
|
+
else:
|
|
212
|
+
if len(self.queue) < 1:
|
|
213
|
+
raise ExpressionParserError("Invalid expression: not enough operands")
|
|
214
|
+
|
|
215
|
+
left = self.queue.pop(-1)
|
|
216
|
+
res = self.operators[operator](left, right)
|
|
217
|
+
|
|
218
|
+
self.queue.append(res)
|
|
219
|
+
|
|
220
|
+
def is_number(self, token: str) -> bool:
|
|
221
|
+
return token.isnumeric() or (len(token) > 2 and token[0] == "0" and token[1] in ("x", "X", "b", "B", "o", "O"))
|
|
222
|
+
|
|
223
|
+
def evaluate(self, context: Optional[dict[str, int]] = None) -> int:
|
|
224
|
+
"""Evaluates an expression using a Shunting-Yard implementation."""
|
|
225
|
+
|
|
226
|
+
self.stack = []
|
|
227
|
+
self.queue = []
|
|
228
|
+
operators = set(self.operators.keys())
|
|
229
|
+
|
|
230
|
+
context = context or {}
|
|
231
|
+
tmp_expression = self.tokens
|
|
232
|
+
|
|
233
|
+
# Unary minus tokens; we change the semantic of '-' depending on the previous token
|
|
234
|
+
for i in range(len(self.tokens)):
|
|
235
|
+
if self.tokens[i] == "-":
|
|
236
|
+
if i == 0:
|
|
237
|
+
self.tokens[i] = "u"
|
|
238
|
+
continue
|
|
239
|
+
if self.tokens[i - 1] in operators or self.tokens[i - 1] == "u" or self.tokens[i - 1] == "(":
|
|
240
|
+
self.tokens[i] = "u"
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
i = 0
|
|
244
|
+
while i < len(tmp_expression):
|
|
245
|
+
current_token = tmp_expression[i]
|
|
246
|
+
if self.is_number(current_token):
|
|
247
|
+
self.queue.append(int(current_token, 0))
|
|
248
|
+
elif current_token in context:
|
|
249
|
+
self.queue.append(int(context[current_token]))
|
|
250
|
+
elif current_token in self.cstruct.consts:
|
|
251
|
+
self.queue.append(int(self.cstruct.consts[current_token]))
|
|
252
|
+
elif current_token == "u":
|
|
253
|
+
self.stack.append(current_token)
|
|
254
|
+
elif current_token == "~":
|
|
255
|
+
self.stack.append(current_token)
|
|
256
|
+
elif current_token == "sizeof":
|
|
257
|
+
if len(tmp_expression) < i + 3 or (tmp_expression[i + 1] != "(" or tmp_expression[i + 3] != ")"):
|
|
258
|
+
raise ExpressionParserError("Invalid sizeof operation")
|
|
259
|
+
self.queue.append(len(self.cstruct.resolve(tmp_expression[i + 2])))
|
|
260
|
+
i += 3
|
|
261
|
+
elif current_token in operators:
|
|
262
|
+
while (
|
|
263
|
+
len(self.stack) != 0 and self.stack[-1] != "(" and (self.precedence(self.stack[-1], current_token))
|
|
264
|
+
):
|
|
265
|
+
self.evaluate_exp()
|
|
266
|
+
self.stack.append(current_token)
|
|
267
|
+
elif current_token == "(":
|
|
268
|
+
if i > 0:
|
|
269
|
+
previous_token = tmp_expression[i - 1]
|
|
270
|
+
if self.is_number(previous_token):
|
|
271
|
+
raise ExpressionParserError(
|
|
272
|
+
f"Parser expected sizeof or an arethmethic operator instead got: '{previous_token}'"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
self.stack.append(current_token)
|
|
276
|
+
elif current_token == ")":
|
|
277
|
+
if i > 0:
|
|
278
|
+
previous_token = tmp_expression[i - 1]
|
|
279
|
+
if previous_token == "(":
|
|
280
|
+
raise ExpressionParserError(
|
|
281
|
+
f"Parser expected an expression, instead received empty parenthesis. Index: {i}"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if len(self.stack) == 0:
|
|
285
|
+
raise ExpressionParserError("Invalid expression")
|
|
286
|
+
|
|
287
|
+
while self.stack[-1] != "(":
|
|
288
|
+
self.evaluate_exp()
|
|
289
|
+
|
|
290
|
+
self.stack.pop(-1)
|
|
291
|
+
else:
|
|
292
|
+
raise ExpressionParserError(f"Unmatched token: '{current_token}'")
|
|
293
|
+
i += 1
|
|
294
|
+
|
|
295
|
+
while len(self.stack) != 0:
|
|
296
|
+
if self.stack[-1] == "(":
|
|
297
|
+
raise ExpressionParserError("Invalid expression")
|
|
298
|
+
|
|
299
|
+
self.evaluate_exp()
|
|
300
|
+
|
|
301
|
+
return self.queue[0]
|
|
@@ -135,7 +135,10 @@ def _dumpstruct(
|
|
|
135
135
|
ci = 0
|
|
136
136
|
out = [f"struct {structure.name}:"]
|
|
137
137
|
foreground, background = None, None
|
|
138
|
-
for field in instance._type.
|
|
138
|
+
for field in instance._type.lookup.values():
|
|
139
|
+
if getattr(field.type, "anonymous", False):
|
|
140
|
+
continue
|
|
141
|
+
|
|
139
142
|
if color:
|
|
140
143
|
foreground, background = colors[ci % len(colors)]
|
|
141
144
|
palette.append((instance._size(field.name), background))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dissect.cstruct
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.11
|
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from dissect import cstruct
|
|
3
3
|
|
|
4
|
+
from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
|
|
4
5
|
from dissect.cstruct.expression import Expression
|
|
5
6
|
|
|
6
7
|
testdata = [
|
|
@@ -44,13 +45,24 @@ testdata = [
|
|
|
44
45
|
("0 | 1", 1),
|
|
45
46
|
("1 | 1", 1),
|
|
46
47
|
("1 | 2", 3),
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
("1 | 2 | 4", 7),
|
|
49
|
+
("1 & 1 * 4", 0),
|
|
50
|
+
("(1 & 1) * 4", 4),
|
|
51
|
+
("4 * 1 + 1", 5),
|
|
49
52
|
("-42", -42),
|
|
50
53
|
("42 + (-42)", 0),
|
|
51
54
|
("A + 5", 13),
|
|
52
55
|
("21 - B", 8),
|
|
53
56
|
("A + B", 21),
|
|
57
|
+
("~1", -2),
|
|
58
|
+
("~(A + 5)", ~13),
|
|
59
|
+
("10l", 10),
|
|
60
|
+
("10ll", 10),
|
|
61
|
+
("10ull", 10),
|
|
62
|
+
("010ULL", 8),
|
|
63
|
+
("0Xf0 >> 4", 0xF),
|
|
64
|
+
("0x1B", 0x1B),
|
|
65
|
+
("0x1b", 0x1B),
|
|
54
66
|
]
|
|
55
67
|
|
|
56
68
|
|
|
@@ -67,11 +79,32 @@ def id_fn(val):
|
|
|
67
79
|
|
|
68
80
|
|
|
69
81
|
@pytest.mark.parametrize("expression, answer", testdata, ids=id_fn)
|
|
70
|
-
def test_expression(expression, answer):
|
|
82
|
+
def test_expression(expression: str, answer: int) -> None:
|
|
71
83
|
parser = Expression(Consts(), expression)
|
|
72
84
|
assert parser.evaluate() == answer
|
|
73
85
|
|
|
74
86
|
|
|
87
|
+
@pytest.mark.parametrize(
|
|
88
|
+
"expression, exception, message",
|
|
89
|
+
[
|
|
90
|
+
("0b", ExpressionTokenizerError, "Invalid binary or hex notation"),
|
|
91
|
+
("0x", ExpressionTokenizerError, "Invalid binary or hex notation"),
|
|
92
|
+
("$", ExpressionTokenizerError, "Tokenizer does not recognize following token '\\$'"),
|
|
93
|
+
("-", ExpressionParserError, "Invalid expression: not enough operands"),
|
|
94
|
+
("(", ExpressionParserError, "Invalid expression"),
|
|
95
|
+
(")", ExpressionParserError, "Invalid expression"),
|
|
96
|
+
("()", ExpressionParserError, "Parser expected an expression, instead received empty parenthesis. Index: 1"),
|
|
97
|
+
("0()", ExpressionParserError, "Parser expected sizeof or an arethmethic operator instead got: '0'"),
|
|
98
|
+
("sizeof)", ExpressionParserError, "Invalid sizeof operation"),
|
|
99
|
+
("sizeof(0 +)", ExpressionParserError, "Invalid sizeof operation"),
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_expression_failure(expression: str, exception: type, message: str) -> None:
|
|
103
|
+
with pytest.raises(exception, match=message):
|
|
104
|
+
parser = Expression(Consts(), expression)
|
|
105
|
+
parser.evaluate()
|
|
106
|
+
|
|
107
|
+
|
|
75
108
|
def test_sizeof():
|
|
76
109
|
d = """
|
|
77
110
|
struct test {
|
|
@@ -28,6 +28,42 @@ def test_dumpstruct(capsys, compiled):
|
|
|
28
28
|
uint32 testval;
|
|
29
29
|
};
|
|
30
30
|
"""
|
|
31
|
+
|
|
32
|
+
cs = cstruct.cstruct()
|
|
33
|
+
cs.load(cdef, compiled=compiled)
|
|
34
|
+
|
|
35
|
+
assert verify_compiled(cs.test, compiled)
|
|
36
|
+
|
|
37
|
+
buf = b"\x39\x05\x00\x00"
|
|
38
|
+
obj = cs.test(buf)
|
|
39
|
+
|
|
40
|
+
dumpstruct(cs.test, buf)
|
|
41
|
+
captured_1 = capsys.readouterr()
|
|
42
|
+
|
|
43
|
+
dumpstruct(obj)
|
|
44
|
+
captured_2 = capsys.readouterr()
|
|
45
|
+
|
|
46
|
+
assert captured_1.out == captured_2.out
|
|
47
|
+
|
|
48
|
+
out_1 = dumpstruct(cs.test, buf, output="string")
|
|
49
|
+
out_2 = dumpstruct(obj, output="string")
|
|
50
|
+
|
|
51
|
+
assert out_1 == out_2
|
|
52
|
+
|
|
53
|
+
with pytest.raises(ValueError) as excinfo:
|
|
54
|
+
dumpstruct(obj, output="generator")
|
|
55
|
+
assert str(excinfo.value) == "Invalid output argument: 'generator' (should be 'print' or 'string')."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_dumpstruct_anonymous(capsys, compiled):
|
|
59
|
+
cdef = """
|
|
60
|
+
struct test {
|
|
61
|
+
struct {
|
|
62
|
+
uint32 testval;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
"""
|
|
66
|
+
|
|
31
67
|
cs = cstruct.cstruct()
|
|
32
68
|
cs.load(cdef, compiled=compiled)
|
|
33
69
|
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, Dict
|
|
4
|
-
|
|
5
|
-
if TYPE_CHECKING:
|
|
6
|
-
from dissect.cstruct import cstruct
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Expression:
|
|
10
|
-
"""Expression parser for simple calculations in definitions."""
|
|
11
|
-
|
|
12
|
-
operators = [
|
|
13
|
-
("*", lambda a, b: a * b),
|
|
14
|
-
("/", lambda a, b: a // b),
|
|
15
|
-
("%", lambda a, b: a % b),
|
|
16
|
-
("+", lambda a, b: a + b),
|
|
17
|
-
("-", lambda a, b: a - b),
|
|
18
|
-
(">>", lambda a, b: a >> b),
|
|
19
|
-
("<<", lambda a, b: a << b),
|
|
20
|
-
("&", lambda a, b: a & b),
|
|
21
|
-
("^", lambda a, b: a ^ b),
|
|
22
|
-
("|", lambda a, b: a | b),
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
def __init__(self, cstruct: cstruct, expression: str):
|
|
26
|
-
self.cstruct = cstruct
|
|
27
|
-
self.expression = expression
|
|
28
|
-
|
|
29
|
-
def __repr__(self) -> str:
|
|
30
|
-
return self.expression
|
|
31
|
-
|
|
32
|
-
def evaluate(self, context: Dict[str, int] = None) -> int:
|
|
33
|
-
context = context or {}
|
|
34
|
-
levels = []
|
|
35
|
-
buf = ""
|
|
36
|
-
|
|
37
|
-
for i in range(len(self.expression)):
|
|
38
|
-
if self.expression[i] == "(":
|
|
39
|
-
levels.append(buf)
|
|
40
|
-
buf = ""
|
|
41
|
-
continue
|
|
42
|
-
|
|
43
|
-
if self.expression[i] == ")":
|
|
44
|
-
if levels[-1] == "sizeof":
|
|
45
|
-
value = len(self.cstruct.resolve(buf))
|
|
46
|
-
levels[-1] = ""
|
|
47
|
-
else:
|
|
48
|
-
value = self.evaluate_part(buf, context)
|
|
49
|
-
buf = levels.pop()
|
|
50
|
-
buf += str(value)
|
|
51
|
-
continue
|
|
52
|
-
|
|
53
|
-
buf += self.expression[i]
|
|
54
|
-
|
|
55
|
-
return self.evaluate_part(buf, context)
|
|
56
|
-
|
|
57
|
-
def evaluate_part(self, buf: str, context: Dict[str, int]) -> int:
|
|
58
|
-
buf = buf.strip()
|
|
59
|
-
|
|
60
|
-
# Very simple way to support an expression(part) that is a single,
|
|
61
|
-
# negative value. To use negative values in more complex expressions,
|
|
62
|
-
# they must be wrapped in brackets, e.g.: 2 * (-5).
|
|
63
|
-
#
|
|
64
|
-
# To have full support for the negation operator a proper expression
|
|
65
|
-
# parser must be build.
|
|
66
|
-
if buf.startswith("-") and buf[1:].isnumeric():
|
|
67
|
-
return int(buf)
|
|
68
|
-
|
|
69
|
-
for operator in self.operators:
|
|
70
|
-
if operator[0] in buf:
|
|
71
|
-
a, b = buf.rsplit(operator[0], 1)
|
|
72
|
-
|
|
73
|
-
return operator[1](self.evaluate_part(a, context), self.evaluate_part(b, context))
|
|
74
|
-
|
|
75
|
-
if buf in context:
|
|
76
|
-
return context[buf]
|
|
77
|
-
|
|
78
|
-
if buf.startswith("0x"):
|
|
79
|
-
return int(buf, 16)
|
|
80
|
-
|
|
81
|
-
if buf in self.cstruct.consts:
|
|
82
|
-
return int(self.cstruct.consts[buf])
|
|
83
|
-
|
|
84
|
-
return int(buf)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dissect.cstruct-3.10.dev2 → dissect.cstruct-3.11}/dissect.cstruct.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|