MemCStruct

Bases: AbstractCStruct

Convert C struct definitions into Python classes.

Attributes:

Name Type Description
__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

Source code in cstruct/mem_cstruct.py
 44
 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
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)

memcpy(destination, source, num)

Copies the values of num bytes from source to the struct memory

Parameters:

Name Type Description Default
destination int

destination address

required
source bytes

source data to be copied

required
num int

number of bytes to copy

required
Source code in cstruct/mem_cstruct.py
87
88
89
90
91
92
93
94
95
96
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)

pack()

Pack the structure data into bytes

Returns:

Name Type Description
bytes bytes

The packed structure

Source code in cstruct/mem_cstruct.py
 98
 99
100
101
102
103
104
105
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]

set_flexible_array_length(flexible_array_length)

Set flexible array length (i.e. number of elements)

Parameters:

Name Type Description Default
flexible_array_length Optional[int]

flexible array length

required
Source code in cstruct/mem_cstruct.py
107
108
109
110
111
112
113
114
115
116
117
118
119
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

unpack_from(buffer, offset=0, flexible_array_length=None)

Unpack bytes containing packed C structure data

Parameters:

Name Type Description Default
buffer Optional[bytes]

bytes to be unpacked

required
offset int

optional buffer offset

0
flexible_array_length Optional[int]

optional flexible array lenght (number of elements)

None
Source code in cstruct/mem_cstruct.py
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
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