0.1.3 - Add user UID, capture source & TCP support
Introduce automatic user UID extraction and optional --user-uid override, include user_uid and capture_source in exported JSON, and prepend UID to export filenames (falls back to unknown). Propagate detected protocol (tcp/udp) through live/libpcap parsing and accept TCP packets for UID detection; adjust libpcap filter to capture TCP as well. Update CLI flags (--copy-clipboard semantics, --user-uid), prompt user when UID is missing, and wire UID through mitmproxy and live capture flows. Bump package/exporter version to 0.1.3 and add tests for UID extraction and filename behavior.
This commit is contained in:
parent
98d97cb36a
commit
df1add6cf1
15 changed files with 283 additions and 49 deletions
|
|
@ -20,6 +20,7 @@ from nte_history_exporter.decoder.protocol import decode_response_records, histo
|
|||
from nte_history_exporter.constants import POOL_META
|
||||
from nte_history_exporter.mappings import ARC_META, CHARACTERS, ITEMS, REWARDS_BY_ID
|
||||
from nte_history_exporter.decoder.protocol import decode_reward_key, infer_reward_type
|
||||
from nte_history_exporter.decoder.user_uid import extract_user_uid
|
||||
from nte_history_exporter.decoder.arc import (
|
||||
arc_request_page,
|
||||
build_arc_rows_from_pairs,
|
||||
|
|
@ -39,8 +40,10 @@ from nte_history_exporter.live_capture.libpcap import (
|
|||
_extract_ipv4_frame,
|
||||
LibpcapUnavailable,
|
||||
)
|
||||
from nte_history_exporter.live_capture.windows_raw import parse_ipv4_packet
|
||||
from nte_history_exporter.live_capture.backends import open_capture_backend
|
||||
from nte_history_exporter.export.json_export import build_export_json
|
||||
from nte_history_exporter.live_capture.runner import export_paths
|
||||
from nte_history_exporter.pool_mappings import load_pool_mappings, pool_meta_from_mapping
|
||||
|
||||
|
||||
|
|
@ -347,10 +350,94 @@ class BoundaryExportTests(unittest.TestCase):
|
|||
|
||||
self.assertEqual(export["format"], "nte-history-export")
|
||||
self.assertIn("exporter", export)
|
||||
self.assertNotIn("user_uid", export)
|
||||
self.assertNotIn("record_hex", export["records"][0])
|
||||
self.assertNotIn("request_msg", export["records"][0])
|
||||
self.assertNotIn("response_msg", export["records"][0])
|
||||
|
||||
def test_export_includes_user_uid_when_provided(self):
|
||||
rows = load_reference_csv("monopoly_history_poc_13_pages_1_to_5_v4.csv")
|
||||
annotated = annotate_groups(rows)
|
||||
export = build_export_json(
|
||||
annotated,
|
||||
[],
|
||||
capture_source="npcap",
|
||||
user_uid="123456789",
|
||||
)
|
||||
|
||||
self.assertEqual(list(export).index("user_uid"), list(export).index("records") - 1)
|
||||
self.assertEqual(export["capture_source"], "npcap")
|
||||
self.assertEqual(export["user_uid"], "123456789")
|
||||
|
||||
def test_export_paths_include_user_uid_banner_and_timestamp(self):
|
||||
_csv_path, json_path = export_paths("limited_character", "218216016349")
|
||||
|
||||
self.assertRegex(
|
||||
json_path.name,
|
||||
r"^218216016349_Limited_\d{8}_\d{6}(?:_\d+)?\.json$",
|
||||
)
|
||||
|
||||
def test_extracts_user_uid_from_record_context(self):
|
||||
payload = (
|
||||
b"\x00" * 24
|
||||
+ (218216016349).to_bytes(8, "little")
|
||||
+ b"\x00\x00\x00\x00\x09\x00\x00\x00TagOthers\x00"
|
||||
)
|
||||
|
||||
self.assertEqual(extract_user_uid(payload), "218216016349")
|
||||
|
||||
def test_extracts_user_uid_from_private_spawn_record_context(self):
|
||||
payload = (
|
||||
b"\x88\x00\x00\x00\x10\x00\x00\x00"
|
||||
+ (218216016349).to_bytes(8, "little")
|
||||
+ b"\x08\x00\x0c\x00\x07\x00\x08\x00\x08\x00\x00\x00"
|
||||
+ b"\x00\x00\x00\x01\x08\x00\x00\x00\x04\x00\x04\x00"
|
||||
+ b"\x04\x00\x00\x00\x16\x00\x00\x00PrivateSpawnInfoRecord\x00"
|
||||
)
|
||||
|
||||
self.assertEqual(extract_user_uid(payload), "218216016349")
|
||||
|
||||
def test_does_not_extract_user_uid_from_wrong_record_offset(self):
|
||||
payload = (
|
||||
b"\x00" * 28
|
||||
+ (218216016349).to_bytes(8, "little")
|
||||
+ b"\x00\x00\x00\x00TagOthers\x00"
|
||||
)
|
||||
|
||||
self.assertIsNone(extract_user_uid(payload))
|
||||
|
||||
def test_does_not_extract_old_eight_digit_false_positive_as_user_uid(self):
|
||||
payload = (
|
||||
b"WholeVehicleData\x00\x00\x00\x00\x00o<\x00\x00\x05\x00\x00\x00"
|
||||
b"\x0b\x00\x00\x00Vehicle015\x00\x0b\x00\x00\x00buyvehicle\x00"
|
||||
b"\x09\x00\x00\x0015363624\x00\x06\x00\x00\x00"
|
||||
)
|
||||
|
||||
self.assertIsNone(extract_user_uid(payload))
|
||||
|
||||
def test_ipv4_parser_extracts_tcp_payload_for_user_uid_detection(self):
|
||||
payload = (
|
||||
(218216016349).to_bytes(8, "little")
|
||||
+ b"\x00\x00\x00\x00\x09\x00\x00\x00TagOthers\x00"
|
||||
)
|
||||
tcp_header = bytearray(20)
|
||||
tcp_header[0:2] = (40000).to_bytes(2, "big")
|
||||
tcp_header[2:4] = (30000).to_bytes(2, "big")
|
||||
tcp_header[12] = 5 << 4
|
||||
total_len = 20 + len(tcp_header) + len(payload)
|
||||
ip_header = bytearray(20)
|
||||
ip_header[0] = 0x45
|
||||
ip_header[2:4] = total_len.to_bytes(2, "big")
|
||||
ip_header[9] = 6
|
||||
ip_header[12:16] = bytes([192, 0, 2, 1])
|
||||
ip_header[16:20] = bytes([198, 51, 100, 2])
|
||||
|
||||
packet = parse_ipv4_packet(bytes(ip_header) + bytes(tcp_header) + payload)
|
||||
|
||||
self.assertIsNotNone(packet)
|
||||
self.assertEqual(packet.protocol, "tcp")
|
||||
self.assertEqual(packet.payload, payload)
|
||||
|
||||
def test_limited_selector_and_marker_decode(self):
|
||||
request = bytearray(45)
|
||||
request[31:35] = (4).to_bytes(4, "little")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue