dissect.cstruct 5.0.dev6__tar.gz → 5.0.dev8__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 (82) hide show
  1. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/PKG-INFO +1 -1
  2. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/__init__.py +12 -0
  3. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/compiler.py +3 -5
  4. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/base.py +5 -24
  5. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/char.py +10 -4
  6. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/packed.py +1 -7
  7. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/structure.py +2 -2
  8. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/wchar.py +7 -0
  9. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/util.py +90 -1
  10. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/PKG-INFO +1 -1
  11. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/scm_file_list.json +52 -52
  12. dissect_cstruct-5.0.dev8/dissect.cstruct.egg-info/scm_version.json +8 -0
  13. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_base.py +19 -0
  14. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_char.py +34 -0
  15. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_wchar.py +29 -0
  16. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_utils.py +30 -0
  17. dissect_cstruct-5.0.dev6/dissect.cstruct.egg-info/scm_version.json +0 -8
  18. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/.git-blame-ignore-revs +0 -0
  19. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/.gitattributes +0 -0
  20. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/CHANGELOG.md +0 -0
  21. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/COPYRIGHT +0 -0
  22. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/LICENSE +0 -0
  23. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/MANIFEST.in +0 -0
  24. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/README.md +0 -0
  25. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/bitbuffer.py +0 -0
  26. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/cstruct.py +0 -0
  27. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/exception.py +0 -0
  28. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/expression.py +0 -0
  29. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/lexer.py +0 -0
  30. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/parser.py +0 -0
  31. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/tools/__init__.py +0 -0
  32. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/tools/stubgen.py +0 -0
  33. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/__init__.py +0 -0
  34. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/enum.py +0 -0
  35. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/flag.py +0 -0
  36. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/int.py +0 -0
  37. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/leb128.py +0 -0
  38. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/pointer.py +0 -0
  39. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect/cstruct/types/void.py +0 -0
  40. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  41. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  42. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  43. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/requires.txt +0 -0
  44. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/dissect.cstruct.egg-info/top_level.txt +0 -0
  45. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/examples/disk.py +0 -0
  46. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/examples/mirai.py +0 -0
  47. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/examples/pe.py +0 -0
  48. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/examples/protobuf.py +0 -0
  49. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/examples/secdesc.py +0 -0
  50. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/pyproject.toml +0 -0
  51. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/setup.cfg +0 -0
  52. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/__init__.py +0 -0
  53. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/_data/testdef.txt +0 -0
  54. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/_docs/Makefile +0 -0
  55. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/_docs/__init__.py +0 -0
  56. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/_docs/conf.py +0 -0
  57. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/_docs/index.rst +0 -0
  58. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/conftest.py +0 -0
  59. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_align.py +0 -0
  60. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_annotations.py +0 -0
  61. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_basic.py +0 -0
  62. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_benchmark.py +0 -0
  63. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_bitbuffer.py +0 -0
  64. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_bitfield.py +0 -0
  65. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_compiler.py +0 -0
  66. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_ctypes.py +0 -0
  67. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_expression.py +0 -0
  68. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_lexer.py +0 -0
  69. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_parser.py +0 -0
  70. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_tools_stubgen.py +0 -0
  71. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_custom.py +0 -0
  72. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_enum.py +0 -0
  73. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_flag.py +0 -0
  74. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_int.py +0 -0
  75. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_leb128.py +0 -0
  76. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_packed.py +0 -0
  77. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_pointer.py +0 -0
  78. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_structure.py +0 -0
  79. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_union.py +0 -0
  80. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/test_types_void.py +0 -0
  81. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tests/utils.py +0 -0
  82. {dissect_cstruct-5.0.dev6 → dissect_cstruct-5.0.dev8}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 5.0.dev6
3
+ Version: 5.0.dev8
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-Expression: Apache-2.0
@@ -30,12 +30,18 @@ from dissect.cstruct.types import (
30
30
  )
