AbstractCEnum

Bases: IntEnum

Abstract C enum to Python class

Source code in cstruct/abstract.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
class AbstractCEnum(IntEnum, metaclass=CEnumMeta):
    """
    Abstract C enum to Python class
    """

    @classmethod
    def parse(
        cls,
        __enum__: Union[str, Tokens, Dict[str, Any]],
        __name__: Optional[str] = None,
        __size__: Optional[int] = None,
        __native_format__: Optional[str] = None,
        **kargs: Dict[str, Any],
    ) -> Type["AbstractCEnum"]:
        """
        Return a new Python Enum class mapping a C enum definition

        Args:
            __enum__: Definition of the enum in C syntax
            __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated
            __size__: Number of bytes that the enum should be read as
            __native_format__: struct module format

        Returns:
            cls: A new class mapping the definition
        """
        cls_kargs: Dict[str, Any] = dict(kargs)
        if __size__ is not None:
            cls_kargs["__size__"] = __size__
        if __native_format__ is not None:
            cls_kargs["__native_format__"] = __native_format__

        if isinstance(__enum__, (str, Tokens)):
            cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs))
        elif isinstance(__enum__, dict):
            cls_kargs.update(__enum__)

        __name__ = cls_kargs.get("__name__") or __name__
        if __name__ is None:
            __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest()
            cls_kargs["__anonymous__"] = True

        cls_kargs.update(cls_kargs["__constants__"])
        return cls(__name__, cls_kargs)

parse(__enum__, __name__=None, __size__=None, __native_format__=None, **kargs) classmethod

Return a new Python Enum class mapping a C enum definition

Parameters:

Name Type Description Default
__enum__ Union[str, Tokens, Dict[str, Any]]

Definition of the enum in C syntax

required
__name__ Optional[str]

Name of the new Enum. If empty, a name based on the enum hash is generated

None
__size__ Optional[int]

Number of bytes that the enum should be read as

None
__native_format__ Optional[str]

struct module format

None

Returns:

Name Type Description
cls Type[AbstractCEnum]

A new class mapping the definition

Source code in cstruct/abstract.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
@classmethod
def parse(
    cls,
    __enum__: Union[str, Tokens, Dict[str, Any]],
    __name__: Optional[str] = None,
    __size__: Optional[int] = None,
    __native_format__: Optional[str] = None,
    **kargs: Dict[str, Any],
) -> Type["AbstractCEnum"]:
    """
    Return a new Python Enum class mapping a C enum definition

    Args:
        __enum__: Definition of the enum in C syntax
        __name__: Name of the new Enum. If empty, a name based on the __enum__ hash is generated
        __size__: Number of bytes that the enum should be read as
        __native_format__: struct module format

    Returns:
        cls: A new class mapping the definition
    """
    cls_kargs: Dict[str, Any] = dict(kargs)
    if __size__ is not None:
        cls_kargs["__size__"] = __size__
    if __native_format__ is not None:
        cls_kargs["__native_format__"] = __native_format__

    if isinstance(__enum__, (str, Tokens)):
        cls_kargs.update(parse_enum_def(__enum__, __cls__=cls, **cls_kargs))
    elif isinstance(__enum__, dict):
        cls_kargs.update(__enum__)

    __name__ = cls_kargs.get("__name__") or __name__
    if __name__ is None:
        __name__ = cls.__name__ + "_" + hashlib.sha1(str(__enum__).encode("utf-8")).hexdigest()
        cls_kargs["__anonymous__"] = True

    cls_kargs.update(cls_kargs["__constants__"])
    return cls(__name__, cls_kargs)

AbstractCStruct

Abstract C struct to Python class

Source code in cstruct/abstract.py
 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
class AbstractCStruct(metaclass=CStructMeta):
    """
    Abstract C struct to Python class
    """

    __size__: int = 0
    " Size in bytes "
    __fields__: List[str] = []
    " Struct/union fileds "
    __fields_types__: Dict[str, FieldType]
    " Dictionary mapping field names to types "
    __byte_order__: Optional[str] = None
    " Byte order "
    __alignment__: int = 0
    " Alignament "
    __is_union__: bool = False
    " True if the class is an union, False if it is a struct "

    def __init__(
        self, buffer: Optional[Union[bytes, BinaryIO]] = None, flexible_array_length: Optional[int] = None, **kargs: Dict[str, Any]
    ) -> None:
        self.set_flexible_array_length(flexible_array_length)
        self.__fields__ = [x for x in self.__fields__]  # Create a copy
        self.__fields_types__ = OrderedDict({k: v.copy() for k, v in self.__fields_types__.items()})  # Create a copy
        if buffer is not None:
            self.unpack(buffer)
        else:
            try:
                self.unpack(buffer)
            except Exception:
                pass
        for key, value in kargs.items():
            setattr(self, key, value)

    @classmethod
    def parse(
        cls,
        __struct__: Union[str, Tokens, Dict[str, Any]],
        __name__: Optional[str] = None,
        __byte_order__: Optional[str] = None,
        __is_union__: Optional[bool] = False,
        **kargs: Dict[str, Any],
    ) -> Type["AbstractCStruct"]:
        """
        Return a new class mapping a C struct/union definition.

        Args:
            __struct__:     definition of the struct (or union) in C syntax
            __name__:       name of the new class. If empty, a name based on the __struct__ hash is generated
            __byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
            __is_union__:   True for union, False for struct

        Returns:
            cls: a new class mapping the defintion
        """
        cls_kargs: Dict[str, Any] = dict(kargs)
        if __byte_order__ is not None:
            cls_kargs["__byte_order__"] = __byte_order__
        if __is_union__ is not None:
            cls_kargs["__is_union__"] = __is_union__
        cls_kargs["__struct__"] = __struct__
        if isinstance(__struct__, (str, Tokens)):
            del cls_kargs["__struct__"]
            cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs))
            cls_kargs["__struct__"] = None
        elif isinstance(__struct__, dict):
            del cls_kargs["__struct__"]
            cls_kargs.update(__struct__)
            cls_kargs["__struct__"] = None
        __name__ = cls_kargs.get("__name__") or __name__
        if __name__ is None:  # Anonymous struct
            __name__ = cls.__name__ + "_" + hashlib.sha1(str(__struct__).encode("utf-8")).hexdigest()
            cls_kargs["__anonymous__"] = True
        cls_kargs["__name__"] = __name__
        return type(__name__, (cls,), cls_kargs)

    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

        Raises:
            CStructException: If flexible array is not present in the structure
        """
        if flexible_array_length is not None:
            # Search for the flexible array
            flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0]
            if flexible_array is None:
                raise CStructException("Flexible array not found in struct")
            flexible_array.vlen = flexible_array_length

    def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool:
        """
        Unpack bytes containing packed C structure data

        Args:
            buffer: bytes or binary stream to be unpacked
            flexible_array_length: flexible array length
        """
        self.set_flexible_array_length(flexible_array_length)
        if hasattr(buffer, "read"):
            buffer = buffer.read(self.size)  # type: ignore
            if not buffer:
                return False
        return self.unpack_from(buffer)

    def unpack_from(
        self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None
    ) -> bool:  # pragma: no cover
        """
        Unpack bytes containing packed C structure data

        Args:
            buffer: bytes to be unpacked
            offset: optional buffer offset
            flexible_array_length: flexible array length
        """
        raise NotImplementedError

    def pack(self) -> bytes:  # pragma: no cover
        """
        Pack the structure data into bytes

        Returns:
            bytes: The packed structure
        """
        raise NotImplementedError

    def clear(self) -> None:
        self.unpack(None)

    def __len__(self) -> int:
        "Actual structure size (in bytes)"
        return self.size

    @property
    def size(self) -> int:
        "Actual structure size (in bytes)"
        if not self.__fields_types__:  # no fields
            return 0
        elif self.__is_union__:  # C union
            # Calculate the sizeof union as size of its largest element
            return max(x.vsize for x in self.__fields_types__.values())
        else:  # C struct
            # Calculate the sizeof struct as last item's offset + size + padding
            last_field_type = list(self.__fields_types__.values())[-1]
            size = last_field_type.offset + last_field_type.vsize
            padding = calculate_padding(self.__byte_order__, self.__alignment__, size)
            return size + padding

    @classmethod
    def sizeof(cls) -> int:
        "Structure size in bytes (flexible array member size is omitted)"
        return cls.__size__

    def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = None) -> str:
        """
        Return memory content in hexadecimal

        Args:
            start_addr: start address
            end_addr: end address
        """
        buffer = StringIO()
        if hasattr(self, "__mem__"):
            mem = self.__mem__[self.__base__ :]
        else:
            mem = self.pack()
        if end_addr is None:
            end_addr = self.size
        for i in range(start_addr or 0, end_addr, 16):
            row = mem[i : min(i + 16, end_addr)]
            buffer.write(f"{i:08x} ")
            for j, c in enumerate(row):
                separator = " " if j == 7 else ""
                buffer.write(f" {c:02x}{separator}")
            for j in range(len(row) - 1, 15):
                separator = " " if j == 7 else ""
                buffer.write(f"   {separator}")
            buffer.write("  |")
            for c in row:
                buffer.write(chr(c) if c >= 32 and c < 127 else ".")
            for j in range(len(row) - 1, 15):
                buffer.write(" ")
            buffer.write("|")
            buffer.write("\n")
        buffer.seek(0, 0)
        return buffer.read()

    def __eq__(self, other: Any) -> bool:
        return other is not None and isinstance(other, self.__class__) and self.__dict__ == other.__dict__

    def __ne__(self, other: Any) -> bool:
        return not self.__eq__(other)

    def __str__(self) -> str:
        result = []
        for field in self.__fields__:
            result.append(field + "=" + str(getattr(self, field, None)))
        return type(self).__name__ + "(" + ", ".join(result) + ")"

    def __repr__(self) -> str:  # pragma: no cover
        return self.__str__()

    def __getstate__(self) -> bytes:
        """
        This method is called and the returned object is pickled
        as the contents for the instance, instead of the contents of
        the instance’s dictionary

        Returns:
            bytes: The packed structure
        """
        return self.pack()

    def __setstate__(self, state: bytes) -> bool:
        """
        This method it is called with the unpickled state

        Args:
            state: bytes to be unpacked
        """
        return self.unpack(state)

__alignment__: int = 0 class-attribute

Alignament

__byte_order__: Optional[str] = None class-attribute

Byte order

__is_union__: bool = False class-attribute

True if the class is an union, False if it is a struct

__size__: int = 0 class-attribute

Size in bytes

size: int property

Actual structure size (in bytes)

__getstate__()

This method is called and the returned object is pickled as the contents for the instance, instead of the contents of the instance’s dictionary

Returns:

Name Type Description
bytes bytes

The packed structure

Source code in cstruct/abstract.py
280
281
282
283
284
285
286
287
288
289
def __getstate__(self) -> bytes:
    """
    This method is called and the returned object is pickled
    as the contents for the instance, instead of the contents of
    the instance’s dictionary

    Returns:
        bytes: The packed structure
    """
    return self.pack()

__len__()

Actual structure size (in bytes)

Source code in cstruct/abstract.py
207
208
209
def __len__(self) -> int:
    "Actual structure size (in bytes)"
    return self.size

__setstate__(state)

This method it is called with the unpickled state

Parameters:

Name Type Description Default
state bytes

bytes to be unpacked

required
Source code in cstruct/abstract.py
291
292
293
294
295
296
297
298
def __setstate__(self, state: bytes) -> bool:
    """
    This method it is called with the unpickled state

    Args:
        state: bytes to be unpacked
    """
    return self.unpack(state)

inspect(start_addr=None, end_addr=None)

Return memory content in hexadecimal

Parameters:

Name Type Description Default
start_addr Optional[int]

start address

None
end_addr Optional[int]

end address

None
Source code in cstruct/abstract.py
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def inspect(self, start_addr: Optional[int] = None, end_addr: Optional[int] = None) -> str:
    """
    Return memory content in hexadecimal

    Args:
        start_addr: start address
        end_addr: end address
    """
    buffer = StringIO()
    if hasattr(self, "__mem__"):
        mem = self.__mem__[self.__base__ :]
    else:
        mem = self.pack()
    if end_addr is None:
        end_addr = self.size
    for i in range(start_addr or 0, end_addr, 16):
        row = mem[i : min(i + 16, end_addr)]
        buffer.write(f"{i:08x} ")
        for j, c in enumerate(row):
            separator = " " if j == 7 else ""
            buffer.write(f" {c:02x}{separator}")
        for j in range(len(row) - 1, 15):
            separator = " " if j == 7 else ""
            buffer.write(f"   {separator}")
        buffer.write("  |")
        for c in row:
            buffer.write(chr(c) if c >= 32 and c < 127 else ".")
        for j in range(len(row) - 1, 15):
            buffer.write(" ")
        buffer.write("|")
        buffer.write("\n")
    buffer.seek(0, 0)
    return buffer.read()

pack()

Pack the structure data into bytes

Returns:

Name Type Description
bytes bytes

The packed structure

Source code in cstruct/abstract.py
195
196
197
198
199
200
201
202
def pack(self) -> bytes:  # pragma: no cover
    """
    Pack the structure data into bytes

    Returns:
        bytes: The packed structure
    """
    raise NotImplementedError

parse(__struct__, __name__=None, __byte_order__=None, __is_union__=False, **kargs) classmethod

Return a new class mapping a C struct/union definition.

Parameters:

Name Type Description Default
__struct__ Union[str, Tokens, Dict[str, Any]]

definition of the struct (or union) in C syntax

required
__name__ Optional[str]

name of the new class. If empty, a name based on the struct hash is generated

None
__byte_order__ Optional[str]

byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER

None
__is_union__ Optional[bool]

True for union, False for struct

False

Returns:

Name Type Description
cls Type[AbstractCStruct]

a new class mapping the defintion

Source code in cstruct/abstract.py
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
@classmethod
def parse(
    cls,
    __struct__: Union[str, Tokens, Dict[str, Any]],
    __name__: Optional[str] = None,
    __byte_order__: Optional[str] = None,
    __is_union__: Optional[bool] = False,
    **kargs: Dict[str, Any],
) -> Type["AbstractCStruct"]:
    """
    Return a new class mapping a C struct/union definition.

    Args:
        __struct__:     definition of the struct (or union) in C syntax
        __name__:       name of the new class. If empty, a name based on the __struct__ hash is generated
        __byte_order__: byte order, valid values are LITTLE_ENDIAN, BIG_ENDIAN, NATIVE_ORDER
        __is_union__:   True for union, False for struct

    Returns:
        cls: a new class mapping the defintion
    """
    cls_kargs: Dict[str, Any] = dict(kargs)
    if __byte_order__ is not None:
        cls_kargs["__byte_order__"] = __byte_order__
    if __is_union__ is not None:
        cls_kargs["__is_union__"] = __is_union__
    cls_kargs["__struct__"] = __struct__
    if isinstance(__struct__, (str, Tokens)):
        del cls_kargs["__struct__"]
        cls_kargs.update(parse_struct_def(__struct__, __cls__=cls, **cls_kargs))
        cls_kargs["__struct__"] = None
    elif isinstance(__struct__, dict):
        del cls_kargs["__struct__"]
        cls_kargs.update(__struct__)
        cls_kargs["__struct__"] = None
    __name__ = cls_kargs.get("__name__") or __name__
    if __name__ is None:  # Anonymous struct
        __name__ = cls.__name__ + "_" + hashlib.sha1(str(__struct__).encode("utf-8")).hexdigest()
        cls_kargs["__anonymous__"] = True
    cls_kargs["__name__"] = __name__
    return type(__name__, (cls,), cls_kargs)

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

Raises:

Type Description
CStructException

If flexible array is not present in the structure

Source code in cstruct/abstract.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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

    Raises:
        CStructException: If flexible array is not present in the structure
    """
    if flexible_array_length is not None:
        # Search for the flexible array
        flexible_array: Optional[FieldType] = [x for x in self.__fields_types__.values() if x.flexible_array][0]
        if flexible_array is None:
            raise CStructException("Flexible array not found in struct")
        flexible_array.vlen = flexible_array_length

sizeof() classmethod

Structure size in bytes (flexible array member size is omitted)

Source code in cstruct/abstract.py
226
227
228
229
@classmethod
def sizeof(cls) -> int:
    "Structure size in bytes (flexible array member size is omitted)"
    return cls.__size__

unpack(buffer, flexible_array_length=None)

Unpack bytes containing packed C structure data

Parameters:

Name Type Description Default
buffer Optional[Union[bytes, BinaryIO]]

bytes or binary stream to be unpacked

required
flexible_array_length Optional[int]

flexible array length

None
Source code in cstruct/abstract.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def unpack(self, buffer: Optional[Union[bytes, BinaryIO]], flexible_array_length: Optional[int] = None) -> bool:
    """
    Unpack bytes containing packed C structure data

    Args:
        buffer: bytes or binary stream to be unpacked
        flexible_array_length: flexible array length
    """
    self.set_flexible_array_length(flexible_array_length)
    if hasattr(buffer, "read"):
        buffer = buffer.read(self.size)  # type: ignore
        if not buffer:
            return False
    return self.unpack_from(buffer)

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]

flexible array length

None
Source code in cstruct/abstract.py
182
183
184
185
186
187
188
189
190
191
192
193
def unpack_from(
    self, buffer: Optional[bytes], offset: int = 0, flexible_array_length: Optional[int] = None
) -> bool:  # pragma: no cover
    """
    Unpack bytes containing packed C structure data

    Args:
        buffer: bytes to be unpacked
        offset: optional buffer offset
        flexible_array_length: flexible array length
    """
    raise NotImplementedError

CEnumMeta

Bases: EnumMeta

Source code in cstruct/abstract.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
class CEnumMeta(EnumMeta):
    class WrapperDict(_EnumDict):
        def __setitem__(self, key: str, value: Any) -> None:
            env = None
            if key == "__enum__":
                env = parse_enum(value)
            elif key == "__def__":
                env = parse_enum_def(value)

            if env is not None:
                # register the enum constants in the object namespace,
                # using the Python Enum class Namespace dict that does the
                # heavy lifting
                for k, v in env["__constants__"].items():
                    super().__setitem__(k, v)
            else:
                return super().__setitem__(key, value)

    @classmethod
    def __prepare__(metacls, cls, bases, **kwds):
        namespace = EnumMeta.__prepare__(cls, bases, **kwds)
        namespace.__class__ = metacls.WrapperDict
        return namespace

    def __new__(metacls: Type["CEnumMeta"], cls: str, bases: Tuple[Type, ...], classdict: _EnumDict, **kwds: Any) -> "CEnumMeta":
        inst = super().__new__(metacls, cls, bases, classdict, **kwds)
        if len(inst) > 0:
            if classdict.get("__native_format__"):  # data type specified
                inst.__size__ = struct.calcsize(classdict["__native_format__"])
            elif "__size__" in classdict:  # size specified
                try:
                    inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
                except KeyError:
                    raise ParserError(f"Enum has invalid size. Needs to be in {ENUM_SIZE_TO_C_TYPE.keys()}")
            else:  # default
                inst.__size__ = DEFAULT_ENUM_SIZE
                inst.__native_format__ = get_native_type(ENUM_SIZE_TO_C_TYPE[inst.__size__]).native_format
                print(f"Warning: __size__ not specified for enum {cls}. Will default to {DEFAULT_ENUM_SIZE} bytes")

            if not classdict.get("__anonymous__", False):
                ENUMS[cls] = inst
        return inst

    @property
    def size(cls) -> int:
        "Enum size (in bytes)"
        return cls.__size__

