|
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 | ) |
| 32 | +from dissect.cstruct.types.base import normalize_endianness |
29 | 33 |
|
30 | 34 | if TYPE_CHECKING: |
31 | | - from collections.abc import Iterable |
| 35 | + from collections.abc import Iterable, Iterator |
32 | 36 | from typing import TypeAlias |
33 | 37 |
|
34 | 38 | from dissect.cstruct.types import ( |
|
39 | 43 |
|
40 | 44 | T = TypeVar("T", bound=BaseType) |
41 | 45 |
|
| 46 | +AllowedEndianness: TypeAlias = Literal["little", "big", "network", "<", ">", "!", "@", "="] |
| 47 | +Endianness: TypeAlias = Literal["<", ">", "!", "@", "="] |
| 48 | + |
42 | 49 |
|
43 | 50 | class cstruct: |
44 | 51 | """Main class of cstruct. All types are registered in here. |
45 | 52 |
|
46 | 53 | Args: |
47 | | - endian: The endianness to use when parsing. |
| 54 | + endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =). |
48 | 55 | pointer: The pointer type to use for pointers. |
49 | 56 | """ |
50 | 57 |
|
51 | 58 | DEF_CSTYLE = 1 |
52 | 59 | DEF_LEGACY = 2 |
53 | 60 |
|
54 | | - def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = None): |
55 | | - self.endian = endian |
| 61 | + def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: str | None = None): |
| 62 | + self.endian = normalize_endianness(endian) |
56 | 63 |
|
57 | 64 | self.consts: dict[str, int | str | bytes] = {} |
58 | 65 | self.types: dict[str, type[BaseType]] = {} |
@@ -279,6 +286,25 @@ def add_custom_type( |
279 | 286 | alignment: The alignment of the type. |
280 | 287 | **kwargs: Additional attributes to add to the type. |
281 | 288 | """ |
| 289 | + # In cstruct 5.0 we changed the function signature of _read and _write |
| 290 | + # Check if the function signature is compatible, and warn if not |
| 291 | + for type_to_check in (type_, type_.ArrayType): |
| 292 | + type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "") |
| 293 | + |
| 294 | + for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"): |
| 295 | + if not hasattr(type_to_check, method): |
| 296 | + continue |
| 297 | + |
| 298 | + signature = inspect.signature(getattr(type_to_check, method)) |
| 299 | + |
| 300 | + # Only warn if the method doesn't accept an endian parameter |
| 301 | + if "endian" not in signature.parameters: |
| 302 | + warnings.warn( |
| 303 | + f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501 |
| 304 | + "Please refer to the changelog of dissect.cstruct 5.0 for more information.", |
| 305 | + stacklevel=2, |
| 306 | + ) |
| 307 | + |
282 | 308 | self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs)) |
283 | 309 |
|
284 | 310 | def add_const(self, name: str, value: Any) -> None: |
@@ -417,6 +443,28 @@ def copy(self) -> cstruct: |
417 | 443 | """ |
418 | 444 | return copy.copy(self) |
419 | 445 |
|
| 446 | + @contextmanager |
| 447 | + def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]: |
| 448 | + """Context manager for temporarily changing the endianness of this cstruct instance.""" |
| 449 | + original = self.endian |
| 450 | + self.endian = normalize_endianness(endian) |
| 451 | + try: |
| 452 | + yield self |
| 453 | + finally: |
| 454 | + self.endian = original |
| 455 | + |
| 456 | + @contextmanager |
| 457 | + def big_endian(self) -> Iterator[cstruct]: |
| 458 | + """Context manager for temporarily changing the endianness to big endian.""" |
| 459 | + with self.endianness(">") as cs: |
| 460 | + yield cs |
| 461 | + |
| 462 | + @contextmanager |
| 463 | + def little_endian(self) -> Iterator[cstruct]: |
| 464 | + """Context manager for temporarily changing the endianness to little endian.""" |
| 465 | + with self.endianness("<") as cs: |
| 466 | + yield cs |
| 467 | + |
420 | 468 | def _make_type( |
421 | 469 | self, |
422 | 470 | name: str, |
|
0 commit comments