MemCStruct

Bases: 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

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

memcpy(destination, source, num)

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

Parameters:
  • destination (int) –

    destination address

  • source (bytes) –

    source data to be copied

  • num (int) –

    number of bytes to copy

cstruct/mem_cstruct.py
88
89
90
91
92
93
94
95
96
97
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:
  • bytes( bytes ) –

    The packed structure

cstruct/mem_cstruct.py
 99
100
101
102
103
104
105
106
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:
  • flexible_array_length (Optional[int]) –

    flexible array length

cstruct/mem_cstruct.py
108
109
110
111
112
113
114
115
116
117
118
119
120
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:
  • buffer (Optional[bytes]) –

    bytes to be unpacked

  • offset (int, default: 0 ) –

    optional buffer offset

  • flexible_array_length (Optional[int], default: None ) –

    optional flexible array lenght (number of elements)

cstruct/mem_cstruct.py
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
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