31
31
  from dissect.cstruct.util import (
32
32
  dumpstruct,
33
+ f16,
34
+ f32,
35
+ f64,
33
36
  hexdump,
34
37
  p8,
35
38
  p16,
36
39
  p32,
37
40
  p64,
38
41
  pack,
42
+ pf16,
43
+ pf32,
44
+ pf64,
39
45
  swap,
40
46
  swap16,
41
47
  swap32,
@@ -75,12 +81,18 @@ __all__ = [
75
81
  "ctypes",
76
82
  "ctypes_type",
77
83
  "dumpstruct",
84
+ "f16",
85
+ "f32",
86
+ "f64",
78
87
  "hexdump",
79
88
  "p8",
80
89
  "p16",
81
90
  "p32",
82
91
  "p64",
83
92
  "pack",
93
+ "pf16",
94
+ "pf32",
95
+ "pf64",
84
96
  "swap",
85
97
  "swap16",
86
98
  "swap32",
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING
11
11
  from dissect.cstruct.bitbuffer import BitBuffer
12
12
  from dissect.cstruct.types import (
13
13
  Array,
14
+ BaseArray,
14
15
  Char,
15
16
  CharArray,
16
17
  Flag,
@@ -24,18 +25,15 @@ from dissect.cstruct.types import (
24
25
  Wchar,
25
26
  WcharArray,
26
27
  )
27
- from dissect.cstruct.types.base import BaseArray
28
28
  from dissect.cstruct.types.enum import EnumMetaType
29
- from dissect.cstruct.types.packed import _struct
29
+ from dissect.cstruct.util import _struct
30
30
 
31
31
  if TYPE_CHECKING:
32
32
  from collections.abc import Iterator
33
33
  from types import MethodType
34
34
 
35
35
  from dissect.cstruct.cstruct import cstruct
36
- from dissect.cstruct.types import (
37
- BaseType,
38
- )
36
+ from dissect.cstruct.types import BaseType
39
37
  from dissect.cstruct.types.structure import Field
40
38
 
41
39
  SUPPORTED_TYPES = (
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar, TypeVar
6
6
 
7
7
  from dissect.cstruct.exception import ArraySizeError
8
8
  from dissect.cstruct.expression import Expression
9
+ from dissect.cstruct.util import normalize_endianness
9
10
 
10
11
  if TYPE_CHECKING:
11
12
  from collections.abc import Callable
@@ -294,8 +295,10 @@ class BaseArray(BaseType):
294
295
  if cls.null_terminated:
295
296
  return cls.type._write_0(stream, data, endian=endian)
296
297
 
297
- if not cls.dynamic and cls.num_entries != (actual_size := len(data)):
298
- raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
298
+ if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
299
+ if remaining < 0:
300
+ raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
301
+ data = data + [cls.type.__default__()] * remaining
299
302
 
300
303
  return cls.type._write_array(stream, data, endian=endian)
301
304
 
@@ -326,27 +329,5 @@ def _is_eof(stream: BinaryIO) -> bool:
326
329
  return False
327
330
 
328
331
 
329
- ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
330
- "<": "<",
331
- ">": ">",
332
- "!": "!",
333
- "@": "@",
334
- "=": "=",
335
- "network": "!",
336
- "little": "<",
337
- "big": ">",
338
- }
339
-
340
-
341
- def normalize_endianness(endian: AllowedEndianness) -> Endianness:
342
- """Normalize an endianness string to one of the standard format characters."""
343
- try:
344
- return ENDIANNESS_MAP[endian]
345
- except KeyError:
346
- raise ValueError(
347
- f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
348
- ) from None
349
-
350
-
351
332
  # As mentioned in the BaseType class, we correctly set the type here
352
333
  MetaType.ArrayType = Array
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING, Any, BinaryIO
4
4
 
5
+ from dissect.cstruct.exception import ArraySizeError
5
6
  from dissect.cstruct.types.base import EOF, BaseArray, BaseType
6
7
 
7
8
  if TYPE_CHECKING:
@@ -22,15 +23,20 @@ class CharArray(bytes, BaseArray):
22
23
  return type.__call__(cls, super()._read(stream, context=context, endian=endian))
23
24
 
24
25
  @classmethod
25
- def _write(cls, stream: BinaryIO, data: bytes, *, endian: Endianness) -> int:
26
- if isinstance(data, list) and data and isinstance(data[0], int):
26
+ def _write(cls, stream: BinaryIO, data: bytes | str | list[int], *, endian: Endianness) -> int:
27
+ if isinstance(data, list):
27
28
  data = bytes(data)
28
-
29
29
  elif isinstance(data, str):
30
30
  data = data.encode("latin-1")
31
31
 
32
32
  if cls.null_terminated:
33
- return stream.write(data + b"\x00")
33
+ data += b"\x00"
34
+
35
+ if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
36
+ if remaining < 0:
37
+ raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
38
+ data += b"\x00" * remaining
39
+
34
40
  return stream.write(data)
35
41
 
36
42
 
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from functools import lru_cache
4
- from struct import Struct
5
3
  from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
6
4
 
7
5
  from dissect.cstruct.types.base import EOF, BaseType
6
+ from dissect.cstruct.util import _struct
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from typing_extensions import Self
@@ -12,11 +11,6 @@ if TYPE_CHECKING:
12
11
  from dissect.cstruct.cstruct import Endianness
13
12
 
14
13
 
15
- @lru_cache(1024)
16
- def _struct(endian: str, packchar: str) -> Struct:
17
- return Struct(f"{endian}{packchar}")
18
-
19
-
20
14
  T = TypeVar("T", int, float)
21
15
 
22
16
 
@@ -252,7 +252,7 @@ class StructureMetaType(MetaType):
252
252
  offset = stream.tell()
253
253
 
254
254
  if field.offset is not None and offset != struct_start + field.offset:
255
- # Field is at a specific offset, either alligned or added that way
255
+ # Field is at a specific offset, either aligned or added that way
256
256
  offset = struct_start + field.offset
257
257
  stream.seek(offset)
258
258
 
@@ -318,7 +318,7 @@ class StructureMetaType(MetaType):
318
318
  offset = stream.tell()
319
319
 
320
320
  if field.offset is not None and offset < struct_start + field.offset:
321
- # Field is at a specific offset, either alligned or added that way
321
+ # Field is at a specific offset, either aligned or added that way
322
322
  stream.write(b"\x00" * (struct_start + field.offset - offset))
323
323
  offset = struct_start + field.offset
324
324
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import sys
4
4
  from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar
5
5
 
6
+ from dissect.cstruct.exception import ArraySizeError
6
7
  from dissect.cstruct.types.base import EOF, BaseArray, BaseType
7
8
 
8
9
  if TYPE_CHECKING:
@@ -26,6 +27,12 @@ class WcharArray(str, BaseArray):
26
27
  def _write(cls, stream: BinaryIO, data: str, *, endian: Endianness) -> int:
27
28
  if cls.null_terminated:
28
29
  data += "\x00"
30
+
31
+ if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
32
+ if remaining < 0:
33
+ raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
34
+ data += "\x00" * remaining
35
+
29
36
  return stream.write(data.encode(Wchar.__encoding_map__[endian]))
30
37
 
31
38
 
@@ -3,15 +3,17 @@ from __future__ import annotations
3
3
  import os
4
4
  import pprint
5
5
  import string
6
+ import struct
6
7
  import sys
7
8
  from enum import Enum
9
+ from functools import lru_cache
8
10
  from typing import TYPE_CHECKING
9
11
 
10
12
  if TYPE_CHECKING:
11
13
  from collections.abc import Iterator
12
14
  from typing import Literal
13
15
 
14
- from dissect.cstruct.cstruct import AllowedEndianness
16
+ from dissect.cstruct.cstruct import AllowedEndianness, Endianness
15
17
  from dissect.cstruct.types.base import BaseType
16
18
  from dissect.cstruct.types.structure import Structure
17
19
 
@@ -64,6 +66,28 @@ ENDIANNESS_TO_BYTEORDER_MAP: dict[AllowedEndianness, Literal["big", "little"]] =
64
66
  "big": "big",
65
67
  }
66
68
 
69
+ ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
70
+ "<": "<",
71
+ ">": ">",
72
+ "!": "!",
73
+ "@": "@",
74
+ "=": "=",
75
+ "network": "!",
76
+ "little": "<",
77
+ "big": ">",
78
+ }
79
+
80
+
81
+ def normalize_endianness(endian: AllowedEndianness) -> Endianness:
82
+ """Normalize an endianness string to one of the standard format characters."""
83
+ try:
84
+ return ENDIANNESS_MAP[endian]
85
+ except KeyError:
86
+ raise ValueError(
87
+ f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
88
+ ) from None
89
+
90
+
67
91
  Palette = list[tuple[int, str]]
68
92
 
69
93
 
@@ -327,6 +351,11 @@ def dumpstruct(
327
351
  return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
328
352
 
329
353
 
354
+ @lru_cache(1024)
355
+ def _struct(endian: str, packchar: str) -> struct.Struct:
356
+ return struct.Struct(f"{endian}{packchar}")
357
+
358
+
330
359
  def pack(value: int, size: int | None = None, endian: AllowedEndianness = "little") -> bytes:
331
360
  """Pack an integer value to a given bit size, endianness.
332
361
 
@@ -400,6 +429,36 @@ def p64(value: int, endian: AllowedEndianness = "little") -> bytes:
400
429
  return pack(value, 64, endian)
401
430
 
402
431
 
432
+ def pf16(value: float, endian: AllowedEndianness = "little") -> bytes:
433
+ """Pack a 16 bit float.
434
+
435
+ Arguments:
436
+ value: Value to pack.
437
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
438
+ """
439
+ return _struct(normalize_endianness(endian), "e").pack(value)
440
+
441
+
442
+ def pf32(value: float, endian: AllowedEndianness = "little") -> bytes:
443
+ """Pack a 32 bit float.
444
+
445
+ Arguments:
446
+ value: Value to pack.
447
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
448
+ """
449
+ return _struct(normalize_endianness(endian), "f").pack(value)
450
+
451
+
452
+ def pf64(value: float, endian: AllowedEndianness = "little") -> bytes:
453
+ """Pack a 64 bit float.
454
+
455
+ Arguments:
456
+ value: Value to pack.
457
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
458
+ """
459
+ return _struct(normalize_endianness(endian), "d").pack(value)
460
+
461
+
403
462
  def u8(value: bytes, endian: AllowedEndianness = "little", sign: bool = False) -> int:
404
463
  """Unpack an 8 bit integer.
405
464
 
@@ -444,6 +503,36 @@ def u64(value: bytes, endian: AllowedEndianness = "little", sign: bool = False)
444
503
  return unpack(value, 64, endian, sign)
445
504
 
446
505
 
506
+ def f16(value: bytes, endian: AllowedEndianness = "little") -> float:
507
+ """Unpack a 16 bit float.
508
+
509
+ Arguments:
510
+ value: Value to unpack.
511
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
512
+ """
513
+ return _struct(normalize_endianness(endian), "e").unpack(value)[0]
514
+
515
+
516
+ def f32(value: bytes, endian: AllowedEndianness = "little") -> float:
517
+ """Unpack a 32 bit float.
518
+
519
+ Arguments:
520
+ value: Value to unpack.
521
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
522
+ """
523
+ return _struct(normalize_endianness(endian), "f").unpack(value)[0]
524
+
525
+
526
+ def f64(value: bytes, endian: AllowedEndianness = "little") -> float:
527
+ """Unpack a 64 bit float.
528
+
529
+ Arguments:
530
+ value: Value to unpack.
531
+ endian: Endianness to use (little, big, network, <, >, !, @ or =).
532
+ """
533
+ return _struct(normalize_endianness(endian), "d").unpack(value)[0]
534
+
535
+
447
536
  def swap(value: int, size: int) -> int:
448
537
  """Swap the endianness of an integer with a given bit size.
449
538
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 5.0.dev6
3
+ Version: 5.0.dev8
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-Expression: Apache-2.0
@@ -1,78 +1,78 @@
1
1
  {
2
2
  "files": [
3
- "README.md",
4
- "tox.ini",
5
- "LICENSE",
6
- "pyproject.toml",
3
+ ".gitignore",
7
4
  "COPYRIGHT",
5
+ "README.md",
8
6
  "CHANGELOG.md",
9
7
  ".git-blame-ignore-revs",
8
+ "tox.ini",
10
9
  ".gitattributes",
10
+ "pyproject.toml",
11
11
  "MANIFEST.in",
12
- ".gitignore",
13
- "dissect/cstruct/cstruct.py",
14
- "dissect/cstruct/__init__.py",
12
+ "LICENSE",
13
+ ".github/pull_request_template.md",
14
+ ".github/workflows/dissect-ci.yml",
15
+ "dissect/cstruct/parser.py",
15
16
  "dissect/cstruct/compiler.py",
16
- "dissect/cstruct/expression.py",
17
17
  "dissect/cstruct/exception.py",
18
- "dissect/cstruct/parser.py",
19
- "dissect/cstruct/lexer.py",
20
- "dissect/cstruct/bitbuffer.py",
21
18
  "dissect/cstruct/util.py",
22
- "dissect/cstruct/types/int.py",
23
- "dissect/cstruct/types/__init__.py",
24
- "dissect/cstruct/types/leb128.py",
25
- "dissect/cstruct/types/flag.py",
26
- "dissect/cstruct/types/void.py",
27
- "dissect/cstruct/types/char.py",
19
+ "dissect/cstruct/cstruct.py",
20
+ "dissect/cstruct/bitbuffer.py",
21
+ "dissect/cstruct/lexer.py",
22
+ "dissect/cstruct/expression.py",
23
+ "dissect/cstruct/__init__.py",
24
+ "dissect/cstruct/types/base.py",
28
25
  "dissect/cstruct/types/pointer.py",
29
26
  "dissect/cstruct/types/structure.py",
27
+ "dissect/cstruct/types/void.py",
28
+ "dissect/cstruct/types/int.py",
29
+ "dissect/cstruct/types/flag.py",
30
+ "dissect/cstruct/types/packed.py",
30
31
  "dissect/cstruct/types/enum.py",
31
- "dissect/cstruct/types/base.py",
32
+ "dissect/cstruct/types/char.py",
32
33
  "dissect/cstruct/types/wchar.py",
33
- "dissect/cstruct/types/packed.py",
34
- "dissect/cstruct/tools/__init__.py",
34
+ "dissect/cstruct/types/leb128.py",
35
+ "dissect/cstruct/types/__init__.py",
35
36
  "dissect/cstruct/tools/stubgen.py",
36
- "tests/test_types_char.py",
37
- "tests/test_ctypes.py",
38
- "tests/test_types_custom.py",
39
- "tests/__init__.py",
40
- "tests/test_types_leb128.py",
41
- "tests/test_types_union.py",
42
- "tests/test_tools_stubgen.py",
43
- "tests/test_basic.py",
44
- "tests/test_lexer.py",
45
- "tests/test_types_int.py",
46
- "tests/test_expression.py",
47
- "tests/utils.py",
48
- "tests/test_align.py",
49
- "tests/test_utils.py",
50
- "tests/test_compiler.py",
37
+ "dissect/cstruct/tools/__init__.py",
38
+ "examples/secdesc.py",
39
+ "examples/pe.py",
40
+ "examples/mirai.py",
41
+ "examples/protobuf.py",
42
+ "examples/disk.py",
51
43
  "tests/test_types_enum.py",
44
+ "tests/test_types_pointer.py",
45
+ "tests/test_types_leb128.py",
46
+ "tests/test_annotations.py",
52
47
  "tests/test_types_wchar.py",
53
- "tests/test_bitfield.py",
54
- "tests/test_types_structure.py",
55
48
  "tests/test_parser.py",
56
- "tests/test_annotations.py",
49
+ "tests/test_ctypes.py",
50
+ "tests/test_bitbuffer.py",
57
51
  "tests/conftest.py",
58
- "tests/test_types_void.py",
52
+ "tests/test_types_packed.py",
53
+ "tests/test_types_structure.py",
54
+ "tests/test_types_union.py",
55
+ "tests/test_expression.py",
56
+ "tests/test_tools_stubgen.py",
57
+ "tests/test_types_custom.py",
58
+ "tests/utils.py",
59
+ "tests/test_lexer.py",
59
60
  "tests/test_types_flag.py",
60
61
  "tests/test_benchmark.py",
61
- "tests/test_types_pointer.py",
62
- "tests/test_types_packed.py",
62
+ "tests/test_types_char.py",
63
+ "tests/test_compiler.py",
64
+ "tests/test_types_int.py",
65
+ "tests/test_types_void.py",
63
66
  "tests/test_types_base.py",
64
- "tests/test_bitbuffer.py",
65
- "tests/_data/testdef.txt",
66
- "tests/_docs/__init__.py",
67
+ "tests/test_align.py",
68
+ "tests/test_basic.py",
69
+ "tests/test_bitfield.py",
70
+ "tests/test_utils.py",
71
+ "tests/__init__.py",
72
+ "tests/_docs/conf.py",
67
73
  "tests/_docs/Makefile",
68
74
  "tests/_docs/index.rst",
69
- "tests/_docs/conf.py",
70
- "examples/secdesc.py",
71
- "examples/disk.py",
72
- "examples/pe.py",
73
- "examples/mirai.py",
74
- "examples/protobuf.py",
75
- ".github/pull_request_template.md",
76
- ".github/workflows/dissect-ci.yml"
75
+ "tests/_docs/__init__.py",
76
+ "tests/_data/testdef.txt"
77
77
  ]
78
78
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "5.0.dev0",
3
+ "distance": 8,
4
+ "node": "g3032ad71f5e15148222c2faef49e47112a36bf12",
5
+ "dirty": true,
6
+ "branch": "main",
7
+ "node_date": "2026-07-20"
8
+ }
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import io
3
4
  from typing import TYPE_CHECKING, BinaryIO
4
5
 
5
6
  import pytest
@@ -20,6 +21,24 @@ def test_array_size_mismatch(cs: cstruct) -> None:
20
21
  assert cs.uint8[2]([1, 2]).dumps()
21
22
 
22
23
 
24
+ def test_array_write_padding(cs: cstruct) -> None:
25
+ buf = io.BytesIO()
26
+ cs.uint8[4]._write(buf, [1, 2], endian=cs.endian)
27
+ assert buf.getvalue() == b"\x01\x02\x00\x00"
28
+
29
+ cdef = """
30
+ struct point {
31
+ uint8 x;
32
+ uint8 y;
33
+ };
34
+ """
35
+ cs.load(cdef)
36
+
37
+ buf = io.BytesIO()
38
+ cs.point[4]._write(buf, [cs.point(x=1, y=2)], endian=cs.endian)
39
+ assert buf.getvalue() == b"\x01\x02" + b"\x00\x00" * 3
40
+
41
+
23
42
  def test_eof(cs: cstruct, compiled: bool) -> None:
24
43
  cdef = """
25
44
  struct test_char {
@@ -5,6 +5,8 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  import pytest
7
7
 
8
+ from dissect.cstruct.exception import ArraySizeError
9
+
8
10
  if TYPE_CHECKING:
9
11
  from dissect.cstruct.cstruct import cstruct
10
12
 
@@ -36,6 +38,38 @@ def test_char_array_write(cs: cstruct) -> None:
36
38
  assert cs.char[None](buf).dumps() == b"AAAA\x00"
37
39
 
38
40
 
41
+ def test_char_array_write_padding(cs: cstruct) -> None:
42
+ buf = io.BytesIO()
43
+ cs.char[8]._write(buf, "hi", endian=cs.endian)
44
+ assert buf.getvalue() == b"hi\x00\x00\x00\x00\x00\x00"
45
+
46
+ cdef = """
47
+ struct test_struct {
48
+ int x;
49
+ char y[8];
50
+ char z[16];
51
+ };
52
+ """
53
+ cs.load(cdef)
54
+
55
+ obj = cs.test_struct()
56
+ obj.x = 4
57
+ obj.y = "hi"
58
+ obj.z = "bye"
59
+
60
+ assert len(obj.dumps()) == len(cs.test_struct)
61
+ assert (
62
+ obj.dumps()
63
+ == b"\x04\x00\x00\x00hi\x00\x00\x00\x00\x00\x00bye\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
64
+ )
65
+
66
+
67
+ def test_char_array_write_size_error(cs: cstruct) -> None:
68
+ buf = io.BytesIO()
69
+ with pytest.raises(ArraySizeError):
70
+ cs.char[4]._write(buf, b"toolong", endian=cs.endian)
71
+
72
+
39
73
  def test_char_eof(cs: cstruct) -> None:
40
74
  with pytest.raises(EOFError):
41
75
  cs.char(b"")
@@ -5,6 +5,8 @@ from typing import TYPE_CHECKING
5
5
 
6
6
  import pytest
7
7
 
8
+ from dissect.cstruct.exception import ArraySizeError
9
+
8
10
  if TYPE_CHECKING:
9
11
  from dissect.cstruct.cstruct import cstruct
10
12
 
@@ -38,6 +40,33 @@ def test_wchar_array_write(cs: cstruct) -> None:
38
40
  assert cs.wchar[None](buf).dumps() == b"A\x00A\x00A\x00A\x00\x00\x00"
39
41
 
40
42
 
43
+ def test_wchar_array_write_padding(cs: cstruct) -> None:
44
+ buf = io.BytesIO()
45
+ cs.wchar[8]._write(buf, "hi", endian=cs.endian)
46
+ assert buf.getvalue() == b"h\x00i\x00" + b"\x00" * 12
47
+
48
+ cdef = """
49
+ struct test_struct {
50
+ int x;
51
+ wchar y[8];
52
+ wchar z[16];
53
+ };
54
+ """
55
+ cs.load(cdef)
56
+ obj = cs.test_struct()
57
+ obj.x = 4
58
+ obj.y = "hi"
59
+ obj.z = "bye"
60
+ assert len(obj.dumps()) == len(cs.test_struct)
61
+ assert obj.dumps() == b"\x04\x00\x00\x00h\x00i\x00" + (b"\x00" * 12) + b"b\x00y\x00e\x00" + (b"\x00" * 26)
62
+
63
+
64
+ def test_wchar_array_write_size_error(cs: cstruct) -> None:
65
+ buf = io.BytesIO()
66
+ with pytest.raises(ArraySizeError):
67
+ cs.wchar[4]._write(buf, "toolong", endian=cs.endian)
68
+
69
+
41
70
  def test_wchar_be_read(cs: cstruct) -> None:
42
71
  cs.endian = ">"
43
72
 
@@ -259,10 +259,16 @@ def test_pack_unpack() -> None:
259
259
  assert util.p16(1, endian) == b"\x01\x00"
260
260
  assert util.p32(1, endian) == b"\x01\x00\x00\x00"
261
261
  assert util.p64(1, endian) == b"\x01\x00\x00\x00\x00\x00\x00\x00"
262
+ assert util.pf16(1.0, endian) == b"\x00\x3c"
263
+ assert util.pf32(1.0, endian) == b"\x00\x00\x80\x3f"
264
+ assert util.pf64(1.0, endian) == b"\x00\x00\x00\x00\x00\x00\xf0\x3f"
262
265
  assert util.u8(b"\x01", endian, sign) == 1
263
266
  assert util.u16(b"\x01\x00", endian, sign) == 1
264
267
  assert util.u32(b"\x01\x00\x00\x00", endian, sign) == 1
265
268
  assert util.u64(b"\x01\x00\x00\x00\x00\x00\x00\x00", endian, sign) == 1
269
+ assert util.f16(b"\x00\x3c", endian) == 1.0
270
+ assert util.f32(b"\x00\x00\x80\x3f", endian) == 1.0
271
+ assert util.f64(b"\x00\x00\x00\x00\x00\x00\xf0\x3f", endian) == 1.0
266
272
 
267
273
  endian = "big"
268
274
  sign = False
@@ -270,10 +276,16 @@ def test_pack_unpack() -> None:
270
276
  assert util.p16(1, endian) == b"\x00\x01"
271
277
  assert util.p32(1, endian) == b"\x00\x00\x00\x01"
272
278
  assert util.p64(1, endian) == b"\x00\x00\x00\x00\x00\x00\x00\x01"
279
+ assert util.pf16(1.0, endian) == b"\x3c\x00"
280
+ assert util.pf32(1.0, endian) == b"\x3f\x80\x00\x00"
281
+ assert util.pf64(1.0, endian) == b"\x3f\xf0\x00\x00\x00\x00\x00\x00"
273
282
  assert util.u8(b"\x01", endian, sign) == 1
274
283
  assert util.u16(b"\x00\x01", endian, sign) == 1
275
284
  assert util.u32(b"\x00\x00\x00\x01", endian, sign) == 1
276
285
  assert util.u64(b"\x00\x00\x00\x00\x00\x00\x00\x01", endian, sign) == 1
286
+ assert util.f16(b"\x3c\x00", endian) == 1.0
287
+ assert util.f32(b"\x3f\x80\x00\x00", endian) == 1.0
288
+ assert util.f64(b"\x3f\xf0\x00\x00\x00\x00\x00\x00", endian) == 1.0
277
289
 
278
290
  endian = "network"
279
291
  sign = False
@@ -281,10 +293,16 @@ def test_pack_unpack() -> None:
281
293
  assert util.p16(1, endian) == b"\x00\x01"
282
294
  assert util.p32(1, endian) == b"\x00\x00\x00\x01"
283
295
  assert util.p64(1, endian) == b"\x00\x00\x00\x00\x00\x00\x00\x01"
296
+ assert util.pf16(1.0, endian) == b"\x3c\x00"
297
+ assert util.pf32(1.0, endian) == b"\x3f\x80\x00\x00"
298
+ assert util.pf64(1.0, endian) == b"\x3f\xf0\x00\x00\x00\x00\x00\x00"
284
299
  assert util.u8(b"\x01", endian, sign) == 1
285
300
  assert util.u16(b"\x00\x01", endian, sign) == 1
286
301
  assert util.u32(b"\x00\x00\x00\x01", endian, sign) == 1
287
302
  assert util.u64(b"\x00\x00\x00\x00\x00\x00\x00\x01", endian, sign) == 1
303
+ assert util.f16(b"\x3c\x00", endian) == 1.0
304
+ assert util.f32(b"\x3f\x80\x00\x00", endian) == 1.0
305
+ assert util.f64(b"\x3f\xf0\x00\x00\x00\x00\x00\x00", endian) == 1.0
288
306
 
289
307
  endian = "little"
290
308
  sign = True
@@ -292,10 +310,16 @@ def test_pack_unpack() -> None:
292
310
  assert util.p16(-120, endian) == b"\x88\xff"
293
311
  assert util.p32(-120, endian) == b"\x88\xff\xff\xff"
294
312
  assert util.p64(-120, endian) == b"\x88\xff\xff\xff\xff\xff\xff\xff"
313
+ assert util.pf16(-120.0, endian) == b"\x80\xd7"
314
+ assert util.pf32(-120.0, endian) == b"\x00\x00\xf0\xc2"
315
+ assert util.pf64(-120.0, endian) == b"\x00\x00\x00\x00\x00\x00\x5e\xc0"
295
316
  assert util.u8(b"\x88", endian, sign) == -120
296
317
  assert util.u16(b"\x88\xff", endian, sign) == -120
297
318
  assert util.u32(b"\x88\xff\xff\xff", endian, sign) == -120
298
319
  assert util.u64(b"\x88\xff\xff\xff\xff\xff\xff\xff", endian, sign) == -120
320
+ assert util.f16(b"\x80\xd7", endian) == -120.0
321
+ assert util.f32(b"\x00\x00\xf0\xc2", endian) == -120.0
322
+ assert util.f64(b"\x00\x00\x00\x00\x00\x00\x5e\xc0", endian) == -120.0
299
323
 
300
324
  endian = "big"
301
325
  sign = True
@@ -303,10 +327,16 @@ def test_pack_unpack() -> None:
303
327
  assert util.p16(-120, endian) == b"\xff\x88"
304
328
  assert util.p32(-120, endian) == b"\xff\xff\xff\x88"
305
329
  assert util.p64(-120, endian) == b"\xff\xff\xff\xff\xff\xff\xff\x88"
330
+ assert util.pf16(-120.0, endian) == b"\xd7\x80"
331
+ assert util.pf32(-120.0, endian) == b"\xc2\xf0\x00\x00"
332
+ assert util.pf64(-120.0, endian) == b"\xc0\x5e\x00\x00\x00\x00\x00\x00"
306
333
  assert util.u8(b"\x88", endian, sign) == -120
307
334
  assert util.u16(b"\xff\x88", endian, sign) == -120
308
335
  assert util.u32(b"\xff\xff\xff\x88", endian, sign) == -120
309
336
  assert util.u64(b"\xff\xff\xff\xff\xff\xff\xff\x88", endian, sign) == -120
337
+ assert util.f16(b"\xd7\x80", endian) == -120.0
338
+ assert util.f32(b"\xc2\xf0\x00\x00", endian) == -120.0
339
+ assert util.f64(b"\xc0\x5e\x00\x00\x00\x00\x00\x00", endian) == -120.0
310
340
 
311
341
  assert util.pack(1, 24) == b"\x01\x00\x00"
312
342
  assert util.unpack(b"\x01\x00\x00", 24) == 1
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "5.0.dev0",
3
- "distance": 6,
4
- "node": "g8fd15061a9f9618417a62277bc6f2d625768fdd2",
5
- "dirty": true,
6
- "branch": "main",
7
- "node_date": "2026-07-16"
8
- }