xferx 3.0.0__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.
- cstruct/__init__.py +226 -0
- cstruct/abstract.py +406 -0
- cstruct/base.py +75 -0
- cstruct/c_expr.py +205 -0
- cstruct/c_parser.py +495 -0
- cstruct/cenum.py +8 -0
- cstruct/cstruct.py +90 -0
- cstruct/exceptions.py +51 -0
- cstruct/field.py +279 -0
- cstruct/mem_cstruct.py +152 -0
- cstruct/native_types.py +228 -0
- tests/__init__.py +0 -0
- tests/test_appledos.py +96 -0
- tests/test_caps11.py +71 -0
- tests/test_cmd.py +100 -0
- tests/test_decsys.py +169 -0
- tests/test_dms.py +345 -0
- tests/test_dos11.py +154 -0
- tests/test_dos11dectape.py +68 -0
- tests/test_dos11magtest.py +74 -0
- tests/test_files11.py +37 -0
- tests/test_misc.py +169 -0
- tests/test_native.py +32 -0
- tests/test_nova_dgdos.py +131 -0
- tests/test_nova_dgdosmagtape.py +89 -0
- tests/test_os8.py +194 -0
- tests/test_pascal.py +75 -0
- tests/test_prodos.py +202 -0
- tests/test_rsts.py +133 -0
- tests/test_rt11.py +60 -0
- tests/test_solo.py +143 -0
- tests/test_unixv0.py +34 -0
- tests/test_unixv6.py +44 -0
- xferx/VERSION +1 -0
- xferx/__init__.py +0 -0
- xferx/abstract.py +350 -0
- xferx/apple2/__init__.py +0 -0
- xferx/apple2/appledosfs.py +1189 -0
- xferx/apple2/commons.py +170 -0
- xferx/apple2/disk.py +177 -0
- xferx/apple2/pascalfs.py +754 -0
- xferx/apple2/ppm.py +449 -0
- xferx/apple2/prodosfs.py +2142 -0
- xferx/block.py +169 -0
- xferx/cache.py +48 -0
- xferx/commons.py +174 -0
- xferx/native.py +340 -0
- xferx/nova/__init__.py +0 -0
- xferx/nova/dgdosdumpfs.py +760 -0
- xferx/nova/dgdosfs.py +1731 -0
- xferx/nova/dgdosmagtapefs.py +408 -0
- xferx/pdp11/__init__.py +0 -0
- xferx/pdp11/caps11fs.py +556 -0
- xferx/pdp11/dos11fs.py +1120 -0
- xferx/pdp11/dos11magtapefs.py +525 -0
- xferx/pdp11/files11fs.py +789 -0
- xferx/pdp11/rad50.py +73 -0
- xferx/pdp11/rstsfs.py +1150 -0
- xferx/pdp11/rt11fs.py +795 -0
- xferx/pdp11/solofs.py +1119 -0
- xferx/pdp7/__init__.py +0 -0
- xferx/pdp7/block.py +114 -0
- xferx/pdp7/codes.py +362 -0
- xferx/pdp7/decsysfs.py +1149 -0
- xferx/pdp7/unix0fs.py +356 -0
- xferx/pdp8/__init__.py +0 -0
- xferx/pdp8/dmsfs.py +1266 -0
- xferx/pdp8/os8fs.py +1046 -0
- xferx/rx.py +168 -0
- xferx/shell.py +1050 -0
- xferx/tape.py +138 -0
- xferx/uic.py +121 -0
- xferx/unixfs.py +1139 -0
- xferx/volumes.py +242 -0
- xferx-3.0.0.dist-info/METADATA +256 -0
- xferx-3.0.0.dist-info/RECORD +80 -0
- xferx-3.0.0.dist-info/WHEEL +5 -0
- xferx-3.0.0.dist-info/entry_points.txt +2 -0
- xferx-3.0.0.dist-info/licenses/LICENSE +21 -0
- xferx-3.0.0.dist-info/top_level.txt +3 -0
cstruct/__init__.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# Published under the terms of the MIT license.
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to
|
|
8
|
+
# deal in the Software without restriction, including without limitation the
|
|
9
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
10
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
22
|
+
# IN THE SOFTWARE.
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
__author__ = "Andrea Bonomi <andrea.bonomi@gmail.com>"
|
|
26
|
+
__license__ = "MIT"
|
|
27
|
+
__version__ = "6.1"
|
|
28
|
+
__date__ = "15 August 2013"
|
|
29
|
+
|
|
30
|
+
from typing import Any, Dict, Optional, Type, Union
|
|
31
|
+
|
|
32
|
+
from .abstract import AbstractCEnum, AbstractCStruct, CStructMeta
|
|
33
|
+
from .base import (
|
|
34
|
+
BIG_ENDIAN,
|
|
35
|
+
CHAR_ZERO,
|
|
36
|
+
DEFINES,
|
|
37
|
+
ENUMS,
|
|
38
|
+
LITTLE_ENDIAN,
|
|
39
|
+
NATIVE_ORDER,
|
|
40
|
+
STRUCTS,
|
|
41
|
+
TYPEDEFS,
|
|
42
|
+
)
|
|
43
|
+
from .c_parser import parse_struct_def
|
|
44
|
+
from .cenum import CEnum
|
|
45
|
+
from .cstruct import CStruct
|
|
46
|
+
from .mem_cstruct import MemCStruct
|
|
47
|
+
from .native_types import get_native_type
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"LITTLE_ENDIAN",
|
|
51
|
+
"BIG_ENDIAN",
|
|
52
|
+
"NATIVE_ORDER",
|
|
53
|
+
"CHAR_ZERO",
|
|
54
|
+
"CStruct",
|
|
55
|
+
"MemCStruct",
|
|
56
|
+
"CEnum",
|
|
57
|
+
"define",
|
|
58
|
+
"undef",
|
|
59
|
+
"getdef",
|
|
60
|
+
"typedef",
|
|
61
|
+
"get_type",
|
|
62
|
+
"sizeof",
|
|
63
|
+
"parse",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def define(key: str, value: Any) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Define a constant that can be used in the C struct
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
>>> define("INIT_THREAD_SIZE", 16384)
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
key: identifier
|
|
76
|
+
value: value of the constant
|
|
77
|
+
"""
|
|
78
|
+
DEFINES[key] = value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def undef(key: str) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Undefine a symbol that was previously defined with define
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
>>> define("INIT_THREAD_SIZE", 16384)
|
|
87
|
+
>>> undef("INIT_THREAD_SIZE")
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
key: identifier
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
KeyError: If key is not defined
|
|
94
|
+
"""
|
|
95
|
+
del DEFINES[key]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def getdef(key: str) -> Any:
|
|
99
|
+
"""
|
|
100
|
+
Return the value for a constant
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
>>> define("INIT_THREAD_SIZE", 16384)
|
|
104
|
+
>>> getdef("INIT_THREAD_SIZE")
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
key: identifier
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
KeyError: If key is not defined
|
|
111
|
+
"""
|
|
112
|
+
return DEFINES[key]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def typedef(type_: str, alias: str) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Define an alias name for a data type
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
>>> typedef("int", "status")
|
|
121
|
+
>>> sizeof("status")
|
|
122
|
+
4
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
type_: data type
|
|
126
|
+
alias: new alias name
|
|
127
|
+
"""
|
|
128
|
+
TYPEDEFS[alias] = type_
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_type(type_: str) -> Any:
|
|
132
|
+
"""
|
|
133
|
+
Get a data type (struct, union, enum) by name
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
>>> get_type("struct Position")
|
|
137
|
+
<class 'abc.Position'>
|
|
138
|
+
>>> get_type("enum htmlfont")
|
|
139
|
+
<enum 'htmlfont'>
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
class: data type class
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
KeyError: If type is not defined
|
|
149
|
+
"""
|
|
150
|
+
while type_ in TYPEDEFS:
|
|
151
|
+
type_ = TYPEDEFS[type_]
|
|
152
|
+
if isinstance(type_, CStructMeta):
|
|
153
|
+
return type_
|
|
154
|
+
elif type_.startswith("struct ") or type_.startswith("union "):
|
|
155
|
+
kind, type_ = type_.split(" ", 1)
|
|
156
|
+
try:
|
|
157
|
+
return STRUCTS[type_]
|
|
158
|
+
except KeyError:
|
|
159
|
+
raise KeyError(f"Unknown {kind} `{type_}`")
|
|
160
|
+
elif type_.startswith("enum "):
|
|
161
|
+
kind, type_ = type_.split(" ", 1)
|
|
162
|
+
try:
|
|
163
|
+
return ENUMS[type_]
|
|
164
|
+
except KeyError:
|
|
165
|
+
raise KeyError(f"Unknown {kind} `{type_}`")
|
|
166
|
+
else:
|
|
167
|
+
return get_native_type(type_)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def sizeof(type_: str) -> int:
|
|
171
|
+
"""
|
|
172
|
+
Return the size of the type.
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
>>> sizeof("struct Position")
|
|
176
|
+
16
|
|
177
|
+
>>> sizeof('enum htmlfont')
|
|
178
|
+
4
|
|
179
|
+
>>> sizeof("int")
|
|
180
|
+
4
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
type_: C type, struct or union (e.g. 'short int' or 'struct ZYZ'), enum or native type
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
size: size in bytes
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
KeyError: If type is not defined
|
|
190
|
+
"""
|
|
191
|
+
while type_ in TYPEDEFS:
|
|
192
|
+
type_ = TYPEDEFS[type_]
|
|
193
|
+
data_type = get_type(type_)
|
|
194
|
+
return data_type.sizeof()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def parse(
|
|
198
|
+
__struct__: str, __cls__: Optional[Type[AbstractCStruct]] = None, **kargs: Dict[str, Any]
|
|
199
|
+
) -> Union[Type[AbstractCStruct], Type[AbstractCEnum], None]:
|
|
200
|
+
"""
|
|
201
|
+
Return a new class mapping a C struct/union/enum definition.
|
|
202
|
+
If the string does not contains any definition, return None.
|
|
203
|
+
If the string contains multiple struct/union/enum definitions, returns the last definition.
|
|
204
|
+
|
|
205
|
+
Examples:
|
|
206
|
+
>>> cstruct.parse('struct Pair { unsigned char a; unsigned char b; };')
|
|
207
|
+
<class 'abc.Pair'>
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
__struct__ (str): definition of the struct (or union/enum) in C syntax
|
|
211
|
+
__cls__ (type): super class - CStruct(default) or MemCStruct
|
|
212
|
+
__byte_order__ (str): byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
cls: __cls__ subclass
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
cstruct.exceptions.ParserError: Parsing exception
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
if __cls__ is None:
|
|
222
|
+
__cls__ = MemCStruct
|
|
223
|
+
cls_def = parse_struct_def(__struct__, __cls__=__cls__, process_muliple_definition=True, **kargs)
|
|
224
|
+
if cls_def is None:
|
|
225
|
+
return None
|
|
226
|
+
return cls_def["__cls__"].parse(cls_def, **kargs)
|
cstruct/abstract.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# Published under the terms of the MIT license.
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to
|
|
8
|
+
# deal in the Software without restriction, including without limitation the
|
|
9
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
10
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
22
|
+
# IN THE SOFTWARE.
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import struct
|
|
27
|
+
from abc import ABCMeta
|
|
28
|
+
from collections import OrderedDict
|
|
29
|
+
from enum import EnumMeta, IntEnum, _EnumDict
|
|
30
|
+
from io import StringIO
|
|
31
|
+
from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Type, Union
|
|
32
|
+
|
|
33
|
+
from .base import DEFAULT_ENUM_SIZE, ENUM_SIZE_TO_C_TYPE, ENUMS, STRUCTS
|
|
34
|
+
from .c_parser import Tokens, parse_enum, parse_enum_def, parse_struct, parse_struct_def
|
|
35
|
+
from .exceptions import CStructException, ParserError
|
|
36
|
+
from .field import FieldType, calculate_padding
|
|
37
|
+
from .native_types import get_native_type
|
|
38
|
+
|
|
39
|
+
__all__ = ["CStructMeta", "AbstractCStruct", "CEnumMeta", "AbstractCEnum"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CStructMeta(ABCMeta):
|
|
43
|
+
__size__: int = 0
|
|
44
|
+
|
|
45
|
+
def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]:
|
|
46
|
+
__struct__ = namespace.get("__struct__", None)
|
|
47
|
+
namespace["__cls__"] = bases[0] if bases else None
|
|
48
|
+
# Parse the struct
|
|
49
|
+
if "__struct__" in namespace:
|
|
50
|
+
if isinstance(namespace["__struct__"], (str, Tokens)):
|
|
51
|
+
namespace.update(parse_struct(**namespace))
|
|
52
|
+
__struct__ = True
|
|
53
|
+
if "__def__" in namespace:
|
|
54
|
+
namespace.update(parse_struct_def(**namespace))
|
|
55
|
+
__struct__ = True
|
|
56
|
+
# Create the new class
|
|
57
|
+
new_class: Type[Any] = super().__new__(metacls, name, bases, namespace)
|
|
58
|
+
# Register the class
|
|
59
|
+
if __struct__ is not None and not namespace.get("__anonymous__"):
|
|
60
|
+
STRUCTS[name] = new_class
|
|
61
|
+
return new_class
|
|
62
|
+
|
|
63
|
+
def __len__(cls) -> int:
|
|
64
|
+
"Structure size (in bytes)"
|
|
65
|
+
return cls.__size__
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def size(cls) -> int:
|
|
69
|
+
"Structure size (in bytes)"
|
|
70
|
+
return cls.__size__
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AbstractCStruct(metaclass=CStructMeta):
|
|
74
|
+
"""
|
|
75
|
+
Abstract C struct to Python class
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
__size__: int = 0
|
|
79
|
+
" Size in bytes "
|
|
80
|
+
__fields__: List[str] = []
|
|
81
|
+
" Struct/union fields "
|
|
82
|
+
__fields_types__: Dict[str, FieldType]
|
|
83
|
+
" Dictionary mapping field names to types "
|
|
84
|
+
__byte_order__: Optional[str] = None
|
|
85
|
+
" Byte order "
|
|
86
|
+
__alignment__: int = 0
|
|
87
|
+
" Alignment "
|
|
88
|
+
__is_union__: bool = False
|
|
89
|
+
" True if the class is an union, False if it is a struct "
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self, buffer: Optional[Union[bytes, BinaryIO]] = None, flexible_array_length: Optional[int] = None, **kargs: Dict[str, Any]
|
|
93
|
+
) -> None:
|
|
94
|
+
self.set_flexible_array_length(flexible_array_length)
|
|
95
|
+
self.__fields__ = [x for x in self.__fields__] # Create a copy
|
|
96
|
+
self.__fields_types__ = OrderedDict({k: v.copy() for k, v in self.__fields_types__.items()}) # Create a copy
|
|
97
|
+
if buffer is not None:
|
|
98
|
+
self.unpack(buffer)
|
|
99
|
+
else:
|
|
100
|
+
try:
|
|
101
|
+
self.unpack(buffer)
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
for key, value in kargs.items():
|
|
105
|
+
setattr(self, key, value)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def parse(
|
|
109
|
+
cls,
|
|
110
|
+
__struct__: Union[str, Tokens, Dict[str, Any]],
|
|
111
|
+
__name__: Optional[str] = None,
|
|
112
|
+
__byte_order__: Optional[str] = None,
|
|
113
|
+
__is_union__: Optional[bool] = False,
|
|
114
|
+
**kargs: Dict[str, Any],
|
|
115
|
+
) -> Type["AbstractCStruct"]:
|
|
116
|
+
"""
|
|
117
|
+
Return a new class mapping a C struct/union definition.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
__struct__: definition of the struct (or union) in C syntax
|
|
121
|
+
__name__: name of the new class. If empty, a name based on the __struct__ hash is generated
|
|
122
|
+
__byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
|
|
123
|
+
__is_union__: True for union, False for struct
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
cls: a new class mapping the definition
|
|
127
|
+
"""
|
|
128
|
+
cls_kargs: Dict[str, Any] = dict(kargs)
|
|
129
|
+
if __byte_order__ is not None:
|
|
130
|
+
cls_kargs["__byte_order__"] = __byte_order__
|
|
131
|
+
if __is_union__ is not None:
|
|
132
|
+
cls_kargs["__is_union__"] = __is_union__
|
|
133
|
+
cls_kargs["__struct__"] = __struct__
|
|
134
|
+
if isinstance(__struct__, (str, Tokens)):
|
|
135
|
+
del cls_kargs["__struct__"]
|
|
136
|
+
cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs))
|
|
137
|
+
cls_kargs["__struct__"] = None
|
|
138
|
+
elif isinstance(__struct__, dict):
|
|
139
|
+
del cls_kargs["__struct__"]
|
|
140
|
+
cls_kargs.update(__struct__)
|
|
141
|
+
cls_kargs["__struct__"] = None
|
|
142
|
+
__name__ = cls_kargs.get("__name__") or __name__
|
|
143
|
+
if __name__ is None: # Anonymous struct
|
|
144
|
+
__name__ = cls.__name__ + "_" + hashlib.sha1(str(__struct__).encode("utf-8")).hexdigest()
|
|
145
|
+
cls_kargs["__anonymous__"] = True
|
|
146
|
+
cls_kargs["__name__"] = __name__
|
|
147
|
+
return type(__name__, (cls,), cls_kargs)
|
|
148
|
+
|
|
149
|
+
def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Set flexible array length (i.e. number of elements)
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
flexible_array_length: flexible array length
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
CStructException: If flexible array is not present in the structure
|
|
158
|
+
"""
|
|
159
|
+
if flexible_array_length is not None:
|
|
160
|
+
# Search for the flexible array
|
|
161
|
+
flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0]
|
|
162
|
+
if flexible_array is None:
|
|
163
|
+
raise CStructException("Flexible array not found in struct")
|
|
164
|
+
flexible_array.vlen_ex = flexible_array_length
|
|
165
|
+
|
|
166
|
+
def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Unpack bytes containing packed C structure data
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
buffer: bytes or binary stream to be unpacked
|
|
172
|
+
flexible_array_length: flexible array length
|
|
173
|
+
"""
|
|
174
|
+
self.set_flexible_array_length(flexible_array_length)
|
|
175
|
+
if hasattr(buffer, "read"):
|
|
176
|
+
buffer = buffer.read(self.size) # type: ignore
|
|
177
|
+
if not buffer:
|
|
178
|
+
return False
|
|
179
|
+
return self.unpack_from(buffer)
|
|
180
|
+
|
|
181
|
+
def unpack_from(
|
|
182
|
+
self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None
|
|
183
|
+
) -> bool: # pragma: no cover
|
|
184
|
+
"""
|
|
185
|
+
Unpack bytes containing packed C structure data
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
buffer: bytes to be unpacked
|
|
189
|
+
offset: optional buffer offset
|
|
190
|
+
flexible_array_length: flexible array length
|
|
191
|
+
"""
|
|
192
|
+
raise NotImplementedError
|
|
193
|
+
|
|
194
|
+
def pack(self) -> bytes: # pragma: no cover
|
|
195
|
+
"""
|
|
196
|
+
Pack the structure data into bytes
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
bytes: The packed structure
|
|
200
|
+
"""
|
|
201
|
+
raise NotImplementedError
|
|
202
|
+
|
|
203
|
+
def pack_into(self, buffer: bytearray, offset: int = 0) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Pack the structure data into a buffer
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
buffer: target buffer (must be large enough to contain the packed structure)
|
|
209
|
+
offset: optional buffer offset
|
|
210
|
+
"""
|
|
211
|
+
tmp = self.pack()
|
|
212
|
+
buffer[offset : offset + len(tmp)] = tmp
|
|
213
|
+
|
|
214
|
+
def clear(self) -> None:
|
|
215
|
+
self.unpack(None)
|
|
216
|
+
|
|
217
|
+
def __len__(self) -> int:
|
|
218
|
+
"Actual structure size (in bytes)"
|
|
219
|
+
return self.size
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def size(self) -> int:
|
|
223
|
+
"Actual structure size (in bytes)"
|
|
224
|
+
if not self.__fields_types__: # no fields
|
|
225
|
+
return 0
|
|
226
|
+
elif self.__is_union__: # C union
|
|
227
|
+
# Calculate the sizeof union as size of its largest element
|
|
228
|
+
return max(x.vsize for x in self.__fields_types__.values())
|
|
229
|
+
else: # C struct
|
|
230
|
+
# Calculate the sizeof struct as last item's offset + size + padding
|
|
231
|
+
last_field_type = list(self.__fields_types__.values())[-1]
|
|
232
|
+
size = last_field_type.offset + last_field_type.vsize
|
|
233
|
+
padding = calculate_padding(self.__byte_order__, self.__alignment__, size)
|
|
234
|
+
return size + padding
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def sizeof(cls) -> int:
|
|
238
|
+
"Structure size in bytes (flexible array member size is omitted)"
|
|
239
|
+
return cls.__size__
|
|
240
|
+
|
|
241
|
+
def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = None) -> str:
|
|
242
|
+
"""
|
|
243
|
+
Return memory content in hexadecimal
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
start_addr: start address
|
|
247
|
+
end_addr: end address
|
|
248
|
+
"""
|
|
249
|
+
buffer = StringIO()
|
|
250
|
+
if hasattr(self, "__mem__"):
|
|
251
|
+
mem = self.__mem__[self.__base__ :]
|
|
252
|
+
else:
|
|
253
|
+
mem = self.pack()
|
|
254
|
+
if end_addr is None:
|
|
255
|
+
end_addr = self.size
|
|
256
|
+
for i in range(start_addr or 0, end_addr, 16):
|
|
257
|
+
row = mem[i : min(i + 16, end_addr)]
|
|
258
|
+
buffer.write(f"{i:08x} ")
|
|
259
|
+
for j, c in enumerate(row):
|
|
260
|
+
separator = " " if j == 7 else ""
|
|
261
|
+
buffer.write(f" {c:02x}{separator}")
|
|
262
|
+
for j in range(len(row) - 1, 15):
|
|
263
|
+
separator = " " if j == 7 else ""
|
|
264
|
+
buffer.write(f" {separator}")
|
|
265
|
+
buffer.write(" |")
|
|
266
|
+
for c in row:
|
|
267
|
+
buffer.write(chr(c) if c >= 32 and c < 127 else ".")
|
|
268
|
+
for j in range(len(row) - 1, 15):
|
|
269
|
+
buffer.write(" ")
|
|
270
|
+
buffer.write("|")
|
|
271
|
+
buffer.write("\n")
|
|
272
|
+
buffer.seek(0, 0)
|
|
273
|
+
return buffer.read()
|
|
274
|
+
|
|
275
|
+
def __eq__(self, other: Any) -> bool:
|
|
276
|
+
return other is not None and isinstance(other, self.__class__) and self.__dict__ == other.__dict__
|
|
277
|
+
|
|
278
|
+
def __ne__(self, other: Any) -> bool:
|
|
279
|
+
return not self.__eq__(other)
|
|
280
|
+
|
|
281
|
+
def __str__(self) -> str:
|
|
282
|
+
result = []
|
|
283
|
+
for field in self.__fields__:
|
|
284
|
+
result.append(field + "=" + str(getattr(self, field, None)))
|
|
285
|
+
return type(self).__name__ + "(" + ", ".join(result) + ")"
|
|
286
|
+
|
|
287
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
288
|
+
return self.__str__()
|
|
289
|
+
|
|
290
|
+
def __getstate__(self) -> bytes:
|
|
291
|
+
"""
|
|
292
|
+
This method is called and the returned object is pickled
|
|
293
|
+
as the contents for the instance, instead of the contents of
|
|
294
|
+
the instance’s dictionary
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
bytes: The packed structure
|
|
298
|
+
"""
|
|
299
|
+
return self.pack()
|
|
300
|
+
|
|
301
|
+
def __setstate__(self, state: bytes) -> bool:
|
|
302
|
+
"""
|
|
303
|
+
This method it is called with the unpickled state
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
state: bytes to be unpacked
|
|
307
|
+
"""
|
|
308
|
+
return self.unpack(state)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class CEnumMeta(EnumMeta):
|
|
312
|
+
__size__: int
|
|
313
|
+
__native_format__: str
|
|
314
|
+
|
|
315
|
+
class WrapperDict(_EnumDict):
|
|
316
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
317
|
+
env = None
|
|
318
|
+
if key == "__enum__":
|
|
319
|
+
env = parse_enum(value)
|
|
320
|
+
elif key == "__def__":
|
|
321
|
+
env = parse_enum_def(value)
|
|
322
|
+
|
|
323
|
+
if env is not None:
|
|
324
|
+
# register the enum constants in the object namespace,
|
|
325
|
+
# using the Python Enum class Namespace dict that does the
|
|
326
|
+
# heavy lifting
|
|
327
|
+
for k, v in env["__constants__"].items():
|
|
328
|
+
super().__setitem__(k, v)
|
|
329
|
+
else:
|
|
330
|
+
return super().__setitem__(key, value)
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def __prepare__(metacls, cls, bases, **kwds):
|
|
334
|
+
namespace = EnumMeta.__prepare__(cls, bases, **kwds)
|
|
335
|
+
namespace.__class__ = metacls.WrapperDict
|
|
336
|
+
return namespace
|
|
337
|
+
|
|
338
|
+
def __new__(metacls: Type["CEnumMeta"], cls: str, bases: Tuple[Type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta":
|
|
339
|
+
inst = super().__new__(metacls, cls, bases, classdict, **kwds)
|
|
340
|
+
if len(inst) > 0:
|
|
341
|
+
if classdict.get("__native_format__"): # data type specified
|
|
342
|
+
inst.__size__ = struct.calcsize(classdict["__native_format__"])
|
|
343
|
+
elif "__size__" in classdict: # size specified
|
|
344
|
+
try:
|
|
345
|
+
inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
|
|
346
|
+
except KeyError:
|
|
347
|
+
raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}")
|
|
348
|
+
else: # default
|
|
349
|
+
inst.__size__ = DEFAULT_ENUM_SIZE
|
|
350
|
+
inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
|
|
351
|
+
print(f"Warning: __size__ not specified for enum {cls}. Will default to {DEFAULT_ENUM_SIZE} bytes")
|
|
352
|
+
|
|
353
|
+
if not classdict.get("__anonymous__", False):
|
|
354
|
+
ENUMS[cls] = inst
|
|
355
|
+
return inst
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def size(cls) -> int:
|
|
359
|
+
"Enum size (in bytes)"
|
|
360
|
+
return cls.__size__
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class AbstractCEnum(IntEnum, metaclass=CEnumMeta):
|
|
364
|
+
"""
|
|
365
|
+
Abstract C enum to Python class
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def parse(
|
|
370
|
+
cls,
|
|
371
|
+
__enum__: Union[str, Tokens, Dict[str, Any]],
|
|
372
|
+
__name__: Optional[str] = None,
|
|
373
|
+
__size__: Optional[int] = None,
|
|
374
|
+
__native_format__: Optional[str] = None,
|
|
375
|
+
**kargs: Dict[str, Any],
|
|
376
|
+
) -> Type["AbstractCEnum"]:
|
|
377
|
+
"""
|
|
378
|
+
Return a new Python Enum class mapping a C enum definition
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
__enum__: Definition of the enum in C syntax
|
|
382
|
+
__name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated
|
|
383
|
+
__size__: Number of bytes that the enum should be read as
|
|
384
|
+
__native_format__: struct module format
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
cls: A new class mapping the definition
|
|
388
|
+
"""
|
|
389
|
+
cls_kargs: Dict[str, Any] = dict(kargs)
|
|
390
|
+
if __size__ is not None:
|
|
391
|
+
cls_kargs["__size__"] = __size__
|
|
392
|
+
if __native_format__ is not None:
|
|
393
|
+
cls_kargs["__native_format__"] = __native_format__
|
|
394
|
+
|
|
395
|
+
if isinstance(__enum__, (str, Tokens)):
|
|
396
|
+
cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs))
|
|
397
|
+
elif isinstance(__enum__, dict):
|
|
398
|
+
cls_kargs.update(__enum__)
|
|
399
|
+
|
|
400
|
+
__name__ = cls_kargs.get("__name__") or __name__
|
|
401
|
+
if __name__ is None:
|
|
402
|
+
__name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest()
|
|
403
|
+
cls_kargs["__anonymous__"] = True
|
|
404
|
+
|
|
405
|
+
cls_kargs.update(cls_kargs["__constants__"])
|
|
406
|
+
return cls(__name__, cls_kargs)
|
cstruct/base.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# Copyright (c) 2013-2025 Andrea Bonomi <andrea.bonomi@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# Published under the terms of the MIT license.
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
# of this software and associated documentation files (the "Software"), to
|
|
8
|
+
# deal in the Software without restriction, including without limitation the
|
|
9
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
10
|
+
# sell copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
# furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
22
|
+
# IN THE SOFTWARE.
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
from typing import TYPE_CHECKING, Any, Dict, Type
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from .abstract import AbstractCEnum, AbstractCStruct
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"LITTLE_ENDIAN",
|
|
32
|
+
"BIG_ENDIAN",
|
|
33
|
+
"NATIVE_ORDER",
|
|
34
|
+
"CHAR_ZERO",
|
|
35
|
+
"STRUCTS",
|
|
36
|
+
"DEFINES",
|
|
37
|
+
"TYPEDEFS",
|
|
38
|
+
"CHAR_ZERO",
|
|
39
|
+
"DEFAULT_ENUM_SIZE",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
LITTLE_ENDIAN = "<"
|
|
43
|
+
"Little-endian, std. size & alignment"
|
|
44
|
+
BIG_ENDIAN = ">"
|
|
45
|
+
"Big-endian, std. size & alignment"
|
|
46
|
+
NATIVE_ORDER = "@"
|
|
47
|
+
"Native order, size & alignment"
|
|
48
|
+
|
|
49
|
+
STRUCTS: Dict[str, Type["AbstractCStruct"]] = {}
|
|
50
|
+
|
|
51
|
+
ENUMS: Dict[str, Type["AbstractCEnum"]] = {}
|
|
52
|
+
|
|
53
|
+
DEFINES: Dict[str, Any] = {}
|
|
54
|
+
|
|
55
|
+
TYPEDEFS: Dict[str, str] = {
|
|
56
|
+
"short int": "short",
|
|
57
|
+
"unsigned short int": "unsigned short",
|
|
58
|
+
"ushort": "unsigned short",
|
|
59
|
+
"long int": "long",
|
|
60
|
+
"unsigned long int": "unsigned long",
|
|
61
|
+
"int8_t": "int8",
|
|
62
|
+
"uint8_t": "uint8",
|
|
63
|
+
"int16_t": "int16",
|
|
64
|
+
"uint16_t": "uint16",
|
|
65
|
+
"int32_t": "int32",
|
|
66
|
+
"uint32_t": "uint32",
|
|
67
|
+
"int64_t": "int64",
|
|
68
|
+
"uint64_t": "uint64",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ENUM_SIZE_TO_C_TYPE: Dict[int, str] = {1: "int8", 2: "int16", 4: "int32", 8: "int64"}
|
|
72
|
+
|
|
73
|
+
CHAR_ZERO = bytes("\0", "ascii")
|
|
74
|
+
|
|
75
|
+
DEFAULT_ENUM_SIZE = 4
|