dissect.target 3.18.dev5__py3-none-any.whl → 3.18.dev6__py3-none-any.whl

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.
@@ -1,13 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import atexit
3
4
  import logging
5
+ import math
6
+ import os
4
7
  import ssl
8
+ import sys
5
9
  import time
6
10
  import urllib
7
11
  from dataclasses import dataclass
8
12
  from functools import lru_cache
9
13
  from pathlib import Path
10
14
  from struct import pack, unpack_from
15
+ from threading import Thread
11
16
  from typing import Any, Callable, Iterator, Optional, Union
12
17
 
13
18
  import paho.mqtt.client as mqtt
@@ -51,6 +56,34 @@ class SeekMessage:
51
56
  data: bytes = b""
52
57
 
53
58
 
59
+ class MQTTTransferRatePerSecond:
60
+ def __init__(self, window_size: int = 10):
61
+ self.window_size = window_size
62
+ self.timestamps = []
63
+ self.bytes = []
64
+
65
+ def record(self, timestamp: float, byte_count: int) -> MQTTTransferRatePerSecond:
66
+ while self.timestamps and (timestamp - self.timestamps[0] > self.window_size):
67
+ self.timestamps.pop(0)
68
+ self.bytes.pop(0)
69
+
70
+ self.timestamps.append(timestamp)
71
+ self.bytes.append(byte_count)
72
+ return self
73
+
74
+ def value(self, current_time: float) -> float:
75
+ if not self.timestamps:
76
+ return 0
77
+
78
+ elapsed_time = current_time - self.timestamps[0]
79
+ if elapsed_time == 0:
80
+ return 0
81
+
82
+ total_bytes = self.bytes[-1] - self.bytes[0]
83
+
84
+ return total_bytes / elapsed_time
85
+
86
+
54
87
  class MQTTStream(AlignedStream):
55
88
  def __init__(self, stream: MQTTConnection, disk_id: int, size: Optional[int] = None):
56
89
  self.stream = stream
@@ -62,12 +95,108 @@ class MQTTStream(AlignedStream):
62
95
  return data
63
96
 
64
97
 
