|
2 | 2 |
|
3 | 3 | import copy |
4 | 4 | import ctypes as _ctypes |
| 5 | +import inspect |
5 | 6 | import struct |
6 | 7 | import sys |
7 | 8 | import types |
| 9 | +import warnings |
| 10 | +from contextlib import contextmanager |
8 | 11 | from pathlib import Path |
9 | | -from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast |
| 12 | +from typing import TYPE_CHECKING, Any, BinaryIO, Literal, TypeVar, cast |
10 | 13 |
|
11 | 14 | from dissect.cstruct.exception import ResolveError |
12 | 15 | from dissect.cstruct.expression import Expression |
|
26 | 29 | Void, |
27 | 30 | Wchar, |
28 | 31 | ) |
29 | | -from dissect.cstruct.types.base import MetaType |
| 32 | +from dissect.cstruct.types.base import MetaType, normalize_endianness |
30 | 33 |
|
31 | 34 | if TYPE_CHECKING: |
32 | | - from collections.abc import Iterable |
| 35 | + from collections.abc import Iterable, Iterator |
33 | 36 | from typing import TypeAlias |
34 | 37 |
|
35 | 38 | from dissect.cstruct.types import ( |
|
40 | 43 |
|
41 | 44 | T = TypeVar("T", bound=BaseType) |
42 | 45 |
|
| 46 | +AllowedEndianness: TypeAlias = Literal["little", "big", "network", "<", ">", "!", "@", "="] |
| 47 | +Endianness: TypeAlias = Literal["<", ">", "!", "@", "="] |
| 48 | + |
43 | 49 |
|
44 | 50 | class cstruct: |
45 | 51 | """Main class of cstruct. All types are registered in here. |
46 | 52 |
|
47 | 53 | Args: |
48 | | - endian: The endianness to use when parsing. |
| 54 | + endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =). |
49 | 55 | pointer: The pointer type to use for pointers. |
50 | 56 | """ |
51 | 57 |
|
52 | 58 | DEF_CSTYLE = 1 |
53 | 59 | DEF_LEGACY = 2 |
54 | 60 |
|
55 | | - def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = None): |
56 | | - self.endian = endian |
| 61 | + def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: str | None = None): |
| 62 | + self.endian = normalize_endianness(endian) |
57 | 63 |
|
58 | 64 | self.consts = {} |
59 | 65 | self.includes = [] |
@@ -216,9 +222,11 @@ def __copy__(self) -> cstruct: |
216 | 222 |
|
217 | 223 | # Update typedefs to point to the new cstruct instance |
218 | 224 | for name, type in self.typedefs.items(): |
| 225 | + # Skip all the default types |
219 | 226 | if name in cs.typedefs: |
220 | 227 | continue |
221 | 228 |
|
| 229 | + # Update the cstruct instance for all other types |
222 | 230 | if isinstance(type, MetaType): |
223 | 231 | new_type = copy.copy(type) |
224 | 232 | new_type.cs = cs |
@@ -265,6 +273,25 @@ def add_custom_type( |
265 | 273 | alignment: The alignment of the type. |
266 | 274 | **kwargs: Additional attributes to add to the type. |
267 | 275 | """ |
| 276 | + # In cstruct 5.0 we changed the function signature of _read and _write |
| 277 | + # Check if the function signature is compatible, and warn if not |
| 278 | + for type_to_check in (type_, type_.ArrayType): |
| 279 | + type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "") |
| 280 | + |
| 281 | + for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"): |
| 282 | + if not hasattr(type_to_check, method): |
| 283 | + continue |
| 284 | + |
| 285 | + signature = inspect.signature(getattr(type_to_check, method)) |
| 286 | + |
| 287 | + # Only warn if the method doesn't accept an endian parameter |
| 288 | + if "endian" not in signature.parameters: |
| 289 | + warnings.warn( |
| 290 | + f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501 |
| 291 | + "Please refer to the changelog of dissect.cstruct 5.0 for more information.", |
| 292 | + stacklevel=2, |
| 293 | + ) |
| 294 | + |
268 | 295 | self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs)) |
269 | 296 |
|
270 | 297 | def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct: |
@@ -388,6 +415,28 @@ def copy(self) -> cstruct: |
388 | 415 | """ |
389 | 416 | return copy.copy(self) |
390 | 417 |
|
| 418 | + @contextmanager |
| 419 | + def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]: |
| 420 | + """Context manager for temporarily changing the endianness of this cstruct instance.""" |
| 421 | + original = self.endian |
| 422 | + self.endian = normalize_endianness(endian) |
| 423 | + try: |
| 424 | + yield self |
| 425 | + finally: |
| 426 | + self.endian = original |
| 427 | + |
| 428 | + @contextmanager |
| 429 | + def big_endian(self) -> Iterator[cstruct]: |
| 430 | + """Context manager for temporarily changing the endianness to big endian.""" |
| 431 | + with self.endianness(">") as cs: |
| 432 | + yield cs |
| 433 | + |
| 434 | + @contextmanager |
| 435 | + def little_endian(self) -> Iterator[cstruct]: |
| 436 | + """Context manager for temporarily changing the endianness to little endian.""" |
| 437 | + with self.endianness("<") as cs: |
| 438 | + yield cs |
| 439 | + |
391 | 440 | def _make_type( |
392 | 441 | self, |
393 | 442 | name: str, |
|
0 commit comments