size: int property

Enum size (in bytes)

CStructMeta

Bases: ABCMeta

Source code in cstruct/abstract.py
43
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
class CStructMeta(ABCMeta):
    __size__: int = 0

    def __new__(metacls: Type[type], name: str, bases: Tuple[str], namespace: Dict[str, Any]) -> Type[Any]:
        __struct__ = namespace.get("__struct__", None)
        namespace["__cls__"] = bases[0] if bases else None
        # Parse the struct
        if "__struct__" in namespace:
            if isinstance(namespace["__struct__"], (str, Tokens)):
                namespace.update(parse_struct(**namespace))
            __struct__ = True
        if "__def__" in namespace:
            namespace.update(parse_struct_def(**namespace))
            __struct__ = True
        # Create the new class
        new_class: Type[Any] = super().__new__(metacls, name, bases, namespace)
        # Register the class
        if __struct__ is not None and not namespace.get("__anonymous__"):
            STRUCTS[name] = new_class
        return new_class

    def __len__(cls) -> int:
        "Structure size (in bytes)"
        return cls.__size__

    @property
    def size(cls) -> int:
        "Structure size (in bytes)"
        return cls.__size__

size: int property

Structure size (in bytes)

__len__()

Structure size (in bytes)

Source code in cstruct/abstract.py
64
65
66
def __len__(cls) -> int:
    "Structure size (in bytes)"
    return cls.__size__