98
+ class MQTTDiagnosticLine:
99
+ def __init__(self, connection: MQTTConnection, total_peers: int):
100
+ self.connection = connection
101
+ self.total_peers = total_peers
102
+ self._columns, self._rows = os.get_terminal_size(0)
103
+ atexit.register(self._detach)
104
+ self._attach()
105
+
106
+ def _attach(self) -> None:
107
+ # save cursor position
108
+ sys.stderr.write("\0337")
109
+ # set top and bottom margins of the scrolling region to default
110
+ sys.stderr.write("\033[r")
111
+ # restore cursor position
112
+ sys.stderr.write("\0338")
113
+ # move cursor down one line in the same column; if at the bottom, the screen scrolls up
114
+ sys.stderr.write("\033D")
115
+ # move cursor up one line in the same column; if at the top, screen scrolls down
116
+ sys.stderr.write("\033M")
117
+ # save cursor position again
118
+ sys.stderr.write("\0337")
119
+ # restrict scrolling to a region from the first line to one before the last line
120
+ sys.stderr.write(f"\033[1;{self._rows - 1}r")
121
+ # restore cursor position after setting scrolling region
122
+ sys.stderr.write("\0338")
123
+
124
+ def _detach(self) -> None:
125
+ # save cursor position
126
+ sys.stderr.write("\0337")
127
+ # move cursor to the specified position (last line, first column)
128
+ sys.stderr.write(f"\033[{self._rows};1H")
129
+ # clear from cursor to end of the line
130
+ sys.stderr.write("\033[K")
131
+ # reset scrolling region to include the entire display
132
+ sys.stderr.write("\033[r")
133
+ # restore cursor position
134
+ sys.stderr.write("\0338")
135
+ # ensure the written content is displayed (flush output)
136
+ sys.stderr.flush()
137
+
138
+ def display(self) -> None:
139
+ # prepare: set background color to blue and text color to white at the beginning of the line
140
+ prefix = "\x1b[44m\x1b[37m\r"
141
+ # reset all attributes (colors, styles) to their defaults afterwards
142
+ suffix = "\x1b[0m"
143
+ # separator to set background color to red and text style to bold
144
+ separator = "\x1b[41m\x1b[1m"
145
+ logo = "TARGETD"
146
+
147
+ start = time.time()
148
+ transfer_rate = MQTTTransferRatePerSecond(window_size=7)
149
+
150
+ while True:
151
+ time.sleep(0.05)
152
+ peers = "?"
153
+ try:
154
+ peers = len(self.connection.broker.peers(self.connection.host))
155
+ except Exception:
156
+ pass
157
+
158
+ recv = self.connection.broker.bytes_received
159
+ now = time.time()
160
+ transfer = transfer_rate.record(now, recv).value(now) / 1000 # convert to KB/s
161
+ failures = self.connection.retries
162
+ seconds_elapsed = round(now - start) % 60
163
+ minutes_elapsed = math.floor((now - start) / 60) % 60
164
+ hours_elapsed = math.floor((now - start) / 60**2)
165
+ timer = f"{hours_elapsed:02d}:{minutes_elapsed:02d}:{seconds_elapsed:02d}"
166
+ display = f"{timer} {peers}/{self.total_peers} peers {transfer:>8.2f} KB p/s {failures:>4} failures"
167
+ rest = self._columns - len(display)
168
+ padding = (rest - len(logo)) * " "
169
+
170
+ # save cursor position
171
+ sys.stderr.write("\0337")
172
+ # move cursor to specified position (last line, first column)
173
+ sys.stderr.write(f"\033[{self._rows};1H")
174
+ # disable line wrapping
175
+ sys.stderr.write("\033[?7l")
176
+ # reset all attributes
177
+ sys.stderr.write("\033[0m")
178
+ # write the display line with prefix, calculated display content, padding, separator, and logo
179
+ sys.stderr.write(prefix + display + padding + separator + logo + suffix)
180
+ # enable line wrapping again
181
+ sys.stderr.write("\033[?7h")
182
+ # restore cursor position
183
+ sys.stderr.write("\0338")
184
+ # flush output to ensure it is displayed
185
+ sys.stderr.flush()
186
+
187
+ def start(self) -> None:
188
+ t = Thread(target=self.display)
189
+ t.daemon = True
190
+ t.start()
191
+
192
+
65
193
  class MQTTConnection:
66
194
  broker = None
67
195
  host = None
68
196
  prev = -1
69
197
  factor = 1
70
198
  prefetch_factor_inc = 10
199
+ retries = 0
71
200
 
72
201
  def __init__(self, broker: Broker, host: str):
73
202
  self.broker = broker
@@ -125,6 +254,7 @@ class MQTTConnection:
125
254
  # message might have not reached agent, resend...
126
255
  self.broker.seek(self.host, disk_id, offset, flength, optimization_strategy)
127
256
  attempts = 0
257
+ self.retries += 1
128
258
 
129
259
  return message.data
130
260
 
@@ -138,6 +268,8 @@ class Broker:
138
268
  mqtt_client = None
139
269
  connected = False
140
270
  case = None
271
+ bytes_received = 0
272
+ monitor = False
141
273
 
142
274
  diskinfo = {}
143
275
  index = {}
@@ -217,6 +349,9 @@ class Broker:
217
349
  if casename != self.case:
218
350
  return
219
351
 
352
+ if self.monitor:
353
+ self.bytes_received += len(msg.payload)
354
+
220
355
  if response == "DISKS":
221
356
  self._on_disk(hostname, msg.payload)
222
357
  elif response == "READ":
@@ -238,9 +373,12 @@ class Broker:
238
373
  self.mqtt_client.publish(f"{self.case}/{host}/INFO")
239
374
 
240
375
  def topology(self, host: str) -> None:
241
- self.topo[host] = []
376
+ if host not in self.topo:
377
+ self.topo[host] = []
242
378
  self.mqtt_client.subscribe(f"{self.case}/{host}/ID")
243
379
  time.sleep(1) # need some time to avoid race condition, i.e. MQTT might react too fast
380
+ # send a simple clear command (invalid, just clears the prev. msg) just in case TOPO is stale
381
+ self.mqtt_client.publish(f"{self.case}/{host}/CLR")
244
382
  self.mqtt_client.publish(f"{self.case}/{host}/TOPO")
245
383
 
246
384
  def connect(self) -> None:
@@ -272,6 +410,7 @@ class Broker:
272
410
  @arg("--mqtt-crt", dest="crt", help="client certificate file")
273
411
  @arg("--mqtt-ca", dest="ca", help="certificate authority file")
274
412
  @arg("--mqtt-command", dest="command", help="direct command to client(s)")
413
+ @arg("--mqtt-diag", action="store_true", dest="diag", help="show MQTT diagnostic information")
275
414
  class MQTTLoader(Loader):
276
415
  """Load remote targets through a broker."""
277
416
 
@@ -292,6 +431,7 @@ class MQTTLoader(Loader):
292
431
  def find_all(path: Path, **kwargs) -> Iterator[str]:
293
432
  cls = MQTTLoader
294
433
  num_peers = 1
434
+
295
435
  if cls.broker is None:
296
436
  if (uri := kwargs.get("parsed_path")) is None:
297
437
  raise LoaderError("No URI connection details have been passed.")
@@ -299,8 +439,13 @@ class MQTTLoader(Loader):
299
439
  cls.broker = Broker(**options)
300
440
  cls.broker.connect()
301
441
  num_peers = int(options.get("peers", 1))
442
+ cls.connection = MQTTConnection(cls.broker, path)
443
+ if options.get("diag", None):
444
+ cls.broker.monitor = True
445
+ MQTTDiagnosticLine(cls.connection, num_peers).start()
446
+ else:
447
+ cls.connection = MQTTConnection(cls.broker, path)
302
448
 
303
- cls.connection = MQTTConnection(cls.broker, path)
304
449
  cls.peers = cls.connection.topo(num_peers)
305
450
  yield from cls.peers
306
451
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.18.dev5
3
+ Version: 3.18.dev6
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -86,7 +86,7 @@ dissect/target/loaders/itunes.py,sha256=rKOhlDRypQBGkuSZudMDS1Mlb9XV6BD5FRvM7tGq
86
86
  dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiBA,1237
87
87
  dissect/target/loaders/local.py,sha256=Ul-LCd_fY7SyWOVR6nH-NqbkuNpxoZVmffwrkvQElU8,16453
88
88
  dissect/target/loaders/log.py,sha256=cCkDIRS4aPlX3U-n_jUKaI2FPSV3BDpfqKceaU7rBbo,1507
89
- dissect/target/loaders/mqtt.py,sha256=D8AmdOz2atD92z8bhjVFi3tC1H7pYmP4UrOCtMgfwMY,10396
89
+ dissect/target/loaders/mqtt.py,sha256=wba1AFopF4ANvA5YiXd6_8T-5kRBLsiKA5xCPmrD7Xk,16024
90
90
  dissect/target/loaders/multiraw.py,sha256=4a3ZST0NwjnfPDxHkcEfAcX2ddUlT_C-rcrMHNg1wp4,1046
91
91
  dissect/target/loaders/ova.py,sha256=6h4O-7i87J394C6KgLsPkdXRAKNwtPubzLNS3vBGs7U,744
92
92
  dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-JUxn8,992
@@ -340,10 +340,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
340
340
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
341
341
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
342
342
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
343
- dissect.target-3.18.dev5.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
344
- dissect.target-3.18.dev5.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
345
- dissect.target-3.18.dev5.dist-info/METADATA,sha256=wBA3ehlJ0xuwPlvLfhY13YfMg2Fm8w8XIzeWGK5-Ksk,11299
346
- dissect.target-3.18.dev5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
347
- dissect.target-3.18.dev5.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
348
- dissect.target-3.18.dev5.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
349
- dissect.target-3.18.dev5.dist-info/RECORD,,
343
+ dissect.target-3.18.dev6.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
344
+ dissect.target-3.18.dev6.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
345
+ dissect.target-3.18.dev6.dist-info/METADATA,sha256=pep2cQIYZZY6yeV07oBPPNq-6e2LnrUig_hkv-5IiEk,11299
346
+ dissect.target-3.18.dev6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
347
+ dissect.target-3.18.dev6.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
348
+ dissect.target-3.18.dev6.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
349
+ dissect.target-3.18.dev6.dist-info/RECORD,,