c-struct-data-parser 0.1.0__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.
- c_struct_data_parser-0.1.0/PKG-INFO +89 -0
- c_struct_data_parser-0.1.0/README.md +80 -0
- c_struct_data_parser-0.1.0/pyproject.toml +20 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/__init__.py +0 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/bytes_reader.py +23 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/data_structures.py +440 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/parser.py +78 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/py.typed +0 -0
- c_struct_data_parser-0.1.0/src/c_struct_data_parser/reader_abc.py +23 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: c-struct-data-parser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Declaratively define and parse C data structures
|
|
5
|
+
Author: Gregory.Kuhn
|
|
6
|
+
Author-email: Gregory.Kuhn <gregory.kuhn@analog.com>
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# c_struct_data_parser
|
|
11
|
+
|
|
12
|
+
Intended to facilitate declarative, dynamic definition of C data structures.
|
|
13
|
+
The library will autogenerate a parser which you can connect to a `BytesReader` or some IO based variant.
|
|
14
|
+
|
|
15
|
+
You're better off looking at https://github.com/fox-it/dissect.cstruct
|
|
16
|
+
|
|
17
|
+
Examples from `test_basic.py`:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
Struct2Int = create_struct_definition(
|
|
21
|
+
"Struct2Int",
|
|
22
|
+
{
|
|
23
|
+
"value_a": Int4Definition,
|
|
24
|
+
"value_b": Int4Definition,
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_struct2Int() -> None:
|
|
30
|
+
value_a = 0x12345678
|
|
31
|
+
value_b = 0x99887766
|
|
32
|
+
bs = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
|
|
33
|
+
bytes_reader = BytesReader(address=0, bs=bs)
|
|
34
|
+
struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
|
|
35
|
+
assert struct_2_int.value_a.value == value_a
|
|
36
|
+
assert struct_2_int.value_b.value == value_b
|
|
37
|
+
|
|
38
|
+
print(struct_2_int)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
BitFieldsExample = create_bit_fields_definition(
|
|
42
|
+
"BitFieldsExample",
|
|
43
|
+
Int4Definition,
|
|
44
|
+
{
|
|
45
|
+
"field_a": 4,
|
|
46
|
+
"reserved_1": 5,
|
|
47
|
+
"field_b": 5,
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_bit_fields() -> None:
|
|
53
|
+
val = 0x123456
|
|
54
|
+
bs = int_to_le_bytes_4(val)
|
|
55
|
+
bytes_reader = BytesReader(address=0, bs=bs)
|
|
56
|
+
bit_fields_example, new_reader = BitFieldsExample.parser(bytes_reader)
|
|
57
|
+
assert bit_fields_example.field_a.value == val & 0xF
|
|
58
|
+
assert bit_fields_example.field_b.value == (val >> 9) & 0x1F
|
|
59
|
+
print(bit_fields_example)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
Struct2IntPointer = create_pointer_definition(
|
|
63
|
+
Struct2Int,
|
|
64
|
+
Int4Definition,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_pointer_type() -> None:
|
|
69
|
+
value_a = 0x12345678
|
|
70
|
+
value_b = 0x99887766
|
|
71
|
+
bs_struct = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
|
|
72
|
+
bs_pointer = int_to_le_bytes_4(0x2000)
|
|
73
|
+
bs = b"".join([bs_struct, bs_pointer])
|
|
74
|
+
bytes_reader = BytesReader(address=0x2000, bs=bs)
|
|
75
|
+
|
|
76
|
+
struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
|
|
77
|
+
assert struct_2_int.value_a.value == value_a
|
|
78
|
+
assert struct_2_int.value_b.value == value_b
|
|
79
|
+
|
|
80
|
+
p_struct_2_int, new_reader_2 = Struct2IntPointer.parser(new_reader)
|
|
81
|
+
|
|
82
|
+
print(p_struct_2_int)
|
|
83
|
+
|
|
84
|
+
struct_2_int_copy = p_struct_2_int.resolve(new_reader_2)
|
|
85
|
+
|
|
86
|
+
assert struct_2_int_copy.value_a.value == value_a
|
|
87
|
+
assert struct_2_int_copy.value_b.value == value_b
|
|
88
|
+
|
|
89
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# c_struct_data_parser
|
|
2
|
+
|
|
3
|
+
Intended to facilitate declarative, dynamic definition of C data structures.
|
|
4
|
+
The library will autogenerate a parser which you can connect to a `BytesReader` or some IO based variant.
|
|
5
|
+
|
|
6
|
+
You're better off looking at https://github.com/fox-it/dissect.cstruct
|
|
7
|
+
|
|
8
|
+
Examples from `test_basic.py`:
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
Struct2Int = create_struct_definition(
|
|
12
|
+
"Struct2Int",
|
|
13
|
+
{
|
|
14
|
+
"value_a": Int4Definition,
|
|
15
|
+
"value_b": Int4Definition,
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_struct2Int() -> None:
|
|
21
|
+
value_a = 0x12345678
|
|
22
|
+
value_b = 0x99887766
|
|
23
|
+
bs = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
|
|
24
|
+
bytes_reader = BytesReader(address=0, bs=bs)
|
|
25
|
+
struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
|
|
26
|
+
assert struct_2_int.value_a.value == value_a
|
|
27
|
+
assert struct_2_int.value_b.value == value_b
|
|
28
|
+
|
|
29
|
+
print(struct_2_int)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
BitFieldsExample = create_bit_fields_definition(
|
|
33
|
+
"BitFieldsExample",
|
|
34
|
+
Int4Definition,
|
|
35
|
+
{
|
|
36
|
+
"field_a": 4,
|
|
37
|
+
"reserved_1": 5,
|
|
38
|
+
"field_b": 5,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_bit_fields() -> None:
|
|
44
|
+
val = 0x123456
|
|
45
|
+
bs = int_to_le_bytes_4(val)
|
|
46
|
+
bytes_reader = BytesReader(address=0, bs=bs)
|
|
47
|
+
bit_fields_example, new_reader = BitFieldsExample.parser(bytes_reader)
|
|
48
|
+
assert bit_fields_example.field_a.value == val & 0xF
|
|
49
|
+
assert bit_fields_example.field_b.value == (val >> 9) & 0x1F
|
|
50
|
+
print(bit_fields_example)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
Struct2IntPointer = create_pointer_definition(
|
|
54
|
+
Struct2Int,
|
|
55
|
+
Int4Definition,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_pointer_type() -> None:
|
|
60
|
+
value_a = 0x12345678
|
|
61
|
+
value_b = 0x99887766
|
|
62
|
+
bs_struct = b"".join(map(int_to_le_bytes_4, [value_a, value_b]))
|
|
63
|
+
bs_pointer = int_to_le_bytes_4(0x2000)
|
|
64
|
+
bs = b"".join([bs_struct, bs_pointer])
|
|
65
|
+
bytes_reader = BytesReader(address=0x2000, bs=bs)
|
|
66
|
+
|
|
67
|
+
struct_2_int, new_reader = Struct2Int.parser(bytes_reader)
|
|
68
|
+
assert struct_2_int.value_a.value == value_a
|
|
69
|
+
assert struct_2_int.value_b.value == value_b
|
|
70
|
+
|
|
71
|
+
p_struct_2_int, new_reader_2 = Struct2IntPointer.parser(new_reader)
|
|
72
|
+
|
|
73
|
+
print(p_struct_2_int)
|
|
74
|
+
|
|
75
|
+
struct_2_int_copy = p_struct_2_int.resolve(new_reader_2)
|
|
76
|
+
|
|
77
|
+
assert struct_2_int_copy.value_a.value == value_a
|
|
78
|
+
assert struct_2_int_copy.value_b.value == value_b
|
|
79
|
+
|
|
80
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "c-struct-data-parser"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Declaratively define and parse C data structures"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Gregory.Kuhn", email = "gregory.kuhn@analog.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build>=0.9.8,<0.10.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
15
|
+
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
"pre-commit>=4.5.1",
|
|
19
|
+
"pytest>=9.0.2",
|
|
20
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .reader_abc import AddressData, Reader
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BytesReader(Reader):
|
|
5
|
+
def __init__(self, address: int, bs: bytes):
|
|
6
|
+
super().__init__(address)
|
|
7
|
+
self.bs = bs
|
|
8
|
+
|
|
9
|
+
def read(self, size: int) -> AddressData:
|
|
10
|
+
current = self.offset
|
|
11
|
+
self.offset += size
|
|
12
|
+
read_bytes = self.bs[current : current + size]
|
|
13
|
+
if len(read_bytes) < size:
|
|
14
|
+
raise ValueError(f"Failed to read requested number of bytes: {size}")
|
|
15
|
+
return self.address + current, read_bytes
|
|
16
|
+
|
|
17
|
+
def new_reader(self, address: int) -> Reader:
|
|
18
|
+
if address < self.address:
|
|
19
|
+
raise ValueError(
|
|
20
|
+
f"Supplied address: {address:#010x} cannot be lower than original address: {self.address:#010x}"
|
|
21
|
+
)
|
|
22
|
+
offset = address - self.address
|
|
23
|
+
return type(self)(address, self.bs[offset:])
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from functools import partial, reduce
|
|
7
|
+
from itertools import chain
|
|
8
|
+
from operator import attrgetter, methodcaller
|
|
9
|
+
from textwrap import indent
|
|
10
|
+
from typing import Dict, Literal, Optional, Sequence, Tuple, Type
|
|
11
|
+
|
|
12
|
+
from .parser import create_repeat_parser, create_sequence_parser, Parser, Reader
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
int_from_bytes_le = partial(int.from_bytes, byteorder="little")
|
|
16
|
+
|
|
17
|
+
get_parser = attrgetter("parser")
|
|
18
|
+
from_register = partial(methodcaller, "from_register")
|
|
19
|
+
get_size = attrgetter("size")
|
|
20
|
+
|
|
21
|
+
indent_4 = partial(indent, prefix=" ")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Metadata:
|
|
26
|
+
address: int
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DataDefinition(ABC):
|
|
30
|
+
size: int = 0
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def parser(cls, reader: Reader) -> Tuple[DataDefinition, Reader]: ...
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
metadata: Optional[Metadata],
|
|
39
|
+
):
|
|
40
|
+
self.metadata = metadata
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
cls_name = type(self).__name__
|
|
44
|
+
return f"{cls_name}({self.add_metadata_repr()})"
|
|
45
|
+
|
|
46
|
+
def add_metadata_repr(self, *args: str) -> str:
|
|
47
|
+
m = self.metadata
|
|
48
|
+
args_str = ", ".join(args)
|
|
49
|
+
if m:
|
|
50
|
+
prefix = ", " if args else ""
|
|
51
|
+
return f"{args_str}{prefix}Metadata(address={m.address:#x})"
|
|
52
|
+
return args_str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class IntDefinition(DataDefinition):
|
|
56
|
+
size: int = 0 # num bytes
|
|
57
|
+
byteorder: Literal["little", "big"] = "little"
|
|
58
|
+
_enum_type: Optional[Type[Enum]] = None
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
value: int | Enum,
|
|
63
|
+
metadata: Optional[Metadata] = None,
|
|
64
|
+
):
|
|
65
|
+
|
|
66
|
+
super().__init__(metadata)
|
|
67
|
+
if isinstance(value, int):
|
|
68
|
+
self.value = value
|
|
69
|
+
elif self._enum_type and isinstance(value, self._enum_type):
|
|
70
|
+
self.value = value.value
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Unexpected value type: {type(value)}")
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def parser(cls, reader: Reader) -> Tuple[IntDefinition, Reader]:
|
|
76
|
+
address, data_bytes = reader.read(cls.size)
|
|
77
|
+
return cls(int.from_bytes(data_bytes, byteorder=cls.byteorder), metadata=Metadata(address)), reader
|
|
78
|
+
|
|
79
|
+
def __repr__(self) -> str:
|
|
80
|
+
cls_name = type(self).__name__
|
|
81
|
+
val = self.value
|
|
82
|
+
enum_type = self._enum_type
|
|
83
|
+
if enum_type:
|
|
84
|
+
try:
|
|
85
|
+
val_str = str(enum_type(val))
|
|
86
|
+
except ValueError:
|
|
87
|
+
val_str = hex(val)
|
|
88
|
+
else:
|
|
89
|
+
val_str = hex(val)
|
|
90
|
+
|
|
91
|
+
return f"{cls_name}({self.add_metadata_repr(val_str)})"
|
|
92
|
+
|
|
93
|
+
def __str__(self) -> str:
|
|
94
|
+
cls_name = type(self).__name__
|
|
95
|
+
val = self.value
|
|
96
|
+
enum_type = self._enum_type
|
|
97
|
+
if enum_type:
|
|
98
|
+
try:
|
|
99
|
+
val_str = repr(enum_type(val)) # repr(Enum(<num>)) looks more like str(Enum(<num>)) and vice versa
|
|
100
|
+
except ValueError:
|
|
101
|
+
val_str = f"{val:#x} ({val}) not a valid Enumeration of type: {enum_type}"
|
|
102
|
+
else:
|
|
103
|
+
val_str = hex(val)
|
|
104
|
+
|
|
105
|
+
return f"{cls_name}({self.add_metadata_repr(val_str)})"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Int4Definition(IntDefinition):
|
|
109
|
+
size: int = 4
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Int1Definition(IntDefinition):
|
|
113
|
+
size: int = 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Int2Definition(IntDefinition):
|
|
117
|
+
size: int = 2
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_int_definition(
|
|
121
|
+
name: str,
|
|
122
|
+
int_definition: Type[IntDefinition],
|
|
123
|
+
enumeration: Optional[Type[Enum]] = None,
|
|
124
|
+
) -> Type[IntDefinition]:
|
|
125
|
+
attrs = {"_enum_type": enumeration} if enumeration else {}
|
|
126
|
+
return type(
|
|
127
|
+
name,
|
|
128
|
+
(int_definition,),
|
|
129
|
+
attrs,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class StructDefinition(DataDefinition):
|
|
134
|
+
size: int = 0
|
|
135
|
+
field_types: Dict[str, Type[DataDefinition]] = {}
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
fields: Sequence[DataDefinition],
|
|
140
|
+
metadata: Optional[Metadata] = None,
|
|
141
|
+
):
|
|
142
|
+
super().__init__(metadata)
|
|
143
|
+
self.fields = tuple(fields)
|
|
144
|
+
fields_len = len(fields)
|
|
145
|
+
field_types_len = len(self.field_types)
|
|
146
|
+
if fields_len != field_types_len:
|
|
147
|
+
raise ValueError(f"Supplied number of fields: {fields_len} doesn't match expectation: {field_types_len}")
|
|
148
|
+
for (k, t), v in zip(self.field_types.items(), fields):
|
|
149
|
+
if not isinstance(v, t):
|
|
150
|
+
raise ValueError(f"Got unexpected value: {v} for type: {t}")
|
|
151
|
+
setattr(self, k, v)
|
|
152
|
+
|
|
153
|
+
def __str__(self) -> str:
|
|
154
|
+
cls_name = type(self).__name__
|
|
155
|
+
|
|
156
|
+
fields_strs = map(lambda k, v: f"{k}: {v}", self.field_types.keys(), map(str, self.fields))
|
|
157
|
+
fields_strs_indented = map(indent_4, fields_strs)
|
|
158
|
+
fields_str = "\n".join(fields_strs_indented)
|
|
159
|
+
return f"{cls_name} {self.add_metadata_repr()}:\n{fields_str}"
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def parser(cls, reader: Reader) -> Tuple[StructDefinition, Reader]:
|
|
163
|
+
raise NotImplementedError(f"{cls} is abstract")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class ForwardPointer:
|
|
168
|
+
address_type: Type[IntDefinition]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def create_forward_pointer(
|
|
172
|
+
address_type: Type[IntDefinition],
|
|
173
|
+
) -> ForwardPointer:
|
|
174
|
+
return ForwardPointer(address_type)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def replace_forward_pointer(
|
|
178
|
+
target_type: Type[DataDefinition], field_type: Type[DataDefinition] | ForwardPointer
|
|
179
|
+
) -> Type[DataDefinition]:
|
|
180
|
+
if isinstance(field_type, ForwardPointer):
|
|
181
|
+
return _create_pointer_definition_from_forward_pointer(target_type, field_type)
|
|
182
|
+
else:
|
|
183
|
+
return field_type
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def create_struct_definition(
|
|
187
|
+
struct_name: str,
|
|
188
|
+
field_types: Dict[str, Type[DataDefinition] | ForwardPointer],
|
|
189
|
+
) -> Type[StructDefinition]:
|
|
190
|
+
|
|
191
|
+
new_struct = type(
|
|
192
|
+
struct_name,
|
|
193
|
+
(StructDefinition,),
|
|
194
|
+
{},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
normalized_field_types = {k: replace_forward_pointer(new_struct, v) for (k, v) in field_types.items()}
|
|
198
|
+
sequence_parser = create_sequence_parser(*map(get_parser, normalized_field_types.values()))
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def parser(cls: Type[StructDefinition], reader: Reader) -> Tuple[StructDefinition, Reader]:
|
|
202
|
+
parsed_field_results, new_reader = sequence_parser(reader)
|
|
203
|
+
metadata = Metadata(parsed_field_results[0].metadata.address)
|
|
204
|
+
return cls(parsed_field_results, metadata=metadata), new_reader
|
|
205
|
+
|
|
206
|
+
new_struct.field_types = normalized_field_types
|
|
207
|
+
new_struct.parser = parser
|
|
208
|
+
new_struct.size = sum(map(get_size, normalized_field_types.values()))
|
|
209
|
+
|
|
210
|
+
return new_struct
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class BitFieldDefinition:
|
|
214
|
+
size: int
|
|
215
|
+
position: int
|
|
216
|
+
mask: int
|
|
217
|
+
|
|
218
|
+
def __init__(
|
|
219
|
+
self,
|
|
220
|
+
value: int,
|
|
221
|
+
):
|
|
222
|
+
if value > self.mask:
|
|
223
|
+
raise ValueError(f"Passed in value: {value:#x} greater than mask: {self.mask:#x}")
|
|
224
|
+
self.value = value
|
|
225
|
+
|
|
226
|
+
def __repr__(self):
|
|
227
|
+
cls_name = type(self).__name__
|
|
228
|
+
return f"{cls_name}({self.value:#x})"
|
|
229
|
+
|
|
230
|
+
def __str__(self):
|
|
231
|
+
cls_name = type(self).__name__
|
|
232
|
+
return f"{cls_name}: {self.value:#x}"
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def from_register(cls, value: int) -> BitFieldDefinition:
|
|
236
|
+
return cls((value >> cls.position) & cls.mask)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class BitFieldsDefinition(DataDefinition):
|
|
240
|
+
field_types: Dict[str, Type[BitFieldDefinition]]
|
|
241
|
+
IntDefinition: Type[IntDefinition]
|
|
242
|
+
size: int
|
|
243
|
+
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
value: int,
|
|
247
|
+
metadata: Optional[Metadata],
|
|
248
|
+
):
|
|
249
|
+
super().__init__(metadata)
|
|
250
|
+
self.value = value
|
|
251
|
+
self.fields = tuple(map(from_register(value), self.field_types.values()))
|
|
252
|
+
fields_len = len(self.fields)
|
|
253
|
+
field_types_len = len(self.field_types)
|
|
254
|
+
|
|
255
|
+
if fields_len != field_types_len:
|
|
256
|
+
raise ValueError(f"Supplied number of fields: {fields_len} doesn't match expectation: {field_types_len}")
|
|
257
|
+
for (k, t), v in zip(self.field_types.items(), self.fields):
|
|
258
|
+
if not isinstance(v, t):
|
|
259
|
+
raise ValueError(f"Got unexpected value: {v} for type: {t}")
|
|
260
|
+
setattr(self, k, v)
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def parser(cls, reader: Reader) -> Tuple[BitFieldsDefinition, Reader]:
|
|
264
|
+
int_definition, new_reader = cls.IntDefinition.parser(reader)
|
|
265
|
+
return (
|
|
266
|
+
cls(
|
|
267
|
+
int_definition.value,
|
|
268
|
+
metadata=int_definition.metadata,
|
|
269
|
+
),
|
|
270
|
+
new_reader,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def __repr__(self) -> str:
|
|
274
|
+
cls_name = type(self).__name__
|
|
275
|
+
return f"{cls_name}({self.add_metadata_repr(hex(self.value))})"
|
|
276
|
+
|
|
277
|
+
def __str__(self) -> str:
|
|
278
|
+
cls_name = type(self).__name__
|
|
279
|
+
fields_strs = map(str, self.fields)
|
|
280
|
+
fields_strs_indented = map(indent_4, fields_strs)
|
|
281
|
+
fields_str = "\n".join(fields_strs_indented)
|
|
282
|
+
return f"{cls_name}({self.add_metadata_repr(hex(self.value))})\n{fields_str}"
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
BitFieldDef = Tuple[str, int]
|
|
286
|
+
BitFieldDefs = Dict[str, int]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _create_bit_field_definition_from_bit_field_def(position: int, bf_def: BitFieldDef) -> Type[BitFieldDefinition]:
|
|
290
|
+
name, size = bf_def
|
|
291
|
+
bit_field_definition = type(
|
|
292
|
+
name,
|
|
293
|
+
(BitFieldDefinition,),
|
|
294
|
+
{
|
|
295
|
+
"size": size,
|
|
296
|
+
"position": position,
|
|
297
|
+
"mask": (1 << size) - 1,
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return bit_field_definition
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
PosFieldsTup = Tuple[int, Tuple[Type[BitFieldDefinition], ...]]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _create_fields_reducer(pos_fields_tup: PosFieldsTup, field_def: BitFieldDef) -> PosFieldsTup:
|
|
308
|
+
pos, fields = pos_fields_tup
|
|
309
|
+
name, size = field_def
|
|
310
|
+
new_field = _create_bit_field_definition_from_bit_field_def(pos, field_def)
|
|
311
|
+
return (pos + size, tuple(chain(fields, [new_field])))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def create_bit_fields_definition(
|
|
315
|
+
name: str,
|
|
316
|
+
int_definition: Type[IntDefinition],
|
|
317
|
+
field_defs: BitFieldDefs,
|
|
318
|
+
) -> Type[BitFieldsDefinition]:
|
|
319
|
+
empty_tuple = ()
|
|
320
|
+
|
|
321
|
+
size, fields = reduce(_create_fields_reducer, field_defs.items(), (0, empty_tuple))
|
|
322
|
+
fields_dict = dict(zip(field_defs.keys(), fields, strict=True))
|
|
323
|
+
return type(
|
|
324
|
+
name,
|
|
325
|
+
(BitFieldsDefinition,),
|
|
326
|
+
{
|
|
327
|
+
"field_types": fields_dict,
|
|
328
|
+
"IntDefinition": int_definition,
|
|
329
|
+
"size": int_definition.size,
|
|
330
|
+
},
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class PointerDefinition(DataDefinition):
|
|
335
|
+
size: int = 0
|
|
336
|
+
target_type: Type[DataDefinition]
|
|
337
|
+
address_type: Type[IntDefinition] = Int4Definition
|
|
338
|
+
is_forward_ref: bool = False
|
|
339
|
+
|
|
340
|
+
def __init__(
|
|
341
|
+
self,
|
|
342
|
+
address: int,
|
|
343
|
+
metadata: Optional[Metadata],
|
|
344
|
+
):
|
|
345
|
+
super().__init__(metadata)
|
|
346
|
+
self.address = address
|
|
347
|
+
|
|
348
|
+
def __repr__(self) -> str:
|
|
349
|
+
cls_name = type(self).__name__
|
|
350
|
+
return f"{cls_name}({self.add_metadata_repr(hex(self.address))})"
|
|
351
|
+
|
|
352
|
+
def __str__(self) -> str:
|
|
353
|
+
cls_name = type(self).__name__
|
|
354
|
+
|
|
355
|
+
target_name = self.target_type.__name__
|
|
356
|
+
p_str = f"pointer to object of type: {target_name}"
|
|
357
|
+
return (
|
|
358
|
+
f"{cls_name}({self.add_metadata_repr(hex(self.address))})\n*{self.address:#x} -> {target_name} :: {p_str}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
@classmethod
|
|
362
|
+
def parser(cls, reader: Reader) -> Tuple[PointerDefinition, Reader]:
|
|
363
|
+
int_definition, new_reader = cls.address_type.parser(reader)
|
|
364
|
+
return (cls(int_definition.value, metadata=int_definition.metadata), new_reader)
|
|
365
|
+
|
|
366
|
+
def resolve(self, reader: Reader) -> DataDefinition:
|
|
367
|
+
if self.address == 0:
|
|
368
|
+
raise ValueError(f"Attempting to resolve a null pointer: {self}")
|
|
369
|
+
new_reader = reader.new_reader(self.address)
|
|
370
|
+
resolved, _ = self.target_type.parser(new_reader)
|
|
371
|
+
return resolved
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def create_pointer_definition(
|
|
375
|
+
target_type: Type[DataDefinition],
|
|
376
|
+
address_type: Type[IntDefinition],
|
|
377
|
+
) -> Type[PointerDefinition]:
|
|
378
|
+
return type(
|
|
379
|
+
f"Pointer2{target_type.__name__}",
|
|
380
|
+
(PointerDefinition,),
|
|
381
|
+
{
|
|
382
|
+
"target_type": target_type,
|
|
383
|
+
"address_type": address_type,
|
|
384
|
+
"size": target_type.size,
|
|
385
|
+
},
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _create_pointer_definition_from_forward_pointer(
|
|
390
|
+
target_type: Type[DataDefinition],
|
|
391
|
+
forward_pointer: ForwardPointer,
|
|
392
|
+
) -> Type[PointerDefinition]:
|
|
393
|
+
return create_pointer_definition(
|
|
394
|
+
target_type=target_type,
|
|
395
|
+
address_type=forward_pointer.address_type,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class ArrayDefinition(DataDefinition):
|
|
400
|
+
target_type: Type[DataDefinition]
|
|
401
|
+
size: int
|
|
402
|
+
_fields_parser: Parser
|
|
403
|
+
_array_splitter = "\n" + 80 * "=" + "\n"
|
|
404
|
+
|
|
405
|
+
def __init__(
|
|
406
|
+
self,
|
|
407
|
+
fields: Sequence[DataDefinition],
|
|
408
|
+
metadata: Optional[Metadata],
|
|
409
|
+
):
|
|
410
|
+
super().__init__(metadata)
|
|
411
|
+
self.fields = fields
|
|
412
|
+
|
|
413
|
+
def __str__(self) -> str:
|
|
414
|
+
cls_name = type(self).__name__
|
|
415
|
+
target_cls = self.target_type.__name__
|
|
416
|
+
fields_strs = map(str, self.fields)
|
|
417
|
+
fields_strs_indented = map(indent_4, fields_strs)
|
|
418
|
+
fields_str = self._array_splitter.join(fields_strs_indented)
|
|
419
|
+
return f"{cls_name}:: {target_cls}[{self.size}] {self.add_metadata_repr()}:\n{fields_str}"
|
|
420
|
+
|
|
421
|
+
@classmethod
|
|
422
|
+
def parser(cls, reader: Reader) -> Tuple[ArrayDefinition, Reader]:
|
|
423
|
+
fields, new_reader = cls._fields_parser(reader)
|
|
424
|
+
return cls(fields, metadata=fields[0].metadata), new_reader
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def create_array_definition(
|
|
428
|
+
target_type: Type[DataDefinition],
|
|
429
|
+
size: int,
|
|
430
|
+
) -> Type[ArrayDefinition]:
|
|
431
|
+
fields_parser = create_repeat_parser(size, target_type.parser)
|
|
432
|
+
return type(
|
|
433
|
+
f"{target_type.__name__}Array",
|
|
434
|
+
(ArrayDefinition,),
|
|
435
|
+
{
|
|
436
|
+
"target_type": target_type,
|
|
437
|
+
"size": size,
|
|
438
|
+
"_fields_parser": fields_parser,
|
|
439
|
+
},
|
|
440
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from functools import reduce
|
|
5
|
+
from itertools import chain, repeat
|
|
6
|
+
from typing import (
|
|
7
|
+
Any,
|
|
8
|
+
Callable,
|
|
9
|
+
Tuple,
|
|
10
|
+
TypeVar,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Reader(ABC):
|
|
15
|
+
|
|
16
|
+
def __init__(self, address: int):
|
|
17
|
+
self.address = address
|
|
18
|
+
self.offset = 0
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def read(self, size: int) -> bytes:
|
|
22
|
+
self.offset += size
|
|
23
|
+
return bytes(size)
|
|
24
|
+
|
|
25
|
+
def new_reader(self, address: int) -> Reader:
|
|
26
|
+
return type(self)(address)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
T = TypeVar("T")
|
|
30
|
+
A = TypeVar("A")
|
|
31
|
+
B = TypeVar("B")
|
|
32
|
+
|
|
33
|
+
Parser = Callable[[Reader], Tuple[T, Reader]]
|
|
34
|
+
MakeParser = Callable[[A], Parser[B]]
|
|
35
|
+
ResultsTup = Tuple[Tuple[Any], Reader]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fmap_parser(func: Callable[[A], B], parser: Parser[A]) -> Parser[B]:
|
|
39
|
+
def fmapped_parser(reader: Reader) -> Tuple[B, Reader]:
|
|
40
|
+
result_from_parser, result_reader = parser(reader)
|
|
41
|
+
return func(result_from_parser), result_reader
|
|
42
|
+
|
|
43
|
+
return fmapped_parser
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def combine_parsers(
|
|
47
|
+
parser_a: Parser[A],
|
|
48
|
+
make_parser_b: MakeParser[A, B],
|
|
49
|
+
) -> Parser:
|
|
50
|
+
">>= (bind) if you squint"
|
|
51
|
+
|
|
52
|
+
def combined_parser(reader: Reader) -> Tuple[B, Reader]:
|
|
53
|
+
val_a, reader_a = parser_a(reader)
|
|
54
|
+
parser_b = make_parser_b(val_a)
|
|
55
|
+
return parser_b(reader_a)
|
|
56
|
+
|
|
57
|
+
return combined_parser
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parser_reducer(results_tup: ResultsTup, parser: Parser) -> ResultsTup:
|
|
61
|
+
results, reader = results_tup
|
|
62
|
+
parser_res, parser_reader = parser(reader)
|
|
63
|
+
new_results = tuple(chain(results, [parser_res]))
|
|
64
|
+
return new_results, parser_reader
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_sequence_parser(*parsers: Parser) -> Parser[Tuple[Any]]:
|
|
68
|
+
empty_tuple = ()
|
|
69
|
+
|
|
70
|
+
def sequence_parser(reader: Reader) -> ResultsTup:
|
|
71
|
+
initial: Tuple[Tuple[Any, ...], Reader] = (empty_tuple, reader)
|
|
72
|
+
return reduce(parser_reducer, parsers, initial)
|
|
73
|
+
|
|
74
|
+
return sequence_parser
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_repeat_parser(num: int, parser: Parser) -> Parser:
|
|
78
|
+
return create_sequence_parser(*repeat(parser, num))
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
AddressData = Tuple[int, bytes]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Reader(ABC):
|
|
11
|
+
|
|
12
|
+
def __init__(self, address: int):
|
|
13
|
+
self.address = address
|
|
14
|
+
self.offset = 0
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def read(self, size: int) -> AddressData:
|
|
18
|
+
current_offset = self.offset
|
|
19
|
+
self.offset += size
|
|
20
|
+
return (self.address + current_offset, bytes(size))
|
|
21
|
+
|
|
22
|
+
def new_reader(self, address: int) -> Reader:
|
|
23
|
+
return type(self)(address)
|