diff --git a/dissect/cstruct/bitbuffer.py b/dissect/cstruct/bitbuffer.py index b8b7c8e..8150546 100644 --- a/dissect/cstruct/bitbuffer.py +++ b/dissect/cstruct/bitbuffer.py @@ -19,11 +19,11 @@ def __init__(self, stream: BinaryIO, *, endian: str): def read(self, field_type: type[BaseType], bits: int) -> int: if self._remaining == 0 or self._type != field_type: - if field_type.size is None: + if field_type.__size__ is None: raise ValueError("Reading variable-length fields is unsupported") self._type = field_type - self._remaining = field_type.size * 8 + self._remaining = field_type.__size__ * 8 self._buffer = field_type._read(self.stream, endian=self.endian) if isinstance(self._buffer, bytes): @@ -51,17 +51,17 @@ def write(self, field_type: type[BaseType], data: int, bits: int) -> None: if self._type: self.flush() - if field_type.size is None: + if field_type.__size__ is None: raise ValueError("Writing variable-length fields is unsupported") - self._remaining = field_type.size * 8 + self._remaining = field_type.__size__ * 8 self._type = field_type - if self._type is None or self._type.size is None: + if self._type is None or self._type.__size__ is None: raise ValueError("Invalid state") if self.endian == "<": - self._buffer |= data << (self._type.size * 8 - self._remaining) + self._buffer |= data << (self._type.__size__ * 8 - self._remaining) else: self._buffer |= data << (self._remaining - bits) diff --git a/dissect/cstruct/compiler.py b/dissect/cstruct/compiler.py index a096cad..ce2d11d 100644 --- a/dissect/cstruct/compiler.py +++ b/dissect/cstruct/compiler.py @@ -60,7 +60,7 @@ def compile(structure: type[Structure]) -> type[Structure]: - return Compiler(structure.cs).compile(structure) + return Compiler(structure.__cs__).compile(structure) class Compiler: @@ -119,7 +119,7 @@ def generate_source(self) -> str: """ if any(field.bits for field in self.fields): - preamble += "bit_reader = BitBuffer(stream, endian=endian)\n" + preamble += "bit_reader = BitBuffer(stream, endian=cls.__cs__.endian)\n" read_code = "\n".join(self._generate_fields()) @@ -224,22 +224,22 @@ def align_to_field(field: Field) -> Iterator[str]: yield from flush() if self.align: - yield f"stream.seek(-stream.tell() & (cls.alignment - 1), {io.SEEK_CUR})" + yield f"stream.seek(-stream.tell() & (cls.__alignment__ - 1), {io.SEEK_CUR})" def _generate_structure(self, field: Field) -> Iterator[str]: template = f""" - {"_s = stream.tell()" if field.type.dynamic else ""} + {"_s = stream.tell()" if field.type.__dynamic__ else ""} r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian) - {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""} + {f's["{field._name}"] = stream.tell() - _s' if field.type.__dynamic__ else ""} """ yield dedent(template) def _generate_array(self, field: Field) -> Iterator[str]: template = f""" - {"_s = stream.tell()" if field.type.dynamic else ""} + {"_s = stream.tell()" if field.type.__dynamic__ else ""} r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian) - {f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""} + {f's["{field._name}"] = stream.tell() - _s' if field.type.__dynamic__ else ""} """ yield dedent(template) @@ -253,8 +253,8 @@ def _generate_bits(self, field: Field) -> Iterator[str]: field_type = field_type.type if issubclass(field_type, Char): - field_type = field_type.cs.uint8 - lookup = "cls.cs.uint8" + field_type = field_type.__cs__.uint8 + lookup = "cls.__cs__.uint8" template = f""" _t = {lookup} @@ -283,13 +283,13 @@ def _generate_packed(self, fields: list[Field]) -> Iterator[str]: read_type = _get_read_type(self.cs, field_type.type) if issubclass(read_type, (Char, Wchar, Int)): - count *= read_type.size + count *= read_type.__size__ getter = f"buf[{size}:{size + count}]" else: getter = f"data[{slice_index}:{slice_index + count}]" slice_index += count elif issubclass(read_type, (Char, Wchar, Int)): - getter = f"buf[{size}:{size + read_type.size}]" + getter = f"buf[{size}:{size + read_type.__size__}]" else: getter = f"data[{slice_index}]" slice_index += 1 @@ -308,8 +308,8 @@ def _generate_packed(self, fields: list[Field]) -> Iterator[str]: if issubclass(field_type.type, Int): reads.append(f"_b = {getter}") - item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.size}]") - list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.size})]" + item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.__size__}]") + list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.__size__})]" elif issubclass(field_type.type, Pointer): item_parser = "_et.__new__(_et, e, stream, context=r, endian=endian)" list_comp = f"[{item_parser} for e in {getter}]" @@ -329,7 +329,7 @@ def _generate_packed(self, fields: list[Field]) -> Iterator[str]: reads.append(f'r["{field._name}"] = {parser}') reads.append("") # Generates a newline in the resulting code - size += field_type.size + size += field_type.__size__ fmt = _optimize_struct_fmt(info) if fmt == "x" or (len(fmt) == 2 and fmt[1] == "x"): @@ -382,9 +382,9 @@ def _generate_struct_info(cs: cstruct, fields: list[Field], align: bool = False) # Other types are byte based # We don't actually unpack anything here but slice directly out of the buffer elif issubclass(read_type, (Char, Wchar, Int)): - yield field, count * read_type.size, "x" + yield field, count * read_type.__size__, "x" - size = count * read_type.size + size = count * read_type.__size__ imaginary_offset += size if current_offset is not None: current_offset += size diff --git a/dissect/cstruct/cstruct.py b/dissect/cstruct/cstruct.py index 85ec36f..0bb34d7 100644 --- a/dissect/cstruct/cstruct.py +++ b/dissect/cstruct/cstruct.py @@ -206,7 +206,28 @@ def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: if load: self.load(load) - def __getattr__(self, attr: str) -> Any: + def __getattr__( + self, attr: str + ) -> ( + type[ + LEB128 + | BaseType + | Char + | Enum + | Flag + | Int + | Packed[int] + | Packed[float] + | Pointer + | Structure + | Union + | Void + | Wchar + ] + | int + | str + | bytes + ): try: return self.consts[attr] except KeyError: @@ -227,7 +248,7 @@ def __copy__(self) -> cstruct: # Update types to point to the new cstruct instance for name, type_ in self.types.items(): new_type = copy.copy(type_) - new_type.cs = cs + new_type.__cs__ = cs cs.add_type(name, new_type, replace=True) for name, value in self.consts.items(): @@ -481,10 +502,10 @@ def _make_type( attrs = attrs or {} attrs.update( { - "cs": self, - "size": size, - "dynamic": size is None, - "alignment": alignment or size, + "__cs__": self, + "__size__": size, + "__dynamic__": size is None, + "__alignment__": alignment or size, } ) return types.new_class(name, bases, {}, lambda ns: ns.update(attrs)) @@ -494,12 +515,12 @@ def _make_array(self, type_: T, num_entries: int | Expression | None) -> type[Ar if num_entries is None: null_terminated = True size = None - elif isinstance(num_entries, Expression) or type_.dynamic: + elif isinstance(num_entries, Expression) or type_.__dynamic__: size = None else: - if type_.size is None: + if type_.__size__ is None: raise ValueError(f"Cannot create array of dynamic type: {type_.__name__}") - size = num_entries * type_.size + size = num_entries * type_.__size__ name = f"{type_.__name__}[]" if null_terminated else f"{type_.__name__}[{num_entries}]" @@ -511,7 +532,7 @@ def _make_array(self, type_: T, num_entries: int | Expression | None) -> type[Ar "null_terminated": null_terminated, } - return cast("type[Array]", self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)) + return cast("type[Array]", self._make_type(name, bases, size, alignment=type_.__alignment__, attrs=attrs)) def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]: return cast("type[Int]", self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})) @@ -538,8 +559,8 @@ def _make_pointer(self, target: type[BaseType]) -> type[Pointer]: return self._make_type( f"{target.__name__}*", (Pointer,), - self.pointer.size, - alignment=self.pointer.alignment, + self.pointer.__size__, + alignment=self.pointer.__alignment__, attrs={"type": target}, ) @@ -568,6 +589,15 @@ def _make_union( ) -> type[Structure]: return self._make_struct(name, fields, align=align, anonymous=anonymous, base=Union) + @property + def typedefs(self) -> dict[str, type[BaseType]]: + warnings.warn( + "The 'typedefs' property is deprecated, use 'types' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.types + if TYPE_CHECKING: # ruff: noqa: PYI042 _int = int diff --git a/dissect/cstruct/tools/stubgen.py b/dissect/cstruct/tools/stubgen.py index a21f740..526f821 100644 --- a/dissect/cstruct/tools/stubgen.py +++ b/dissect/cstruct/tools/stubgen.py @@ -159,7 +159,7 @@ def generate_structure_stub( indent = " " * 4 args = ["self"] - for field_name, field in structure.fields.items(): + for field_name, field in structure.__members__.items(): inlined = False # If it's a structure and not globally defined, add an inline stub for it @@ -167,7 +167,7 @@ def generate_structure_stub( while issubclass(nested_type, types.BaseArray): nested_type = nested_type.type - if issubclass(nested_type, types.Structure) and nested_type.__name__ not in structure.cs.types: + if issubclass(nested_type, types.Structure) and nested_type.__name__ not in structure.__cs__.types: inlined = True inline_stub = generate_structure_stub(nested_type, cs_prefix=cs_prefix, module_prefix=module_prefix) diff --git a/dissect/cstruct/types/base.py b/dissect/cstruct/types/base.py index 7bc2ade..ab7a421 100644 --- a/dissect/cstruct/types/base.py +++ b/dissect/cstruct/types/base.py @@ -1,6 +1,7 @@ from __future__ import annotations import functools +import warnings from io import BytesIO from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar @@ -21,13 +22,13 @@ class MetaType(type): """Base metaclass for cstruct type classes.""" - cs: cstruct + __cs__: cstruct """The cstruct instance this type class belongs to.""" - size: int | None + __size__: int | None """The size of the type in bytes. Can be ``None`` for dynamic sized types.""" - dynamic: bool + __dynamic__: bool """Whether or not the type is dynamically sized.""" - alignment: int | None + __alignment__: int | None """The alignment of the type in bytes. A value of ``None`` will be treated as 1-byte aligned.""" # This must be the actual type, but since Array is a subclass of BaseType, we correct this at the bottom of the file @@ -41,10 +42,12 @@ def __call__(cls, *args, **kwargs) -> Self: # type: ignore stream = args[0] if _is_readable_type(stream): - endian = normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.cs.endian + endian = ( + normalize_endianness(endian) if (endian := kwargs.get("endian")) is not None else cls.__cs__.endian + ) return cls._read(stream, endian=endian) - if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.size: + if issubclass(cls, bytes) and isinstance(stream, bytes) and len(stream) == cls.__size__: # Shortcut for char/bytes type return type.__call__(cls, *args, **kwargs) @@ -55,7 +58,7 @@ def __call__(cls, *args, **kwargs) -> Self: # type: ignore def __getitem__(cls, num_entries: int | Expression | None) -> type[BaseArray]: """Create a new array with the given number of entries.""" - return cls.cs._make_array(cls, num_entries) + return cls.__cs__._make_array(cls, num_entries) def __bool__(cls) -> bool: """Type class is always truthy.""" @@ -63,10 +66,10 @@ def __bool__(cls) -> bool: def __len__(cls) -> int: """Return the byte size of the type.""" - if cls.size is None: + if cls.__size__ is None: raise TypeError("Dynamic size") - return cls.size + return cls.__size__ def __default__(cls) -> Self: # type: ignore """Return the default value of this type.""" @@ -83,7 +86,7 @@ def reads(cls, data: bytes | memoryview | bytearray, *, endian: AllowedEndiannes Returns: The parsed value of this type. """ - endian = normalize_endianness(endian) if endian is not None else cls.cs.endian + endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian return cls._read(BytesIO(data), endian=endian) def read(cls, obj: BinaryIO | bytes | memoryview | bytearray, *, endian: AllowedEndianness | None = None) -> Self: # type: ignore @@ -103,7 +106,7 @@ def read(cls, obj: BinaryIO | bytes | memoryview | bytearray, *, endian: Allowed if not _is_readable_type(obj): raise TypeError("Invalid object type") - endian = normalize_endianness(endian) if endian is not None else cls.cs.endian + endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian return cls._read(obj, endian=endian) def write(cls, stream: BinaryIO, value: Any, *, endian: AllowedEndianness | None = None) -> int: @@ -118,7 +121,7 @@ def write(cls, stream: BinaryIO, value: Any, *, endian: AllowedEndianness | None Returns: The amount of bytes written. """ - endian = normalize_endianness(endian) if endian is not None else cls.cs.endian + endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian return cls._write(stream, value, endian=endian) def dumps(cls, value: Any, *, endian: AllowedEndianness | None = None) -> bytes: @@ -132,7 +135,7 @@ def dumps(cls, value: Any, *, endian: AllowedEndianness | None = None) -> bytes: Returns: The raw bytes of this type. """ - endian = normalize_endianness(endian) if endian is not None else cls.cs.endian + endian = normalize_endianness(endian) if endian is not None else cls.__cs__.endian out = BytesIO() cls._write(out, value, endian=endian) @@ -210,6 +213,46 @@ def _write_0(cls, stream: BinaryIO, array: list[Self], *, endian: Endianness) -> """ return cls._write_array(stream, [*array, cls.__default__()], endian=endian) + @property + def cs(cls) -> cstruct: + """Access the cstruct object for this type.""" + warnings.warn( + "The 'cs' property is deprecated, use '__cs__' instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__cs__ + + @property + def size(cls) -> int | None: + """Access the size of this type.""" + warnings.warn( + "The 'size' property is deprecated, use '__size__', len(cls) or sizeof(cls) instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__size__ + + @property + def dynamic(cls) -> bool: + """Access whether this type is dynamically sized.""" + warnings.warn( + "The 'dynamic' property is deprecated, use '__dynamic__' instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__dynamic__ + + @property + def alignment(cls) -> int | None: + """Access the alignment of this type.""" + warnings.warn( + "The 'alignment' property is deprecated, use '__alignment__' instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__alignment__ + class _overload: """Descriptor to use on the ``write`` and ``dumps`` methods on cstruct types. @@ -240,10 +283,10 @@ class BaseType(metaclass=MetaType): def __len__(self) -> int: """Return the byte size of the type.""" - if self.__class__.size is None: + if self.__class__.__size__ is None: raise TypeError("Dynamic size") - return self.__class__.size + return self.__class__.__size__ T = TypeVar("T", bound=BaseType) @@ -281,7 +324,7 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia num = EOF elif isinstance(cls.num_entries, Expression): try: - num = max(0, cls.num_entries.evaluate(cls.cs, context)) + num = max(0, cls.num_entries.evaluate(cls.__cs__, context)) except Exception: if cls.num_entries.expression != "EOF": raise @@ -294,7 +337,7 @@ def _write(cls, stream: BinaryIO, data: list[Any], *, endian: Endianness) -> int if cls.null_terminated: return cls.type._write_0(stream, data, endian=endian) - if not cls.dynamic and cls.num_entries != (actual_size := len(data)): + if not cls.__dynamic__ and cls.num_entries != (actual_size := len(data)): raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.") return cls.type._write_array(stream, data, endian=endian) diff --git a/dissect/cstruct/types/char.py b/dissect/cstruct/types/char.py index 3f72397..da56b7e 100644 --- a/dissect/cstruct/types/char.py +++ b/dissect/cstruct/types/char.py @@ -15,7 +15,7 @@ class CharArray(bytes, BaseArray): @classmethod def __default__(cls) -> Self: - return type.__call__(cls, b"\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries)) + return type.__call__(cls, b"\x00" * (0 if cls.__dynamic__ or cls.null_terminated else cls.num_entries)) @classmethod def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: diff --git a/dissect/cstruct/types/enum.py b/dissect/cstruct/types/enum.py index a2bb790..9c14e7e 100644 --- a/dissect/cstruct/types/enum.py +++ b/dissect/cstruct/types/enum.py @@ -54,11 +54,11 @@ def __call__( raise TypeError("Enum can only be created from int type") enum_cls = super().__call__(name, *args, **kwargs) - enum_cls.cs = cs + enum_cls.__cs__ = cs enum_cls.type = type_ - enum_cls.size = type_.size - enum_cls.dynamic = type_.dynamic - enum_cls.alignment = type_.alignment + enum_cls.__size__ = type_.__size__ + enum_cls.__dynamic__ = type_.__dynamic__ + enum_cls.__alignment__ = type_.__alignment__ _fix_alias_members(enum_cls) diff --git a/dissect/cstruct/types/int.py b/dissect/cstruct/types/int.py index c5b7fa6..6991d66 100644 --- a/dissect/cstruct/types/int.py +++ b/dissect/cstruct/types/int.py @@ -18,10 +18,10 @@ class Int(int, BaseType): @classmethod def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: - data = stream.read(cls.size) + data = stream.read(cls.__size__) - if len(data) != cls.size: - raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}") + if len(data) != cls.__size__: + raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}") return cls.from_bytes(data, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed) @@ -39,4 +39,4 @@ def _read_0(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, end @classmethod def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int: - return stream.write(data.to_bytes(cls.size, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed)) + return stream.write(data.to_bytes(cls.__size__, ENDIANNESS_TO_BYTEORDER_MAP[endian], signed=cls.signed)) diff --git a/dissect/cstruct/types/packed.py b/dissect/cstruct/types/packed.py index b095e28..1f53872 100644 --- a/dissect/cstruct/types/packed.py +++ b/dissect/cstruct/types/packed.py @@ -27,10 +27,10 @@ class Packed(BaseType, Generic[T]): @classmethod def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: - data = stream.read(cls.size) + data = stream.read(cls.__size__) - if len(data) != cls.size: - raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}") + if len(data) != cls.__size__: + raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}") return cls.__new__(cls, _struct(endian, cls.packchar).unpack(data)[0]) @@ -41,9 +41,9 @@ def _read_array( if count == EOF: data = stream.read() length = len(data) - count = length // cls.size + count = length // cls.__size__ else: - length = cls.size * count + length = cls.__size__ * count data = stream.read(length) fmt = _struct(endian, f"{count}{cls.packchar}") @@ -59,10 +59,10 @@ def _read_0(cls, stream: BinaryIO, context: dict[str, Any] | None = None, *, end fmt = _struct(endian, cls.packchar) while True: - data = stream.read(cls.size) + data = stream.read(cls.__size__) - if len(data) != cls.size: - raise EOFError(f"Read {len(data)} bytes, but expected {cls.size}") + if len(data) != cls.__size__: + raise EOFError(f"Read {len(data)} bytes, but expected {cls.__size__}") if (value := fmt.unpack(data)[0]) == 0: break diff --git a/dissect/cstruct/types/pointer.py b/dissect/cstruct/types/pointer.py index 10e0481..14958f0 100644 --- a/dissect/cstruct/types/pointer.py +++ b/dissect/cstruct/types/pointer.py @@ -74,17 +74,17 @@ def method(self: Self, other: int) -> Self: def __default__(cls) -> Self: return cls.__new__( cls, - cls.cs.pointer.__default__(), + cls.__cs__.pointer.__default__(), None, context=None, - endian=cls.cs.endian, + endian=cls.__cs__.endian, ) @classmethod def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: return cls.__new__( cls, - cls.cs.pointer._read(stream, context=context, endian=endian), + cls.__cs__.pointer._read(stream, context=context, endian=endian), stream, context=context, endian=endian, @@ -92,7 +92,7 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia @classmethod def _write(cls, stream: BinaryIO, data: int, *, endian: Endianness) -> int: - return cls.cs.pointer._write(stream, data, endian=endian) + return cls.__cs__.pointer._write(stream, data, endian=endian) def dereference(self, *, endian: AllowedEndianness | None = None) -> T | None: """Dereference the pointer and read the value it points to. diff --git a/dissect/cstruct/types/structure.py b/dissect/cstruct/types/structure.py index e8b1342..642424e 100644 --- a/dissect/cstruct/types/structure.py +++ b/dissect/cstruct/types/structure.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +import warnings from collections import ChainMap from collections.abc import MutableMapping from contextlib import contextmanager @@ -43,7 +44,7 @@ def __init__(self, name: str | None, type_: type[BaseType], bits: int | None = N self.type = type_ self.bits = bits self.offset = offset - self.alignment = type_.alignment or 1 + self.alignment = type_.__alignment__ or 1 def __repr__(self) -> str: bits_str = f" : {self.bits}" if self.bits else "" @@ -53,11 +54,9 @@ def __repr__(self) -> str: class StructureMetaType(MetaType): """Base metaclass for cstruct structure type classes.""" - # TODO: resolve field types in _update_fields, remove resolves elsewhere? - - fields: dict[str, Field] + __members__: dict[str, Field] """Mapping of field names to :class:`Field` objects, including "folded" fields from anonymous structures.""" - lookup: dict[str, Field] + __lookup__: dict[str, Field] """Mapping of "raw" field names to :class:`Field` objects. E.g. holds the anonymous struct and not its fields.""" __fields__: list[Field] """List of :class:`Field` objects for this structure. This is the structures' Single Source Of Truth.""" @@ -96,23 +95,23 @@ def _update_fields( if field._name in lookup and field._name != "_": raise ValueError(f"Duplicate field name: {field._name}") - if not field.type.dynamic: - static_sizes[field._name] = field.type.size + if not field.type.__dynamic__: + static_sizes[field._name] = field.type.__size__ if isinstance(field.type, StructureMetaType) and field.name is None: - for anon_field in field.type.fields.values(): + for anon_field in field.type.__members__.values(): attr = f"{field._name}.{anon_field.name}" classdict[anon_field.name] = property(attrgetter(attr), attrsetter(attr)) - lookup.update(field.type.fields) + lookup.update(field.type.__members__) else: lookup[field._name] = field raw_lookup[field._name] = field field_names = lookup.keys() - classdict["fields"] = lookup - classdict["lookup"] = raw_lookup + classdict["__members__"] = lookup + classdict["__lookup__"] = raw_lookup classdict["__fields__"] = fields classdict["__static_sizes__"] = static_sizes classdict["__bool__"] = _generate__bool__(field_names) @@ -138,7 +137,9 @@ def _update_fields( from dissect.cstruct import compiler try: - classdict["_read"] = compiler.Compiler(cls.cs).compile_read(fields, cls.__name__, align=cls.__align__) + classdict["_read"] = compiler.Compiler(cls.__cs__).compile_read( + fields, cls.__name__, align=cls.__align__ + ) classdict["__compiled__"] = True except Exception: # Revert _read to the slower loop based method @@ -148,17 +149,17 @@ def _update_fields( # TODO: compile _write # TODO: generate cached_property for lazy reading - classdict["size"] = size - classdict["alignment"] = alignment - classdict["dynamic"] = size is None + classdict["__size__"] = size + classdict["__alignment__"] = alignment + classdict["__dynamic__"] = size is None return classdict def _calculate_size_and_offsets(cls, fields: list[Field], align: bool = False) -> tuple[int | None, int]: """Iterate all fields in this structure to calculate the field offsets and total structure size. - If a structure has a dynamic field, further field offsets will be set to None and self.dynamic - will be set to True. + If a structure has a dynamic field, further field offsets will be set to None and ``self.__dynamic__`` + will be set to ``True``. """ # The current offset, set to None if we become dynamic offset = 0 @@ -197,17 +198,17 @@ def _calculate_size_and_offsets(cls, fields: list[Field], align: bool = False) - # Moved to a bit field of another type, e.g. uint16 f1 : 8, uint32 f2 : 8; or field_type != bits_type # Still processing a bit field, but it's at a different offset due to alignment or a manual offset - or (bits_type is not None and offset > bits_field_offset + bits_type.size) + or (bits_type is not None and offset > bits_field_offset + bits_type.__size__) ): # ... if any of this is true, we have to move to the next field bits_type = field_type - bits_count = bits_type.size * 8 + bits_count = bits_type.__size__ * 8 bits_remaining = bits_count bits_field_offset = offset if offset is not None: # We're not dynamic, update the structure size and current offset - offset += bits_type.size + offset += bits_type.__size__ field.offset = bits_field_offset @@ -275,12 +276,12 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia value = field.type._read(stream, context=result, endian=endian) result[field._name] = value - if field.type.dynamic: + if field.type.__dynamic__: sizes[field._name] = stream.tell() - offset if cls.__align__: # Align the stream - stream.seek(-stream.tell() & (cls.alignment - 1), io.SEEK_CUR) + stream.seek(-stream.tell() & (cls.__alignment__ - 1), io.SEEK_CUR) # Using type.__call__ directly calls the __init__ method of the class # This is faster than calling cls() and bypasses the metaclass __call__ method @@ -350,7 +351,7 @@ def _write(cls, stream: BinaryIO, data: Structure, *, endian: Endianness) -> int if cls.__align__: # Align the stream - stream.write(b"\x00" * (-stream.tell() & (cls.alignment - 1))) + stream.write(b"\x00" * (-stream.tell() & (cls.__alignment__ - 1))) return num @@ -413,7 +414,7 @@ def _deps(type_: StructureMetaType, seen: set[MetaType]) -> list[MetaType | str] for token in dim._tokens: if ( token.type in IDENTIFIER_TYPES - and token.value in cls.cs.consts + and token.value in cls.__cs__.consts and token.value not in seen ): deps.append(token.value) @@ -464,7 +465,7 @@ def _render_struct(type_: StructureMetaType, name: str) -> list[str]: blocks = [] for type_ in deps: if isinstance(type_, str): - consts.append(f"#define {type_} {cls.cs.consts[type_]!r}") + consts.append(f"#define {type_} {cls.__cs__.consts[type_]!r}") elif isinstance(type_, EnumMetaType): blocks.append(type_.cdef()) else: @@ -472,6 +473,26 @@ def _render_struct(type_: StructureMetaType, name: str) -> list[str]: return ("\n".join(consts) + "\n\n" if consts else "") + ("\n\n".join(blocks) if blocks else "") + @property + def fields(cls) -> dict[str, Field]: + """List of :class:`Field` objects for this structure.""" + warnings.warn( + "The 'fields' property is deprecated, use '__members__' instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__members__ + + @property + def lookup(cls) -> dict[str, Field]: + """Mapping of "raw" field names to :class:`Field` objects.""" + warnings.warn( + "The 'lookup' property is deprecated, use '__lookup__' instead.", + DeprecationWarning, + stacklevel=2, + ) + return cls.__lookup__ + class Structure(BaseType, metaclass=StructureMetaType): """Base class for cstruct structure type classes. @@ -493,7 +514,7 @@ def __getitem__(self, item: str) -> Any: def __repr__(self) -> str: values = [] - for name, field in self.__class__.fields.items(): + for name, field in self.__class__.__members__.items(): value = self[name] if issubclass(field.type, int) and not issubclass(field.type, (Pointer, Enum)): value = hex(value) @@ -529,13 +550,13 @@ def __setitem__(self, key: str, value: Any) -> None: raise KeyError(key) def __contains__(self, key: str) -> bool: - return key in self._struct.__class__.fields + return key in self._struct.__class__.__members__ def __iter__(self) -> Iterator[str]: - return iter(self._struct.__class__.fields) + return iter(self._struct.__class__.__members__) def __len__(self) -> int: - return len(self._struct.__class__.fields) + return len(self._struct.__class__.__members__) def __repr__(self) -> str: return repr(dict(self)) @@ -555,18 +576,18 @@ def __call__(cls, *args, **kwargs) -> Self: # type: ignore # If we don't have a _buf attribute, we haven't read from a stream and are initializing with values # Set default internal attributes object.__setattr__(obj, "_buf", None) - object.__setattr__(obj, "_endian", cls.cs.endian) + object.__setattr__(obj, "_endian", cls.__cs__.endian) # Calling with non-stream args or kwargs means we are initializing with values if (args and not (len(args) == 1 and (_is_readable_type(args[0]) or _is_buffer_type(args[0])))) or kwargs: # We don't support user initialization of dynamic unions yet - if cls.dynamic: + if cls.__dynamic__: raise NotImplementedError("Initializing a dynamic union is not yet supported") # User (partial) initialization, rebuild the union # First user-provided field is the one used to rebuild the union arg_fields = (field._name for _, field in zip(args, cls.__fields__, strict=False)) - kwarg_fields = (name for name in kwargs if name in cls.lookup) + kwarg_fields = (name for name in kwargs if name in cls.__lookup__) if (first_field := next(chain(arg_fields, kwarg_fields), None)) is not None: obj._rebuild(first_field) elif not args and not kwargs: @@ -605,12 +626,12 @@ def _read_fields( result = {} sizes = {} - if cls.size is None: + if cls.__size__ is None: offset = stream.tell() buf = stream else: offset = 0 - buf = io.BytesIO(stream.read(cls.size)) + buf = io.BytesIO(stream.read(cls.__size__)) for field in cls.__fields__: field_type = field.type @@ -623,13 +644,13 @@ def _read_fields( value = field_type._read(buf, context=result, endian=endian) result[field._name] = value - if field.type.dynamic: + if field.type.__dynamic__: sizes[field._name] = buf.tell() - start return result, sizes def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> Self: # type: ignore - if cls.size is None: + if cls.__size__ is None: start = stream.tell() result, sizes = cls._read_fields(stream, context=context, endian=endian) size = stream.tell() - start @@ -638,7 +659,7 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia else: result = {} sizes = {} - buf = stream.read(cls.size) + buf = stream.read(cls.__size__) # Create the object and set the values # Using type.__call__ directly calls the __init__ method of the class @@ -650,7 +671,7 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia object.__setattr__(obj, "_buf", buf) object.__setattr__(obj, "_endian", endian) - if cls.size is not None: + if cls.__size__ is not None: obj._update() # Proxify any nested structures @@ -659,14 +680,14 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia return obj def _write(cls, stream: BinaryIO, data: Union, *, endian: Endianness) -> int: - if cls.dynamic: + if cls.__dynamic__: raise NotImplementedError("Writing dynamic unions is not yet supported") offset = stream.tell() expected_offset = offset + len(cls) # Sort by largest field - fields = sorted(cls.__fields__, key=lambda e: e.type.size or 0, reverse=True) + fields = sorted(cls.__fields__, key=lambda e: e.type.__size__ or 0, reverse=True) anonymous_struct = False # Try to write by largest field @@ -701,7 +722,7 @@ def __eq__(self, other: object) -> bool: return self.__class__ is other.__class__ and bytes(self) == bytes(other) def __setattr__(self, attr: str, value: Any) -> None: - if self.__class__.dynamic: + if self.__class__.__dynamic__: raise NotImplementedError("Modifying a dynamic union is not yet supported") super().__setattr__(attr, value) @@ -709,10 +730,10 @@ def __setattr__(self, attr: str, value: Any) -> None: def _rebuild(self, attr: str) -> None: if (cur_buf := getattr(self, "_buf", None)) is None: - cur_buf = b"\x00" * self.__class__.size + cur_buf = b"\x00" * self.__class__.__size__ buf = io.BytesIO(cur_buf) - field = self.__class__.lookup[attr] + field = self.__class__.__lookup__[attr] if field.offset: buf.seek(field.offset) diff --git a/dissect/cstruct/types/wchar.py b/dissect/cstruct/types/wchar.py index 3c91120..43dd52d 100644 --- a/dissect/cstruct/types/wchar.py +++ b/dissect/cstruct/types/wchar.py @@ -16,7 +16,7 @@ class WcharArray(str, BaseArray): @classmethod def __default__(cls) -> WcharArray: - return type.__call__(cls, "\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries)) + return type.__call__(cls, "\x00" * (0 if cls.__dynamic__ or cls.null_terminated else cls.num_entries)) @classmethod def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endian: Endianness) -> WcharArray: diff --git a/dissect/cstruct/util.py b/dissect/cstruct/util.py index 00b5ad3..ac890c3 100644 --- a/dissect/cstruct/util.py +++ b/dissect/cstruct/util.py @@ -488,7 +488,7 @@ def sizeof(type_: type[BaseType] | BaseType) -> int: def offsetof(type_: type[Structure], name: str) -> int: """Get the offset of a field in a structure.""" - if (field := type_.fields.get(name)) is None: + if (field := type_.__members__.get(name)) is None: raise ValueError(f"Structure '{type_.__name__}' does not have a field named '{name}'") if (offset := field.offset) is None: raise ValueError(f"Field '{field._name}' of structure '{type_.__name__}' does not have a known offset") diff --git a/tests/test_align.py b/tests/test_align.py index b938cc4..1d26f20 100644 --- a/tests/test_align.py +++ b/tests/test_align.py @@ -26,8 +26,8 @@ def test_align_struct(cs: cstruct, compiled: bool) -> None: fields = cs.test.__fields__ assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 32 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 32 assert fields[0].offset == 0x00 assert fields[1].offset == 0x08 assert fields[2].offset == 0x10 @@ -46,7 +46,7 @@ def test_align_struct(cs: cstruct, compiled: bool) -> None: assert fh.tell() == 32 for name, value in obj.__values__.items(): - assert cs.test.fields[name].offset == value + assert cs.test.__members__[name].offset == value assert obj.dumps() == buf @@ -61,8 +61,8 @@ def test_align_union(cs: cstruct) -> None: cs.load(cdef, align=True) assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 8 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 8 buf = """ 00 00 00 01 00 00 00 02 @@ -88,8 +88,8 @@ def test_align_union_tail(cs: cstruct) -> None: cs.load(cdef, align=True) assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 16 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 16 def test_align_array(cs: cstruct, compiled: bool) -> None: @@ -108,8 +108,8 @@ def test_align_array(cs: cstruct, compiled: bool) -> None: fields = cs.test.__fields__ assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 64 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 64 assert fields[0].offset == 0x00 assert fields[1].offset == 0x08 assert fields[2].offset == 0x28 @@ -153,8 +153,8 @@ def test_align_struct_array(cs: cstruct, compiled: bool) -> None: fields = cs.test.__fields__ assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 16 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 16 assert fields[0].offset == 0x00 assert fields[1].offset == 0x08 @@ -319,8 +319,8 @@ def test_align_pointer(cs: cstruct, compiled: bool) -> None: fields = cs.test.__fields__ assert cs.test.__align__ - assert cs.test.alignment == 8 - assert cs.test.size == 24 + assert cs.test.__alignment__ == 8 + assert len(cs.test) == 24 assert fields[0].offset == 0x00 assert fields[1].offset == 0x08 assert fields[2].offset == 0x10 diff --git a/tests/test_basic.py b/tests/test_basic.py index 64a2192..5a940fb 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re import textwrap from io import BytesIO from typing import TYPE_CHECKING, BinaryIO @@ -155,7 +156,7 @@ def test_type_replace(cs: cstruct) -> None: def test_reserved_attribute_names(cs: cstruct) -> None: cs.load("struct load { uint8 a; };") assert callable(cs.load) - assert cs.resolve("load").fields["a"].type is cs.uint8 + assert cs.resolve("load").__members__["a"].type is cs.uint8 cs.load("struct consts { uint8 a; };") assert isinstance(cs.consts, dict) @@ -184,7 +185,7 @@ def test_const_type_name_collision(cs: cstruct) -> None: cs.load("struct foo { uint8 a; };") cs.load("#define foo 1") assert cs.foo == 1 - assert cs.resolve("foo").fields["a"].type is cs.uint8 + assert cs.resolve("foo").__members__["a"].type is cs.uint8 def test_typedef(cs: cstruct) -> None: @@ -517,12 +518,12 @@ def test_array_class_name(cs: cstruct) -> None: def test_size_and_aligment(cs: cstruct) -> None: test = cs._make_int_type("test", 1, False, alignment=8) - assert test.size == 1 - assert test.alignment == 8 + assert test.__size__ == 1 + assert test.__alignment__ == 8 test = cs._make_packed_type("test", "B", int, alignment=8) - assert test.size == 1 - assert test.alignment == 8 + assert test.__size__ == 1 + assert test.__alignment__ == 8 def test_dynamic_substruct_size(cs: cstruct) -> None: @@ -538,8 +539,8 @@ def test_dynamic_substruct_size(cs: cstruct) -> None: """ cs.load(cdef) - assert cs.sub.dynamic - assert cs.test.dynamic + assert cs.sub.__dynamic__ + assert cs.test.__dynamic__ def test_dumps_write_overload(cs: cstruct) -> None: @@ -596,8 +597,8 @@ def test_copy(cs: cstruct) -> None: assert "test2" in cs_copy.types # Verify that types in the copied cstruct reference the copied cstruct - assert cs_copy.resolve("test").cs is cs_copy - assert cs_copy.resolve("test2").cs is cs_copy + assert cs_copy.resolve("test").__cs__ is cs_copy + assert cs_copy.resolve("test2").__cs__ is cs_copy def test_cdef(cs: cstruct) -> None: @@ -653,7 +654,7 @@ def test_cdef(cs: cstruct) -> None: reparsed.load(cs.cdef()) assert reparsed.consts == cs.consts for name in ("Color", "Point", "Val", "Coord", "Header"): - assert getattr(cs, name).size == getattr(reparsed, name).size + assert len(getattr(cs, name)) == len(getattr(reparsed, name)) def test_cdef_defines(cs: cstruct) -> None: @@ -702,3 +703,41 @@ def test_endianness(cs: cstruct) -> None: with cs.little_endian() as little_endian_cs: assert little_endian_cs.endian == "<" assert little_endian_cs.uint32(b"\x01\x00\x00\x00") == 1 + + +def test_deprecation_warnings(cs: cstruct) -> None: + cdef = """ + struct test { + uint8 a; + }; + """ + cs.load(cdef) + + with pytest.warns(DeprecationWarning, match=re.escape("The 'cs' property is deprecated, use '__cs__' instead.")): + _ = cs.test.cs + + with pytest.warns( + DeprecationWarning, + match=re.escape("The 'size' property is deprecated, use '__size__', len(cls) or sizeof(cls) instead."), + ): + _ = cs.test.size + + with pytest.warns( + DeprecationWarning, match=re.escape("The 'dynamic' property is deprecated, use '__dynamic__' instead.") + ): + _ = cs.test.dynamic + + with pytest.warns( + DeprecationWarning, match=re.escape("The 'alignment' property is deprecated, use '__alignment__' instead.") + ): + _ = cs.test.alignment + + with pytest.warns( + DeprecationWarning, match=re.escape("The 'fields' property is deprecated, use '__members__' instead.") + ): + _ = cs.test.fields + + with pytest.warns( + DeprecationWarning, match=re.escape("The 'lookup' property is deprecated, use '__lookup__' instead.") + ): + _ = cs.test.lookup diff --git a/tests/test_compiler.py b/tests/test_compiler.py index d78bbff..bb3e68c 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -243,10 +243,7 @@ def test_generate_packed_read_offsets(cs: cstruct) -> None: def test_generate_structure_read(cs: cstruct) -> None: - mock_type = Mock() - mock_type.__anonymous__ = False - - field = Field("a", mock_type) + field = Field("a", Mock(__alignment__=None, __dynamic__=True, __anonymous__=False)) code = next(compiler._ReadSourceGenerator(cs, [field])._generate_structure(field)) expected = """ @@ -259,10 +256,7 @@ def test_generate_structure_read(cs: cstruct) -> None: def test_generate_structure_read_anonymous(cs: cstruct) -> None: - mock_type = Mock() - mock_type.__anonymous__ = True - - field = Field("a", mock_type) + field = Field("a", Mock(__alignment__=None, __dynamic__=True, __anonymous__=True)) code = next(compiler._ReadSourceGenerator(cs, [field])._generate_structure(field)) expected = """ @@ -275,7 +269,7 @@ def test_generate_structure_read_anonymous(cs: cstruct) -> None: def test_generate_array_read(cs: cstruct) -> None: - field = Field("a", Mock()) + field = Field("a", Mock(__alignment__=None, __dynamic__=True)) code = next(compiler._ReadSourceGenerator(cs, [field])._generate_array(field)) expected = """ diff --git a/tests/test_parser.py b/tests/test_parser.py index 54273f0..0ddd9d4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -65,10 +65,10 @@ def test_nested_structs(cs: cstruct, compiled: bool) -> None: for i in range(4): assert obj.a[i].b == i - assert cs.nest.fields["a"].type.__name__ == "__anonymous_0__[4]" - assert cs.nest.fields["a"].type.type.__name__ == "__anonymous_0__" + assert cs.nest.__members__["a"].type.__name__ == "__anonymous_0__[4]" + assert cs.nest.__members__["a"].type.type.__name__ == "__anonymous_0__" - assert cs.also_nest.fields["d"].type == cs.named + assert cs.also_nest.__members__["d"].type == cs.named def test_preserve_comment_newlines() -> None: @@ -135,8 +135,8 @@ def test_dynamic_substruct_size(cs: cstruct) -> None: """ cs.load(cdef) - assert cs.sub.dynamic - assert cs.test.dynamic + assert cs.sub.__dynamic__ + assert cs.test.__dynamic__ def test_struct_names(cs: cstruct) -> None: @@ -182,8 +182,8 @@ def test_includes(cs: cstruct) -> None: assert cs.includes == ["", "myLib.h"] assert cs.myStruct.__name__ == "myStruct" - assert len(cs.myStruct.fields) == 1 - assert cs.myStruct.fields.get("charVal") + assert len(cs.myStruct.__members__) == 1 + assert cs.myStruct.__members__.get("charVal") def test_typedef_pointer(cs: cstruct) -> None: @@ -397,10 +397,10 @@ def test_conditional_in_struct(cs: cstruct) -> None: cs.load(cdef) assert "t_bitfield" in cs.types - assert "fval" in cs.t_bitfield.fields - assert "bit0" in cs.t_bitfield.fields["fval"].type.fields - assert "bit1" in cs.t_bitfield.fields["fval"].type.fields - assert "bit2" not in cs.t_bitfield.fields["fval"].type.fields + assert "fval" in cs.t_bitfield.__members__ + assert "bit0" in cs.t_bitfield.__members__["fval"].type.__members__ + assert "bit1" in cs.t_bitfield.__members__["fval"].type.__members__ + assert "bit2" not in cs.t_bitfield.__members__["fval"].type.__members__ def test_conditional_parsing_error(cs: cstruct) -> None: @@ -451,12 +451,12 @@ def test_multiple_declarators(cs: cstruct) -> None: cs.load(cdef) assert "test" in cs.types - assert all(field in cs.test.fields for field in ("a", "b", "c", "d", "e")) - assert cs.test.fields["a"].type == cs.uint32 - assert cs.test.fields["b"].type == cs.uint32 - assert cs.test.fields["c"].type == cs.uint32 - assert cs.test.fields["d"].type.__name__ == "__anonymous_0__" - assert cs.test.fields["e"].type is cs.test.fields["d"].type + assert all(field in cs.test.__members__ for field in ("a", "b", "c", "d", "e")) + assert cs.test.__members__["a"].type == cs.uint32 + assert cs.test.__members__["b"].type == cs.uint32 + assert cs.test.__members__["c"].type == cs.uint32 + assert cs.test.__members__["d"].type.__name__ == "__anonymous_0__" + assert cs.test.__members__["e"].type is cs.test.__members__["d"].type def test_config_flags(cs: cstruct) -> None: @@ -502,17 +502,17 @@ def test_preprocessor_in_struct_body(cs: cstruct) -> None: cs.load(cdef) assert cs.consts["VERSION"] == 2 - assert "always_present" in cs.test.fields - assert "version" in cs.test.fields - assert "basic" in cs.test.fields - assert "extra" not in cs.test.fields - assert "bonus" in cs.test.fields - assert "should_not_exist" not in cs.test.fields + assert "always_present" in cs.test.__members__ + assert "version" in cs.test.__members__ + assert "basic" in cs.test.__members__ + assert "extra" not in cs.test.__members__ + assert "bonus" in cs.test.__members__ + assert "should_not_exist" not in cs.test.__members__ - assert cs.test.fields["always_present"].type == cs.uint32 - assert cs.test.fields["version"].type == cs.uint16 - assert cs.test.fields["basic"].type == cs.uint8 - assert cs.test.fields["bonus"].type == cs.uint64 + assert cs.test.__members__["always_present"].type == cs.uint32 + assert cs.test.__members__["version"].type == cs.uint16 + assert cs.test.__members__["basic"].type == cs.uint8 + assert cs.test.__members__["bonus"].type == cs.uint64 def test_preprocessor_define_from_enum_in_struct(cs: cstruct) -> None: @@ -561,13 +561,13 @@ def test_preprocessor_define_from_enum_in_struct(cs: cstruct) -> None: assert cs.consts["FLAG_SYNACK"] == 3 assert cs.consts["FLAG_BIG"] == "flags.PSH | YOMOMMA" - assert "type" in cs.packet.fields - assert "options" in cs.packet.fields - assert "payload" in cs.packet.fields - assert "checksum" in cs.packet.fields + assert "type" in cs.packet.__members__ + assert "options" in cs.packet.__members__ + assert "payload" in cs.packet.__members__ + assert "checksum" in cs.packet.__members__ - assert cs.packet.fields["options"].type.num_entries == 4 - assert cs.packet.fields["payload"].type.num_entries == 20 + assert cs.packet.__members__["options"].type.num_entries == 4 + assert cs.packet.__members__["payload"].type.num_entries == 20 def test_error_context(cs: cstruct) -> None: diff --git a/tests/test_types_custom.py b/tests/test_types_custom.py index 07d497d..b6f3cb5 100644 --- a/tests/test_types_custom.py +++ b/tests/test_types_custom.py @@ -18,7 +18,7 @@ class EtwPointer(BaseType): @classmethod def __default__(cls) -> int: - return cls.cs.uint64.__default__() + return cls.__cs__.uint64.__default__() @classmethod def _read( @@ -38,13 +38,13 @@ def _write(cls, stream: BinaryIO, data: Any, *, endian: Endianness) -> int: @classmethod def as_32bit(cls) -> None: - cls.type = cls.cs.uint32 - cls.size = 4 + cls.type = cls.__cs__.uint32 + cls.__size__ = 4 @classmethod def as_64bit(cls) -> None: - cls.type = cls.cs.uint64 - cls.size = 8 + cls.type = cls.__cs__.uint64 + cls.__size__ = 8 def test_adding_custom_type(cs: cstruct) -> None: diff --git a/tests/test_types_enum.py b/tests/test_types_enum.py index aa74189..6cf588d 100644 --- a/tests/test_types_enum.py +++ b/tests/test_types_enum.py @@ -20,10 +20,10 @@ def TestEnum(cs: cstruct) -> type[Enum]: def test_enum(cs: cstruct, TestEnum: type[Enum]) -> None: assert issubclass(TestEnum, StdEnum) - assert TestEnum.cs is cs + assert TestEnum.__cs__ is cs assert TestEnum.type is cs.uint8 - assert TestEnum.size == 1 - assert TestEnum.alignment == 1 + assert TestEnum.__size__ == 1 + assert TestEnum.__alignment__ == 1 assert TestEnum.A == 1 assert TestEnum.B == 2 diff --git a/tests/test_types_flag.py b/tests/test_types_flag.py index 37486c1..9922db0 100644 --- a/tests/test_types_flag.py +++ b/tests/test_types_flag.py @@ -22,10 +22,10 @@ def TestFlag(cs: cstruct) -> type[Flag]: def test_flag(cs: cstruct, TestFlag: type[Flag]) -> None: assert issubclass(TestFlag, StdFlag) - assert TestFlag.cs is cs + assert TestFlag.__cs__ is cs assert TestFlag.type is cs.uint8 - assert TestFlag.size == 1 - assert TestFlag.alignment == 1 + assert TestFlag.__size__ == 1 + assert TestFlag.__alignment__ == 1 assert TestFlag.A == 1 assert TestFlag.B == 2 diff --git a/tests/test_types_structure.py b/tests/test_types_structure.py index af6d6ef..7c1b5ce 100644 --- a/tests/test_types_structure.py +++ b/tests/test_types_structure.py @@ -28,13 +28,13 @@ def TestStruct(cs: cstruct) -> type[Structure]: def test_structure(TestStruct: type[Structure]) -> None: assert issubclass(TestStruct, Structure) - assert len(TestStruct.fields) == 2 - assert TestStruct.fields["a"].name == "a" - assert TestStruct.fields["b"].name == "b" - assert repr(TestStruct.fields["a"]) == "" + assert len(TestStruct.__members__) == 2 + assert TestStruct.__members__["a"].name == "a" + assert TestStruct.__members__["b"].name == "b" + assert repr(TestStruct.__members__["a"]) == "" - assert TestStruct.size == 8 - assert TestStruct.alignment == 4 + assert TestStruct.__size__ == 8 + assert TestStruct.__alignment__ == 4 spec = inspect.getfullargspec(TestStruct.__init__) assert spec.args == ["self", "a", "b"] @@ -123,7 +123,7 @@ def test_structure_array_write(TestStruct: type[Structure]) -> None: def test_structure_modify(cs: cstruct) -> None: TestStruct = cs._make_struct("Test", [Field("a", cs.char)]) - assert len(TestStruct.fields) == len(TestStruct.lookup) == 1 + assert len(TestStruct.__members__) == len(TestStruct.__lookup__) == 1 assert len(TestStruct) == 1 spec = inspect.getfullargspec(TestStruct.__init__) assert spec.args == ["self", "a"] @@ -131,7 +131,7 @@ def test_structure_modify(cs: cstruct) -> None: TestStruct.add_field("b", cs.char) - assert len(TestStruct.fields) == len(TestStruct.lookup) == 2 + assert len(TestStruct.__members__) == len(TestStruct.__lookup__) == 2 assert len(TestStruct) == 2 spec = inspect.getfullargspec(TestStruct.__init__) assert spec.args == ["self", "a", "b"] @@ -141,7 +141,7 @@ def test_structure_modify(cs: cstruct) -> None: TestStruct.add_field("c", cs.char) TestStruct.add_field("d", cs.char) - assert len(TestStruct.fields) == len(TestStruct.lookup) == 4 + assert len(TestStruct.__members__) == len(TestStruct.__lookup__) == 4 assert len(TestStruct) == 4 spec = inspect.getfullargspec(TestStruct.__init__) assert spec.args == ["self", "a", "b", "c", "d"] @@ -220,8 +220,8 @@ def test_structure_definitions(cs: cstruct, compiled: bool) -> None: with pytest.raises(ResolveError, match="Unknown type test1"): assert cs.resolve("test1") - assert "a" in cs._test.fields - assert "b" in cs._test.fields + assert "a" in cs._test.__members__ + assert "b" in cs._test.__members__ # This will work but is kind of pointless cdef = """ @@ -338,7 +338,7 @@ def test_structure_definition_simple_be(cs: cstruct, compiled: bool) -> None: assert obj.wstring == "test" assert obj.dumps() == buf - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) @@ -585,8 +585,8 @@ def test_structure_definition_self(cs: cstruct) -> None: """ cs.load(cdef) - assert issubclass(cs.test.fields["b"].type, Pointer) - assert cs.test.fields["b"].type.type is cs.test + assert issubclass(cs.test.__members__["b"].type, Pointer) + assert cs.test.__members__["b"].type.type is cs.test def test_align_struct_in_struct(cs: cstruct) -> None: @@ -659,7 +659,7 @@ def test_structure_default(cs: cstruct, compiled: bool) -> None: assert obj.dumps() == b"\x00" * 57 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) assert cs.test_nested() == cs.test_nested.__default__() @@ -670,7 +670,7 @@ def test_structure_default(cs: cstruct, compiled: bool) -> None: assert obj.dumps() == b"\x00" * 171 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) @@ -728,7 +728,7 @@ def test_structure_default_dynamic(cs: cstruct, compiled: bool) -> None: assert obj.dumps() == b"\x00" * 20 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) assert cs.test_nested() == cs.test_nested.__default__() @@ -738,7 +738,7 @@ def test_structure_default_dynamic(cs: cstruct, compiled: bool) -> None: assert obj.dumps() == b"\x00" * 21 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) @@ -1056,7 +1056,7 @@ def test_cdef_round_trip(cs: cstruct) -> None: for name in ("Header", "Point", "Color", "Val"): original = getattr(cs, name) result = getattr(reparsed, name) - assert original.size == result.size + assert len(original) == len(result) if hasattr(original, "__fields__"): assert [f._name for f in original.__fields__] == [f._name for f in result.__fields__] diff --git a/tests/test_types_union.py b/tests/test_types_union.py index 76175c1..6b58d9d 100644 --- a/tests/test_types_union.py +++ b/tests/test_types_union.py @@ -26,12 +26,12 @@ def TestUnion(cs: cstruct) -> type[Union]: def test_union(TestUnion: type[Union]) -> None: assert issubclass(TestUnion, Union) - assert len(TestUnion.fields) == 2 - assert TestUnion.fields["a"].name == "a" - assert TestUnion.fields["b"].name == "b" + assert len(TestUnion.__members__) == 2 + assert TestUnion.__members__["a"].name == "a" + assert TestUnion.__members__["b"].name == "b" - assert TestUnion.size == 4 - assert TestUnion.alignment == 4 + assert TestUnion.__size__ == 4 + assert TestUnion.__alignment__ == 4 spec = inspect.getfullargspec(TestUnion.__init__) assert spec.args == ["self", "a", "b"] @@ -163,7 +163,7 @@ def test_union_array_write(TestUnion: type[Union]) -> None: def test_union_modify(cs: cstruct) -> None: TestUnion = cs._make_union("Test", [Field("a", cs.char)]) - assert len(TestUnion.fields) == len(TestUnion.lookup) == 1 + assert len(TestUnion.__members__) == len(TestUnion.__lookup__) == 1 assert len(TestUnion) == 1 spec = inspect.getfullargspec(TestUnion.__init__) assert spec.args == ["self", "a"] @@ -171,7 +171,7 @@ def test_union_modify(cs: cstruct) -> None: TestUnion.add_field("b", cs.uint32) - assert len(TestUnion.fields) == len(TestUnion.lookup) == 2 + assert len(TestUnion.__members__) == len(TestUnion.__lookup__) == 2 assert len(TestUnion) == 4 spec = inspect.getfullargspec(TestUnion.__init__) assert spec.args == ["self", "a", "b"] @@ -181,7 +181,7 @@ def test_union_modify(cs: cstruct) -> None: TestUnion.add_field("c", cs.uint16) TestUnion.add_field("d", cs.uint8) - assert len(TestUnion.fields) == len(TestUnion.lookup) == 4 + assert len(TestUnion.__members__) == len(TestUnion.__lookup__) == 4 assert len(TestUnion) == 4 spec = inspect.getfullargspec(TestUnion.__init__) assert spec.args == ["self", "a", "b", "c", "d"] @@ -429,7 +429,7 @@ def test_union_default(cs: cstruct) -> None: assert obj.dumps() == b"\x00" * 8 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType) assert cs.test_nested() == cs.test_nested.__default__() @@ -440,7 +440,7 @@ def test_union_default(cs: cstruct) -> None: assert obj.dumps() == b"\x00" * 24 - for name in obj.fields: + for name in obj.__members__: assert isinstance(getattr(obj, name), BaseType)