dissect.cstruct 4.3.dev1__tar.gz → 4.4.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {dissect_cstruct-4.3.dev1/dissect.cstruct.egg-info → dissect_cstruct-4.4.dev1}/PKG-INFO +2 -2
  2. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/compiler.py +8 -8
  3. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/cstruct.py +11 -9
  4. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/expression.py +6 -7
  5. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/parser.py +17 -25
  6. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/base.py +24 -14
  7. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/char.py +2 -2
  8. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/enum.py +2 -2
  9. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/leb128.py +3 -4
  10. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/pointer.py +4 -4
  11. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/structure.py +13 -11
  12. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/wchar.py +7 -4
  13. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/utils.py +16 -14
  14. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1/dissect.cstruct.egg-info}/PKG-INFO +2 -2
  15. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/pyproject.toml +48 -5
  16. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/conftest.py +1 -1
  17. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_align.py +6 -1
  18. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_basic.py +32 -38
  19. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_bitbuffer.py +7 -3
  20. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_bitfield.py +8 -6
  21. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_compiler.py +9 -5
  22. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_ctypes.py +3 -1
  23. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_expression.py +13 -7
  24. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_parser.py +7 -2
  25. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_base.py +5 -3
  26. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_char.py +8 -4
  27. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_custom.py +10 -8
  28. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_enum.py +42 -15
  29. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_flag.py +26 -13
  30. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_int.py +13 -8
  31. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_leb128.py +9 -5
  32. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_packed.py +22 -17
  33. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_pointer.py +8 -6
  34. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_structure.py +28 -24
  35. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_union.py +13 -8
  36. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_void.py +10 -6
  37. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_types_wchar.py +8 -4
  38. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/test_utils.py +17 -7
  39. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/utils.py +4 -1
  40. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tox.ini +4 -17
  41. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/COPYRIGHT +0 -0
  42. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/LICENSE +0 -0
  43. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/MANIFEST.in +0 -0
  44. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/README.md +0 -0
  45. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/__init__.py +10 -10
  46. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/bitbuffer.py +0 -0
  47. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/exceptions.py +0 -0
  48. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/__init__.py +1 -1
  49. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/flag.py +0 -0
  50. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/int.py +0 -0
  51. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/packed.py +0 -0
  52. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect/cstruct/types/void.py +0 -0
  53. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  54. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  55. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/dissect.cstruct.egg-info/top_level.txt +0 -0
  56. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/examples/disk.py +0 -0
  57. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/examples/mirai.py +0 -0
  58. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/examples/pe.py +0 -0
  59. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/examples/protobuf.py +0 -0
  60. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/examples/secdesc.py +0 -0
  61. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/setup.cfg +0 -0
  62. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/__init__.py +0 -0
  63. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/data/testdef.txt +0 -0
  64. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/docs/Makefile +0 -0
  65. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/docs/conf.py +0 -0
  66. {dissect_cstruct-4.3.dev1 → dissect_cstruct-4.4.dev1}/tests/docs/index.rst +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.cstruct
3
- Version: 4.3.dev1
3
+ Version: 4.4.dev1
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -6,8 +6,7 @@ import io
6
6
  import logging
7
7
  from enum import Enum
8
8
  from textwrap import dedent, indent
9
- from types import MethodType
10
- from typing import TYPE_CHECKING, Iterator
9
+ from typing import TYPE_CHECKING
11
10
 
12
11
  from dissect.cstruct.bitbuffer import BitBuffer
