|
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 | | -from dissect.cstruct.exceptions import ResolveError |
| 14 | +from dissect.cstruct.exceptions import Error, ResolveError |
12 | 15 | from dissect.cstruct.expression import Expression |
13 | 16 | from dissect.cstruct.parser import CStyleParser |
14 | 17 | from dissect.cstruct.types import ( |
|
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 = [] |
@@ -215,9 +221,11 @@ def __copy__(self) -> cstruct: |
215 | 221 |
|
216 | 222 | # Update typedefs to point to the new cstruct instance |
217 | 223 | for name, type in self.typedefs.items(): |
| 224 | + # Skip all the default types |
218 | 225 | if name in cs.typedefs: |
219 | 226 | continue |
220 | 227 |
|
| 228 | + # Update the cstruct instance for all other types |
221 | 229 | if isinstance(type, MetaType): |
222 | 230 | new_type = copy.copy(type) |
223 | 231 | new_type.cs = cs |
@@ -264,6 +272,33 @@ def add_custom_type( |
264 | 272 | alignment: The alignment of the type. |
265 | 273 | **kwargs: Additional attributes to add to the type. |
266 | 274 | """ |
| 275 | + # In cstruct 5.0 we changed the function signature of _read and _write |
| 276 | + # Check if the function signature is compatible, and throw an error if not |
| 277 | + for type_to_check in (type_, type_.ArrayType): |
| 278 | + type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "") |
| 279 | + |
| 280 | + for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"): |
| 281 | + if not hasattr(type_to_check, method): |
| 282 | + continue |
| 283 | + |
| 284 | + signature = inspect.signature(getattr(type_to_check, method)) |
| 285 | + |
| 286 | + # We added a few keyword-only parameters to the function signature, but any custom type will |
| 287 | + # continue to work fine as long as they accept **kwargs |
| 288 | + if not any(param.kind == inspect.Parameter.VAR_KEYWORD for param in signature.parameters.values()): |
| 289 | + raise Error( |
| 290 | + f"Custom type {type_name} has an incompatible {method} method signature. " |
| 291 | + "Please refer to the changelog of dissect.cstruct 5.0 for more information." |
| 292 | + ) |
| 293 | + |
| 294 | + # Only warn if the method doesn't accept an endian parameter |
| 295 | + if "endian" not in signature.parameters: |
| 296 | + warnings.warn( |
| 297 | + f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501 |
| 298 | + "Please refer to the changelog of dissect.cstruct 5.0 for more information.", |
| 299 | + stacklevel=2, |
| 300 | + ) |
| 301 | + |
267 | 302 | self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs)) |
268 | 303 |
|
269 | 304 | def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct: |
@@ -351,6 +386,25 @@ def copy(self) -> cstruct: |
351 | 386 | """ |
352 | 387 | return copy.copy(self) |
353 | 388 |
|
| 389 | + @contextmanager |
| 390 | + def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]: |
| 391 | + """Context manager for temporarily changing the endianness of this cstruct instance.""" |
| 392 | + new = self.copy() |
| 393 | + new.endian = normalize_endianness(endian) |
| 394 | + yield new |
| 395 | + |
| 396 | + @contextmanager |
| 397 | + def big_endian(self) -> Iterator[cstruct]: |
| 398 | + """Context manager for temporarily changing the endianness to big endian.""" |
| 399 | + with self.endianness(">") as cs: |
| 400 | + yield cs |
| 401 | + |
| 402 | + @contextmanager |
| 403 | + def little_endian(self) -> Iterator[cstruct]: |
| 404 | + """Context manager for temporarily changing the endianness to little endian.""" |
| 405 | + with self.endianness("<") as cs: |
| 406 | + yield cs |
| 407 | + |
354 | 408 | def _make_type( |
355 | 409 | self, |
356 | 410 | name: str, |
|
0 commit comments