dissect.cstruct 4.8.dev7__tar.gz → 4.8.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 (77) hide show
  1. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/PKG-INFO +1 -1
  2. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/utils.py +36 -6
  3. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/PKG-INFO +1 -1
  4. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_utils.py +88 -0
  5. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/.git-blame-ignore-revs +0 -0
  6. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/.gitattributes +0 -0
  7. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/CHANGELOG.md +0 -0
  8. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/COPYRIGHT +0 -0
  9. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/LICENSE +0 -0
  10. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/MANIFEST.in +0 -0
  11. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/README.md +0 -0
  12. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/__init__.py +0 -0
  13. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/bitbuffer.py +0 -0
  14. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/compiler.py +0 -0
  15. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/cstruct.py +0 -0
  16. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/exceptions.py +0 -0
  17. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/expression.py +0 -0
  18. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/parser.py +0 -0
  19. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/tools/__init__.py +0 -0
  20. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/tools/stubgen.py +0 -0
  21. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/__init__.py +0 -0
  22. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/base.py +0 -0
  23. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/char.py +0 -0
  24. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/enum.py +0 -0
  25. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/flag.py +0 -0
  26. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/int.py +0 -0
  27. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/leb128.py +0 -0
  28. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/packed.py +0 -0
  29. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/pointer.py +0 -0
  30. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/structure.py +0 -0
  31. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/void.py +0 -0
  32. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect/cstruct/types/wchar.py +0 -0
  33. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  34. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  35. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  36. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/requires.txt +0 -0
  37. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/dissect.cstruct.egg-info/top_level.txt +0 -0
  38. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/examples/disk.py +0 -0
  39. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/examples/mirai.py +0 -0
  40. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/examples/pe.py +0 -0
  41. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/examples/protobuf.py +0 -0
  42. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/examples/secdesc.py +0 -0
  43. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/pyproject.toml +0 -0
  44. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/setup.cfg +0 -0
  45. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/__init__.py +0 -0
  46. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/_data/testdef.txt +0 -0
  47. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/_docs/Makefile +0 -0
  48. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/_docs/__init__.py +0 -0
  49. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/_docs/conf.py +0 -0
  50. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/_docs/index.rst +0 -0
  51. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/conftest.py +0 -0
  52. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_align.py +0 -0
  53. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_annotations.py +0 -0
  54. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_basic.py +0 -0
  55. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_benchmark.py +0 -0
  56. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_bitbuffer.py +0 -0
  57. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_bitfield.py +0 -0
  58. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_compiler.py +0 -0
  59. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_ctypes.py +0 -0
  60. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_expression.py +0 -0
  61. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_parser.py +0 -0
  62. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_tools_stubgen.py +0 -0
  63. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_base.py +0 -0
  64. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_char.py +0 -0
  65. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_custom.py +0 -0
  66. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_enum.py +0 -0
  67. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_flag.py +0 -0
  68. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_int.py +0 -0
  69. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_leb128.py +0 -0
  70. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_packed.py +0 -0
  71. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_pointer.py +0 -0
  72. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_structure.py +0 -0
  73. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_union.py +0 -0
  74. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_void.py +0 -0
  75. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/test_types_wchar.py +0 -0
  76. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tests/utils.py +0 -0
  77. {dissect_cstruct-4.8.dev7 → dissect_cstruct-4.8.dev8}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.8.dev7
3
+ Version: 4.8.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
@@ -94,7 +94,13 @@ HUMAN_COLORS = _human_colors()
94
94
 
95
95
 
96
96
  def _hexdump(
97
- data: bytes, palette: Palette | None = None, offset: int = 0, prefix: str = "", pretty: bool | None = False
97
+ data: bytes,
98
+ *,
99
+ palette: Palette | None = None,
100
+ offset: int = 0,
101
+ prefix: str = "",
102
+ pretty: bool | None = False,
103
+ autoskip: bool = False,
98
104
  ) -> Iterator[str]:
99
105
  """Hexdump some data.
100
106
 
@@ -104,6 +110,7 @@ def _hexdump(
104
110
  offset: Byte offset of the hexdump.
105
111
  prefix: Optional prefix.
106
112
  pretty: Use pretty colors, mutual exclusive with palette.
113
+ autoskip: A single '*' replaces NUL-lines in the output.
107
114
  """
108
115
  if palette:
109
116
  palette = palette[::-1]