13
12
  from dissect.cstruct.types import (
@@ -30,6 +29,9 @@ from dissect.cstruct.types.enum import EnumMetaType
30
29
  from dissect.cstruct.types.packed import _struct
31
30
 
32
31
  if TYPE_CHECKING:
32
+ from collections.abc import Iterator
33
+ from types import MethodType
34
+
33
35
  from dissect.cstruct.cstruct import cstruct
34
36
  from dissect.cstruct.types.structure import Field
35
37
 
@@ -127,8 +129,7 @@ class _ReadSourceGenerator:
127
129
 
128
130
  code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
129
131
 
130
- template = f"def _read(cls, stream, context=None):\n{code}"
131
- return template
132
+ return f"def _read(cls, stream, context=None):\n{code}"
132
133
 
133
134
  def _generate_fields(self) -> Iterator[str]:
134
135
  current_offset = 0
@@ -210,10 +211,9 @@ class _ReadSourceGenerator:
210
211
  else:
211
212
  current_block.append(field)
212
213
 
213
- if current_offset is not None and size is not None:
214
- if not field.bits or (field.bits and bits_rollover):
215
- current_offset += size
216
- bits_rollover = False
214
+ if current_offset is not None and size is not None and (not field.bits or bits_rollover):
215
+ current_offset += size
216
+ bits_rollover = False
217
217
 
218
218
  yield from flush()
219
219
 
@@ -4,7 +4,8 @@ import ctypes as _ctypes
4
4
  import struct
5
5
  import sys
6
6
  import types
7
- from typing import Any, BinaryIO, Iterator
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, BinaryIO
8
9
 
9
10
  from dissect.cstruct.exceptions import ResolveError
10
11
  from dissect.cstruct.expression import Expression
@@ -27,6 +28,9 @@ from dissect.cstruct.types import (
27
28
  Wchar,
28
29
  )
29
30
 
31
+ if TYPE_CHECKING:
32
+ from collections.abc import Iterator
33
+
30
34
 
31
35
  class cstruct:
32
36
  """Main class of cstruct. All types are registered in here.
@@ -134,7 +138,6 @@ class cstruct:
134
138
  "unsigned __int32": "uint32",
135
139
  "unsigned __int64": "uint64",
136
140
  "unsigned __int128": "uint128",
137
- "unsigned __int128": "uint128",
138
141
 
139
142
  "wchar_t": "wchar",
140
143
 
@@ -233,7 +236,7 @@ class cstruct:
233
236
  """
234
237
  self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
235
238
 
236
- def load(self, definition: str, deftype: int = None, **kwargs) -> cstruct:
239
+ def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
237
240
  """Parse structures from the given definitions using the given definition type.
238
241
 
239
242
  Definitions can be parsed using different parsers. Currently, there's
@@ -259,7 +262,7 @@ class cstruct:
259
262
 
260
263
  return self
261
264
 
262
- def loadfile(self, path: str, deftype: int = None, **kwargs) -> None:
265
+ def loadfile(self, path: str, deftype: int | None = None, **kwargs) -> None:
263
266
  """Load structure definitions from a file.
264
267
 
265
268
  The given path will be read and parsed using the .load() function.
@@ -269,7 +272,7 @@ class cstruct:
269
272
  deftype: The definition type to parse the definitions with.
270
273
  **kwargs: Keyword arguments for parsers.
271
274
  """
272
- with open(path) as fh:
275
+ with Path(path).open() as fh:
273
276
  self.load(fh.read(), deftype, **kwargs)
274
277
 
275
278
  def read(self, name: str, stream: BinaryIO) -> Any:
@@ -354,10 +357,10 @@ class cstruct:
354
357
 
355
358
  return self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)
356
359
 
357
- def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int = None) -> type[Int]:
360
+ def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
358
361
  return self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})
359
362
 
360
- def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int = None) -> type[Packed]:
363
+ def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
361
364
  return self._make_type(
362
365
  name,
363
366
  (base, Packed),
@@ -414,8 +417,7 @@ def ctypes(structure: Structure) -> _ctypes.Structure:
414
417
  t = ctypes_type(field.type)
415
418
  fields.append((field.name, t))
416
419
 
417
- tt = type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
418
- return tt
420
+ return type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
419
421
 
420
422
 
421
423
  def ctypes_type(type_: MetaType) -> Any:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import string
4
- from typing import TYPE_CHECKING, Callable
4
+ from typing import TYPE_CHECKING, Callable, ClassVar
5
5
 
6
6
  from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
7
7
 
@@ -21,8 +21,7 @@ class ExpressionTokenizer:
21
21
  def equal(self, token: str, expected: str | set[str]) -> bool:
22
22
  if isinstance(expected, set):
23
23
  return token in expected
24
- else:
25
- return token == expected
24
+ return token == expected
26
25
 
27
26
  def alnum(self, token: str) -> bool:
28
27
  return token.isalnum()
@@ -88,7 +87,7 @@ class ExpressionTokenizer:
88
87
  continue
89
88
 
90
89
  # If token is a single digit, keep looping over expression and build the number
91
- elif self.match(self.digit, consume=False, append=False):
90
+ if self.match(self.digit, consume=False, append=False):
92
91
  token += self.get_token()
93
92
  self.consume()
94
93
 
@@ -154,7 +153,7 @@ class ExpressionTokenizer:
154
153
  class Expression:
155
154
  """Expression parser for calculations in definitions."""
156
155
 
157
- binary_operators = {
156
+ binary_operators: ClassVar[dict[str, Callable[[int, int], int]]] = {
158
157
  "|": lambda a, b: a | b,
159
158
  "^": lambda a, b: a ^ b,
160
159
  "&": lambda a, b: a & b,
@@ -167,12 +166,12 @@ class Expression:
167
166
  "%": lambda a, b: a % b,
168
167
  }
169
168
 
170
- unary_operators = {
169
+ unary_operators: ClassVar[dict[str, Callable[[int], int]]] = {
171
170
  "u": lambda a: -a,
172
171
  "~": lambda a: ~a,
173
172
  }
174
173
 
175
- precedence_levels = {
174
+ precedence_levels: ClassVar[dict[str, int]] = {
176
175
  "|": 0,
177
176
  "^": 1,
178
177
  "&": 2,
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  import re
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from dissect.cstruct import compiler
8
8
  from dissect.cstruct.exceptions import (
@@ -33,7 +33,7 @@ class Parser:
33
33
  Args:
34
34
  data: Data to parse definitions from, usually a string.
35
35
  """
36
- raise NotImplementedError()
36
+ raise NotImplementedError
37
37
 
38
38
 
39
39
  class TokenParser(Parser):
@@ -119,10 +119,8 @@ class TokenParser(Parser):
119
119
  val = val.strip()
120
120
  if not key:
121
121
  continue
122
- if not val:
123
- val = nextval
124
- else:
125
- val = Expression(self.cstruct, val).evaluate(values)
122
+
123
+ val = nextval if not val else Expression(self.cstruct, val).evaluate(values)
126
124
 
127
125
  if enumtype == "flag":
128
126
  high_bit = val.bit_length() - 1
@@ -243,7 +241,7 @@ class TokenParser(Parser):
243
241
  # Dirty trick because the regex expects a ; but we don't want it to be part of the value
244
242
  m = pattern.match(ltok.value + ";")
245
243
  d = ast.literal_eval(m.group(2))
246
- self.cstruct.lookups[m.group(1)] = dict([(self.cstruct.consts[k], v) for k, v in d.items()])
244
+ self.cstruct.lookups[m.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
247
245
 
248
246
  def _parse_field(self, tokens: TokenConsumer) -> Field:
249
247
  type_ = None
@@ -279,10 +277,7 @@ class TokenParser(Parser):
279
277
 
280
278
  if count_expression is not None:
281
279
  # Poor mans multi-dimensional array by abusing the eager regex match of count
282
- if "][" in count_expression:
283
- counts = count_expression.split("][")
284
- else:
285
- counts = [count_expression]
280
+ counts = count_expression.split("][") if "][" in count_expression else [count_expression]
286
281
 
287
282
  for count in reversed(counts):
288
283
  if count == "":
@@ -315,8 +310,7 @@ class TokenParser(Parser):
315
310
  if ntoken == self.TOK.NAME:
316
311
  names.append(ntoken.value.strip())
317
312
  elif ntoken == self.TOK.DEFS:
318
- for name in ntoken.value.strip().split(","):
319
- names.append(name.strip())
313
+ names.extend([name.strip() for name in ntoken.value.strip().split(",")])
320
314
 
321
315
  return names
322
316
 
@@ -333,8 +327,8 @@ class TokenParser(Parser):
333
327
  # it means we have captured a non-quoted (real) comment string.
334
328
  if comment := match.group(2):
335
329
  return "\n" * comment.count("\n") # so we will return empty to remove the comment
336
- else: # otherwise, we will return the 1st group
337
- return match.group(1) # captured quoted-string
330
+ # otherwise, we will return the 1st group
331
+ return match.group(1) # captured quoted-string
338
332
 
339
333
  return regex.sub(_replacer, string)
340
334
 
@@ -429,10 +423,8 @@ class CStyleParser(Parser):
429
423
  val = val.strip()
430
424
  if not key:
431
425
  continue
432
- if not val:
433
- val = nextval
434
- else:
435
- val = Expression(self.cstruct, val).evaluate()
426
+
427
+ val = nextval if not val else Expression(self.cstruct, val).evaluate()
436
428
 
437
429
  if enumtype == "flag":
438
430
  high_bit = val.bit_length() - 1
@@ -535,7 +527,7 @@ class CStyleParser(Parser):
535
527
 
536
528
  for t in r:
537
529
  d = ast.literal_eval(t.group(2))
538
- self.cstruct.lookups[t.group(1)] = dict([(self.cstruct.consts[k], v) for k, v in d.items()])
530
+ self.cstruct.lookups[t.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
539
531
 
540
532
  def parse(self, data: str) -> None:
541
533
  self._constants(data)
@@ -545,23 +537,23 @@ class CStyleParser(Parser):
545
537
 
546
538
 
547
539
  class Token:
548
- __slots__ = ("token", "value", "match")
540
+ __slots__ = ("match", "token", "value")
549
541
 
550
542
  def __init__(self, token: str, value: str, match: re.Match):
551
543
  self.token = token
552
544
  self.value = value
553
545
  self.match = match
554
546
 
555
- def __eq__(self, other):
547
+ def __eq__(self, other: object) -> bool:
556
548
  if isinstance(other, Token):
557
549
  other = other.token
558
550
 
559
551
  return self.token == other
560
552
 
561
- def __ne__(self, other):
553
+ def __ne__(self, other: object) -> bool:
562
554
  return not self == other
563
555
 
564
- def __repr__(self):
556
+ def __repr__(self) -> str:
565
557
  return f"<Token.{self.token} value={self.value!r}>"
566
558
 
567
559
 
@@ -571,7 +563,7 @@ class TokenCollection:
571
563
  self.lookup: dict[str, str] = {}
572
564
  self.patterns: dict[str, re.Pattern] = {}
573
565
 
574
- def __getattr__(self, attr: str):
566
+ def __getattr__(self, attr: str) -> str | Any:
575
567
  try:
576
568
  return self.lookup[attr]
577
569
  except AttributeError:
@@ -59,7 +59,7 @@ class MetaType(type):
59
59
 
60
60
  return cls.size
61
61
 
62
- def default(cls) -> BaseType:
62
+ def __default__(cls) -> BaseType:
63
63
  """Return the default value of this type."""
64
64
  return cls()
65
65
 
@@ -122,7 +122,7 @@ class MetaType(type):
122
122
  stream: The stream to read from.
123
123
  context: Optional reading context.
124
124
  """
125
- raise NotImplementedError()
125
+ raise NotImplementedError
126
126
 
127
127
  def _read_array(cls, stream: BinaryIO, count: int, context: dict[str, Any] | None = None) -> list[BaseType]:
128
128
  """Internal function for reading array values.
@@ -136,11 +136,8 @@ class MetaType(type):
136
136
  """
137
137
  if count == EOF:
138
138
  result = []
139
- while True:
140
- try:
141
- result.append(cls._read(stream, context))
142
- except EOFError:
143
- break
139
+ while not _is_eof(stream):
140
+ result.append(cls._read(stream, context))
144
141
  return result
145
142
 
146
143
  return [cls._read(stream, context) for _ in range(count)]
@@ -154,10 +151,10 @@ class MetaType(type):
154
151
  stream: The stream to read from.
155
152
  context: Optional reading context.
156
153
  """
157
- raise NotImplementedError()
154
+ raise NotImplementedError
158
155
 
159
156
  def _write(cls, stream: BinaryIO, data: Any) -> int:
160
- raise NotImplementedError()
157
+ raise NotImplementedError
161
158
 
162
159
  def _write_array(cls, stream: BinaryIO, array: list[BaseType]) -> int:
163
160
  """Internal function for writing arrays.
@@ -179,7 +176,7 @@ class MetaType(type):
179
176
  stream: The stream to read from.
180
177
  array: The array to write.
181
178
  """
182
- return cls._write_array(stream, array + [cls.default()])
179
+ return cls._write_array(stream, [*array, cls.__default__()])
183
180
 
184
181
 
185
182
  class _overload:
@@ -200,8 +197,7 @@ class _overload:
200
197
  def __get__(self, instance: BaseType | None, owner: MetaType) -> Callable[[Any], bytes]:
201
198
  if instance is None:
202
199
  return functools.partial(self.func, owner)
203
- else:
204
- return functools.partial(self.func, instance.__class__, value=instance)
200
+ return functools.partial(self.func, instance.__class__, value=instance)
205
201
 
206
202
 
207
203
  class BaseType(metaclass=MetaType):
@@ -225,8 +221,10 @@ class ArrayMetaType(MetaType):
225
221
  num_entries: int | Expression | None
226
222
  null_terminated: bool
227
223
 
228
- def default(cls) -> BaseType:
229
- return type.__call__(cls, [cls.type.default()] * (cls.num_entries if isinstance(cls.num_entries, int) else 0))
224
+ def __default__(cls) -> BaseType:
225
+ return type.__call__(
226
+ cls, [cls.type.__default__()] * (cls.num_entries if isinstance(cls.num_entries, int) else 0)
227
+ )
230
228
 
231
229
  def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Array:
232
230
  if cls.null_terminated:
@@ -281,5 +279,17 @@ def _is_buffer_type(value: Any) -> bool:
281
279
  return isinstance(value, (bytes, memoryview, bytearray))
282
280
 
283
281
 
282
+ def _is_eof(stream: BinaryIO) -> bool:
283
+ """Check if the stream has reached EOF."""
284
+ pos = stream.tell()
285
+ stream.read(1)
286
+
287
+ if stream.tell() == pos:
288
+ return True
289
+
290
+ stream.seek(pos)
291
+ return False
292
+
293
+
284
294
  # As mentioned in the BaseType class, we correctly set the type here
285
295
  MetaType.ArrayType = Array
@@ -25,7 +25,7 @@ class CharArray(bytes, BaseType, metaclass=ArrayMetaType):
25
25
  return stream.write(data)
26
26
 
27
27
  @classmethod
28
- def default(cls) -> CharArray:
28
+ def __default__(cls) -> CharArray:
29
29
  return type.__call__(cls, b"\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
30
30
 
31
31
 
@@ -75,5 +75,5 @@ class Char(bytes, BaseType):
75
75
  return stream.write(data)
76
76
 
77
77
  @classmethod
78
- def default(cls) -> Char:
78
+ def __default__(cls) -> Char:
79
79
  return type.__call__(cls, b"\x00")
@@ -27,7 +27,7 @@ class EnumMetaType(EnumMeta, MetaType):
27
27
  ) -> EnumMetaType:
28
28
  if name is None:
29
29
  if value is None:
30
- value = cls.type.default()
30
+ value = cls.type.__default__()
31
31
 
32
32
  if not isinstance(value, int):
33
33
  # value is a parsable value
@@ -82,7 +82,7 @@ class EnumMetaType(EnumMeta, MetaType):
82
82
 
83
83
  def _write_0(cls, stream: BinaryIO, array: list[BaseType]) -> int:
84
84
  data = [entry.value if isinstance(entry, Enum) else entry for entry in array]
85
- return cls._write_array(stream, data + [cls.type.default()])
85
+ return cls._write_array(stream, [*data, cls.type.__default__()])
86
86
 
87
87
 
88
88
  def _fix_alias_members(cls: type[Enum]) -> None:
@@ -28,9 +28,8 @@ class LEB128(int, BaseType):
28
28
  if (b & 0x80) == 0:
29
29
  break
30
30
 
31
- if cls.signed:
32
- if b & 0x40 != 0:
33
- result |= ~0 << shift
31
+ if cls.signed and b & 0x40 != 0:
32
+ result |= ~0 << shift
34
33
 
35
34
  return cls.__new__(cls, result)
36
35
 
@@ -60,7 +59,7 @@ class LEB128(int, BaseType):
60
59
 
61
60
  # function works similar for signed- and unsigned integers, except for the check when to stop
62
61
  # the encoding process.
63
- if (cls.signed and (data == 0 and byte & 0x40 == 0) or (data == -1 and byte & 0x40 != 0)) or (
62
+ if ((cls.signed and (data == 0 and byte & 0x40 == 0)) or (data == -1 and byte & 0x40 != 0)) or (
64
63
  not cls.signed and data == 0
65
64
  ):
66
65
  result.append(byte)
@@ -16,7 +16,7 @@ class Pointer(int, BaseType):
16
16
  _context: dict[str, Any] | None
17
17
  _value: BaseType
18
18
 
19
- def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer:
19
+ def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer: # noqa: PYI034
20
20
  obj = super().__new__(cls, value)
21
21
  obj._stream = stream
22
22
  obj._context = context
@@ -66,8 +66,8 @@ class Pointer(int, BaseType):
66
66
  return type.__call__(self.__class__, int.__or__(self, other), self._stream, self._context)
67
67
 
68
68
  @classmethod
69
- def default(cls) -> Pointer:
70
- return cls.__new__(cls, cls.cs.pointer.default(), None, None)
69
+ def __default__(cls) -> Pointer:
70
+ return cls.__new__(cls, cls.cs.pointer.__default__(), None, None)
71
71
 
72
72
  @classmethod
73
73
  def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Pointer:
@@ -79,7 +79,7 @@ class Pointer(int, BaseType):
79
79
 
80
80
  def dereference(self) -> Any:
81
81
  if self == 0 or self._stream is None:
82
- raise NullPointerDereference()
82
+ raise NullPointerDereference
83
83
 
84
84
  if self._value is None and not issubclass(self.type, Void):
85
85
  # Read current position of file read/write pointer
@@ -7,8 +7,7 @@ from functools import lru_cache
7
7
  from itertools import chain
8
8
  from operator import attrgetter
9
9
  from textwrap import dedent
10
- from types import FunctionType
11
- from typing import Any, BinaryIO, Callable, Iterator
10
+ from typing import TYPE_CHECKING, Any, BinaryIO, Callable
12
11
 
13
12
  from dissect.cstruct.bitbuffer import BitBuffer
14
13
  from dissect.cstruct.types.base import (
@@ -20,11 +19,15 @@ from dissect.cstruct.types.base import (
20
19
  from dissect.cstruct.types.enum import EnumMetaType
21
20
  from dissect.cstruct.types.pointer import Pointer
22
21
 
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterator
24
+ from types import FunctionType
25
+
23
26
 
24
27
  class Field:
25
28
  """Structure field."""
26
29
 
27
- def __init__(self, name: str, type_: MetaType, bits: int = None, offset: int = None):
30
+ def __init__(self, name: str, type_: MetaType, bits: int | None = None, offset: int | None = None):
28
31
  self.name = name
29
32
  self.type = type_
30
33
  self.bits = bits
@@ -70,7 +73,7 @@ class StructureMetaType(MetaType):
70
73
  ):
71
74
  # Shortcut for single char/bytes type
72
75
  return type.__call__(cls, *args, **kwargs)
73
- elif not args and not kwargs:
76
+ if not args and not kwargs:
74
77
  obj = type.__call__(cls)
75
78
  object.__setattr__(obj, "_values", {})
76
79
  object.__setattr__(obj, "_sizes", {})
@@ -259,8 +262,7 @@ class StructureMetaType(MetaType):
259
262
  if field.name:
260
263
  result[field.name] = value
261
264
  continue
262
- else:
263
- bit_buffer.reset()
265
+ bit_buffer.reset()
264
266
 
265
267
  value = field_type._read(stream, result)
266
268
 
@@ -325,7 +327,7 @@ class StructureMetaType(MetaType):
325
327
 
326
328
  value = getattr(data, field.name, None)
327
329
  if value is None:
328
- value = field_type.default()
330
+ value = field_type.__default__()
329
331
 
330
332
  if field.bits:
331
333
  if isinstance(field_type, EnumMetaType):
@@ -540,7 +542,7 @@ class Union(Structure, metaclass=UnionMetaType):
540
542
 
541
543
  _buf: bytes
542
544
 
543
- def __eq__(self, other: Any) -> bool:
545
+ def __eq__(self, other: object) -> bool:
544
546
  return self.__class__ is other.__class__ and bytes(self) == bytes(other)
545
547
 
546
548
  def __setattr__(self, attr: str, value: Any) -> None:
@@ -560,7 +562,7 @@ class Union(Structure, metaclass=UnionMetaType):
560
562
  buf.seek(field.offset)
561
563
 
562
564
  if (value := getattr(self, attr)) is None:
563
- value = field.type.default()
565
+ value = field.type.__default__()
564
566
 
565
567
  field.type._write(buf, value)
566
568
 
@@ -774,7 +776,7 @@ def _generate_structure__init__(fields: list[Field]) -> FunctionType:
774
776
  template: FunctionType = _make_structure__init__(len(field_names))
775
777
  return type(template)(
776
778
  template.__code__.replace(
777
- co_consts=(None, *[field.type.default() for field in fields]),
779
+ co_consts=(None, *[field.type.__default__() for field in fields]),
778
780
  co_names=(*field_names,),
779
781
  co_varnames=("self", *field_names),
780
782
  ),
@@ -794,7 +796,7 @@ def _generate_union__init__(fields: list[Field]) -> FunctionType:
794
796
  template: FunctionType = _make_union__init__(len(field_names))
795
797
  return type(template)(
796
798
  template.__code__.replace(
797
- co_consts=(None, *sum([(field.name, field.type.default()) for field in fields], ())),
799
+ co_consts=(None, *sum([(field.name, field.type.__default__()) for field in fields], ())),
798
800
  co_varnames=("self", *field_names),
799
801
  ),
800
802
  template.__globals__,
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
- from typing import Any, BinaryIO
4
+ from typing import Any, BinaryIO, ClassVar
5
5
 
6
6
  from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
7
7
 
@@ -9,6 +9,8 @@ from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
9
9
  class WcharArray(str, BaseType, metaclass=ArrayMetaType):
10
10
  """Wide-character array type for reading and writing UTF-16 strings."""
11
11
 
12
+ __slots__ = ()
13
+
12
14
  @classmethod
13
15
  def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> WcharArray:
14
16
  return type.__call__(cls, ArrayMetaType._read(cls, stream, context))
@@ -20,7 +22,7 @@ class WcharArray(str, BaseType, metaclass=ArrayMetaType):
20
22
  return stream.write(data.encode(Wchar.__encoding_map__[cls.cs.endian]))
21
23
 
22
24
  @classmethod
23
- def default(cls) -> WcharArray:
25
+ def __default__(cls) -> WcharArray:
24
26
  return type.__call__(cls, "\x00" * (0 if cls.dynamic or cls.null_terminated else cls.num_entries))
25
27
 
26
28
 
@@ -29,7 +31,8 @@ class Wchar(str, BaseType):
29
31
 
30
32
  ArrayType = WcharArray
31
33
 
32
- __encoding_map__ = {
34
+ __slots__ = ()
35
+ __encoding_map__: ClassVar[dict[str, str]] = {
33
36
  "@": f"utf-16-{sys.byteorder[0]}e",
34
37
  "=": f"utf-16-{sys.byteorder[0]}e",
35
38
  "<": "utf-16-le",
@@ -75,5 +78,5 @@ class Wchar(str, BaseType):
75
78
  return stream.write(data.encode(cls.__encoding_map__[cls.cs.endian]))
76
79
 
77
80
  @classmethod
78
- def default(cls) -> Wchar:
81
+ def __default__(cls) -> Wchar:
79
82
  return type.__call__(cls, "\x00")