45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 | class MemCStruct(AbstractCStruct):
"""
Convert C struct definitions into Python classes.
Attributes:
__struct__ (str): definition of the struct (or union) in C syntax
__byte_order__ (str): byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
__is_union__ (bool): True for union definitions, False for struct definitions
__mem__: mutable character buffer
__size__ (int): size of the structure in bytes (flexible array member size is omitted)
__fields__ (list): list of structure fields
__fields_types__ (dict): dictionary mapping field names to types
"""
__mem__ = None
__base__ = 0
def unpack_from(self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None) -> bool:
"""
Unpack bytes containing packed C structure data
Args:
buffer: bytes to be unpacked
offset: optional buffer offset
flexible_array_length: optional flexible array lenght (number of elements)
"""
self.set_flexible_array_length(flexible_array_length)
self.__base__ = offset # Base offset
if buffer is None:
# the buffer is one item larger than its size and the last element is NUL
self.__mem__ = ctypes.create_string_buffer(self.size + 1)
elif isinstance(buffer, ctypes.Array):
self.__mem__ = buffer
elif isinstance(buffer, int):
# buffer is a pointer
self.__mem__ = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_char * self.size)).contents
else:
self.__mem__ = ctypes.create_string_buffer(buffer)
for field, field_type in self.__fields_types__.items():
if field_type.is_struct or field_type.is_union:
setattr(self, field, field_type.unpack_from(self.__mem__, offset))
return True
def memcpy(self, destination: int, source: bytes, num: int) -> None:
"""
Copies the values of num bytes from source to the struct memory
Args:
destination: destination address
source: source data to be copied
num: number of bytes to copy
"""
ctypes.memmove(ctypes.byref(self.__mem__, destination), source, num)
def pack(self) -> bytes:
"""
Pack the structure data into bytes
Returns:
bytes: The packed structure
"""
return self.__mem__.raw[self.__base__ : self.__base__ + self.size]
def set_flexible_array_length(self, flexible_array_length: Optional[int]) -> None:
"""
Set flexible array length (i.e. number of elements)
Args:
flexible_array_length: flexible array length
"""
super().set_flexible_array_length(flexible_array_length)
if self.__mem__ is not None:
try:
ctypes.resize(self.__mem__, self.size + 1)
except ValueError:
pass
def __getattr__(self, attr: str) -> Any:
field_type = self.__fields_types__[attr]
result = field_type.unpack_from(self.__mem__, self.__base__)
if isinstance(result, list):
return CStructList(result, name=attr, parent=self)
else:
return result
def __setattr__(self, attr: str, value: Any) -> None:
field_type = self.__fields_types__.get(attr)
if field_type is None:
object.__setattr__(self, attr, value)
elif field_type.is_struct or field_type.is_union:
object.__setattr__(self, attr, value)
else: # native
if field_type.flexible_array and len(value) != field_type.vlen:
# flexible array size changed, resize the buffer
field_type.vlen = len(value)
ctypes.resize(self.__mem__, self.size + 1)
addr = field_type.offset + self.__base__
self.memcpy(addr, field_type.pack(value), field_type.vsize)
def on_change_list(self, attr: str, key: int, value: Any) -> None:
field_type = self.__fields_types__[attr]
# Calculate the single field format and size
fmt = (self.__byte_order__ + field_type.fmt[-1]) if self.__byte_order__ is not None else field_type.fmt[-1]
size = struct.calcsize(fmt)
# Calculate the single field memory position
addr = field_type.offset + self.__base__ + size * key
# Update the memory
self.memcpy(addr, struct.pack(fmt, value), size)
|