@@ -114,6 +121,9 @@ def _hexdump(
114
121
 
115
122
  remaining = 0
116
123
  active = None
124
+ in_null_run = False
125
+ in_collapsed_null_run = False
126
+ last_offset = len(data) - 16
117
127
 
118
128
  for i in range(0, len(data), 16):
119
129
  values = ""
@@ -166,17 +176,32 @@ def _hexdump(
166
176
  if j == 7:
167
177
  values += " "
168
178
 
179
+ if autoskip and 0 < i < last_offset and data[i : i + 16] == b"\x00" * 16:
180
+ if in_null_run:
181
+ if not in_collapsed_null_run:
182
+ yield "*"
183
+ in_collapsed_null_run = True
184
+ continue
185
+
186
+ # Keep the first interior NUL line visible, collapse from the second onwards.
187
+ in_null_run = True
188
+ else:
189
+ in_null_run = False
190
+ in_collapsed_null_run = False
191
+
169
192
  chars = "".join(chars)
170
193
  yield f"{prefix}{offset + i:08x} {values:48s} {chars}"
171
194
 
172
195
 
173
196
  def hexdump(
174
197
  data: bytes,
198
+ *,
175
199
  palette: Palette | None = None,
176
200
  offset: int = 0,
177
201
  prefix: str = "",
178
202
  output: str = "print",
179
203
  pretty: bool | None = None,
204
+ autoskip: bool = False,
180
205
  ) -> Iterator[str] | str | None:
181
206
  """Hexdump some data.
182
207
 
@@ -190,6 +215,7 @@ def hexdump(
190
215
  prefix: Optional prefix.
191
216
  output: Output format, can be 'print', 'generator' or 'string'.
192
217
  pretty: Use pretty colors for improved human readability.
218
+ autoskip: A single '*' replaces NUL-lines in the output.
193
219
  """
194
220
  # Enable pretty colors by default if ...
195
221
  if (
@@ -200,7 +226,7 @@ def hexdump(
200
226
  ):
201
227
  pretty = True
202
228
 
203
- generator = _hexdump(data, palette, offset, prefix, pretty)
229
+ generator = _hexdump(data, palette=palette, offset=offset, prefix=prefix, pretty=pretty, autoskip=autoskip)
204
230
  if output == "print":
205
231
  print("\n".join(generator))
206
232
  return None
@@ -217,6 +243,7 @@ def _dumpstruct(
217
243
  offset: int,
218
244
  color: bool,
219
245
  output: str,
246
+ autoskip: bool,
220
247
  ) -> str | None:
221
248
  palette = []
222
249
  colors = [
@@ -258,11 +285,11 @@ def _dumpstruct(
258
285
 
259
286
  if output == "print":
260
287
  print()
261
- hexdump(data, palette, offset=offset)
288
+ hexdump(data, palette=palette, offset=offset, autoskip=autoskip)
262
289
  print()
263
290
  print(out)
264
291
  elif output == "string":
265
- return f"\n{hexdump(data, palette, offset=offset, output='string')}\n\n{out}"
292
+ return f"\n{hexdump(data, palette=palette, offset=offset, output='string', autoskip=autoskip)}\n\n{out}"
266
293
  return None
267
294
 
268
295
 
@@ -271,6 +298,7 @@ def dumpstruct(
271
298
  data: bytes | None = None,
272
299
  offset: int = 0,
273
300
  color: bool = True,
301
+ autoskip: bool = False,
274
302
  output: str = "print",
275
303
  ) -> str | None:
276
304
  """Dump a structure or parsed structure instance.
@@ -281,15 +309,17 @@ def dumpstruct(
281
309
  obj: Structure to dump.
282
310
  data: Bytes to parse the Structure on, if obj is not a parsed Structure already.
283
311
  offset: Byte offset of the hexdump.
312
+ color: Colorize the hexdump and structure output.
313
+ autoskip: A single '*' replaces NUL-lines in the output.
284
314
  output: Output format, can be 'print' or 'string'.
285
315
  """
286
316
  if output not in ("print", "string"):
287
317
  raise ValueError(f"Invalid output argument: {output!r} (should be 'print' or 'string').")
288
318
 
289
319
  if isinstance(obj, Structure):
290
- return _dumpstruct(obj, obj.dumps(), offset, color, output)
320
+ return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
291
321
  if issubclass(obj, Structure) and data is not None:
292
- return _dumpstruct(obj(data), data, offset, color, output)
322
+ return _dumpstruct(obj(data), data, offset, color, output, autoskip)
293
323
  raise ValueError("Invalid arguments")
294
324
 
295
325
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.8.dev7
3
+ Version: 4.8.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
@@ -53,6 +53,94 @@ def test_hexdump_pretty(capsys: pytest.CaptureFixture) -> None:
53
53
  )
54
54
 
55
55
 
56
+ def test_hexdump_autoskip_collapses_middle_null_run() -> None:
57
+ """Keep first interior NUL line, then collapse the rest of that run to '*'."""
58
+ # Layout: [A data line] [3 NUL lines] [B data line]
59
+ # Expected: [A line] [first NUL line] [*] [B line] = 4 lines
60
+ data = (b"A" * 16) + (b"\x00" * 48) + (b"B" * 16)
61
+
62
+ out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
63
+ assert out is not None
64
+
65
+ lines = out.splitlines()
66
+ assert len(lines) == 4
67
+ assert lines[0].startswith("00000000") # A line
68
+ assert lines[1].startswith("00000010") # First NUL line is kept
69
+ assert lines[2] == "*" # Remaining NUL lines collapsed
70
+ assert lines[3].startswith("00000040") # B line
71
+
72
+
73
+ def test_hexdump_autoskip_keeps_edge_null_lines() -> None:
74
+ """Do not collapse first/last hexdump lines even when they are all NUL bytes."""
75
+ # Layout: [3 NUL lines]
76
+ # Expected: all lines are kept (only one interior line, so nothing is repeated there)
77
+ data = b"\x00" * 48
78
+
79
+ out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
80
+ assert out is not None
81
+
82
+ lines = out.splitlines()
83
+ assert len(lines) == 3
84
+ assert lines[0].startswith("00000000") # First line kept (edge)
85
+ assert lines[1].startswith("00000010") # Single interior NUL line is kept
86
+ assert lines[2].startswith("00000020") # Last line kept (edge)
87
+
88
+
89
+ def test_hexdump_autoskip_separate_null_runs() -> None:
90
+ """Emit one '*' per interior NUL run, after keeping each run's first interior NUL line."""
91
+ # Layout: [A data] [2 NUL lines] [B data] [2 NUL lines] [C data]
92
+ # Expected: [A line] [NUL line] [*] [B line] [NUL line] [*] [C line] = 7 lines
93
+ data = (b"A" * 16) + (b"\x00" * 32) + (b"B" * 16) + (b"\x00" * 32) + (b"C" * 16)
94
+
95
+ out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
96
+ assert out is not None
97
+
98
+ lines = out.splitlines()
99
+ assert len(lines) == 7
100
+ assert lines[0].startswith("00000000") # A line
101
+ assert lines[1].startswith("00000010") # First NUL line of first run kept
102
+ assert lines[2] == "*" # Remaining NUL lines of first run collapsed
103
+ assert lines[3].startswith("00000030") # B line
104
+ assert lines[4].startswith("00000040") # First NUL line of second run kept
105
+ assert lines[5] == "*" # Remaining NUL lines of second run collapsed
106
+ assert lines[6].startswith("00000060") # C line
107
+
108
+
109
+ def test_hexdump_autoskip_single_interior_null_line_is_not_collapsed() -> None:
110
+ """Keep a single interior all-NUL line visible when autoskip is enabled."""
111
+ # Layout: [A data line] [1 NUL line] [B data line]
112
+ # Expected: [A line] [NUL line] [B line] = 3 lines (no repeated interior NUL line)
113
+ data = (b"A" * 16) + (b"\x00" * 16) + (b"B" * 16)
114
+
115
+ out = utils.hexdump(data, output="string", pretty=False, autoskip=True)
116
+ assert out is not None
117
+
118
+ lines = out.splitlines()
119
+ assert len(lines) == 3
120
+ assert lines[0].startswith("00000000") # A line
121
+ assert lines[1].startswith("00000010") # Single NUL line is kept
122
+ assert lines[2].startswith("00000020") # B line
123
+
124
+
125
+ def test_hexdump_autoskip_false_does_not_collapse() -> None:
126
+ """Keep all lines expanded and never emit '*' when autoskip is disabled."""
127
+ # Layout: [A data line] [3 NUL lines] [B data line]
128
+ # Expected: [A line] [NUL line] [NUL line] [NUL line] [B line] = 5 lines (no * when disabled)
129
+ data = (b"A" * 16) + (b"\x00" * 48) + (b"B" * 16)
130
+
131
+ out = utils.hexdump(data, output="string", pretty=False, autoskip=False)
132
+ assert out is not None
133
+
134
+ lines = out.splitlines()
135
+ assert len(lines) == 5
136
+ assert all(line != "*" for line in lines) # No * when autoskip disabled
137
+ assert lines[0].startswith("00000000") # A line
138
+ assert lines[1].startswith("00000010") # First NUL line (expanded)
139
+ assert lines[2].startswith("00000020") # Second NUL line (expanded)
140
+ assert lines[3].startswith("00000030") # Third NUL line (expanded)
141
+ assert lines[4].startswith("00000040") # B line
142
+
143
+
56
144
  def test_hexdump_pretty_print_conditions(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None:
57
145
  """Test if we respec the ``NO_COLOR`` environment variable and ``pretty=False`` argument."""
58
146
  # Test regular print output behavior